Александр Крижановский, natsys lab

20
Про сокеты и миллионы пакетов в секунду с одного CPU ядра Александр Крижановский

Upload: ontico

Post on 30-Jun-2015

1.179 views

Category:

Documents


3 download

DESCRIPTION

HighLoad++ 2013

TRANSCRIPT

Page 1: Александр Крижановский, NatSys Lab

Про сокеты и миллионы пакетов в секунду с одного CPU ядра

Александр Крижановский

Page 2: Александр Крижановский, NatSys Lab

О сокетах (серверных)

● путь пакета в Linux с адаптера в TCP-сокет;● установление новых соединений, мультиплексирование и чтение из сокетов;

● как ускорить прикладной сервер (оптимизация accept(2), MSI-X и RPS/RFS, GRO);

● можно быстрее: Oracle Reliable Datagram Sockets● еще быстрее: переход к полностью синхронным сокетам

● В основном интересовала скорость установления соединений

Page 3: Александр Крижановский, NatSys Lab

Путь пакета (recv)

1.RSS (Receive Side Scaling)

2.MSI-X очереди

3.DCA (Direct Cache Access)

=> User/kernel: копирования

=> асинхронность получения и чтения

Page 4: Александр Крижановский, NatSys Lab

Типовой сценарий

    listen(listen_sd, 5);    epoll_ctl(wd, EPOLL_CTL_ADD, listen_sd, &ev);

    while (1) {        n = epoll_wait(wd, ev, 64, ­1);        for (int i = 0; i < n; ++i) {            if (ev[i].data.fd == listen_sd) {                new_sd = accept(listen_sd, NULL, NULL);            } else {                recv(ev[i].data.fd, msg, READ_SZ, 0);            }        }    }

Page 5: Александр Крижановский, NatSys Lab

Чтение из сокета: recvfrom(2)

● sockfd_lookup_light() - захватывает файловый дескриптор, получает соответствующий сокет

● sock­>ops­>recvmsg() => tcp_recvmsg()– skb_queue_walk(&sk­>sk_receive_queue, skb) —

проходим по списку буферов● skb_copy_datagram_iovec() - копируем● tcp_cleanup_rbuf() - очищаем место в буфере

и отправляем ACK● tcp_rcv_space_adjust() - пересчитываем

свободное место в сокетном буфере

Page 6: Александр Крижановский, NatSys Lab

Мультиплексирование: epoll_wait(2)● epoll_wait()   ep_poll()   → →__add_wait_queue_exclusive() — встаем в очередь ожидания и засыпаем

● epoll_ctl(EPOLL_CTL_ADD)   ep_insert()   → →tcp_poll() — добавляет сокет в очередь ожидания

● tcp_v4_rcv()   tcp_v4_do_rcv()   → →tcp_rcv_state_process()   tcp_data_queue()   → →sk­>sk_data_ready()– ep_poll_callback() - разбудить процессы в

очереди ожидания

Page 7: Александр Крижановский, NatSys Lab

Установление новых соединений: listen(2) & accept(2)● inet_csk_listen_start() - аллоцирует место в

очереди, равное backlog (второй аргумент listen(2))

● inet_csk_accept()   →inet_csk_wait_for_connect()   → wait (schedule)

● tcp_v4_do_rcv()   tcp_child_process()→– tcp_rcv_state_process()

● tcp_set_state(sk, TCP_ESTABLISHED);sk­>sk_state_change(sk);

– parent­>sk_data_ready(parent, 0);

Page 8: Александр Крижановский, NatSys Lab

Пример переключения контекста: «Наш клиент?»1.epoll_wait(2)

2.accept(2)

3.getpeername(2)

4.=> не наш клиент: close(2)

7 контекст свитчей

Хорошо: user/kernel context switch не инвалидирует кэши

Page 9: Александр Крижановский, NatSys Lab

Пример переключения контекста: «Наш клиент?»1.epoll_wait(2)

2.accept(2)

3.getpeername(2)

4.=> не наш клиент: close(2)

7 контекст свитчей

Хорошо: user/kernel context switch не инвалидирует кэши

Page 10: Александр Крижановский, NatSys Lab

