Практика Jingdong Netty, архитектура контейнера длинных соединений TCP-шлюза Jingmai

Архитектура

Jingmai создает шлюз с 2014 года и перешел от HTTP-шлюза к TCP-шлюзу. В 2016 году был завершен высокодоступный, высокопроизводительный и высокостабильный TCP-шлюз для длинных соединений на основе Netty4.x + Protobuf3.x для реализации восходящей и нисходящей связи между ПК и приложением. В этой статье основное внимание уделяется предыстории, архитектуре и практике применения Netty для TCP-шлюза Jingmai.

задний план

В первые дни Jingmai создавал функции длинных соединений HTTP и TCP в основном для отправки уведомлений о сообщениях и не применялся к шлюзу API. С постепенным углубленным изучением NIO и пониманием фреймворка Netty, а также все более высокими требованиями к стабильности системной связи, идея использования шлюза приложений технологии NIO для реализации вызовов API-запросов стала проявляться. реализовано и, наконец, реализовано в 2016 году. Полностью поддерживает бизнес-операции.

Благодаря множеству улучшений, в том числе контейнеру постоянных соединений TCP, сериализации Protobuf, структуре вызова обобщения служб и т. д., производительность более чем в 10 раз выше, чем у HTTP-шлюза, а стабильность также намного выше, чем у HTTP-шлюза.

Архитектура

На основе Netty построен контейнер длинных соединений TCP-шлюза Jingmai, который служит уровнем доступа к шлюзу для обеспечения вызовов запросов API службы.

1. Структура сети

Клиент получает доступ к TCP-шлюзу через доменное имя + порт. Операторы с разными доменными именами соответствуют разным виртуальным IP-адресам. Виртуальный IP-адрес публикуется на LVS, и LVS перенаправляет запрос на внутренний HAProxy, который затем перенаправляет запрос на внутренний IP+порт Netty.

LVS перенаправляет его на серверную часть HAProxy, запрос проходит через LVS, но ответ возвращается клиенту напрямую с помощью HAProxy, что является режимом DR LVS.

2. Архитектура контейнера длинных соединений TCP-шлюза

Основным компонентом шлюза TCP является Netty, а моделью NIO Netty является модель реактора Reactor (Reactor эквивалентен мультиплексору Selector с функцией распределения). Каждое соединение соответствует каналу (многоканальный относится к нескольким каналам, мультиплексирование относится к нескольким соединениям, мультиплексирующим поток или небольшое количество потоков, в Netty относится к EventLoop), канал соответствует уникальному ChannelPipeline, а несколько обработчиков последовательно добавленный в конвейер, каждый обработчик связан с уникальным ChannelHandlerContext.

Обработчик контейнера постоянного соединения TCP-шлюза помещается в Pipeline. Мы знаем, что TCP относится к транспортному уровню OSI, поэтому создание механизма управления сеансом для создания сеансового уровня для предоставления услуг прикладного уровня может значительно снизить сложность системы.

Таким образом, каждый Канал соответствует Соединению, а Соединение соответствует Сессии.Сессия управляется Менеджером Сессии.Сеанс и Соединение находятся во взаимно-однозначном соответствии.Активное состояние.

Каждый запрос сеанса (ChannelRead) сеанса вызывает уровень службы через механизм прокси-сервера.После завершения запроса данных он записывается в ChannelHandlerConext, а затем отправляется в канал. То же самое относится и к активной передаче данных в нисходящем направлении.Найдите активную сессию с помощью диспетчера сеансов и опросите ChannelHandlerContext, написанный в сеансе, чтобы реализовать логику широковещательной или двухточечной передачи данных.

Практика применения Netty

Шлюз Jingmai TCP использует Netty Channel для передачи данных, Protobuf для сериализации и десериализации, каждый запрос будет инкапсулирован в байтовый поток двоичных байтов, на протяжении всего жизненного цикла канал поддерживает длительное соединение, а не каждый раз, когда все вызовы воссоздают канал для достижения повторное использование ссылки.

1. Модель ввода-вывода TCP-шлюза Netty Server.

  1. Создайте ServerBootstrap, установите пул потоков BossGroup и WorkerGroup.
  2. привязать указанный порт, чтобы начать прослушивание и прием клиентских подключений. (Если в системе есть только один серверный порт для прослушивания, число потоков в группе потоков BossGroup устанавливается равным 1.)
  3. Зарегистрируйте childHandler в ChannelPipeline для обработки кадров запроса в клиентской ссылке.

Во-вторых, модель потоков TCP-шлюза.

