Концепции libp2p. Publish/Subscribe
Перевод статьи с портала проекта Libp2p.
Публикация/Подписка (Publish/Subscribe, сокр. pub/sub или PubSub - прим. перев.) - это система, в которой одноранговые узлы (в дальнейшем - просто "узлы" или "пиры" (peers) - прим. перев.) объединяются вокруг интересующих их тем. Говорят, что пиры, заинтересованные в какой-либо теме, подписаны на эту тему:
Узлы могут отправлять сообщения по темам. Каждое сообщение доставляется всем пирам, подписанным на тему:
Примеры использования pub/sub:
Чаты. Каждая комната - это pub/sub-тема, и участники чата отправляют сообщения, которые получают все остальные собеседники в комнате.
Обмен файлами. Каждая тема pub/sub посвящена файлу, который можно загрузить. Качающие и раздающие объявляют в теме pub/sub, какие части файла у них есть, и координируют загрузки, которые будут происходить за пределами системы pub/sub.
Цели разработки
В одноранговой системе публикации/подписки все пиры участвуют в доставке сообщений по сети. Существует несколько различных дизайнов p2p-систем pub/sub, которые предлагают разные компромиссы. Желательные свойства включают:
Надежность: все сообщения доставляются всем участникам, подписавшимся на тему.
Скорость: сообщения доставляются быстро.
Эффективность: сеть не переполняется копиями сообщений.
Устойчивость: узлы могут присоединяться к сети и выходить из неё, не нарушая её. Нет центральной точки отказа.
Масштаб: темы могут иметь очень большое количество подписчиков и обрабатывать гигантский объём сообщений.
Простота: система проста для понимания и реализации. Каждому узлу требуется помнить лишь небольшое количество состояний.
Libp2p в настоящее время использует дизайн под названием gossipsub. Он назван так потому, что узлы "сплетничают" между собой о том, какие сообщения они видели, и используют эту информацию для поддержания сети доставки сообщений.
Обнаружение
Прежде, чем пир сможет подписаться на тему, он должен найти другие узлы и установить с ними сетевые соединения. Система pub/sub не может сама находить новые узлы. Она полагается на то, что приложение умеет это делать, такой процесс называется обнаружением окружающих узлов.
Возможные методы обнаружения пиров включают:
Распределенные хеш-таблицы (DHT)
Трансляции по локальной сети
Обмен списками существующих пиров
Централизованные трекеры или места встречи
Списки bootstrap-пиров
К примеру, в приложении BitTorrent, в процессе загрузки файлов, большинство вышеперечисленных методов уже будут использоваться. Повторно используя пиры, найденные в то время, как BitTorrent выполняет свою обычную работу, приложение может также создать надежную сеть публикации/подписки.
Обнаруженные узлы опрашиваются: поддерживают ли они протокол pub/sub, и если да - добавляются в pub/sub-сеть.
Типы взаимодействия (пиринга) узлов
По протоколу gossipsub узлы взаимодействуют двумя способами - обмениваясь либо полными сообщениями (full-message peerings), либо только служебной информацией (metadata-only peerings). Общая сетевая структура состоит из двух соответствующих сетей:
Обмен полными сообщениями
При данном типе взаимодействия каждый узел подключен лишь к малому числу других узлов. (В спецификации gossipsub эта редко связанная сеть называется меш-сеткой (mesh), а узлы в ней называются элементами сетки (mesh members)).
Полезно ограничивать количество полных сообщений, ибо это позволяет контролировать объём сетевого трафика; каждый узел пересылает сообщения только нескольким одноранговым узлам, но не всем сразу. У каждого узла есть некоторое количество "целевых" узлов, с которыми он стремится соединиться. В следующем примере каждый пир в идеале будет подключен к 3 другим одноранговым узлам, в то время как допустимое количество соединений находится в диапазоне 2-4:
(Обратите внимание: числа на иллюстрациях, подсвеченные светло-лиловым цветом, могут выбираться разработчиком.)
Степень пиринга или связности (также называемая степенью сети или D) устанавливает компромисс между скоростью, надежностью, отказоустойчивостью и эффективностью сети. Более высокая степень пиринга ускоряет доставку сообщений, с большей вероятностью достичь всех подписчиков и с меньшей вероятностью сбоя в работе сети при покидании сети одним из узлов. Однако, высокая степень пиринга также вызывает отправку дополнительных избыточных копий каждого сообщения по сети, увеличивая требования к сетевой полосе пропускания.
В реализации libp2p по умолчанию идеальная степень пиринга в сети - 6, при этом приемлемы значения от 4 до 12.
Обмен мета-данными
В дополнение к редко-связанной сети обмена полными сообщениями существует также плотно-вязанная сеть обмена метаданными. Эта сеть состоит из всех соединений (такие соединения для краткости ещё будем называть "служебными" - прим. перев.), не относящихся к обмену полными сообщениями, и служит для оповещения узлов о том, какие сообщения доступны, выполняя функции поддержки сети обмена полными сообщениями.
Прививка и обрезка
Соединения пиров являются двунаправленными - это означает, что любые два связанных узла считают своё соединение либо обменом полными сообщениями, либо оба узла считают свое соединение обменом только метаданными. Любой узел может изменить тип соединения, уведомив партнера. Прививка (grafting) - это процесс преобразования соединения, открытого для передачи только метаданных, в соединение обмена полными сообщениями (будем называть такие соединения "полными" - прим. перев.). Обрезка (pruning) - это обратный процесс; преобразование передачи полного сообщения в передачу только метаданных (или, другими словами, преобразование полного соединения в служебное):
Если у какого-либо узла окажется слишком мало полных соединений, он случайным образом выберет служебные соединения и привьёт их до полных:
И наоборот, когда пир имеет избыток полных соединений, он случайным образом обрезает некоторые из них до статуса служебных:
В реализации libp2p каждый пир выполняет серию проверок каждую 1 секунду. Такие проверки называются сердцебиением (heartbeat), во время них происходит прививка и обрезка.
Подписка и отписка
Узлы отслеживают, на какие темы подписаны их непосредственные партнеры. Используя эту информацию, каждый пир может составить представление о темах вокруг себя и о том, какие узлы подписаны на каждую тему:
Отслеживание подписок происходит путем отправки сообщений о подписке (subscribe) и отказе от подписки (unsubscribe). Когда между двумя узлами устанавливается новое соединение, они начинают с отправки друг другу списка тем, на которые они подписаны:
Впоследствии, всякий раз, когда пир подписывается или отказывается от подписки на тему, он будет отправлять каждому из своих партнеров сообщение о подписке или отписке. Эти сообщения отправляются всем подсоединенным пирам, независимо от того, подписан ли принимающий узел на рассматриваемую тему:
Сообщения о подписке и отписке идут рука об руку с сообщениями о прививке и обрезке. Когда узел подписывается на тему, он выбирает несколько пиров, которые станут его партнерами с полным соединением по этой теме, и отправляет им сообщения о прививке одновременно с сообщениями о подписке:
Когда же узел отписывается от темы, он оповещает своих партнеров с полным соединением о том, что соединения будут обрезаны до служебных, одновременно с оповещением об отписке:
Отправка сообщений
Когда узел хочет опубликовать сообщение, он отправляет копию сообщения всем партнерам с полным соединением, к которым он подключен:
Точно так же, когда пир получает новое сообщение от другого пира, он сохраняет сообщение и пересылает его копию всем другим пирам, к которым он подключен полным соединением:
В спецификации gossipsub пиры также называются роутерами - из-за этой своей функции маршрутизации, которую они выполняют при передаче сообщений по сети. Узлы хранят список недавно просмотренных сообщений. Это позволяет им реагировать на сообщение лишь единожды - когда они его видят впервые - и игнорировать повторные передачи уже просмотренных сообщений.
Пиры могут также предпринимать проверку содержимого каждого полученного сообщения. Что считать годным (valid) или негодным (invalid) - зависит от приложения. К примеру, программа чата может требовать, чтобы все сообщения были короче 100 символов. Если приложение сообщает libp2p, что полученное сообщение негодное, такое сообщение будет отброшено и далее по сети распространяться не будет.
Сплетни
Пиры сплетничают о сообщениях, которые недавно видели. Каждую 1 секунду каждый пир случайным образом выбирает 6 партнеров по служебным сообщениям и отправляет им список недавно просмотренных тематических сообщений.
Сплетни дают пирам возможность отслеживать, не пропустили ли они сообщения в сети обмена полными сообщениями. Если узел замечает, что он постоянно пропускает сообщения, он может установить новые полные соединения с теми узлами, у которых сообщения имеются. Вот иллюстрация того, как конкретное сообщение может быть запрошено через сеть служебных сообщений (обмена мета-данными):
В спецификации gossipsub сплетни, объявляющие о недавно увиденных сообщениях, называются сообщениями IHAVE, а запросы конкретных сообщений называются сообщениями IWANT.
Развеивание
Пиры могут публиковать сообщения в темах, на которые они не подписаны. Есть несколько специальных правил, определяющих, как это сделать, чтобы гарантировать надежную доставку таких сообщений. Поначалу, когда узел хочет опубликовать сообщение в теме, на которую он не подписан, он случайным образом выбирает 6 узлов (3 показаны ниже), подписанных на данную тему, и запоминает их как веерные (fan-out) узлы для данной темы:
В отличие от других типов пиринга, веерные соединения являются однонаправленными; они всегда указывают с узла вне темы на узел, подписанный на тему. Узлам, подписанным на тему, не сообщается, что они были выбраны, и они по-прежнему рассматривают эти соединения как любые другие соединения обмена мета-данными. Всякий раз, когда отправитель хочет отправить сообщение, он отправляет сообщение своим партнерам по развеиванию (веерным узлам), которые затем распространяют сообщение среди подписчиков темы:
Если отправитель собирается отправить сообщение, но замечает, что некоторые из его веерных партнеров стали недоступны, он случайным образом выберет дополнительные пиры, чтобы их количество вновь увеличилось до 6. Когда же пир подписывается на тему, если он уже имеет несколько веерных узлов, то он предпочтет, чтобы именно они стали партнерами с полным соединением:
Если в течение 2 минут не отправлять сообщения в тему, все веерные узлы для этой темы будут преданы забвению:
Сетевые пакеты
В действительности пакеты, которые узлы отправляют друг другу по сети, представляют собой комбинацию всех различных типов сообщений, рассмотренных в данном обзоре (тематические сообщения приложения, "имею"/"хочу" (have/want), подписка/отписка (subscribe/unsubscribe), прививка/обрезка (graft/prune)). Такая структура позволяет группировать несколько разных запросов и отправлять их в одном сетевом пакете.
Вот графическое представление общей структуры сетевых пакетов:
Точную схему Protocol Buffers, используемую для кодирования сетевых пакетов, смотрите в спецификации.
Состояние
Вот краткое представление состояния (state), которое каждый пир должен сохранять, дабы участвовать в сети публикации/подписки (pub/sub):
Подписки: список тем, на которые данный узел подписан.
Веерные темы: это темы, в которые недавно были отправлены сообщения, но на которые нет подписки. Для каждой темы запоминается время последнего отправленного в неё сообщения.
Список пиров, к которым данный пир в настоящее время подключен: по каждому узлу хранится список всех тем, на которые он подписан, а также информация о типе соединения для каждой темы.
Давеча виденные сообщения: кеш недавно просмотренных сообщений. Он используется для обнаружения и игнорирования повторно полученных сообщений. Для каждого сообщения хранятся сведения о том, кто его отправил, и порядковый номер - что достаточно для однозначной идентификации любого сообщения. Для самых недавних сообщений сохраняется полное их содержимое, чтобы их можно было отправить, по запросу, любому узлу.
Больше информации
Для получения более подробной информации и обсуждения различных реализаций pub/sub, которые повлияли на дизайн gossipsub, откройте спецификацию gossipsub. Подробные сведения о реализации gossipsub смотрите в файле gossipsub.go в исходниках проекта go-libp2p-pubsub, который является канонической реализацией gossipsub в libp2p.
Перевод: Алексей Силин (StarVer)