Reaktor I/O reaktor bulistir-fitur pinuh

Reaktor I/O reaktor bulistir-fitur pinuh

perkenalan

reaktor I/O (ulir tunggal loop acara) nyaéta pola pikeun nulis software-beban luhur, dipaké dina loba solusi populér:

Dina artikel ieu, urang bakal kasampak di seluk beluk hiji reaktor I / O na kumaha gawéna, nulis hiji palaksanaan dina kirang ti 200 garis kode, sarta nyieun prosés server HTTP basajan leuwih 40 juta requests / mnt.

foreword

  • Tulisan ieu ditulis pikeun ngabantosan ngartos fungsi reaktor I / O, sareng ku kituna ngartos résiko nalika dianggo.
  • Pangetahuan dasar-dasar diperyogikeun pikeun ngartos tulisan. basa C sarta sababaraha pangalaman dina ngembangkeun aplikasi jaringan.
  • Sadaya kode ditulis dina basa C sacara ketat nurutkeun (ati-ati: PDF panjang) kana standar C11 pikeun Linux Ubuntu jeung sadia dina GitHub.

Naha ngalakukeun eta?

Kalayan popularitas Internét anu ningkat, pangladén wéb mimiti kedah nanganan sajumlah ageung sambungan sakaligus, ku kituna dua pendekatan dicoba: ngahalangan I / O dina sajumlah ageung benang OS sareng non-blocking I / O dina kombinasi sareng sistem béwara acara, ogé disebut "pamilih sistem" (epoll/kqueue/IOCP/ jsb).

Pendekatan munggaran ngalibatkeun nyiptakeun benang OS anyar pikeun unggal sambungan anu asup. Kakuranganna nyaéta skalabilitas anu goréng: sistem operasi kedah nerapkeun seueur transisi konteks и nelepon sistem. Éta operasi mahal tur bisa ngakibatkeun kurangna RAM bébas kalawan jumlah impressive sambungan.

Vérsi nu dirobah highlights jumlah tetep tina threads (thread pool), ku kituna nyegah sistem tina aborting palaksanaan, tapi dina waktos anu sareng ngenalkeun masalah anyar: lamun thread pool ayeuna diblokir ku operasi dibaca panjang, lajeng sockets séjén anu geus bisa nampa data moal bisa. ngalakukeun kitu.

Pendekatan kadua ngagunakeun sistem béwara acara (pamilih sistem) disadiakeun ku OS. Tulisan ieu ngabahas jinis pamilih sistem anu paling umum, dumasar kana béwara (kajadian, béwara) ngeunaan kesiapan pikeun operasi I/O, tinimbang dina bewara ngeunaan parantosan maranéhna. Conto saderhana pamakeanna tiasa diwakilan ku diagram blok ieu:

Reaktor I/O reaktor bulistir-fitur pinuh

Beda antara pendekatan ieu nyaéta kieu:

  • Ngablokir operasi I / O ngagantungkeun aliran pamaké dugi kadugi ka OS leres defragments asup pakét IP kana aliran bait (TCP, narima data) atawa moal aya cukup spasi sadia dina panyangga nulis internal pikeun ngirim saterusna via NIC (ngirim data).
  • Pamilih sistem langkungna waktos ngabéjaan program yén OS enggeus pakét IP defragmented (TCP, panarimaan data) atawa cukup spasi dina panyangga nulis internal enggeus sadia (ngirim data).

Pikeun nyimpulkeun éta, nyéépkeun utas OS pikeun unggal I / O mangrupikeun runtah kakuatan komputasi, sabab kanyataanna, benang henteu ngalakukeun padamelan anu mangpaat (ku kituna istilah "interupsi software"). Pamilih sistem ngarengsekeun masalah ieu, ngamungkinkeun program pangguna ngagunakeun sumber daya CPU langkung ékonomis.

Modél reaktor I/O

Reaktor I/O tindakan minangka lapisan antara pamilih sistem jeung kode pamaké. Prinsip operasi na digambarkeun ku diagram blok handap:

