Сеть Интернет по своей архитектуре допускает возможность прямого обмена трафиком между любыми узлами, но все же в большинстве сценариев используется асимметричный вариант использования с относительно небольшим количеством узлов, отдающих содержание (объединенных в CDN, кэширующие сети (например, Google Global Cache), либо отдельные зеркала, расположенные на высокоскоростных каналах). При многих достоинствах такой подход не лишен и серъезных недостатков, прежде всего из-за значительной разбалансированности сети и перегрузке некоторых каналов связи при относительно небольшом трафике на других.
Решением этой проблемы могло стать использование сетей, основанных на прямом обмене трафиком (peer-to-peer или p2p), но создание полностью децентрализованной сети представляет значительную сложность, поэтому во многих случаях все же оставляют некоторые общие реестры, хранящие информацию об узлах-носителях определенного содержания (так, например, работают торрент-трекеры) и на которых регистрируются клиенты сети при подключении. Основным недостатком такого псевдодентрализованного подхода является возможность относительно простой остановки функционирования сети через блокировку соответствующих трекеров. Альтернативой могут быть полностью децентрализованные сети и мы рассмотрим в этой статье основные подходы к их реализации на примере свободного протокола и сети Peernet.
Прежде всего договоримся о терминологии:
peer - это участник сети обмена трафиком, предоставляет в общее пользование информацию, адресуемую по названию или хэшу содержания
network - виртуальное объединение peer, чаще всего каждый peer сохраняет информацию о нескольких доступных. Network строится поверх существующих протоколов маршрутизации и во многих случаях протоколы сети относятся к уровню приложения.
hash - цифровая подпись, которая создается по содержанию хранимого объекта и достаточно большой длины, чтобы уменьшить вероятность коллизий
chunk - единица содержания (блок), часто для оптимизации работы сети peer представляют информации в виде набора блоков, и peer может содержать неполный файл (например, если загрузка еще не была завершена), но загруженные блоки уже становятся доступны для загрузки другими peer
DHT (Distributed Hash Table) - распределенная между peer таблица размещения содержания, создается динамически и обновляется при появлении-удалении peer из сети
root peer - наиболее известные peer, адреса которых известны и которые ожидаются быть доступными в сети продолжительное время. Важно, что даже при отсутствии root peer сеть не потеряет функциональность, но процесс составления топологии сети будет замедлен
broadcast - механизм доставки сообщений в сетях, при которых получателями являются все доступные узлы
multicast - механизм подписки на получение сообщений со специального группового адреса, позволяет доставлять сообщение любому количеству получателей без необходимости дублирования пакетов и увеличения загрузки сетевого канала
UPnP (Universal plug and play) - архитектура прямого взаимодействия устройств в сети (может использовать mDNS или другие механизмы для взаимного обнаружения устройств и уведомления о доступных возможностях)
Blockchain - может использоваться дополнительно для создания механизма коллективной валидации выполненных транзакций (например, публикации нового файла или обновления информации о топологии сети).
UDP Hole Punching (Reliable UDP) - протокол для организации возможности прямого взаимодействия peer, находящихся за NAT (Network Address Translation)-шлюзами (например, любой домашний wi-fi router).
При создании сети также необходимо предусмотреть возможность шифрования передаваемых данных и взаимной валидации peer (с использованием инфраструктуры открытых ключей или иным способом, например через blockchain).
Наиболее сложны в реализации для распределенных сетей алгоритмы обнаружения соседних peer (здесь можно использовать возможности broadcast-рассылок по локальной сети, root peer для получения информации о доступных peer вблизи (или внутри) нашей автономной системы от корневых peer, а также могут использовать публичные дайджесты со списком активных peer, кэширующие серверы, собирающие актуальную информацию о доступности peer в сети и др. Также peer должен контролировать сохранность информации о топологии (например, рассылая периодические ping-сообщения другим peer) и обновлять свою локальную копию списка доступных peer. Также peer сохраняет свою часть распределенной хэш-таблицы и получает копию таблиц других известных peer.
Вторая значительная сложность полностью децентрализованных сетей - поиск запрашиваемых пользователей элементов содержания. Идеальная ситуация - когда пользователь уже имеет хэш необходимого содержания (например, такой механизм используется в magnet-links в протоколе Torrent, либо в сетях с контентно-адресуемыми ресурсами, например Interplanetary Filesystem (IPFS). В случае, если пользователь ищет содержание по названию файла, есть значительная вероятность, что на разных peer файл может иметь различные названия. В этом случае peer должен предоставить два механизма поиска:
по фрагменту названия (в этом случае первый peer, который вернет совпадение, будет считаться источником названия файла, а в остальных peer поиск будет выполняться уже по хэшу)
по хэшу содержания (преимущественный способ, используется если известен хэш, например после получения первого результат поиска)
Для создания распределенной таблицы хэшей (DHT) часто используют OpenDHT или Kademlia. Среди наиболее известных реализаций децентрализованных сетей известны Gnutella (Napster), EDonkey (ED2K),
Среди существующих решений, которые реализуют модель p2p-распределенного хранения можно назвать PeerGos, Filecoin, Plexus, Somnia, Torrent без трекеров. Мы рассмотрим пример запуска децентрализованной сети хранения на основе Peernet, который представляет как протокол взаимодействия peer, так и SDK на Go, а также клиент для Android, утилиту командной строки и расширения браузера для доступа к ресурсам peernet. Библиотека для поддержки Android собирается с использованием Gomobile, который был рассмотрен в этой статье.
Peernet core отвечает за запуск peer и подключение к существующей сети. Для идентификации peer используется закрытый ключ, который генерируется при первом запуске. Для программного запуска peer можно использовать следующий фрагмент кода:
package main
import (
"fmt"
"github.com/PeernetOfficial/core"
)
func main() {
backend, status, err := core.Init("MyNetwork/1.0", "Config.yaml", nil, nil)
if status != core.ExitSuccess {
fmt.Printf("Error %d initializing backend: %s\n", status, err.Error())
return
}
backend.Connect()
}
Также может быть запущен webapi (для этого можно использовать как пакет github.com/PeernetOfficial/core/webapi, так и возможности утилиты командной строки, в этом случае в ней указывается интерфейс и порт для публикации webapi, а также уникальный UUID для API Key). WebAPI представляет возможности для получения актуального состояния сервера, управления warehouse (виртуальная группа публикации ресурсов), поиска содержания, регистрации peer в blockchain, публикации ресурсов и запросе для получения локальной копии ресурса (скачивания из сети). Ресурсы идентифицируются по названию, дате создания, типу файла (перечисление) и хэшу. Более подробно возможности webapi можно посмотреть в документации.
После подключения к сети (для этого используются возможности root peers, обнаружение через UPnP и broadcast в локальной сети, поиск через Kademlia DHT) peer регистрируется в общем blockchain и передает в него свою хэш-таблицу. При поиске информации из других peer также используется blockchain и поисковые возможности, предоставляемые API. По запросу к API ресурс может быть загружен локально (при этом он становится доступным для других peer сети), также можно опубликовать новый ресурс (для файла публикуются метаданные, определяется хэш и описание добавляется в Blockchain DHT).
Конфигурация peer определяет адреса root peer (по умолчанию указано 5), расположение каталогов для хранения логов, blockchain, warehouse, каталога с данными и GeoIP-базы данных. Также можно разрешить использовать UPnP и настройки firewall, настройки кэша blockchain.
Узнать текущее состояние подключения к сети можно как через API, так и через утилиту командной строки:
status
- текущее состояние публикацииpeer list
- получить информацию о подключенных соседних peerdebug key self
- получить информацию о текущей паре с открытым ключомdebug key create
- создать новую пару с открытым ключом
Peernet выглядит очень интересным и достаточно быстрым решением для реализации архитектуры полностью децентрализованного хранения и может использоваться как для создания транспорта для уменьшения нагрузки на глобальную сеть, так и для создания децентрализованных p2p-приложений с разделяемыми данными.
Материал подготовлен в преддверии старта курса "Архитектура и шаблоны проектирования". Также по ссылке вы найдете запись бесплатного урока курса.
Узнать подробнее о курсе