Анализ связи Linux IO и модели потока Reactor

Linux

Автор | Сюй Гуанмин

Миндальный бэкэнд-инженер. Молодые программисты, сосредоточьтесь на серверных технологиях и пестицидах.

содержание

С постоянным улучшением производительности компьютерного оборудования количество ядер ЦП сервера увеличивается.Чтобы в полной мере использовать вычислительную мощность многоядерных ЦП и повысить эффективность обработки и одновременную производительность системы, многопоточное параллельное программирование становится все более важным. Большинство сетевых фреймворков, написанных на C++ или Java, проектируются и разрабатываются на основе режима Reactor. Режим Reactor управляется событиями и особенно подходит для обработки массовых событий ввода-вывода. Сегодня мы кратко поговорим о модели потоков Reactor, Основное содержание разделено на следующие части:

  • Классическая модель связи ввода/вывода;

  • Детали модели потока реактора;

  • Модель потока Reactor в нескольких режимах;

  • Практика потоковой модели Netty Reactor;

Модель связи ввода-вывода

Давайте сначала поговорим о связи ввода-вывода. Когда дело доходит до связи ввода-вывода, часто существует четыре типа синхронного ввода-вывода, асинхронного ввода-вывода, блокирующего ввода-вывода и неблокирующего ввода-вывода. Разница между синхронизацией, асинхронностью, блокировкой и неблокировкой часто неясна, разные люди имеют разный уровень знаний, и трудно прийти к единому мнению по поводу концепции. Предыстория, обсуждаемая в этой статье, — это сетевой ввод-вывод в среде Linux.

Один анализ процесса ввода/вывода

Для сетевого ввода-вывода (в качестве примера возьмем read) будут задействованы два системных объекта: один — процесс или поток, вызывающий ввод-вывод, а другой — системное ядро ​​(ядро). Когда происходит операция чтения, она проходит две фазы (важно помнить об этих двух фазах, потому что разница между разными моделями ввода-вывода заключается в том, что на каждой фазе они обрабатываются по-разному):

  • Первый этап: ожидание готовности данных;

  • Второй этап: копирование данных из ядра в процесс;

Пять моделей ввода/вывода

В томе UNIX® Network Programming Volume Ричарда Стивенса упоминаются 5 моделей ввода-вывода:

  1. Блокирующий ввод/вывод (синхронный блокирующий ввод/вывод)

  2. Неблокирующий ввод-вывод (синхронный неблокирующий ввод-вывод)

  3. Мультиплексирование ввода/вывода

  4. Ввод-вывод, управляемый сигналом (ввод-вывод, управляемый сигналом, редко используется на практике, не поддерживается Java)

  5. Асинхронный ввод-вывод

Далее мы объясним и сравним пять моделей ввода/вывода.

Blocking I/O

В Linux все Сокеты по умолчанию блокируются, то есть блокируются. Во время типичной операции чтения поток выглядит следующим образом:

Когда пользовательский процесс вызывает системный вызов recvfrom, вызов ввода-вывода проходит следующие два этапа:

  1. Подготовка данных: для сетевых запросов много раз данные не поступали в начале (например, не был получен полный UDP-пакет), в это время ядру приходится ждать поступления достаточного количества данных. На стороне пользовательского процесса весь процесс будет заблокирован.

  2. Возврат данных: как только ядро ​​ожидает готовности данных, оно скопирует данные из ядра в пользовательскую память, а затем ядро ​​вернет результат, а пользовательский процесс освободит состояние блока и снова запустится.

Nonblocking IO

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

Когда пользовательский процесс выполняет операцию чтения, конкретный процесс делится на следующие три процесса:

  1. Начать подготовку данных: если данные в Ядре не готовы, оно не заблокирует пользовательский процесс, а сразу вернет ошибку.

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

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

I/O multiplexing

Этот тип ввода-вывода также можно назвать вводом-выводом, управляемым событиями. Преимущество select/epoll в Linux заключается в том, что один процесс может одновременно обрабатывать ввод-вывод нескольких сетевых подключений. Его основной принцип заключается в том, что select/epoll будет постоянно опрашивать все сокеты, за которые он отвечает, и уведомлять пользовательский процесс, когда в сокет поступают данные. Процесс показан на рисунке:

Когда пользовательский процесс вызывает select:

  1. Весь процесс будет заблокирован, и при этом ядро ​​будет "мониторить" все сокеты, за которые отвечает select.Когда данные в любом сокете будут готовы, select вернется.

  2. Затем пользовательский процесс вызывает операцию чтения для копирования данных из ядра в пользовательский процесс. На данный момент она мало чем отличается от блокирующей диаграммы ввода/вывода, а даже хуже. Потому что здесь необходимо использовать два системных вызова (select и recvfrom), а блокирующий ввод-вывод вызывает только один системный вызов (recvfrom).

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

Asynchronous IO

Асинхронный ввод-вывод под Linux, то есть асинхронный ввод-вывод, используется редко (требуется поддержка системы высокого уровня). Его процесс выглядит следующим образом:

Когда пользовательский процесс выполняет операцию чтения, конкретный процесс:

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

  2. С точки зрения ядра, когда оно получает асинхронное чтение, оно немедленно возвращается, поэтому оно не будет генерировать никакого блока для пользовательского процесса. Затем ядро ​​будет ждать, пока данные будут готовы, а затем скопирует данные в память пользователя.Когда все это будет сделано, ядро ​​пошлет сигнал пользовательскому процессу, чтобы сообщить ему, что операция чтения завершена.


Обобщите их соответствующие функции с помощью четырех вышеупомянутых моделей связи ввода-вывода.

  • Блокирующий ввод-вывод характеризуется блокировкой на обеих фазах выполнения ввода-вывода.

  • Неблокирующий ввод-вывод характеризуется отсутствием блокировки, если данные ядра не готовы.

  • Преимущество мультиплексирования ввода-вывода заключается в том, что оно может одновременно обрабатывать несколько соединений с помощью 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 есть три модели многопоточности, и пользователи могут выбрать подходящую модель для своей среды.

  1. однопоточная модель

  2. Многопоточная модель (один Reactor)

  3. Многопоточная модель (несколько реакторов)

однопоточный режим

Однопоточный режим — это простейшая модель 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 в это время.

полный текст


Вас также могут заинтересовать следующие статьи:

Мы набираем инженеров Java Заинтересованные студенты могут отправить свои резюме на rd-hr@xingren.com.

Миндальная технологическая станция

Длительный нажмите QR-код слева, чтобы следить за нами, есть группа энтузиастов молодых людей, с нетерпением ждут встречи с вами.