Reaktor I/O reaktor bulistir-fitur pinuh

  • Hayu atuh ngingetkeun yén hiji acara mangrupa bewara yen stop kontak tangtu bisa ngalakukeun hiji non-blocking I / O operasi.
  • Panangan acara mangrupikeun fungsi anu disebut ku reaktor I / O nalika hiji acara ditampi, anu teras ngalaksanakeun operasi I / O anu henteu ngahalangan.

Kadé dicatet yén reaktor I / O nyaeta ku harti single-threaded, tapi euweuh stopping konsép dipaké dina lingkungan multi-threaded dina nisbah 1 thread: 1 reaktor, kukituna ngadaur mulangkeunana sadayana cores CPU.

Реализация

Urang bakal nempatkeun antarmuka umum dina file reactor.h, jeung palaksanaan - di reactor.c. reactor.h bakal diwangun ku pengumuman di handap ieu:

Témbongkeun deklarasi dina reaktor.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);

Struktur reaktor I/O diwangun ku deskriptor file pamilih epoll и tabél hash GHashTable, nu peta unggal stop kontak ka CallbackData (struktur pawang acara sareng argumen pangguna pikeun éta).

Témbongkeun Reaktor jeung CallbackData

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

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

Punten dicatet yén kami parantos ngaktipkeun kamampuan pikeun nanganan tipe teu lengkep nurutkeun indéks dina. DI reactor.h urang nyatakeun struktur reactor, sareng di reactor.c urang nangtukeun eta, kukituna nyegah pamaké ti eksplisit ngarobah widang na. Ieu salah sahiji pola nyumputkeun data, nu succinctly fits kana semantik C.

fungsi reactor_register, reactor_deregister и reactor_reregister ngamutahirkeun daptar sockets dipikaresep tur pakait pawang acara dina Pamilih Sistim na tabel Hash.

Témbongkeun fungsi pendaptaran

#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;
}

Saatos reaktor I / O geus intercepted acara kalawan descriptor fd, eta nelepon pawang acara saluyu, nu eta pas fd, bit topeng acara dihasilkeun sarta pointer pamaké pikeun void.

Témbongkeun fungsi 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;
}

Pikeun nyimpulkeun, ranté sauran fungsi dina kode pangguna bakal nyandak bentuk ieu:

Reaktor I/O reaktor bulistir-fitur pinuh

server threaded tunggal

Dina raraga nguji reaktor I / O dina beban tinggi, urang bakal nulis web server HTTP basajan nu responds kana pamundut wae kalawan gambar.

Rujukan gancang kana protokol HTTP

HTTP - ieu protokol tingkat aplikasi, utamana dipaké pikeun interaksi server-browser.

HTTP bisa gampang dipaké leuwih ngangkut protokol TCP, ngirim jeung nampa talatah dina format nu tangtu spésifikasi.

Format Paménta

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

  • CRLF mangrupa runtuyan dua karakter: r и n, misahkeun baris kahiji pamundut, headers jeung data.
  • <КОМАНДА> - salah sahiji CONNECT, DELETE, GET, HEAD, OPTIONS, PATCH, POST, PUT, TRACE. Browser bakal ngirim paréntah ka server kami GET, hartina "Kirimkeun kuring eusi file."
  • <URI> - identifier sumberdaya seragam. Contona, upami URI = /index.html, lajeng klien nu requests kaca utama loka éta.
  • <ВЕРСИЯ HTTP> - versi protokol HTTP dina format nu HTTP/X.Y. Versi anu paling sering dianggo ayeuna nyaéta HTTP/1.1.
  • <ЗАГОЛОВОК N> mangrupakeun pasangan konci-nilai dina format <КЛЮЧ>: <ЗНАЧЕНИЕ>, dikirim ka server pikeun analisis salajengna.
  • <ДАННЫЕ> - data diperlukeun ku server pikeun ngalakukeun operasi. Mindeng éta basajan JSON atawa format sejen.

Format Tanggapan

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

  • <КОД СТАТУСА> mangrupa angka ngalambangkeun hasil operasi. server kami bakal salawasna balik status 200 (operasi suksés).
  • <ОПИСАНИЕ СТАТУСА> - ngagambarkeun string tina kode status. Pikeun kode status 200 ieu OK.
  • <ЗАГОЛОВОК N> - lulugu tina format anu sarua sakumaha dina pamundut. Urang bakal mulangkeun judul Content-Length (ukuran file) jeung Content-Type: text/html (tipe data balik).
  • <ДАННЫЕ> - data dipénta ku pamaké. Dina kasus urang, ieu jalan ka gambar di HTML.

