Реактори мукаммали луч-C I/O

Реактори мукаммали луч-C I/O

Муқаддима

Реактори I/O (риштаи ягона даври ҳодиса) намунаи навиштани нармафзори пурбор аст, ки дар бисёр ҳалли маъмул истифода мешавад:

Дар ин мақола, мо ба василаҳои реактори I/O ва чӣ тавр кор кардани он дида мебароем, татбиқро дар камтар аз 200 сатри код нависед ва раванди оддии сервери HTTP-ро беш аз 40 миллион дархост дар як дақиқа эҷод кунед.

Пешгуфтор

  • Мақола барои фаҳмидани кори реактори I/O ва аз ин рӯ фаҳмидани хатарҳо ҳангоми истифодаи он навишта шудааст.
  • Барои фаҳмидани мақола дониши асосҳо лозим аст. Забони C ва баъзе таҷриба дар таҳияи барномаҳои шабакавӣ.
  • Ҳама кодҳо бо забони C ба таври қатъӣ мувофиқи (Огоҳӣ: PDF дароз) ба стандарти C11 барои Linux ва дастрас GitHub.

Чаро ин зарур аст?

Бо маъруфияти афзояндаи Интернет, веб-серверҳо лозим омад, ки шумораи зиёди пайвастҳоро дар як вақт идора кунанд ва аз ин рӯ, ду равиш санҷида шуданд: бастани I/O дар шумораи зиёди риштаҳои ОС ва баста нашудани I/O дар якҷоягӣ бо системаи огоҳии рӯйдодҳо, ки онро "интихоби система" низ меноманд (epoll/навбат/IOCP/ва ғайра).

Равиши аввал эҷоди риштаи нави OS барои ҳар як пайвасти воридотӣ иборат буд. Камбудии он миқёспазирии суст аст: системаи оператсионӣ бояд бисёр чизҳоро амалӣ кунад гузаришҳои контекстӣ и зангҳои системавӣ. Онҳо амалиётҳои гаронбаҳо мебошанд ва метавонанд ба нарасидани RAM-и ройгон бо шумораи таъсирбахши пайвастҳо оварда расонанд.

Варианти тағирёфта таъкид мекунад шумораи муайяни риштаҳо (хавзи ришта), ба ин васила системаро аз қатъ кардани иҷроиш пешгирӣ мекунад, аммо дар айни замон мушкилоти навро ба вуҷуд меорад: агар ҳавзи ришта дар айни замон бо амалиёти хондани тӯлонӣ баста шуда бошад, пас дигар розеткаҳое, ки аллакай маълумот гирифта метавонанд, наметавонанд чунин кунед.

Усули дуюм истифода мебарад системаи огоҳии ҳодиса (селектори система), ки аз ҷониби ОС таъмин карда шудааст. Ин мақола намуди маъмултарини селектори системаро, ки дар асоси огоҳиҳо (ҳодисаҳо, огоҳиҳо) дар бораи омодагӣ ба амалиёти воридотӣ ва берунӣ асос ёфтааст, баррасӣ мекунад. огоҳинома дар бораи анҷоми онҳо. Намунаи соддашудаи истифодаи онро метавон бо диаграммаи блоки зерин нишон дод:

Реактори мукаммали луч-C I/O

Фарқи байни ин равишҳо чунин аст:

  • Бастани амалиёти вуруд/чор боздоштан ҷараёни корбар тото он даме, ки ОС дуруст аст дефрагментатсияҳо воридшаванда Бастаҳои IP ба ҷараёни байт (TCP, гирифтани маълумот) ё дар буферҳои дохилии навиштан барои фиристодани минбаъда тавассути NIC (фиристодани маълумот).
  • Интихоби система бо гузашти вақт барномаро огоҳ мекунад, ки ОС аллакай бастаҳои IP-и дефрагментацияшуда (TCP, қабули маълумот) ё фазои кофӣ дар буферҳои дохилии навиштан аллакай дастрас (фиристодани маълумот).