Шлюз TCP использует пул потоков Netty.Существует три группы пулов потоков, а именно BossGroup, WorkerGroup и ExecutorGroup. Среди них BossGroup используется для получения TCP-соединений от клиентов, WorkerGroup используется для обработки ввода-вывода, выполнения системных задач и запланированных задач, а ExecutorGroup используется для обработки бизнес-шифрования и дешифрования шлюза, ограничения тока, маршрутизации и пересылки запросов к серверная служба сканирования и другие бизнес-операции.

NioEventLoop — это поток Netty Reactor, его роль:

  1. Boss Group: как серверный поток Acceptor, он используется для приема клиентской ссылки и пересылки ее потоку в WorkerGroup.
  2. Worker Group: Как поток ввода-вывода, он отвечает за чтение и запись ввода-вывода, чтение сообщений из SocketChannel или запись сообщений в SocketChannel.
  3. Очередь задач/Очередь задач с задержкой: В качестве потока задач синхронизации выполнять задачи синхронизации, такие как обнаружение бездействия канала и отправка контрольных сообщений.

3. Диаграмма последовательности выполнения TCP-шлюза

Шаги с 1 по 9 — это последовательность создания сервера Netty, а шаги с 10 по 13 — последовательность создания контейнера шлюза TCP.

  • шаг первый: Создайте экземпляр ServerBootstrap.ServerBootstrap — это вспомогательный класс запуска сервера Netty.
  • Шаг 2: Установите и привяжите пул потоков Reactor, EventLoopGroup — это пул потоков Reactor Netty, EventLoop отвечает за все каналы, зарегистрированные в этом потоке.
  • Шаг 3: Установите и привяжите канал сервера, Netty Server должен создать объект NioServerSocketChannel.
  • Шаг 4: ChannelPipeline создается при установлении TCP-соединения. ChannelPipeline — это, по сути, цепочка обязанностей, которая отвечает за ChannelHandler и выполняет его.
  • Шаг 5: добавить и установить ChannelHandler, ChannelHandler последовательно добавляется в ChannelPipeline.
  • Шаг 6: привяжите порт прослушивания и запустите сервер, а также зарегистрируйте NioServerSocketChannel с помощью Selector.
  • Шаг седьмой: опрос селектора, EventLoop отвечает за планирование и выполнение операций опроса селектора.
  • Шаг 8: выполнить уведомление о событии сетевого запроса, опросить готовый канал и выполнить ChannelPipeline с помощью EventLoop.
  • Шаг девятый: выполнить систему Netty и бизнес-обработчик ChannelHandler, запланировать и выполнить ChannelHandler ChannelPipeline по очереди.
  • Шаг десятый: внутренняя служба вызывается через прокси-сервер, и после события ChannelRead внутренняя служба отправляется путем эмиссии.
  • шаг одиннадцатый: Создать сеанс, сеанс и соединение взаимозависимы.
  • Шаг 12: Создать соединение, соединение сохраняет ChannelHandlerContext.
  • Шаг тринадцатый: добавьте SessionListener, который прослушивает такие события, как SessionCreate и SessionDestory.

Четыре, анализ исходного кода шлюза TCP

1. Управление сеансом

Сеанс — это связь сеанса, установленная между клиентом и сервером. SessionId, время создания соединения, последнее событие доступа, Connection и SessionListener сохраняются в информации о сеансе, а контекстная информация Netty ChannelHandlerContext сохраняется в Connection. Информация о сеансе сеанса сохраняется в диспетчере памяти SessionManager.

Создайте исходный код сеанса

Путем анализа исходного кода, если Сессия уже существует, уничтожить Сессию, но это требует особого внимания.При создании Сессии нельзя создавать те Каналы, которые отключаются и переподключаются, иначе Канал будет уничтожен по ошибке. Потому что, если Connection(2) установлено на канале, который уже установил Connection(1), ввод метода session.close закроет cxt, а каналы Connection(1) и Connection(2) будут закрыты. После отключения установите соединение Connection(3), т.к. Сессия имеет определенную задержку, Connection(3) и Connection(1/2) не совпадают, но Channel может быть одним и тем же.

Следовательно, как поступить, если канал отключен и переобучен, конкретный метод состоит в том, чтобы сохранить SessionId в канале, и каждый запрос события оценивает, есть ли SessionId в канале, и если есть SessionId в канале, он оценивается как отключенный и повторно подключенный Канал.

2. Сердцебиение

