Netty
Netty — это библиотека сетевого взаимодействия, построенная вокруг неблокирующего ввода-вывода и модели event loop.
В Kora она используется как низкоуровневый сетевой транспорт для модулей, которым требуется эффективно обрабатывать соединения и сетевые события.
Модуль не предоставляет собственного пользовательского программного интерфейса.
Он существует для того, чтобы все части приложения, работающие поверх Netty, использовали один event loop и одно решение о транспорте,
а не поднимали каждый свои потоки ввода-вывода.
Поверх него построен драйвер кэша Redis, и собственный транспорт Netty внутри сервиса можно построить на тех же самых компонентах.
Эти настройки полезны, когда приложению требуется управлять сетевым транспортом, количеством потоков ввода-вывода
или выбором платформенного транспорта.
Значения по умолчанию подходят большинству сервисов, но их можно задать явно при высокой сетевой нагрузке или особых требованиях окружения.
Модуль настраивает транспорт Netty и цикл событий Netty в рамках Kora.
Подключение¶
Обычно модуль не требуется подключать вручную: модули Kora, которым нужен Netty, сами наследуют NettyModule
и приносят его как транзитивную зависимость.
Модуль кэша Redis — как раз такой случай: LettuceRedisCacheModule наследует LettuceModule,
а LettuceModule наследует NettyModule.
Подключать модуль явно нужно только тогда, когда внутри сервиса строится собственный транспорт Netty,
который должен разделять event loop со всем остальным приложением:
Зависимость build.gradle:
Модуль:
Зависимость build.gradle.kts:
Модуль:
Что предоставляется¶
NettyModule добавляет в контейнер зависимостей следующие общие компоненты:
NettyTransportConfig— конфигурация, привязанная к секцииnetty: предпочитаемый транспорт и количество потоков группevent loop.- Рабочая
EventLoopGroupс тегом@Tag(NettyModule.EventLoopWorker.class)— общийevent loop, который обрабатывает установленные соединения и сетевой ввод-вывод. На нем работают клиенты поверх Netty. EventLoopGroupприема соединений (boss) с тегом@Tag(NettyModule.EventLoopBoss.class)— отдельныйevent loop, предназначенный для серверныхbootstrap, принимающих входящие соединения, чтобы прием соединения не задерживался вводом-выводом по уже установленным.NettyEventLoopFactoryс теми же двумя тегами — фабрики, из которых собираются группы выше.NettyChannelFactory— фабрика, создающая каналы Netty, соответствующие выбранному транспорту.
Обе группы собираются из одного и того же выбора транспорта и имеют одинаковый размер, заданный параметром threads,
поэтому рабочие и boss-потоки всегда одного вида.
Обе группы event loop управляются жизненным циклом Kora:
они корректно останавливаются после освобождения всех зависящих от них компонентов, поэтому ручное управление не требуется.
Фабрики event loop, обе группы и NettyChannelFactory зарегистрированы как компоненты по умолчанию,
поэтому любой из них можно заменить, объявив компонент того же типа с тем же тегом.
Компоненты, которые строят собственный транспорт Netty, внедряют эти компоненты напрямую:
@Component
public final class MyNettyTransport {
private final Bootstrap bootstrap;
public MyNettyTransport(@Tag(NettyModule.EventLoopWorker.class) EventLoopGroup workerGroup,
NettyChannelFactory channelFactory) {
this.bootstrap = new Bootstrap()
.group(workerGroup)
.channelFactory(channelFactory.build());
}
}
Конфигурация¶
Пример конфигурации, описанной в интерфейсе NettyTransportConfig:
- Предпочитаемый транспорт:
NIO,EPOLL,KQUEUEилиURING(необязательный, если не указан, транспорт выбирается автоматически). - Количество потоков в каждой группе
event loop(по умолчанию: количество процессоров, доступных Netty, умноженное на2). Рабочая и boss-группы имеют одинаковый размер, заданный этим значением.
- Предпочитаемый транспорт:
NIO,EPOLL,KQUEUEилиURING(необязательный, если не указан, транспорт выбирается автоматически). - Количество потоков в каждой группе
event loop(по умолчанию: количество процессоров, доступных Netty, умноженное на2). Рабочая и boss-группы имеют одинаковый размер, заданный этим значением.
Количество потоков по умолчанию берется из NettyRuntime.availableProcessors() самой Netty: она опирается на
Runtime.availableProcessors(), но это значение можно переопределить системным свойством io.netty.availableProcessors.
Задавайте это свойство или threads явно, если лимит процессоров контейнера не совпадает с тем, что видит JVM.
Корневая секция netty относится только к этому модулю.
Драйверы со встроенным стеком Netty настраивают его в своей собственной секции:
например, у драйвера Cassandra есть вложенная секция cassandra.advanced.netty,
которая настраивает собственные группы event loop драйвера и не имеет отношения к общему транспорту, описанному здесь.
Транспорт¶
Параметр transport задает предпочитаемый транспорт Netty:
NIO- стандартныйтранспортJava NIO, доступен всегда.EPOLL-платформенный транспортLinux.KQUEUE-платформенный транспортmacOS / BSD.URING-платформенный транспортLinuxio_uring.
Если параметр transport не задан, Kora выбирает первый доступный во время выполнения транспорт в порядке:
URINGEPOLLKQUEUENIO
Доступность платформенного транспорта означает сразу две вещи: его классы присутствуют в пути классов
и Netty сообщает, что платформа поддерживается.
Если указанный платформенный транспорт не проходит любую из этих проверок, Kora переходит к тому же порядку выбора, а не падает при старте.
NIO — единственное значение, которое всегда применяется ровно так, как указано, потому что у него нет проверки доступности.
Обратите внимание, что автоматический порядок предпочитает io_uring перед epoll:
добавление платформенного транспорта io_uring в путь классов времени выполнения Linux-сервиса меняет выбранный транспорт,
даже если в конфигурации ничего не менялось.
Задавайте transport явно, если выбор должен быть одинаковым во всех окружениях.
Один и тот же выбор применяется к обеим группам event loop и к фабрике каналов,
поэтому каналы и event loop, на котором они регистрируются, всегда относятся к одному транспорту.
Платформенный транспорт¶
Модуль компилируется с классами платформенного транспорта, но не приносит их во время выполнения,
поэтому для использования EPOLL, KQUEUE или URING соответствующая зависимость Netty должна присутствовать в пути классов времени выполнения:
| Транспорт | Артефакт | Платформа | Классификаторы |
|---|---|---|---|
EPOLL |
io.netty:netty-transport-native-epoll |
Linux | linux-x86_64, linux-aarch_64, linux-riscv64 |
KQUEUE |
io.netty:netty-transport-native-kqueue |
macOS / BSD | osx-x86_64, osx-aarch_64 |
URING |
io.netty:netty-transport-native-io_uring |
Linux | linux-x86_64, linux-aarch_64, linux-riscv64 |
Классификатор должен соответствовать целевой платформе, а версия — остальным артефактам io.netty в пути классов.
Kora 2.0 приносит Netty 4.2.17.Final:
Зависимость build.gradle:
Зависимость build.gradle.kts:
Платформенный артефакт транзитивно приносит соответствующий артефакт netty-transport-classes-*, объявлять его отдельно не требуется.
Сервис, собираемый под несколько платформ, объявляет по одному платформенному артефакту на платформу: они сосуществуют в пути классов,
а проверка доступности выбирает тот, который реально работает.
Совет
Обычно достаточно не задавать transport явно и оставить автоматический выбор.
Платформенный транспорт стоит подключать осознанно: например, если он нужен для производительности или для возможностей Netty, недоступных в NIO.
Для нативного образа GraalVM модуль поставляет метаданные, которые инициализируют классы io.netty во время выполнения,
что и требуется платформенным транспортам.
Фабрика каналов¶
NettyChannelFactory — это общий внедряемый компонент, который создает экземпляры ChannelFactory Netty, соответствующие выбранному транспорту.
Это точка внедрения для компонентов, которые строят собственный клиентский или серверный bootstrap Netty и хотят получать каналы, согласованные с выбранным транспортом:
build()/build(boolean domainSocket)—ChannelFactory<Channel>для клиентских каналов.buildServer()/buildServer(boolean domainSocket)—ChannelFactory<ServerChannel>для серверных каналов.
Перегрузки без аргументов делегируют вызов перегрузкам с boolean при domainSocket = false и создают стандартные сокет-каналы TCP.
Передача domainSocket = true запрашивает канал Unix domain socket:
каждый транспорт, включая NIO, предоставляет реализацию как клиентского, так и серверного канала домен-сокета.
Фабрика event loop¶
NettyEventLoopFactory — фабрика с единственным методом, который собирает EventLoopGroup для выбранного транспорта
с заданным количеством потоков.
Kora регистрирует две такие фабрики с тегами @Tag(NettyModule.EventLoopWorker.class) и @Tag(NettyModule.EventLoopBoss.class)
и собирает из них две общие группы.
Внедряйте фабрику вместо группы, когда компоненту нужна изолированная EventLoopGroup, а не общая —
например, когда его ввод-вывод не должен конкурировать с остальным приложением:
@Component
public final class MyIsolatedTransport implements Lifecycle {
private final EventLoopGroup eventLoopGroup;
public MyIsolatedTransport(@Tag(NettyModule.EventLoopWorker.class) NettyEventLoopFactory factory) {
this.eventLoopGroup = factory.build();
}
@Override
public void init() { }
@Override
public void release() throws Exception {
eventLoopGroup.shutdownGracefully().get();
}
}
Созданная так группа не управляется контейнером, поэтому за ее остановку отвечает создавший ее компонент — как в примере выше, через жизненный цикл компонента.
Фабрика потоков¶
Каждая группа event loop собирается с ThreadFactory.
По умолчанию Kora использует DefaultThreadFactory из Netty,
которая создает потоки-демоны с именами на основе netty-kora-worker для рабочей группы и netty-kora-boss для boss-группы.
Чтобы настроить именование или приоритет потоков, предоставьте компонент ThreadFactory с тегом той группы, к которой он относится.
Теги разные, поэтому рабочие и boss-потоки настраиваются независимо,
а если предоставить только один из них, второй останется на значении по умолчанию:
@KoraApp
public interface Application extends LettuceRedisCacheModule {
@Tag(NettyModule.EventLoopWorker.class)
default ThreadFactory nettyWorkerThreadFactory() {
return new DefaultThreadFactory("netty-worker", true);
}
@Tag(NettyModule.EventLoopBoss.class)
default ThreadFactory nettyBossThreadFactory() {
return new DefaultThreadFactory("netty-boss", true);
}
}
@KoraApp
interface Application : LettuceRedisCacheModule {
@Tag(NettyModule.EventLoopWorker::class)
fun nettyWorkerThreadFactory(): ThreadFactory = DefaultThreadFactory("netty-worker", true)
@Tag(NettyModule.EventLoopBoss::class)
fun nettyBossThreadFactory(): ThreadFactory = DefaultThreadFactory("netty-boss", true)
}
Собственные фабрики потоков Kora намеренно создают потоки-демоны: группы event loop освобождаются контейнером,
и потоки-демоны не должны удерживать JVM живой, если путь остановки был пропущен.
Пользовательской ThreadFactory стоит сохранить это свойство.