Saya ingin berbicara tentang hal seperti
DPDK - ini adalah kerangka kerja untuk bekerja dengan jaringan yang melewati kernel. Yaitu Anda dapat langsung menulis dari userland \ untuk membaca dalam antrian kartu jaringan, tanpa perlu panggilan sistem apa pun. Ini menghemat banyak overhead untuk menyalin dan banyak lagi. Sebagai contoh, saya akan menulis aplikasi yang memberikan halaman uji melalui http dan membandingkan kecepatannya dengan nginx.
DPDK dapat diunduh di
sini . Jangan gunakan Stabil - itu tidak bekerja untuk saya di EC2, ambil 18,05 - semuanya dimulai dengan itu. Sebelum Anda mulai, Anda perlu memesan
hugepage dalam
sistem untuk operasi normal kerangka kerja. Pada prinsipnya, aplikasi pengujian dapat diluncurkan dengan opsi untuk bekerja tanpa hugepage, tetapi saya selalu memasukkannya. * Jangan lupa perbarui-grub setelah grub-mkconfig * Setelah Anda selesai dengan hugepages, segera buka ./usertools/dpdk-setup.py - benda ini akan mengumpulkan dan mengonfigurasi semua yang lain. Di Google Anda dapat menemukan instruksi yang merekomendasikan pengumpulan dan pengaturan sesuatu untuk mem-bypass dpdk-setup.py - jangan lakukan ini. Yah, Anda bisa melakukannya, hanya dengan saya, sementara saya tidak menggunakan dpdk-setup.py, tidak ada yang berhasil. Secara singkat, urutan tindakan di dalam dpdk-setup.py:
- membangun x86_x64 linux
- memuat modul kernel igb uio
- peta hugepage ke / mnt / besar
- ikat nic yang diinginkan di uio (jangan lupa lakukan ifconfig ethX sebelumnya)
Setelah itu, Anda bisa membuat contoh dengan menjalankan make di direktori bersamanya. Anda hanya perlu membuat variabel lingkungan RTE_SDK yang mengarah ke direktori dengan DPDK.
Di sinilah letak kode contoh lengkap. Ini terdiri dari inisialisasi, implementasi versi primitif tcp / ip dan http parser primitif. Mari kita mulai dengan inisialisasi.
int main(int argc, char** argv) { int ret;
Pada saat itu, ketika melalui dpdk-setup.py kita mengikat antarmuka jaringan yang dipilih ke driver dpdk, antarmuka jaringan ini tidak lagi dapat diakses oleh kernel. Setelah itu, kartu jaringan akan merekam setiap paket yang datang ke antarmuka ini melalui DMA dalam antrian yang kami sediakan untuk itu.
Dan di sini adalah loop pemrosesan paket.
struct rte_mbuf* packets[MAX_PACKETS]; uint16_t rx_current_queue = 0; while (1) {
Fungsi rte_eth_rx_burst digunakan untuk membaca paket dari antrian.Jika ada sesuatu dalam antrian, ia akan membaca paket dan meletakkannya ke dalam array. Jika tidak ada dalam antrian, 0 akan dikembalikan, dalam hal ini, Anda harus segera memanggilnya lagi. Ya, pendekatan ini "menghabiskan" waktu CPU untuk apa-apa jika saat ini tidak ada data di jaringan, tetapi jika kami sudah mengambil dpdk, maka ini dianggap bukan kasus kami. * Penting,
fungsi ini bukan thread-safe , tidak dapat dibaca dari antrian yang sama dalam proses yang berbeda * Setelah memproses paket, rte_pktmbuf_free harus dipanggil. Untuk mengirim paket, Anda dapat menggunakan fungsi rte_eth_tx_burst, yang menempatkan rte_mbuf diterima dari rte_pktmbuf_alloc ke dalam antrian kartu jaringan.
Setelah header paket dibongkar, penting untuk membangun sesi tcp. Protokol tcp penuh dengan berbagai kasus khusus, situasi khusus dan bahaya penolakan layanan. Implementasi tcp yang kurang lebih lengkap adalah latihan yang sangat baik untuk pengembang yang berpengalaman, namun demikian, tidak termasuk dalam kerangka kerja yang dijelaskan di sini. Dalam contoh, tcp diimplementasikan cukup untuk pengujian. Menerapkan tabel sesi berdasarkan tabel
hash yang disertakan dengan dpdk , mengatur dan memutus koneksi tcp, mentransmisikan dan menerima data tanpa memperhitungkan kehilangan akun dan penyusunan ulang paket. Tabel hash dari dpdk memiliki batasan penting yang bisa Anda baca tetapi tidak bisa menulis ke banyak utas. Contoh dibuat single-threaded dan masalah ini tidak penting di sini, dan dalam hal memproses lalu lintas pada beberapa core, Anda dapat menggunakan RSS, mengirim tabel hash dan melakukannya tanpa memblokir.
Implementasi TCP tatic void process_tcp(struct rte_mbuf* m, struct tcp_hdr* tcp_header, struct tcp_key* key, void* data, size_t data_size) { TRACE; struct tcp_state* state; if (rte_hash_lookup_data(g_clients, key, (void**)&state) < 0)
Pengurai http hanya akan mendukung GET untuk membaca URL dari sana dan mengembalikan html dengan URL yang diminta.
Pengurai HTTP static void feed_http(void* data, size_t data_size, struct tcp_state* state) { TRACE; size_t remaining_data = data_size; char* current = (char*)data; struct http_state* http = &state->http; if (http->state == HTTP_BAD_STATE) { TRACE; return; } while (remaining_data > 0) { switch(http->state) { case HTTP_START: { if (*current == 'G') { http->state = HTTP_READ_G; } else { http->state = HTTP_BAD_STATE; } break; } case HTTP_READ_G: { if (*current == 'E') { http->state = HTTP_READ_E; } else { http->state = HTTP_BAD_STATE; } break; } case HTTP_READ_E: { if (*current == 'T') { http->state = HTTP_READ_T; } else { http->state = HTTP_BAD_STATE; } break; } case HTTP_READ_T: { if (*current == ' ') { http->state = HTTP_READ_SPACE; } else { http->state = HTTP_BAD_STATE; } break; } case HTTP_READ_SPACE: { if (*current != ' ') { http->request_url[http->request_url_size] = *current; ++http->request_url_size; if (http->request_url_size > MAX_URL_SIZE) { http->state = HTTP_BAD_STATE; } } else { http->state = HTTP_READ_URL; http->request_url[http->request_url_size] = '\0'; } break; } case HTTP_READ_URL: { if (*current == '\r') { http->state = HTTP_READ_R1; } break; } case HTTP_READ_R1: { if (*current == '\n') { http->state = HTTP_READ_N1; } else if (*current == '\r') { http->state = HTTP_READ_R1; } else { http->state = HTTP_READ_URL; } break; } case HTTP_READ_N1: { if (*current == '\r') { http->state = HTTP_READ_R2; } else { http->state = HTTP_READ_URL; } break; } case HTTP_READ_R2: { if (*current == '\n') { TRACE; char content_length[32]; sprintf(content_length, "%lu", g_http_part2_size - 4 + http->request_url_size + g_http_part3_size); size_t content_length_size = strlen(content_length); size_t total_data_size = g_http_part1_size + g_http_part2_size + g_http_part3_size + http->request_url_size + content_length_size; struct tcp_hdr* tcp_header; struct rte_mbuf* packet = build_packet(state, total_data_size, &tcp_header); if (packet != NULL) { tcp_header->rx_win = TX_WINDOW_SIZE; tcp_header->sent_seq = htonl(state->my_seq_sent); tcp_header->recv_ack = htonl(state->remote_seq + data_size); #ifdef KEEPALIVE state->my_seq_sent += total_data_size; #else state->my_seq_sent += total_data_size + 1;
Setelah contoh siap, Anda dapat membandingkan kinerjanya dengan nginx. Karena Saya tidak bisa membuat stand nyata di rumah, saya menggunakan amazon EC2. EC2 melakukan koreksi dalam pengujian - saya harus meninggalkan Koneksi: tutup permintaan, karena suatu tempat di 300k rps paket SYN mulai turun beberapa detik setelah dimulainya tes. Rupanya, ada semacam perlindungan terhadap SYN-banjir, sehingga permintaan itu dibuat tetap hidup. Pada EC2, dpdk tidak berfungsi pada semua instance, misalnya pada m1.medium tidak berfungsi. Dudukan menggunakan 1 instance r4.8xlarge dengan aplikasi dan 2 instans r4.8xlarge untuk membuat beban. Mereka berkomunikasi pada antarmuka jaringan hotel melalui subnet VPC pribadi. Saya mencoba memuat dengan berbagai utilitas: ab, wrk, h2load, pengepungan. Yang paling nyaman adalah wrk, karena ab adalah single-threaded dan menghasilkan statistik yang terdistorsi jika ada kesalahan pada jaringan.
Dengan banyak lalu lintas di EC2, sejumlah tetes tertentu dapat diamati, untuk aplikasi biasa ini tidak akan terlihat, tetapi dalam kasus ab, setiap transmisi ulang memakan waktu dan ab total, sehingga data jumlah rata-rata permintaan per detik tidak sesuai. Alasan untuk drop adalah misteri yang terpisah untuk ditangani, bagaimanapun, fakta bahwa ada masalah tidak hanya ketika menggunakan dpdk, tetapi juga dengan nginx, menunjukkan bahwa ini sepertinya bukan contoh dari sesuatu yang salah.
Saya melakukan tes dalam dua tahap, pertama saya menjalankan wrk pada 1 instance, kemudian pada 2. Jika total kinerja dari 2 instance adalah 1, maka itu berarti saya tidak mengalami kinerja wrk itu sendiri.
Hasil uji contoh dpdk pada r4.8xlargeluncurkan wrk c 1 instance
root@ip-172-30-0-127:~# wrk -t64 -d10s -c4000 --latency http://172.30.4.10/testu
Running 10s test @ http://172.30.4.10/testu
64 threads and 4000 connections
Thread Stats Avg Stdev Max +/- Stdev
Latency 32.19ms 63.43ms 817.26ms 85.01%
Req/Sec 15.97k 4.04k 113.97k 93.47%
Latency Distribution
50% 2.58ms
75% 17.57ms
90% 134.94ms
99% 206.03ms
10278064 requests in 10.10s, 1.70GB read
Socket errors: connect 0, read 17, write 0, timeout 0
Requests/sec: 1017645.11
Transfer/sec: 172.75MB
Menjalankan wrk dari 2 instance sekaligus
root@ip-172-30-0-127:~# wrk -t64 -d10s -c4000 --latency http://172.30.4.10/testu
Running 10s test @ http://172.30.4.10/testu
64 threads and 4000 connections
Thread Stats Avg Stdev Max +/- Stdev
Latency 67.28ms 119.20ms 1.64s 88.90%
Req/Sec 7.99k 4.58k 132.62k 96.67%
Latency Distribution
50% 2.31ms
75% 103.45ms
90% 191.51ms
99% 563.56ms
5160076 requests in 10.10s, 0.86GB read
Socket errors: connect 0, read 2364, write 0, timeout 1
Requests/sec: 510894.92
Transfer/sec: 86.73MB
root@ip-172-30-0-225:~# wrk -t64 -d10s -c4000 --latency http://172.30.4.10/testu
Running 10s test @ http://172.30.4.10/testu
64 threads and 4000 connections
Thread Stats Avg Stdev Max +/- Stdev
Latency 74.87ms 148.64ms 1.64s 93.45%
Req/Sec 8.22k 2.59k 42.51k 81.21%
Latency Distribution
50% 2.41ms
75% 110.42ms
90% 190.66ms
99% 739.67ms
5298083 requests in 10.10s, 0.88GB read
Socket errors: connect 0, read 0, write 0, timeout 148
Requests/sec: 524543.67
Transfer/sec: 89.04MB
Nginx memberikan hasil seperti ituluncurkan wrk c 1 instance
root@ip-172-30-0-127:~# wrk -t64 -d10s -c4000 --latency http://172.30.4.10/testu
Running 10s test @ http://172.30.4.10/testu
64 threads and 4000 connections
Thread Stats Avg Stdev Max +/- Stdev
Latency 14.36ms 56.41ms 1.92s 95.26%
Req/Sec 15.27k 3.30k 72.23k 83.53%
Latency Distribution
50% 3.38ms
75% 6.82ms
90% 10.95ms
99% 234.99ms
9813464 requests in 10.10s, 2.12GB read
Socket errors: connect 0, read 1, write 0, timeout 3
Requests/sec: 971665.79
Transfer/sec: 214.94MB
Menjalankan wrk dari 2 instance sekaligus
root@ip-172-30-0-127:~# wrk -t64 -d10s -c4000 --latency http://172.30.4.10/testu
Running 10s test @ http://172.30.4.10/testu
64 threads and 4000 connections
Thread Stats Avg Stdev Max +/- Stdev
Latency 52.91ms 82.19ms 1.04s 82.93%
Req/Sec 8.05k 3.09k 55.62k 89.11%
Latency Distribution
50% 3.66ms
75% 94.87ms
90% 171.83ms
99% 354.26ms
5179253 requests in 10.10s, 1.12GB read
Socket errors: connect 0, read 134, write 0, timeout 0
Requests/sec: 512799.10
Transfer/sec: 113.43MB
root@ip-172-30-0-225:~# wrk -t64 -d10s -c4000 --latency http://172.30.4.10/testu
Running 10s test @ http://172.30.4.10/testu
64 threads and 4000 connections
Thread Stats Avg Stdev Max +/- Stdev
Latency 64.38ms 121.56ms 1.67s 90.32%
Req/Sec 7.30k 2.54k 34.94k 82.10%
Latency Distribution
50% 3.68ms
75% 103.32ms
90% 184.05ms
99% 561.31ms
4692290 requests in 10.10s, 1.01GB read
Socket errors: connect 0, read 2, write 0, timeout 21
Requests/sec: 464566.93
Transfer/sec: 102.77MB
konfigurasi nginxpengguna www-data;
pekerja_proses otomatis;
pid /run/nginx.pid;
worker_rlimit_nofile 50000;
acara {
koneksi pekerja 10.000;
}
http {
sendfile aktif;
tcp_nopush aktif;
tcp_nodelay aktif;
keepalive_timeout 65;
types_hash_max_size 2048;
termasuk /etc/nginx/mime.types;
default_type text / plain;
error_log /var/log/nginx/error.log;
access_log off;
server {
dengarkan 80 backlog default_server = 10.000 reuseport;
lokasi / {
kembalikan 200 "answer.padding _____________________________________________________________";
}
}
}
Secara total, kita melihat bahwa dalam kedua contoh kita mendapatkan sekitar 1 juta permintaan per detik, hanya nginx yang menggunakan semua 32 cpu untuk ini, dan dpdk hanya satu. Mungkin EC2 lagi meletakkan babi dan 1M rps adalah batasan jaringan, tetapi bahkan jika itu, hasilnya tidak banyak terdistorsi, karena menambahkan contoh keterlambatan formulir
untuk (int x = 0; x <100; ++ x) http β request_url [0] = 'a' + (http-> request_url [0]% 10) sebelum mengirim paket, itu sudah mengurangi rps, yang berarti pemuatan cpu hampir penuh dengan pekerjaan yang bermanfaat.
Dalam perjalanan percobaan, satu misteri ditemukan, yang saya masih belum bisa pecahkan. Jika Anda mengaktifkan pelepasan checksum, mis., Perhitungan checksum untuk header ip dan tcp oleh kartu jaringan itu sendiri, maka kinerja keseluruhan turun, dan latensi membaik.
Inilah awalnya dengan offloading diaktifkan
root@ip-172-30-0-127:~# wrk -t64 -d10s -c4000 --latency http://172.30.4.10/testu
Running 10s test @ http://172.30.4.10/testu
64 threads and 4000 connections
Thread Stats Avg Stdev Max +/- Stdev
Latency 5.91ms 614.33us 28.35ms 96.17%
Req/Sec 10.48k 1.51k 69.89k 98.78%
Latency Distribution
50% 5.91ms
75% 6.01ms
90% 6.19ms
99% 6.99ms
6738296 requests in 10.10s, 1.12GB read
Requests/sec: 667140.71
Transfer/sec: 113.25MB
Dan di sini dengan checksum di cpu
root@ip-172-30-0-127:~# wrk -t64 -d10s -c4000 --latency http://172.30.4.10/testu
Running 10s test @ http://172.30.4.10/testu
64 threads and 4000 connections
Thread Stats Avg Stdev Max +/- Stdev
Latency 32.19ms 63.43ms 817.26ms 85.01%
Req/Sec 15.97k 4.04k 113.97k 93.47%
Latency Distribution
50% 2.58ms
75% 17.57ms
90% 134.94ms
99% 206.03ms
10278064 requests in 10.10s, 1.70GB read
Socket errors: connect 0, read 17, write 0, timeout 0
Requests/sec: 1017645.11
Transfer/sec: 172.75MB
OK, saya bisa menjelaskan penurunan kinerja dengan fakta bahwa kartu jaringan melambat, meskipun ini aneh, itu harus dipercepat. Tetapi mengapa, dengan perhitungan checksum pada peta latensi, ternyata hampir konstan sama dengan 6 ms, dan jika Anda mengandalkan cpu, maka ia mengapung dari 2,5 ms ke 817 ms? Tugas ini akan sangat disederhanakan oleh dudukan non-virtual dengan koneksi langsung, tetapi sayangnya saya tidak memiliki ini. DPDK sendiri tidak berfungsi pada semua kartu jaringan, dan sebelum menggunakannya, Anda perlu memeriksa
daftarnya .
Dan akhirnya, survei.