Хулоса, захира кардани як риштаи OS барои ҳар як воридот / баромад сарфи қувваи ҳисоббарорӣ аст, зеро дар асл, риштаҳо кори фоиданок намекунанд (аз ин рӯ истилоҳ "қатъи нармафзор"). Интихоби система ин мушкилотро ҳал мекунад ва ба барномаи корбар имкон медиҳад, ки захираҳои CPU-ро сарфакорона истифода барад.

Модели реактори I/O

Реактори I/O ҳамчун қабати байни селектори система ва рамзи корбар амал мекунад. Принсипи кори он бо диаграммаи блоки зерин тасвир шудааст:

Реактори мукаммали луч-C I/O

  • Ёдовар мешавам, ки ҳодиса ин огоҳиномаест, ки розеткаи муайян қодир аст амалиёти бебандкунии вуруди баромадро иҷро кунад.
  • Коркарди рӯйдодҳо функсияест, ки ҳангоми қабули ҳодиса аз ҷониби реактори I/O даъват карда мешавад, ки пас аз он амалиёти воридот ва баромади бебандро иҷро мекунад.

Қайд кардан муҳим аст, ки реактори I/O аз рӯи таърифи як ришта аст, аммо ҳеҷ чиз барои истифодаи консепсия дар муҳити чанд ришта бо таносуби 1 ришта: 1 реактор монеъ намешавад ва ба ин васила ҳамаи ядроҳои CPU коркард карда мешавад.

Реализация

Мо интерфейси умумиро дар файл ҷойгир мекунем reactor.h, ва татбики - дар reactor.c. reactor.h аз эълонхои зерин иборат аст:

Нишон додани эъломияҳо дар reactor.h

typedef struct reactor Reactor;

/*
 * Указатель на функцию, которая будет вызываться I/O реактором при поступлении
 * события от системного селектора.
 */
typedef void (*Callback)(void *arg, int fd, uint32_t events);

/*
 * Возвращает `NULL` в случае ошибки, не-`NULL` указатель на `Reactor` в
 * противном случае.
 */
Reactor *reactor_new(void);

/*
 * Освобождает системный селектор, все зарегистрированные сокеты в данный момент
 * времени и сам I/O реактор.
 *
 * Следующие функции возвращают -1 в случае ошибки, 0 в случае успеха.
 */
int reactor_destroy(Reactor *reactor);

int reactor_register(const Reactor *reactor, int fd, uint32_t interest,
                     Callback callback, void *callback_arg);
int reactor_deregister(const Reactor *reactor, int fd);
int reactor_reregister(const Reactor *reactor, int fd, uint32_t interest,
                       Callback callback, void *callback_arg);

/*
 * Запускает цикл событий с тайм-аутом `timeout`.
 *
 * Эта функция передаст управление вызывающему коду если отведённое время вышло
 * или/и при отсутствии зарегистрированных сокетов.
 */
int reactor_run(const Reactor *reactor, time_t timeout);

Сохтори реактори I/O иборат аст аз тавсифкунандаи файл интихобкунанда epoll и мизҳои ҳаш GHashTable, ки хар як розеткаро ба CallbackData (сохтори коркардкунандаи ҳодиса ва далели корбар барои он).

Reactor ва CallbackData -ро нишон диҳед

struct reactor {
    int epoll_fd;
    GHashTable *table; // (int, CallbackData)
};

typedef struct {
    Callback callback;
    void *arg;
} CallbackData;

Лутфан қайд кунед, ки мо қобилияти коркардро фаъол кардем навъи нопурра аз руи индекс. ДАР reactor.h сохторро эълон мекунем reactor, дар ҳоле ки дар reactor.c мо онро муайян мекунем ва ба ин васила ба корбар имкон намедиҳем, ки майдонҳои худро ба таври возеҳ тағир диҳад. Ин яке аз намунаҳост пинҳон кардани маълумот, ки ба таври мухтасар ба семантикаи C мувофиқат мекунад.

Функсияҳо reactor_register, reactor_deregister и reactor_reregister рӯйхати розеткаҳои таваҷҷӯҳ ва коркардкунандагони рӯйдодҳои мувофиқро дар селектори система ва ҷадвали hash навсозӣ кунед.

Функсияҳои сабти номро нишон диҳед

