Автор | Сюй Гуанмин
Миндальный бэкэнд-инженер. Молодые программисты, сосредоточьтесь на серверных технологиях и пестицидах.
содержание
С постоянным улучшением производительности компьютерного оборудования количество ядер ЦП сервера увеличивается.Чтобы в полной мере использовать вычислительную мощность многоядерных ЦП и повысить эффективность обработки и одновременную производительность системы, многопоточное параллельное программирование становится все более важным. Большинство сетевых фреймворков, написанных на C++ или Java, проектируются и разрабатываются на основе режима Reactor. Режим Reactor управляется событиями и особенно подходит для обработки массовых событий ввода-вывода. Сегодня мы кратко поговорим о модели потоков Reactor, Основное содержание разделено на следующие части:
-
Классическая модель связи ввода/вывода;
-
Детали модели потока реактора;
-
Модель потока Reactor в нескольких режимах;
-
Практика потоковой модели Netty Reactor;
Модель связи ввода-вывода
Давайте сначала поговорим о связи ввода-вывода. Когда дело доходит до связи ввода-вывода, часто существует четыре типа синхронного ввода-вывода, асинхронного ввода-вывода, блокирующего ввода-вывода и неблокирующего ввода-вывода. Разница между синхронизацией, асинхронностью, блокировкой и неблокировкой часто неясна, разные люди имеют разный уровень знаний, и трудно прийти к единому мнению по поводу концепции. Предыстория, обсуждаемая в этой статье, — это сетевой ввод-вывод в среде Linux.
Один анализ процесса ввода/вывода
Для сетевого ввода-вывода (в качестве примера возьмем read) будут задействованы два системных объекта: один — процесс или поток, вызывающий ввод-вывод, а другой — системное ядро (ядро). Когда происходит операция чтения, она проходит две фазы (важно помнить об этих двух фазах, потому что разница между разными моделями ввода-вывода заключается в том, что на каждой фазе они обрабатываются по-разному):
-
Первый этап: ожидание готовности данных;
-
Второй этап: копирование данных из ядра в процесс;
Пять моделей ввода/вывода
В томе UNIX® Network Programming Volume Ричарда Стивенса упоминаются 5 моделей ввода-вывода:
-
Блокирующий ввод/вывод (синхронный блокирующий ввод/вывод)
-
Неблокирующий ввод-вывод (синхронный неблокирующий ввод-вывод)
-
Мультиплексирование ввода/вывода
-
Ввод-вывод, управляемый сигналом (ввод-вывод, управляемый сигналом, редко используется на практике, не поддерживается Java)
-
Асинхронный ввод-вывод
Далее мы объясним и сравним пять моделей ввода/вывода.
Blocking I/O
В Linux все Сокеты по умолчанию блокируются, то есть блокируются. Во время типичной операции чтения поток выглядит следующим образом:
Когда пользовательский процесс вызывает системный вызов recvfrom, вызов ввода-вывода проходит следующие два этапа:
-
Подготовка данных: для сетевых запросов много раз данные не поступали в начале (например, не был получен полный UDP-пакет), в это время ядру приходится ждать поступления достаточного количества данных. На стороне пользовательского процесса весь процесс будет заблокирован.
-
Возврат данных: как только ядро ожидает готовности данных, оно скопирует данные из ядра в пользовательскую память, а затем ядро вернет результат, а пользовательский процесс освободит состояние блока и снова запустится.
Nonblocking IO
Под Linux можно сделать сокет неблокирующим, то есть неблокирующим. При выполнении операции чтения на неблокирующем сокете последовательность действий выглядит следующим образом:
Когда пользовательский процесс выполняет операцию чтения, конкретный процесс делится на следующие три процесса:
-
Начать подготовку данных: если данные в Ядре не готовы, оно не заблокирует пользовательский процесс, а сразу вернет ошибку.
-
Подготовка данных: с точки зрения пользовательского процесса, после того, как он инициирует операцию чтения, ему не нужно ждать, а результат будет получен немедленно. Когда пользовательский процесс считает, что результатом является ошибка, он знает, что данные не готовы, поэтому он может снова отправить операцию чтения (повторное вращение).
-
Как только данные в ядре готовы и снова получен системный вызов от пользовательского процесса, он немедленно копирует данные в пользовательскую память и возвращается.
I/O multiplexing
Этот тип ввода-вывода также можно назвать вводом-выводом, управляемым событиями. Преимущество select/epoll в Linux заключается в том, что один процесс может одновременно обрабатывать ввод-вывод нескольких сетевых подключений. Его основной принцип заключается в том, что select/epoll будет постоянно опрашивать все сокеты, за которые он отвечает, и уведомлять пользовательский процесс, когда в сокет поступают данные. Процесс показан на рисунке:
Когда пользовательский процесс вызывает select:
-
Весь процесс будет заблокирован, и при этом ядро будет "мониторить" все сокеты, за которые отвечает select.Когда данные в любом сокете будут готовы, select вернется.
-
Затем пользовательский процесс вызывает операцию чтения для копирования данных из ядра в пользовательский процесс. На данный момент она мало чем отличается от блокирующей диаграммы ввода/вывода, а даже хуже. Потому что здесь необходимо использовать два системных вызова (select и recvfrom), а блокирующий ввод-вывод вызывает только один системный вызов (recvfrom).
-
На практике в модели мультиплексирования ввода-вывода каждый сокет обычно настроен как неблокирующий, но, как показано на рисунке выше, весь пользовательский процесс фактически все время блокируется. Просто процесс блокируется функцией выбора, а не блоком ввода-вывода сокета.
Asynchronous IO
Асинхронный ввод-вывод под Linux, то есть асинхронный ввод-вывод, используется редко (требуется поддержка системы высокого уровня). Его процесс выглядит следующим образом:
Когда пользовательский процесс выполняет операцию чтения, конкретный процесс:
-
После того, как пользовательский процесс инициирует операцию чтения, ему не нужно ждать, он сразу же получает результат и может сразу приступить к другим действиям.
-
С точки зрения ядра, когда оно получает асинхронное чтение, оно немедленно возвращается, поэтому оно не будет генерировать никакого блока для пользовательского процесса. Затем ядро будет ждать, пока данные будут готовы, а затем скопирует данные в память пользователя.Когда все это будет сделано, ядро пошлет сигнал пользовательскому процессу, чтобы сообщить ему, что операция чтения завершена.
Обобщите их соответствующие функции с помощью четырех вышеупомянутых моделей связи ввода-вывода.
-
Блокирующий ввод-вывод характеризуется блокировкой на обеих фазах выполнения ввода-вывода.
-
Неблокирующий ввод-вывод характеризуется отсутствием блокировки, если данные ядра не готовы.
-
Преимущество мультиплексирования ввода-вывода заключается в том, что оно может одновременно обрабатывать несколько соединений с помощью select. (Если количество обрабатываемых соединений не очень велико, веб-сервер, использующий select/epoll, не обязательно лучше, чем веб-сервер, использующий многопоточность + блокирующий ввод-вывод, и задержка может быть больше. Преимущество select/epoll Это не быстрее для одного соединения, а для большего количества соединений.)
-
Характерной чертой асинхронного ввода-вывода является то, что клиент не имеет состояния блокировки в течение всего процесса вызова, но ему требуется поддержка более высокой версии системы.
модель общения в жизни
Введение вышеописанных пяти моделей ввода-вывода, как-то скучно, на самом деле в жизни есть похожие «модели общения».Чтобы помочь пониманию, мы используем неуместный пример, когда мы просим девушку поесть на ужин, чтобы проиллюстрировать эти Модели ввода-вывода. /Модель вывода (Предположим, я хочу использовать WeChat, чтобы позвонить нескольким девушкам, чтобы поесть прямо сейчас):
-
Отправьте WeChat, чтобы спросить, готова ли первая девушка, девушка будет ждать, пока не будет отправлен второй ответ (блокирующий ввод-вывод).
-
Отправьте WeChat, чтобы спросить, в порядке ли первая девушка, если девушка не отвечает, оставьте его в покое и отправьте его второй, но вы продолжите спрашивать, в порядке ли девушка, которая не ответила раньше (неблокирующий ввод/вывод).
-
Соберите все родственные документы в группу WeChat и один раз спросите в группе, кто готов ответить (мультиплексирование ввода/вывода).
-
Напрямую сообщите сестре на бумаге время и адрес приема пищи, и вы можете пойти сами (асинхронный ввод-вывод).
Модель резьбы Reactor
Что такое Реактор?
Реактор - это режим обработки.Реакторный режим является относительно распространенным режимом для обработки параллельного ввода-вывода. Он используется для синхронизации ввода-вывода. Основная идея заключается в регистрации всех событий ввода-вывода для обработки на центральном мультиплексоре ввода-вывода, в то время как основной поток/процесс блокируется на мультиплексоре; как только событие ввода-вывода поступает или готово (файловый дескриптор или сокет для чтения и записи), мультиплексор возвращает и отправляет соответствующее событие ввода-вывода, зарегистрированное заранее, в соответствующий процессор.
Реактор также является механизмом реализации.Reactor реализован с использованием событийно-управляемого механизма, который отличается от обычных вызовов функций: приложение не вызывает активно API для завершения обработки, а, наоборот, Reactor реверсирует процесс обработки события, и приложению необходимо предоставить соответствующие interface и Registered with Reactor, если произойдет соответствующее событие, Reactor будет активно вызывать интерфейс, зарегистрированный приложением, эти интерфейсы также называются «функциями обратного вызова». «Голливудский принцип» — лучший способ описать Reactor: не звоните нам, мы позвоним вам.
Зачем использовать Реактор?
Вообще говоря, за счет мультиплексирования ввода-вывода режим epoll уже может заставить сервер иметь сотни тысяч одновременных подключений при сохранении очень высокого TPS.Зачем вам нужен режим Reactor? Причина в том, что сложность программирования собственного мультиплексирования ввода-вывода относительно высока.
Каждый сетевой запрос может включать в себя несколько запросов ввода-вывода.По сравнению с традиционным однопоточным методом полной обработки жизненного цикла запроса, мультиплексирование ввода-вывода не является естественным для человеческого мозга, потому что в программировании программиста при обработке запроса A, предполагается, что запрос должен пройти несколько операций ввода-вывода A1-An (два ввода-вывода могут быть разделены на длительное время).После каждой операции ввода-вывода, когда вызывается мультиплексирование ввода-вывода, ввод-вывод В ответ вызова мультиплексирования /O, весьма вероятно, что A больше нет, но возвращается запрос B. То есть запрос A часто прерывается запросом B, а когда запрос B обрабатывается, он снова прерывается запросом C. С таким мышлением программирование подвержено ошибкам.
Модель резьбы Reactor
В Reactor есть три модели многопоточности, и пользователи могут выбрать подходящую модель для своей среды.
-
однопоточная модель
-
Многопоточная модель (один Reactor)
-
Многопоточная модель (несколько реакторов)
однопоточный режим
Однопоточный режим — это простейшая модель Reactor. Поток Reactor является универсальным, ответственным за демультиплексирование сокетов, прием новых соединений и отправку запросов в цепочки обработчиков. Эта модель подходит для сценариев, в которых компоненты бизнес-процессов в цепочке процессоров могут быть завершены быстро. Однако эта однопоточная модель не может в полной мере использовать многоядерные ресурсы, поэтому на практике она мало используется.
Многопоточный режим (один Reactor)
Эта модель использует многопоточность (пул потоков) в части цепочки обработчиков событий (Handler), а также является широко используемой моделью для серверных программ.
Многопоточный режим (multi-Reactor)
По сравнению с многопоточной моделью Single Rector, она делит Reactor на две части: mainReactor отвечает за мониторинг и прием новых соединений, а затем отправляет установленный сокет на subReactor через мультиплексор (Acceptor). SubReactor отвечает за мультиплексирование подключенных сокетов, чтение и запись сетевых данных, функции бизнес-обработки, которые для завершения передаются пулу рабочих потоков. Обычно количество субреакторов может быть равно количеству процессоров.
Использование реактора
Многие продукты с открытым исходным кодом в области программного обеспечения используют модель Ractor, например Netty.
Практика Netty Reactor
Потоковая модель на стороне сервера
Поток мониторинга сервера отделен от потока ввода-вывода, как и в многопоточной модели Reactor, принцип его работы следующий:
Создание пользовательского потока сервера
-
Две группы EventLoopGroups были созданы при создании сервера. Группа потоков bossGroup на самом деле является пулом потоков Acceptor, который отвечает за обработку клиентского запроса TCP-подключения. workerGroup — это группа потоков, которая действительно отвечает за операции чтения и записи ввода-вывода. Отсюда вы можете узнать, что Netty — это модель с несколькими реакторами.
-
Класс ServerBootstrap — это вспомогательный класс, используемый Netty для запуска NIO, что удобно для разработки. Передайте группу потоков в ServerBootstrap через групповой метод, установите для канала значение NioServerSocketChannel, затем установите параметры TCP для NioServerSocketChannel и, наконец, привяжите класс обработки событий ввода-вывода ChildChannelHandler.
-
После того, как вспомогательный класс завершит настройку, вызовите метод bind для привязки порта прослушивания, Netty вернет ChannelFuture, а f.channel().closeFuture().sync() получит результат синхронной блокировки.
-
Вызовите группу потоков shutdownGracefully, чтобы корректно запустить и освободить ресурсы.
public class TimeServer {
public void bind(int port) {
// 配置服务端的NIO线程组
EventLoopGroup bossGroup = new NioEventLoopGroup();
EventLoopGroup workGroup = new NioEventLoopGroup();
try {
ServerBootstrap b = new ServerBootstrap();
b.group(bossGroup, workGroup).channel(NioServerSocketChannel.class)
.option(ChannelOption.SO_BACKLOG, 1024)
.childHandler(new ChildChannelHandler());
// 绑定端口,同步等待成功
ChannelFuture f = b.bind(port).sync();
// 等待服务端监听端口关闭
f.channel().closeFuture().sync();
} catch (Exception e) {
e.printStackTrace();
} finally {
// 释放线程池资源
bossGroup.shutdownGracefully();
workGroup.shutdownGracefully();
}
}
private class ChildChannelHandler extends ChannelInitializer<SocketChannel> {
@Override
protected void initChannel(SocketChannel ch) throws Exception {
ch.pipeline().addLast(new TimeServerHandler());
}
}}
скопировать код
Обработка потоков ввода/вывода сервера (TimeServerHandler)
-
Метод exceptionCaught: вызывается, когда возникает исключение при обработке ввода-вывода, закрывает ChannelHandlerContext и освобождает ресурсы.
-
Метод channelRead: это метод, который фактически обрабатывает чтение и запись данных, а также считывает данные запроса через buf.readBytes. Отправьте соответствующее сообщение клиенту через ctx.write(resp).
-
Метод channelReadComplete: для повышения производительности Netty write сначала записывает данные в буферный массив, а метод flush может отправлять все сообщения из буферного массива в SocketChannel.
public class TimeServerHandler extends ChannelHandlerAdapter {
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause)
throws Exception {
ctx.close();
}
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg)
throws Exception {
// msg转Buf
ByteBuf buf = (ByteBuf) msg;
// 创建缓冲中字节数的字节数组
byte[] req = new byte[buf.readableBytes()];
// 写入数组
buf.readBytes(req);
String body = new String(req, "UTF-8");
String currenTime = "QUERY TIME ORDER".equalsIgnoreCase(body) ? new Date(
System.currentTimeMillis()).toString() : "BAD ORDER";
// 将要返回的信息写入Buffer
ByteBuf resp = Unpooled.copiedBuffer(currenTime.getBytes());
// buffer写入通道
ctx.write(resp);
}
@Override
public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
// write读入缓冲数组后通过invoke flush写入通道
ctx.flush();
}
}
скопировать код
Суммировать
Узнайте о Reactor из приведенного выше. Наконец, я суммирую преимущества и недостатки использования модели Reactor.
-
преимущество
-
Отклик быстрый, хотя сам Reactor по-прежнему синхронен и не должен блокироваться одним временем синхронизации.
-
Программирование относительно простое, что позволяет в наибольшей степени избежать сложных проблем с многопоточностью и синхронизацией, а также избежать накладных расходов на переключение многопоточности/процесса.
-
Масштабируемость: увеличьте количество Reactors за счет параллельного программирования, чтобы полностью использовать ресурсы ЦП.
-
Возможность повторного использования, сама структура Reactor не имеет ничего общего с конкретной логикой обработки событий и имеет высокую степень повторного использования.
-
недостаток
-
По сравнению с традиционной простой моделью Reactor увеличивает определенную сложность, поэтому существуют определенные пороги, а отладка относительно сложна.
-
Для режима Reactor требуется поддержка базового демультиплексора синхронных событий, такого как Selector в Java, и поддержка системных вызовов select в операционной системе.
-
Однопоточный режим Reactor по-прежнему реализуется в том же потоке, когда ввод-вывод читает и записывает данные.Даже если используется механизм multi-Reactor, если в канале, который совместно использует Reactor, происходит долгосрочное чтение и запись данных, это повлияет на Reactor.Соответствующее время других каналов, например, при передаче больших файлов, операция ввода-вывода повлияет на соответствующее время других клиентов, поэтому для такого рода операций использование традиционного Thread-Per-Connection может быть лучшим выбором или использовать режим Proactor в это время.
полный текст
Вас также могут заинтересовать следующие статьи:
-
Исследование интеграционных тестов в среде микросервисов (1) — Service Stub & Mock
-
Исследование интеграционного тестирования в среде микросервисов (2) — контрактное тестирование
-
Путь стартапа к контейнеризации (3) - Будущее за контейнерами
-
Обработка сложных бизнес-состояний: от шаблонов состояний до конечных автоматов
Мы набираем инженеров Java Заинтересованные студенты могут отправить свои резюме на rd-hr@xingren.com.
Миндальная технологическая станция
Длительный нажмите QR-код слева, чтобы следить за нами, есть группа энтузиастов молодых людей, с нетерпением ждут встречи с вами.