Статьи

Whirlpool: микросервисы с использованием Netty и Kafka

Вступление

В моем последнем блоге я представил Netty, который используется в качестве веб-сервера. Этот пример работал хорошо … до тех пор, пока необходим широковещательный сервер.

Большую часть времени это не очень полезно. Более вероятно, что каждый клиент должен получать только данные, предназначенные для него, а трансляции зарезервированы для особых обстоятельств, таких как «Сервер отключается через 15 минут!» Другой особенностью этого конкретного примера сервера было то, что все было автономно. Монолитные приложения хороши для примеров, но в современных средах распределенные микросервисы намного лучше. Масштабируемость и надежность имеют первостепенное значение.

Нетти и Кафка великолепны вместе. Нетти отлично справляется с огромным количеством клиентов, а Кафка отлично справляется с совместной работой множества служб. В совокупности они — сладкое место в развитии. Тем не менее, есть некоторые «ошибки», которые могут сделать его громоздким. Надеемся, что этот блог, наряду с примером архитектуры microservice / Netty и полностью работающим кодом, поможет смягчить раздражения и придаст сладости.

Перво наперво

Код для примера находится здесь .

Существует подробный README, в котором описано, что необходимо для настройки среды. Я старался держать требования как минимум, просто Java 8 и Maven . SLF4J и Logback используются для регистрации. Я установил скрипты для Mac OSX и Ubuntu (14.04, запущенный в контейнере Parallels — это то, с чем я тестировал), поэтому прошу прощения, если вы разрабатываете на Windows. Весь код написан на Java, и я видел там руководства по Kafka для Windows, поэтому все должно работать там. Сборка Maven также должна создавать цели, которые можно запускать, поэтому, установив Zookeeper / Kafka с небольшой консистентной смазкой (вы можете следить за сценарием, чтобы узнать, какие настройки необходимы), запускать его вручную не составит большого труда. Окна.

ПРИМЕЧАНИЕ. Как объясняется в README.md , скрипт удалит все существующие установки и данные Zookeeper / Kafka. Если у вас есть существующая установка, не используйте скрипт!

После установки и настройки предварительных требований, запустите mvn package если вы не используете скрипт, или maclocal_run.sh (или linuxlocal_run.sh ), если вы используете. Сценарий загружает (если он еще не загружен) Zk / Kafka, устанавливает их, настраивает их, запускает, запускает mvn package , запускает службы и, наконец, запускает сервер. Как только он запустится, не поддавайтесь желанию отойти от оболочки, потому что он автоматически открывает новые вкладки для каждой части архитектуры. После запуска сервера Whirlpool вы готовы к работе.

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

Как тизер, вот пользовательский интерфейс (вы также можете увидеть это из README.md на GitHub).