#define REACTOR_CTL(reactor, op, fd, interest)                                 
    if (epoll_ctl(reactor->epoll_fd, op, fd,                                   
                  &(struct epoll_event){.events = interest,                    
                                        .data = {.fd = fd}}) == -1) {          
        perror("epoll_ctl");                                                   
        return -1;                                                             
    }

int reactor_register(const Reactor *reactor, int fd, uint32_t interest,
                     Callback callback, void *callback_arg) {
    REACTOR_CTL(reactor, EPOLL_CTL_ADD, fd, interest)
    g_hash_table_insert(reactor->table, int_in_heap(fd),
                        callback_data_new(callback, callback_arg));
    return 0;
}

int reactor_deregister(const Reactor *reactor, int fd) {
    REACTOR_CTL(reactor, EPOLL_CTL_DEL, fd, 0)
    g_hash_table_remove(reactor->table, &fd);
    return 0;
}

int reactor_reregister(const Reactor *reactor, int fd, uint32_t interest,
                       Callback callback, void *callback_arg) {
    REACTOR_CTL(reactor, EPOLL_CTL_MOD, fd, interest)
    g_hash_table_insert(reactor->table, int_in_heap(fd),
                        callback_data_new(callback, callback_arg));
    return 0;
}

Пас аз он ки реактори I/O ҳодисаро бо тавсифкунанда боздошт fd, он коркардкунандаи рӯйдодҳои мувофиқро даъват мекунад, ки ба он мегузарад fd, ниқоби каме рӯйдодҳои тавлидшуда ва ишоракунандаи корбар ба void.

Функсияи reactor_run() -ро нишон диҳед

int reactor_run(const Reactor *reactor, time_t timeout) {
    int result;
    struct epoll_event *events;
    if ((events = calloc(MAX_EVENTS, sizeof(*events))) == NULL)
        abort();

    time_t start = time(NULL);

    while (true) {
        time_t passed = time(NULL) - start;
        int nfds =
            epoll_wait(reactor->epoll_fd, events, MAX_EVENTS, timeout - passed);

        switch (nfds) {
        // Ошибка
        case -1:
            perror("epoll_wait");
            result = -1;
            goto cleanup;
        // Время вышло
        case 0:
            result = 0;
            goto cleanup;
        // Успешная операция
        default:
            // Вызвать обработчиков событий
            for (int i = 0; i < nfds; i++) {
                int fd = events[i].data.fd;

                CallbackData *callback =
                    g_hash_table_lookup(reactor->table, &fd);
                callback->callback(callback->arg, fd, events[i].events);
            }
        }
    }

cleanup:
    free(events);
    return result;
}

Барои ҷамъбаст, занҷири зангҳои функсионалӣ дар коди корбар шакли зеринро мегирад:

Реактори мукаммали луч-C I/O

Сервери риштаи ягона

Барои санҷидани реактори I/O дар зери сарбории баланд, мо веб-сервери оддии HTTP менависем, ки ба ҳар дархост бо тасвир ҷавоб медиҳад.

Истиноди зуд ба протоколи HTTP

HTTP — ин протокол аст сатҳи татбиқ, асосан барои ҳамкории сервер ва браузер истифода мешавад.

HTTP-ро метавон ба осонӣ истифода бурд нақлиёт протокол TCP, фиристодан ва қабули паёмҳо дар формати муайяншуда мушаххасот.

Формати дархост

<КОМАНДА> <URI> <ВЕРСИЯ HTTP>CRLF
<ЗАГОЛОВОК 1>CRLF
<ЗАГОЛОВОК 2>CRLF
<ЗАГОЛОВОК N>CRLF CRLF
<ДАННЫЕ>

  • CRLF пайдарпаии ду аломат аст: r и n, ҷудо кардани сатри якуми дархост, сарлавҳаҳо ва маълумот.
  • <КОМАНДА> - яке аз CONNECT, DELETE, GET, HEAD, OPTIONS, PATCH, POST, PUT, TRACE. Браузер ба сервери мо фармон мефиристад GET, маънои "Мундариҷаи файлро ба ман фиристед".
  • <URI> - идентификатори ягонаи захираҳо. Масалан, агар URI = /index.html, пас муштарӣ саҳифаи асосии сайтро дархост мекунад.
  • <ВЕРСИЯ HTTP> — версияи протоколи HTTP дар формат HTTP/X.Y. Варианти маъмултарин имрӯз истифода мешавад HTTP/1.1.
  • <ЗАГОЛОВОК N> як ҷуфти калид-арзиш дар формат аст <КЛЮЧ>: <ЗНАЧЕНИЕ>, барои таҳлили минбаъда ба сервер фиристода шуд.
  • <ДАННЫЕ> — маълумоте, ки сервер барои иҷрои амалиёт талаб мекунад. Аксар вақт ин оддӣ аст JSON ё ягон формати дигар.