file http_server.c (Server threaded tunggal) ngawengku file common.h, anu ngandung prototipe fungsi ieu:

Témbongkeun prototipe fungsi di common.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);

Makro fungsional ogé dijelaskeun SAFE_CALL() jeung fungsina didefinisikeun fail(). Makro ngabandingkeun nilai ekspresi jeung kasalahan, sarta lamun kondisi bener, nelepon fungsi fail():

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

fungsi fail() nyitak argumen anu diliwatan ka terminal (sapertos printf()) sareng ngeureunkeun program nganggo kode 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);
}

fungsi new_server() mulih descriptor file tina stop kontak "server" dijieun ku panggero sistem socket(), bind() и listen() sarta sanggup narima sambungan asup dina mode non-blocking.

Témbongkeun fungsi 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;
}

  • Catet yén stop kontak mimitina didamel dina modeu non-blocking nganggo bandéra SOCK_NONBLOCKsangkan dina fungsi on_accept() (baca deui) panggero sistem accept() teu eureun palaksanaan thread.
  • upami reuse_port sarua jeung true, teras fungsi ieu bakal ngonpigurasikeun stop kontak sareng pilihan SO_REUSEPORT ngaliwatan setsockopt()ngagunakeun port sarua dina lingkungan multi-threaded (tingali bagian "Multi-threaded server").

Panyatur acara on_accept() disebut sanggeus OS ngahasilkeun hiji acara EPOLLIN, dina hal ieu hartina sambungan anyar bisa ditarima. on_accept() narima sambungan anyar, pindah ka mode non-blocking tur ngadaptar kalawan Handler acara on_recv() dina reaktor I/O.

Témbongkeun on_accept () fungsi

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);
}

Panyatur acara on_recv() disebut sanggeus OS ngahasilkeun hiji acara EPOLLIN, dina hal ieu hartina sambungan nu didaptarkeun on_accept(), siap nampi data.

on_recv() maca data tina sambungan dugi pamundut HTTP sagemblengna nampi, lajeng registers Handler a on_send() pikeun ngirim respon HTTP. Lamun klien megatkeun sambungan, stop kontak nu deregistered sarta ditutup ngagunakeun close().

Témbongkeun fungsi 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);
    }
}

Panyatur acara on_send() disebut sanggeus OS ngahasilkeun hiji acara EPOLLOUT, hartina sambungan nu didaptarkeun on_recv(), siap ngirim data. Pungsi ieu ngirimkeun réspon HTTP anu ngandung HTML sareng gambar ka klien teras ngarobih panangan acara deui on_recv().

Témbongkeun on_send () fungsi

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);
}

Sarta pamustunganana, dina file http_server.c, dina fungsi main() urang nyieun hiji I / O reaktor ngagunakeun reactor_new(), Jieun stop kontak server na ngadaptar eta, mimitian reaktor ngagunakeun reactor_run() pikeun persis hiji menit, lajeng urang ngaleupaskeun sumberdaya tur kaluar tina program.

Témbongkeun 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);
}

Hayu urang pariksa yen sagalana jalan sakumaha nu diharapkeun. Nyusun (chmod a+x compile.sh && ./compile.sh dina akar proyék) sareng ngaluncurkeun server anu ditulis nyalira, buka http://127.0.0.1:18470 dina browser sareng tingali naon anu kami ngarepkeun:

Reaktor I/O reaktor bulistir-fitur pinuh

Pangukuran kinerja

Témbongkeun spésifikasi mobil kuring

$ 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

Hayu urang ngukur kinerja server single-threaded. Hayu urang muka dua terminal: dina hiji urang bakal ngajalankeun ./http_server, dina béda- wrk. Saatos menit, statistik di handap ieu bakal ditingalikeun dina terminal kadua:

$ 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

Server single-threaded kami tiasa ngolah langkung ti 11 juta pamundut per menit tina 100 sambungan. Teu hasil goréng, tapi éta bisa ningkat?

