Что такое Нетти
Начать с http
С Netty вы можете реализовать свой собственный HTTP-сервер, FTP-сервер, UDP-сервер, RPC-сервер, сервер WebSocket, прокси-сервер для Redis, прокси-сервер для MySQL и т. д.
Давайте рассмотрим принципы традиционных HTTP-серверов.
1. Создайте ServerSocket, прослушайте и привяжите порт
2. Серия клиентов для запроса этого порта
3. Сервер использует Accept для получения объекта подключения Socket от клиента.
4. Запустите новый поток для обработки соединения
4.1 Чтение сокета для получения потока байтов
4.2 Декодировать протокол и получить объект запроса Http
4.3. Обработайте запрос Http, получите результат и инкапсулируйте его в объект HttpResponse.
4.4 Протокол кодирования, сериализация результата в поток байтов Написать сокет, отправить поток байтов клиенту
5. Продолжайте повторять шаг 3.
Причина, по которой HTTP-сервер называется HTTP-сервером, заключается в том, что протокол кодирования и декодирования является протоколом HTTP.Если протокол является протоколом Redis, он становится сервером Redis, если протоколом является WebSocket, он становится сервером WebSocket и скоро. Используя Netty, вы можете настроить протокол кодека и реализовать свой собственный сервер для конкретного протокола.
NIO
Выше показан традиционный сервер, который обрабатывает http, но в среде с высокой степенью параллелизма количество потоков будет больше, а загрузка системы будет выше, поэтому есть NIO.
Прежде чем говорить о NIO, давайте поговорим о BIO (Blocking IO), как вы понимаете это Blocking?
- Когда клиент слушает (Listen), Accept блокируется, только когда приходит новое соединение, Accept возвращается, и основной поток может продолжаться.
- При чтении и записи сокетов чтение блокируется, и только когда приходит сообщение с запросом, чтение может вернуться, и дочерний поток может продолжить обработку.
- При чтении и записи сокетов запись блокируется, только когда клиент получает сообщение, запись может вернуться, и дочерний поток может продолжить чтение следующего запроса.
В традиционном режиме BIO все потоки блокируются от начала до конца, и эти потоки просто ждут, занимают системные ресурсы и ничего не делают.
Так как же NIO обеспечивает неблокировку? Он использует механизм событий. Он может использовать один поток для завершения логики Accept, операций чтения и записи и обработки запросов. Если ничего не нужно делать, он не будет зацикливаться до бесконечности, а приостановит поток до наступления следующего события и продолжит работу Такой поток называется потоком NIO. В псевдокоде:while true {
events = takeEvents(fds) // 获取事件,如果没有事件,线程就休眠
for event in events {
if event.isAcceptable {
doAccept() // 新链接来了
} elif event.isReadable {
request = doRead() // 读消息
if request.isComplete() {
doProcess()
}
} elif event.isWriteable {
doWrite() // 写消息
}
}
}
Модель резьбы Reactor
Однопоточная модель Reactor
Один поток NIO + один поток принятия:
Многопоточная модель Reactor
Модель ведущий-ведомый реактора
Netty можно гибко настроить на основе трех вышеуказанных моделей.
Суммировать
Netty основана на NIO, а Netty обеспечивает более высокий уровень абстракции поверх NIO.
В Netty соединения Accept могут обрабатываться отдельным пулом потоков, а операции чтения и записи обрабатываются другим пулом потоков.
Прием соединений и операции чтения и записи также могут обрабатываться с использованием одного и того же пула потоков. Логика обработки запросов может обрабатываться с использованием отдельного пула потоков или вместе с потоками чтения и записи. Каждый поток в пуле потоков является потоком NIO. Пользователи могут собирать в соответствии с реальной ситуацией и создавать высокопроизводительную модель параллелизма, отвечающую системным требованиям.
Почему выбирают Нетти
Если вы не используете netty и используете собственный JDK, возникают следующие проблемы:
Для Нетти его API простой, высокопроизводительный и активный в сообществе (его используют dubbo, RocketMQ и т. д.)
Что такое склеивание/распаковка TCP
Феномен
Сначала посмотрите на следующий код.В этом коде используется netty для повторной записи данных со стороны клиента на сторону сервера 100 раз.ByteBuf — это байтовый контейнер netty, в котором хранятся данные, которые необходимо отправить.
public class FirstClientHandler extends ChannelInboundHandlerAdapter {
@Override
public void channelActive(ChannelHandlerContext ctx) {
for (int i = 0; i < 1000; i++) {
ByteBuf buffer = getByteBuf(ctx);
ctx.channel().writeAndFlush(buffer);
}
}
private ByteBuf getByteBuf(ChannelHandlerContext ctx) {
byte[] bytes = "你好,我的名字是1234567!".getBytes(Charset.forName("utf-8"));
ByteBuf buffer = ctx.alloc().buffer();
buffer.writeBytes(bytes);
return buffer;
}
}
Данные, считанные со стороны клиента:
Как видно из консольного вывода сервера, есть три вида вывода
- Один из них — обычный строковый вывод.
- Во-первых, несколько строк «липнут» вместе, и мы определяем этот ByteBuf как липкий пакет.
- Во-первых, строка «распаковывается» для формирования сломанного пакета, и мы определяем этот ByteBuf как полупакет.
Анализировать причину через явление
Прикладной уровень использует Netty, но для операционной системы он распознает только протокол TCP.Хотя наш прикладной уровень отправляет данные в единицах ByteBuf, а сервер считывает их в соответствии с Bytebuf, они все равно отправляются в соответствии с байтовым потоком когда они поступают в нижележащую операционную систему.Поэтому, когда данные поступают на сервер, они также считываются в виде потока байтов, а затем на уровне приложения Netty они пересобираются в ByteBuf, и ByteBuf здесь может быть не равным ByteBuf, отправляемому клиентом последовательно. Следовательно, нам нужно собрать пакеты данных нашего прикладного уровня на стороне клиента в соответствии с пользовательским протоколом, а затем собрать пакеты данных в соответствии с нашим протоколом прикладного уровня на стороне сервера.Этот процесс обычно называется распаковкой на стороне сервера. , так и на стороне клиента. Концы называются липкими пакетами.
Как решить
В отсутствие Netty, если пользователю необходимо распаковать самостоятельно, основной принцип заключается в непрерывном чтении данных из буфера TCP.После каждого чтения необходимо судить, является ли это полным пакетом данных.Если текущие считанные данные недостаточно сплайсинга в полный пакет служебных данных, затем сохраните данные и продолжайте чтение из буфера TCP до тех пор, пока не будет получен полный пакет данных. Если текущих прочитанных данных плюс уже прочитанных данных достаточно для объединения в пакет данных, то уже прочитанные данные объединяются с данными, прочитанными на этот раз, для формирования полного пакета бизнес-данных и передаются в бизнес-логику. , Данные по-прежнему сохраняются, чтобы в следующий раз попытаться выполнить сплайсинг с прочитанными данными.
В Netty встроено множество типов распаковщиков, мы просто используем их напрямую:
После выбора распаковщика просто добавьте распаковщик в chanelPipeline в клиентской части и серверной части кода:
В приведенном выше примере:
Клиент:
ch.pipeline().addLast(new FixedLengthFrameDecoder(31));
Сервер:
ch.pipeline().addLast(new FixedLengthFrameDecoder(31));
Нулевая копия Нетти
традиционная копия
При отправке данных традиционная реализация:
1. `File.read(bytes)`
2. `Socket.send(bytes)`
Этот подход требует четырех копий данных и четырех переключателей контекста:
1. Данные считываются с диска в буфер чтения ядра.
2. Данные копируются из буфера ядра в пользовательский буфер3. Данные копируются из пользовательского буфера в буфер сокета ядра.
4. Данные копируются из буфера сокета ядра в буфер интерфейса сетевой карты (аппаратно)
Концепция нулевой копии
Очевидно, что второй и третий шаги выше не нужны.С помощью метода FileChannel.transferTo из java можно избежать двух вышеупомянутых избыточных копий (конечно, для этого требуется поддержка базовой операционной системы).
Нулевая копия в Netty
Netty в основном использует bytebuffer для отправки и получения сообщений, а bytebuffer использует внешнюю память (DirectMemory) для прямого чтения и записи Socket.
Внутренний процесс выполнения Netty
Сервер:
клиент
Суммировать
Вышеизложенное является моим знанием Netty.Если у вас есть разные мнения, добро пожаловать в обсуждение!