джакузи

  • Чтобы добавить символ акции, введите его (т. Е. «GOOG») и нажмите кнопку «А» под «Акции». Чтобы удалить его, нажмите X.
  • Чтобы добавить веб-сайт для проверки его работоспособности или отсутствия, введите полный URL-адрес (например, http://facebook.com ) и нажмите кнопку A в разделе «UpDown». Чтобы удалить его, нажмите X.
  • Чтобы добавить проверку погоды, введите город, штат (например, «chicago, il») и нажмите кнопку «А» в разделе «Город, штат». Чтобы удалить его, нажмите X.
  • Подписки сохраняются после обновления страницы и даже входа / выхода (с тем же идентификатором пользователя), поскольку они хранятся в памяти каждого сервиса. «Реальная» система, конечно, будет использовать базу данных.
  • Подписки обновляются каждые 10 секунд, поэтому я не перегружаю API Yahoo, поэтому наберитесь терпения после добавления данных.

Архитектура

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

Я выбрал настройку Kafka с темой команд для каждой службы и темой данных для каждой службы. Все может также просто использовать одну глобальную тему, когда читатели решают, что обрабатывать, но разделение вещей делает это более ясным и чистым.

Вот диаграмма того, как данные проходят через Kafka. Это было сделано с помощью бесплатной веб-утилиты Keyhole под названием Mockola . Обратите внимание, что сервер знает обо всех темах, а службы — только о своих темах. cmd используются для отправки команд сервисам, а разделы с данными (те, у которых нет -cmd ) используются для отправки данных из сервисов. Опять же, все это может быть обработано по одной теме bus , но гораздо проще увидеть, что происходит, выделив их.

whirlpool_architecture

Сервисы

Теперь поговорим об услугах. Все три очень похожи, поэтому есть основная служба, которая выполняет большую часть работы. Каждый сервис имеет три потока, которые обрабатываются Java ExecutorService . Приятной особенностью службы Executor является то, что она автоматически перезапускает поток, если что-то идет не так. Это помогает устойчивости.

Каждый сервис запускается сам, сообщая базовому классу, какую тему и командную тему использовать. Затем базовый класс запускает три потока: один для чтения команд из темы cmd, один для периодического сбора данных для клиентов и один для отправки данных в теме данных. Эти потоки взаимодействуют с использованием неблокирующих классов ConcurrentLinkedQueue Java ConcurrentLinkedQueue и ConcurrentHashMap . В хэш-карте хранятся наборы подписок для каждого пользователя, а в очереди хранятся ответы, готовые для отправки в раздел данных.

Поток для каждого сервиса — это три потока, работающие одновременно. Reader использует Kafka Consumer для чтения команд из своего раздела команд. На основании команды подписка добавляется или удаляется. Этот поток довольно глуп, потому что он не просит службу выполнять какую-либо проверку по запросу, он просто слепо добавляет все, что отправлено в подписку. Рабочий код, очевидно, добавит вызов, чтобы попросить службу проверить команду, прежде чем разрешить подписку успешно. Создается ответ для добавления в тему, а затем он ждет следующей команды.

ПРИМЕЧАНИЕ . Несколько слов о данных, размещаемых по темам. Я использую JSON в качестве транспортного формата, но XML или что-то еще, что вы хотите, тоже будет работать. Важно то, что все согласны с форматом данных и придерживаются его. Общий модуль имеет классы POJO, которые определяют контракты, которым будут соответствовать данные. Вещи, которые обычно полезны для всех сообщений, — это временная метка, тип сообщения и идентификатор клиента.

Еще одна полезная вещь будет метка времени истечения. Эти примеры сообщений просто живут вечно. Класс Message просматривает только тип и идентификатор сообщения. Это используется сервером, чтобы определить, какой тип сообщения должен быть обработан, и кто заинтересован в сообщении. Без этого очень трудно, если не невозможно, обрабатывать данные. Теперь форматы сообщений могут быть довольно сложными, некоторые используют заголовки и разделы для описания сложных данных. Этот пример пытается сделать все максимально простым.

Netty Server

Давайте пройдемся по серверу по одному классу за раз.

NettyHttpFileHandler

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

WebSocketHelper

Первый элемент, который может сбить с толку — это переменная класса clientAttr . Хранение данных в канале Netty требует их подключения к AttributeKey . Это похоже на экземпляр Atomic из параллельных классов Java — он предоставляет контейнер для данных. Мы будем хранить идентификатор клиента (в нашем случае имя пользователя, но это может быть также и идентификатор сеанса), чтобы мы могли выяснить, какой канал должен получать сообщения.

Метод realWriteAndFlush() устанавливает соответствующие заголовки, длину содержимого и файл cookie. Затем он пишет и сбрасывает ответ HTTP. линия

1
channel.writeAndFlush(LastHttpContent.EMPTY_LAST_CONTENT);

сообщает Netty, что это конец данных, которые нужно записать клиенту, поэтому Netty отправит их.

СПЕЦИАЛЬНОЕ ПРИМЕЧАНИЕ : Что касается создания файлов cookie, убедитесь, что флаг HTTP Only НЕ установлен. Если это так, JavaScript не может увидеть cookie, и при этом он не будет отправлен с запросом на обновление WebSocket. Это позволяет создавать собственный метод управления обновлением страниц и сессий.

Еще одна вещь, касающаяся файлов cookie, заключается в использовании STRICT-версии кодировщика файлов cookie Netty, чтобы не допускать использования нескольких файлов cookie с одним и тем же именем. Я не уверен, когда было бы полезно допустить такую ​​ситуацию.

WebSocketMessageHandler

Этот класс просто определяет интерфейс, который WhirlpoolServerHandler использует для связи с WhirlpoolMessageHandler .

WhirlpoolMessageHandler

Здесь существует связь между Нетти и Кафкой. Два Исполнителя обрабатывают поток чтения и поток записи.

Поток писателя ищет сообщения в очереди запросов (подробнее о том, откуда эти сообщения приходят через минуту) и помещает сообщения в соответствующий раздел команд Kafka.

Поток читателей ищет входящие сообщения по темам данных Kafka, ищет правильный канал для каждой темы и записывает сообщения в эти темы.

Когда клиент отправляет сообщение через WebSockets, WhirlpoolServerHandler удостоверится в получении полного сообщения, а затем вызовет handleMessage() . Этот метод выясняет, является ли оно допустимым сообщением, затем добавляет запрос в очередь запросов, чтобы поток считывателей мог забрать его и передать Kafka.

WhirlpoolServerHandler

В этом классе есть несколько интересных вещей. Во-первых, он может определить разницу между сообщениями HTTP, REST и WebSocket. Метод переопределения Netty, который делает это, является channelRead0 . Это метод, который Netty использует, чтобы сообщить нам, когда приходит сообщение, и какой это тип сообщения. Для вызовов HTTP и REST вызывается handleHttpRequest , а для веб- handleWebSocketFrame вызывается handleWebSocketFrame .

Метод handleHttpRequest считывает куки-файл, если он есть. На POSTs он ищет логин и выход из системы. Для входа в систему он определяет имя пользователя / пароль, создает cookie и предотвращает несколько входов в систему с одним и тем же именем. Весь этот код будет разделен с дополнительной защитой, добавленной в производственную версию приложения. Для выхода из системы он ищет канал, очищает его, закрывает и истекает cookie.

Для WebSocketUpgrade он просит Netty обработать сложное рукопожатие, необходимое для запуска websocket. Когда это завершено, он добавляет пользователя в канал, который был создан во время рукопожатия. Это где пользователь подключен к каналу, и было бы не очень легко, если бы cookie не попал в запрос.

Единственное, на что следует обратить внимание, это то, что этот класс настроен для обработки клиентов, закодированных для SPA (одностраничного приложения), так как он перенаправляет любой нераспознанный вызов в index.html .

Другие методы в классе больше для информационных целей и будут использоваться в сложных ситуациях.

WhirlpoolServer

Этот класс запускает сервер Netty и создает конвейер канала. Это стандартный класс для Netty, который следует за примерами Netty.

Последние мысли

Очевидно, что в этот код можно добавить гораздо больше. Несколько экземпляров каждого сервиса и сервера могут быть запущены одновременно, а Zk / Kafka может быть кластеризован для обеспечения устойчивости. Отличная утилита, которая проверяет отказоустойчивость микросервисных приложений, — это еще одна бесплатная утилита Keyhole с открытым исходным кодом, которая называется TroubleMaker . У меня еще не было возможности протестировать этот пример, но я с нетерпением жду возможности.

Мы не касались безопасности, и хотя раньше я надеялся показать интеграцию Netty с Shiro , это очень сложная тема. Все, что я могу сказать об этом, — это то, что это возможно, но я еще не обнял все части настолько, чтобы сформулировать целостный блог.

Я надеюсь, что вам понравился блог и вы нашли код полезным. Свяжитесь со мной через блог или Twitter ( @johnwboardman, где я всегда ценю новых подписчиков ).