Формати ҷавоб

<ВЕРСИЯ HTTP> <КОД СТАТУСА> <ОПИСАНИЕ СТАТУСА>CRLF
<ЗАГОЛОВОК 1>CRLF
<ЗАГОЛОВОК 2>CRLF
<ЗАГОЛОВОК N>CRLF CRLF
<ДАННЫЕ>

  • <КОД СТАТУСА> рақамест, ки натиҷаи амалиётро ифода мекунад. Сервери мо ҳамеша ҳолати 200-ро бармегардонад (амали бомуваффақият).
  • <ОПИСАНИЕ СТАТУСА> — тасвири сатри рамзи вазъ. Барои рамзи ҳолати 200 ин аст OK.
  • <ЗАГОЛОВОК N> — сарлавҳаи ҳамон формати дар дархост. Мо унвонҳоро бармегардонем Content-Length (андозаи файл) ва Content-Type: text/html (навъи маълумот баргашт).
  • <ДАННЫЕ> — маълумоте, ки корбар дархост кардааст. Дар ҳолати мо, ин роҳ ба тасвир дар аст HTML.

файл http_server.c (сервери риштаи ягона) файлро дар бар мегирад common.h, ки прототипҳои функсияҳои зеринро дар бар мегирад:

Нишон додани прототипҳои функсия дар умумӣ.h

/*
 * Обработчик событий, который вызовется после того, как сокет будет
 * готов принять новое соединение.
 */
static void on_accept(void *arg, int fd, uint32_t events);

/*
 * Обработчик событий, который вызовется после того, как сокет будет
 * готов отправить HTTP ответ.
 */
static void on_send(void *arg, int fd, uint32_t events);

/*
 * Обработчик событий, который вызовется после того, как сокет будет
 * готов принять часть HTTP запроса.
 */
static void on_recv(void *arg, int fd, uint32_t events);

/*
 * Переводит входящее соединение в неблокирующий режим.
 */
static void set_nonblocking(int fd);

/*
 * Печатает переданные аргументы в stderr и выходит из процесса с
 * кодом `EXIT_FAILURE`.
 */
static noreturn void fail(const char *format, ...);

/*
 * Возвращает файловый дескриптор сокета, способного принимать новые
 * TCP соединения.
 */
static int new_server(bool reuse_port);

Макроси функсионалӣ низ тавсиф шудааст SAFE_CALL() ва функсия муайян карда мешавад fail(). Макрос арзиши ифодаро бо хато муқоиса мекунад ва агар шарт дуруст бошад, функсияро даъват мекунад fail():