Пульс используется для определения того, жив ли еще подключенный клиент.Клиент отправляет пакет пульса на сервер через регулярные промежутки времени, и сервер обновляет время последнего доступа к сеансу после получения пульса. При обнаружении сеанса длительного соединения на стороне сервера опросите коллекцию сеансов, чтобы определить, истекло ли время последнего доступа.Если оно истекает, закройте сеанс и соединение, в том числе удалив его из памяти, и одновременно выйдите из канала время.

С помощью анализа исходного кода после успешного создания каждого сеанса к сеансу будет добавлен TcpHeartbeatListener для отслеживания обнаружения пульса. TcpHeartbeatListener — это поток демона, который реализует интерфейс SessionListener. Он опрашивает сеансы через обычный сон, чтобы проверить, есть ли просроченные сеансы. Если есть сеанс с истекшим сроком действия, сеанс закрывается.

В то же время обратите внимание на метод session.connect, в методе connect будут добавлены слушатели, добавленные сеансом, он будет циклически вызывать события sessionCreated всех листнеров, в которых также вызывается TcpHeartbeatListener во время этого процесса.

3. Восходящая линия передачи данных

Данные вверх по течению относятся конкретно к отправке данных от клиента к серверу, и данные получены из метода channelRead ChannelHander. Данные включают в себя создание сеансов, отправку тактов, запросы данных и т. д. Здесь следует отметить, что данные channelRead включают в себя данные, которые клиент активно запрашивает у сервера, и данные, возвращаемые нисходящим уведомлением сервера клиенту, поэтому при обработке данных объекта идентификатор данных используется, чтобы отличить, является ли он представляет собой запрос-ответ или уведомление-ответ.

4. Нисходящий канал данных

Нисходящие данные отправляются на все серверы через механизм широковещательной рассылки MQ.После того, как все серверы получат сообщение, они получают все сеансы сеанса, удерживаемые текущим сервером, и выполняют уведомление о широковещательной передаче данных по нисходящей линии. Если это двухточечный нисходящий канал передачи данных, данные сначала передаются на все серверы.Каждый день сервер оценивает, является ли переданный конец сеансом, удерживаемым текущим сервером.Если он считает, что информация в сообщении данные находятся в текущей службе, отправьте их, в противном случае отбросьте.

С помощью анализа исходного кода нисходящая линия данных отправляет данные через NotifyProxy.Следует отметить, что Netty — это NIO.Если уведомление нисходящей линии связи должно получить возвращаемое значение, оно должно быть асинхронным по отношению к синхронизации, поэтому NotifyFuture — это метод для реализации java.util. concurrent.Future , установив время ожидания, после того как channelRead получит данные восходящей линии связи, свяжите метод NotifyFuture с помощью seq.

Данные нисходящей линии связи отправляются через метод отправки TcpConnector, а метод отправки заключается в записи в канал с помощью метода writeAndFlush ChannelHandlerContext и реализации нисходящей линии данных.Здесь следует отметить, что ранее был другой метод записи, ср. await, который блокируется блокировкой.Чтобы определить, успешна ли запись, этот способ записи иногда используется, за исключением BlockingOperationException.

Как использовать блокировку для получения возвращаемого значения

Я задал вопрос об исключении BlockingOperationException на StackOverflow, и мне посчастливилось получить ответ от Нормана Маурера (одного из основных участников Netty).

Окончательный вывод грубо анализирует, что при выполнении метода записи Netty определит, является ли текущий поток EventLoop, назначенным каналу, и если да, то поток выполнит операцию ввода-вывода, иначе исполнитель будет отправлен на выделение. При выполнении метода await поток выполнения будет получен от исполнителя. Здесь требуется checkDeadLock, чтобы определить, являются ли поток выполнения и текущий поток одним и тем же потоком. Если это так, он будет обнаружен как взаимоблокировка и будет выброшено исключение BlockingOperationException.

Суммировать

В этой статье кратко представлена ​​архитектура TCP-шлюза Jingmai с использованием Netty для реализации контейнера постоянного соединения, последовательно излагаются ключевые моменты, связанные с созданием контейнера постоянного соединения TCP, и кратко анализируется исходный код. В процессе разработки Jingmai у Netty есть много практических приложений, таких как Netty4.11 + HTTP2 для отправки сообщений APN и так далее.

об авторе

Чжан Сонгран, архитектор торгового отдела R&D торгового центра Jingdong Mall. Богатый опыт в исследованиях и разработках и архитектуре построения высокопроизводительных, высокодоступных крупномасштабных распределенных систем. Он присоединился к JD.com в 2013 году и в настоящее время отвечает за системные исследования и разработку сервисного шлюза Jingmai.

благодарныйЮтада ХикаруОбзор этой статьи.