Что если приходит сразу много клиентов?    listen(listen_sd, 5);    epoll_ctl(wd, EPOLL_CTL_ADD, listen_sd, &ev);

    while (1) {

        n = epoll_wait(wd, ev, 64, ­1);

        for (int i = 0; i < n; ++i) {

            if (ev[i].data.fd == listen_sd)

                new_sd = accept(listen_sd, NULL, NULL);

        }

    }

Page 11: Александр Крижановский, NatSys Lab

Оптимизация accept(2)

BRECHT T., PARIAG D., GAMMO L. «accept()able strategies for improving web server performance.»

    listen(listen_sd, 1000);    fcntl(sd, F_SETFL, flags | O_NONBLOCK);    epoll_ctl(wd, EPOLL_CTL_ADD, listen_sd, &ev)      while (1) {        n = epoll_wait(wd, ev, 64, ­1);        for (int i = 0; i < n; ++i) {            if (ev[i].data.fd == listen_sd)                do {                    new_sd = accept(listen_sd, NULL, NULL);                } while (new_sd >= 0);        }    }

Page 12: Александр Крижановский, NatSys Lab

MSI-X, RPS, RFS

1. RSS (Receive Packet Steering)

2. RFS (Receive Flow Steering)

3. MSI-X не всегда хорошо балансирует трафик => RPS

4. Но MSI-X - «железный»

Page 13: Александр Крижановский, NatSys Lab

Сокетные каллбеки

● sk_data_ready — вызывается при получении данных на сокете

● sk_state_change — изменение состояния сокета ● sk_write_space — у сокета появилось место в

буфере записи ● sk_error_report — ошибка на сокете ● sk_backlog_rcv — чтение из очереди отложенных

сегментов (!tcp_low_latency)● sk_destruct — вызывается на удалении сокета

Page 14: Александр Крижановский, NatSys Lab

Oracle Reliable Datagram Sockets (RDS)Ядерная реализация сокетов (linux/net/rds):● нет копирований и переключений контекстов● основная работа происходит на калбеках сокетов

Функции, необходимые accept(), могут спать => wait queue

Page 15: Александр Крижановский, NatSys Lab

RDS accept

    int rds_tcp_accept_one(struct socket *sock) {        sock­>ops­>accept(sock, new_sock, O_NONBLOCK);    }

    void rds_tcp_accept_worker(struct work_struct *work) {        while (rds_tcp_accept_one(rds_tcp_listen_sock) == 0);    }

    DECLARE_WORK(rds_tcp_listen_work, rds_tcp_accept_worker);

    void rds_tcp_listen_data_ready(struct sock *sk, int bytes) {        if (sk­>sk_state == TCP_LISTEN)            queue_work(rds_wq, &rds_tcp_listen_work);    }

    sock­>sk­>sk_data_ready = rds_tcp_listen_data_ready;

Page 16: Александр Крижановский, NatSys Lab

Synchronous Sockets

● Всё делается только в softirq ● Хорошо подходят для большого числа

короткоживущих соединений● Нужен патч ядра● Могут быть вынесены в некрасивую библиотеку

ядра:– upcalls & downcalls– Но код с ней в ~2 раза короче (200 vs 364)

Page 17: Александр Крижановский, NatSys Lab

Synchronous Sockets: пример

    struct { SsProto proto;} my_proto; 

    struct socket *listen_sock;

    int my_read(void *proto, char *data, int len) {        /* Read application level data. */    }

    int my_conn_new(struct sock *sock) {        /* Handle a new TCP connection */    }

    SsHooks ssocket_hooks = {.connection_new = my_connection_new};

    int my_init(void) {        ss_hooks_register(&ssocket_hooks);        ss_proto_push_handler((SsProto *)&my_proto, my_read);        ss_tcp_set_listen(listen_sock­>sk, (SsProto *)&my_proto);    }

Page 18: Александр Крижановский, NatSys Lab

Скорость установления соединений

Page 19: Александр Крижановский, NatSys Lab

Запросы в скунду в зависимости от числа соединений

Page 20: Александр Крижановский, NatSys Lab

Спасибо!

Synchronous Sockets (+патч):https://github.com/krizhanovsky/sync_socket

[email protected]