#define SAFE_CALL(call, error)                                                 
    do {                                                                       
        if ((call) == error) {                                                   
            fail("%s", #call);                                                 
        }                                                                      
    } while (false)

функсия fail() далелҳои гузаштаро ба терминал чоп мекунад (масалан printf()) ва барномаро бо код қатъ мекунад EXIT_FAILURE:

static noreturn void fail(const char *format, ...) {
    va_list args;
    va_start(args, format);
    vfprintf(stderr, format, args);
    va_end(args);
    fprintf(stderr, ": %sn", strerror(errno));
    exit(EXIT_FAILURE);
}

функсия new_server() дескриптори файли васлаки "сервер"-ро, ки тавассути зангҳои система сохта шудааст, бармегардонад socket(), bind() и listen() ва қодир ба қабули пайвастҳои воридотӣ дар ҳолати ғайрибандӣ.

Функсияи new_server()-ро нишон диҳед

static int new_server(bool reuse_port) {
    int fd;
    SAFE_CALL((fd = socket(AF_INET, SOCK_STREAM | SOCK_NONBLOCK, IPPROTO_TCP)),
              -1);

    if (reuse_port) {
        SAFE_CALL(
            setsockopt(fd, SOL_SOCKET, SO_REUSEPORT, &(int){1}, sizeof(int)),
            -1);
    }

    struct sockaddr_in addr = {.sin_family = AF_INET,
                               .sin_port = htons(SERVER_PORT),
                               .sin_addr = {.s_addr = inet_addr(SERVER_IPV4)},
                               .sin_zero = {0}};

    SAFE_CALL(bind(fd, (struct sockaddr *)&addr, sizeof(addr)), -1);
    SAFE_CALL(listen(fd, SERVER_BACKLOG), -1);
    return fd;
}

  • Аҳамият диҳед, ки розетка дар аввал бо истифода аз парчам дар ҳолати ғайрибандӣ сохта мешавад SOCK_NONBLOCKто ки дар функсия on_accept() (бештар хонед) занги системавӣ accept() ичрои риштаро бозмедорад.
  • агар reuse_port баробар аст true, пас ин функсия розеткаро бо опсия танзим мекунад SO_REUSEPORT ба воситаи setsockopt()барои истифода бурдани як порт дар муҳити чанд ришта (нигаред ба бахши “Сервери чанд ришта”).

Дастури рӯйдодҳо on_accept() пас аз тавлиди OS ҳодиса даъват карда мешавад EPOLLIN, дар ин ҳолат маънои онро дорад, ки пайвасти нав метавонад қабул карда шавад. on_accept() пайвасти навро қабул мекунад, онро ба ҳолати ғайрибандӣ мегузарад ва бо коркардкунандаи ҳодиса сабти ном мекунад on_recv() дар реактори I/O.

Функсияи on_accept()-ро нишон диҳед

static void on_accept(void *arg, int fd, uint32_t events) {
    int incoming_conn;
    SAFE_CALL((incoming_conn = accept(fd, NULL, NULL)), -1);
    set_nonblocking(incoming_conn);
    SAFE_CALL(reactor_register(reactor, incoming_conn, EPOLLIN, on_recv,
                               request_buffer_new()),
              -1);
}

Дастури рӯйдодҳо on_recv() пас аз тавлиди OS ҳодиса даъват карда мешавад EPOLLIN, дар ин ҳолат маънои онро дорад, ки пайвастшавӣ ба қайд гирифта шудааст on_accept(), барои гирифтани маълумот омода аст.

on_recv() маълумотро аз пайвастшавӣ то пурра қабул шудани дархости HTTP мехонад ва сипас коркардкунандаро сабт мекунад on_send() барои фиристодани посухи HTTP. Агар муштарӣ пайвастро вайрон кунад, розетка аз қайд хориҷ карда мешавад ва бо истифода аз он баста мешавад close().

Нишон додани функсия on_recv()

static void on_recv(void *arg, int fd, uint32_t events) {
    RequestBuffer *buffer = arg;

    // Принимаем входные данные до тех пор, что recv возвратит 0 или ошибку
    ssize_t nread;
    while ((nread = recv(fd, buffer->data + buffer->size,
                         REQUEST_BUFFER_CAPACITY - buffer->size, 0)) > 0)
        buffer->size += nread;

    // Клиент оборвал соединение
    if (nread == 0) {
        SAFE_CALL(reactor_deregister(reactor, fd), -1);
        SAFE_CALL(close(fd), -1);
        request_buffer_destroy(buffer);
        return;
    }

    // read вернул ошибку, отличную от ошибки, при которой вызов заблокирует
    // поток
    if (errno != EAGAIN && errno != EWOULDBLOCK) {
        request_buffer_destroy(buffer);
        fail("read");
    }

    // Получен полный HTTP запрос от клиента. Теперь регистрируем обработчика
    // событий для отправки данных
    if (request_buffer_is_complete(buffer)) {
        request_buffer_clear(buffer);
        SAFE_CALL(reactor_reregister(reactor, fd, EPOLLOUT, on_send, buffer),
                  -1);
    }
}

Дастури рӯйдодҳо on_send() пас аз тавлиди OS ҳодиса даъват карда мешавад EPOLLOUT, маънои онро дорад, ки пайвастшавӣ ба қайд гирифта шудааст on_recv(), барои фиристодани маълумот омода аст. Ин функсия посухи HTTP-ро бо HTML бо тасвир ба муштарӣ мефиристад ва сипас коркардкунандаи рӯйдодро ба он бармегардонад on_recv().

Функсияи on_send() -ро нишон диҳед

static void on_send(void *arg, int fd, uint32_t events) {
    const char *content = "<img "
                          "src="https://habrastorage.org/webt/oh/wl/23/"
                          "ohwl23va3b-dioerobq_mbx4xaw.jpeg">";
    char response[1024];
    sprintf(response,
            "HTTP/1.1 200 OK" CRLF "Content-Length: %zd" CRLF "Content-Type: "
            "text/html" DOUBLE_CRLF "%s",
            strlen(content), content);

    SAFE_CALL(send(fd, response, strlen(response), 0), -1);
    SAFE_CALL(reactor_reregister(reactor, fd, EPOLLIN, on_recv, arg), -1);
}

Ва ниҳоят, дар файл http_server.c, дар вазифа main() мо бо истифода аз реактори I/O эҷод мекунем reactor_new(), васлаки сервер эҷод кунед ва онро сабт кунед, реакторро истифода баред reactor_run() маҳз як дақиқа, ва он гоҳ мо захираҳоро озод мекунем ва аз барнома мебароем.

Намоиши http_server.c

#include "reactor.h"

static Reactor *reactor;

#include "common.h"

int main(void) {
    SAFE_CALL((reactor = reactor_new()), NULL);
    SAFE_CALL(
        reactor_register(reactor, new_server(false), EPOLLIN, on_accept, NULL),
        -1);
    SAFE_CALL(reactor_run(reactor, SERVER_TIMEOUT_MILLIS), -1);
    SAFE_CALL(reactor_destroy(reactor), -1);
}

Биёед тафтиш кунем, ки ҳама чиз мувофиқи интизорӣ кор мекунад. Тартиб додан (chmod a+x compile.sh && ./compile.sh дар решаи лоиҳа) ва сервери худнависиро оғоз кунед, кушоед http://127.0.0.1:18470 дар браузер ва бубинед, ки мо чӣ интизор будем:

Реактори мукаммали луч-C I/O

Андозаи иҷроиш

Хусусиятҳои мошини маро нишон диҳед

$ screenfetch
 MMMMMMMMMMMMMMMMMMMMMMMMMmds+.        OS: Mint 19.1 tessa
 MMm----::-://////////////oymNMd+`     Kernel: x86_64 Linux 4.15.0-20-generic
 MMd      /++                -sNMd:    Uptime: 2h 34m
 MMNso/`  dMM    `.::-. .-::.` .hMN:   Packages: 2217
 ddddMMh  dMM   :hNMNMNhNMNMNh: `NMm   Shell: bash 4.4.20
     NMm  dMM  .NMN/-+MMM+-/NMN` dMM   Resolution: 1920x1080
     NMm  dMM  -MMm  `MMM   dMM. dMM   DE: Cinnamon 4.0.10
     NMm  dMM  -MMm  `MMM   dMM. dMM   WM: Muffin
     NMm  dMM  .mmd  `mmm   yMM. dMM   WM Theme: Mint-Y-Dark (Mint-Y)
     NMm  dMM`  ..`   ...   ydm. dMM   GTK Theme: Mint-Y [GTK2/3]
     hMM- +MMd/-------...-:sdds  dMM   Icon Theme: Mint-Y
     -NMm- :hNMNNNmdddddddddy/`  dMM   Font: Noto Sans 9
      -dMNs-``-::::-------.``    dMM   CPU: Intel Core i7-6700 @ 8x 4GHz [52.0°C]
       `/dMNmy+/:-------------:/yMMM   GPU: NV136
          ./ydNMMMMMMMMMMMMMMMMMMMMM   RAM: 2544MiB / 7926MiB
             .MMMMMMMMMMMMMMMMMMM

Биёед кори сервери як риштаро чен кунем. Биёед ду терминалро кушоем: дар яке мо кор мекунем ./http_server, дар дигар - вайрон. Пас аз як дақиқа, дар терминали дуюм омори зерин нишон дода мешавад:

$ wrk -c100 -d1m -t8 http://127.0.0.1:18470 -H "Host: 127.0.0.1:18470" -H "Accept-Language: en-US,en;q=0.5" -H "Connection: keep-alive"
Running 1m test @ http://127.0.0.1:18470
  8 threads and 100 connections
  Thread Stats   Avg      Stdev     Max   +/- Stdev
    Latency   493.52us   76.70us  17.31ms   89.57%
    Req/Sec    24.37k     1.81k   29.34k    68.13%
  11657769 requests in 1.00m, 1.60GB read
Requests/sec: 193974.70
Transfer/sec:     27.19MB

Сервери як риштаи мо тавонист беш аз 11 миллион дархостро дар як дақиқа аз 100 пайваст коркард кунад. Натичаи бад нест, аммо онро бехтар кардан мумкин аст?

Сервери бисёрсоҳавӣ

Тавре ки дар боло зикр гардид, реактори I/O-ро дар риштаҳои алоҳида сохтан мумкин аст ва ба ин васила ҳамаи ядроҳои CPU-ро истифода мебарад. Биёед ин равишро дар амал татбиқ кунем:

Намоиши http_server_multithreaded.c

#include "reactor.h"

static Reactor *reactor;
#pragma omp threadprivate(reactor)

#include "common.h"

int main(void) {
#pragma omp parallel
    {
        SAFE_CALL((reactor = reactor_new()), NULL);
        SAFE_CALL(reactor_register(reactor, new_server(true), EPOLLIN,
                                   on_accept, NULL),
                  -1);
        SAFE_CALL(reactor_run(reactor, SERVER_TIMEOUT_MILLIS), -1);
        SAFE_CALL(reactor_destroy(reactor), -1);
    }
}

Акнун хар як ришта сохиби худ аст реактор:

static Reactor *reactor;
#pragma omp threadprivate(reactor)

Лутфан қайд кунед, ки аргументи функсия new_server() ҳимоят мекунанд true. Ин маънои онро дорад, ки мо интихобро ба васлаки сервер таъин мекунем SO_REUSEPORTки онро дар мухити серсоха истифода баранд. Шумо метавонед тафсилоти бештар хонед дар ин ҷо.

Давраи дуюм

Акнун биёед кори сервери бисёр риштаро чен кунем:

$ wrk -c100 -d1m -t8 http://127.0.0.1:18470 -H "Host: 127.0.0.1:18470" -H "Accept-Language: en-US,en;q=0.5" -H "Connection: keep-alive"
Running 1m test @ http://127.0.0.1:18470
  8 threads and 100 connections
  Thread Stats   Avg      Stdev     Max   +/- Stdev
    Latency     1.14ms    2.53ms  40.73ms   89.98%
    Req/Sec    79.98k    18.07k  154.64k    78.65%
  38208400 requests in 1.00m, 5.23GB read
Requests/sec: 635876.41
Transfer/sec:     89.14MB

Шумораи дархостҳои коркардшуда дар 1 дақиқа ~ 3.28 маротиба зиёд шуд! Аммо мо аз шумораи мудаввар ҳамагӣ XNUMX миллион кам будем, аз ин рӯ биёед кӯшиш кунем, ки инро ислоҳ кунем.

Аввало, биёед ба омори тавлидшуда назар андозем комил:

$ sudo perf stat -B -e task-clock,context-switches,cpu-migrations,page-faults,cycles,instructions,branches,branch-misses,cache-misses ./http_server_multithreaded

 Performance counter stats for './http_server_multithreaded':

     242446,314933      task-clock (msec)         #    4,000 CPUs utilized          
         1 813 074      context-switches          #    0,007 M/sec                  
             4 689      cpu-migrations            #    0,019 K/sec                  
               254      page-faults               #    0,001 K/sec                  
   895 324 830 170      cycles                    #    3,693 GHz                    
   621 378 066 808      instructions              #    0,69  insn per cycle         
   119 926 709 370      branches                  #  494,653 M/sec                  
     3 227 095 669      branch-misses             #    2,69% of all branches        
           808 664      cache-misses                                                

      60,604330670 seconds time elapsed

Истифодаи CPU Affinity, тартиб додан бо -march=native, PGO, зиёд шудани шумораи боздидхо кэш, афзоиш MAX_EVENTS ва истифода мебаранд EPOLLET афзоиши назарраси нишондихандаро таъмин накард. Аммо агар шумо шумораи пайвастҳои ҳамзамон зиёд кунед, чӣ мешавад?

Омори 352 пайвасти ҳамзамон:

$ wrk -c352 -d1m -t8 http://127.0.0.1:18470 -H "Host: 127.0.0.1:18470" -H "Accept-Language: en-US,en;q=0.5" -H "Connection: keep-alive"
Running 1m test @ http://127.0.0.1:18470
  8 threads and 352 connections
  Thread Stats   Avg      Stdev     Max   +/- Stdev
    Latency     2.12ms    3.79ms  68.23ms   87.49%
    Req/Sec    83.78k    12.69k  169.81k    83.59%
  40006142 requests in 1.00m, 5.48GB read
Requests/sec: 665789.26
Transfer/sec:     93.34MB

Натиҷаи дилхоҳ ба даст омад ва бо он диаграммаи ҷолибе, ки вобастагии шумораи дархостҳои коркардшуда дар 1 дақиқа аз шумораи пайвастҳоро нишон медиҳад:

Реактори мукаммали луч-C I/O

Мо мебинем, ки пас аз якчанд сад пайвастшавӣ, шумораи дархостҳои коркардшуда барои ҳарду сервер якбора кам мешавад (дар версияи бисёрсоҳавӣ ин бештар намоён аст). Оё ин ба татбиқи стек Linux TCP/IP алоқаманд аст? Озод ҳис кунед, ки дар шарҳҳо тахминҳои худро дар бораи ин рафтори графикӣ ва оптимизатсияҳо барои вариантҳои бисёрҷабҳа ва як ришта нависед.

чи тавр қайд кард дар шарҳҳо, ин санҷиши корбарӣ рафтори реактори I/O-ро дар зери бори воқеӣ нишон намедиҳад, зеро қариб ҳамеша сервер бо пойгоҳи додаҳо ҳамкорӣ мекунад, гузоришҳоро мебарорад, криптографияро бо TLS г., ки дар натиља бори якранг (динамикї) мешавад. Санҷишҳо дар якҷоягӣ бо ҷузъҳои тарафи сеюм дар мақола дар бораи проактори I/O гузаронида мешаванд.

Камбудиҳои реактори I/O

Шумо бояд фаҳмед, ки реактори I/O аз камбудиҳои худ холӣ нест, аз ҷумла:

  • Истифодаи реактори I/O дар муҳити чанд ришта то андозае мушкилтар аст, зеро шумо бояд ҷараёнҳоро дастӣ идора кунед.
  • Амалия нишон медихад, ки дар аксар мавридхо сарборй якранг аст, ки ин метавонад боиси канда шудани як ришта ва дигаре бо кор банд бошад.
  • Агар як коркардкунандаи рӯйдод риштаро маҳкам кунад, худи селектори система низ баста мешавад, ки ин метавонад боиси пайдо шудани хатогиҳои душвор гардад.

Ин мушкилотро ҳал мекунад Проактори I/O, ки аксар вақт як нақшакаш дорад, ки сарбориро ба ҳавзи риштаҳо баробар тақсим мекунад ва инчунин API-и қулай дорад. Мо дар ин бора баъдтар, дар мақолаи дигари худ сӯҳбат хоҳем кард.

хулоса

Ин аст, ки сафари мо аз назария мустақиман ба ихроҷи профилатор ба охир расид.

Шумо набояд дар ин бора таваққуф кунед, зеро бисёр равишҳои дигари ҷолиб барои навиштани нармафзори шабакавӣ бо сатҳҳои гуногуни роҳат ва суръат мавҷуданд. Ҷолиб, ба андешаи ман, истинодҳо дар зер оварда шудаанд.

То дафъаи оянда!

Лоиҳаҳои ҷолиб

Боз чӣ хондан лозим аст?

Манбаъ: will.com

Илова Эзоҳ