server multithreaded

Sakumaha didadarkeun di luhur, reaktor I / O bisa dijieun dina threads misah, kukituna ngamangpaatkeun sakabéh cores CPU. Hayu urang ngalaksanakeun pendekatan ieu:

Témbongkeun 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);
    }
}

Ayeuna unggal thread milikna sorangan reaktor:

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

Punten dicatet yén argumen fungsi new_server() pangacara true. Ieu ngandung harti yén urang napelkeun pilihan pikeun stop kontak server SO_REUSEPORTngagunakeun éta dina lingkungan multi-threaded. Anjeun tiasa maca langkung rinci di dieu.

Lumpat kadua

Ayeuna hayu urang ngukur kinerja server multi-threaded:

$ 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

Jumlah requests diolah dina 1 menit ngaronjat ku ~3.28 kali! Tapi kami ngan ukur ~ XNUMX juta pondok tina jumlah babak, janten hayu urang nyobian ngalereskeun éta.

Mimitina hayu urang tingali statistik anu dihasilkeun parfum:

$ 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

Ngagunakeun CPU Affinity, kompilasi jeung -march=native, PGO, paningkatan dina jumlah hits cache, naékkeun MAX_EVENTS jeung pamakéan EPOLLET teu masihan kanaékan signifikan dina kinerja. Tapi naon anu lumangsung lamun nambahan jumlah sambungan simultaneous?

Statistik pikeun 352 sambungan sakaligus:

$ 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

Hasil anu dipikahoyong dicandak, sareng kalayan éta grafik anu pikaresepeun anu nunjukkeun gumantungna jumlah pamundut anu diolah dina 1 menit dina jumlah sambungan:

Reaktor I/O reaktor bulistir-fitur pinuh

Kami ningali yén saatos sababaraha ratus sambungan, jumlah pamundut anu diolah pikeun duanana server turun pisan (dina versi multi-threaded ieu langkung jelas). Naha ieu aya hubunganana sareng palaksanaan tumpukan Linux TCP / IP? Ngarasa Luncat nulis asumsi anjeun ngeunaan kabiasaan ieu grafik na optimizations pikeun pilihan multi-threaded na single-threaded dina komentar.

kumaha nyatet dina koméntar, tés kinerja ieu henteu nunjukkeun paripolah réaktor I / O dina beban nyata, sabab ampir sok server berinteraksi sareng pangkalan data, kaluaran log, nganggo kriptografi sareng TLS jeung sajabana, akibatna beban jadi teu seragam (dinamis). Tés sareng komponén pihak katilu bakal dilaksanakeun dina tulisan ngeunaan proaktor I/O.

Kelemahan reaktor I/O

Anjeun kedah ngartos yén reaktor I/O sanés kakuranganana, nyaéta:

  • Ngagunakeun hiji reaktor I / O dina lingkungan multi-threaded téh rada leuwih hese, sabab anjeun kedah ngatur aliran sacara manual.
  • Prakték nunjukkeun yén dina kalolobaan kasus bebanna henteu seragam, anu tiasa nyababkeun hiji logging benang bari anu sanés sibuk ku padamelan.
  • Lamun hiji panangan acara meungpeuk thread a, pamilih sistem sorangan ogé bakal meungpeuk, nu bisa ngakibatkeun bug hésé manggihan.

Ngarengsekeun masalah ieu I/O proaktor, nu mindeng boga scheduler nu merata distributes beban ka pool of threads, sarta ogé ngabogaan API leuwih merenah. Kami bakal ngobrol ngeunaan éta engké, dina tulisan kuring anu sanés.

kacindekan

Ieu dimana lalampahan urang tina téori langsung kana knalpot profiler geus datangna ka tungtung.

Anjeun teu kedah mikirkeun ieu, sabab aya seueur pendekatan anu sami anu pikaresepeun pikeun nyerat parangkat lunak jaringan kalayan tingkat genah sareng kecepatan anu béda. Narikna, dina pamanggih kuring, Tumbu dibere handap.

Dugi waktos salajengna!

proyék metot

Naon deui anu kudu dibaca?

sumber: www.habr.com

Tambahkeun komentar