1M HTTP rps على وحدة معالجة مركزية واحدة. DPDK بدلاً من nginx + linux kernel TCP / IP

أريد أن أتحدث عن شيء مثل DPDK - هذا إطار عمل للعمل مع شبكة تتجاوز النواة. على سبيل المثال يمكنك الكتابة مباشرة من userland \ للقراءة في قائمة انتظار بطاقة الشبكة ، دون الحاجة إلى أي مكالمات نظام. هذا يوفر عليك الكثير من النفقات العامة للنسخ والمزيد. كمثال ، سأكتب تطبيقًا يعطي صفحة اختبار عبر http ويقارن سرعته بـ nginx.

يمكن تنزيل DPDK هنا . لا تأخذ Stable - لم يعمل معي على EC2 ، خذ 18.05 - كل شيء بدأ به. قبل البدء ، تحتاج إلى حجز hugepages في النظام للتشغيل العادي للإطار. من حيث المبدأ ، يمكن تشغيل التطبيقات الاختبارية مع خيار العمل بدون صفحات hugepages ، لكنني قمت دائمًا بتضمينها. * لا تنس تحديث grub بعد grub-mkconfig * بعد الانتهاء من صفحات hugepages ، انتقل فورًا إلى ./usertools/dpdk-setup.py - هذا الشيء سيجمع ويهيئ كل شيء آخر. في Google ، يمكنك العثور على تعليمات توصي بجمع وإعداد شيء لتجاوز dpdk-setup.py - لا تفعل ذلك. حسنًا ، يمكنك فعل ذلك معي فقط ، بينما لم أستخدم dpdk-setup.py ، لم ينجح شيء. باختصار ، تسلسل الإجراءات داخل dpdk-setup.py:

  • بناء لينكس x86_x64
  • تحميل igb uio kernel module
  • خريطة hugepages إلى / mnt / ضخمة
  • ربط nic المطلوب في uio (لا تنس أن تفعل ifconfig ethX down قبل)

بعد ذلك ، يمكنك إنشاء مثال بتشغيل الأمر في الدليل باستخدامه. من الضروري فقط إنشاء متغير بيئة RTE_SDK الذي يشير إلى دليل باستخدام DPDK.

هنا يكمن رمز المثال الكامل. وهو يتألف من التهيئة ، وتنفيذ النسخة البدائية من TCP / IP ومحلل http البدائي. لنبدأ بالتهيئة.

int main(int argc, char** argv) { int ret; //  dpdk ret = rte_eal_init(argc, argv); if (ret < 0) { rte_panic("Cannot init EAL\n"); } //  memory pool g_packet_mbuf_pool = rte_pktmbuf_pool_create("mbuf_pool", 131071, 32, 0, RTE_MBUF_DEFAULT_BUF_SIZE, rte_socket_id()); if (g_packet_mbuf_pool == NULL) { rte_exit(EXIT_FAILURE, "Cannot init mbuf pool\n"); } g_tcp_state_pool = rte_mempool_create("tcp_state_pool", 65535, sizeof(struct tcp_state), 0, 0, NULL, NULL, NULL, NULL, rte_socket_id(), 0); if (g_tcp_state_pool == NULL) { rte_exit(EXIT_FAILURE, "Cannot init tcp_state pool\n"); } //  hash table struct rte_hash_parameters hash_params = { .entries = 64536, .key_len = sizeof(struct tcp_key), .socket_id = rte_socket_id(), .hash_func_init_val = 0, .name = "tcp clients table" }; g_clients = rte_hash_create(&hash_params); if (g_clients == NULL) { rte_exit(EXIT_FAILURE, "No hash table created\n"); } //    1     uint8_t nb_ports = rte_eth_dev_count(); if (nb_ports == 0) { rte_exit(EXIT_FAILURE, "No Ethernet ports - bye\n"); } if (nb_ports > 1) { rte_exit(EXIT_FAILURE, "Not implemented. Too much ports\n"); } //     struct rte_eth_conf port_conf = { .rxmode = { .split_hdr_size = 0, .header_split = 0, /**< Header Split disabled */ .hw_ip_checksum = 0, /**< IP checksum offload disabled */ .hw_vlan_filter = 0, /**< VLAN filtering disabled */ .jumbo_frame = 0, /**< Jumbo Frame Support disabled */ .hw_strip_crc = 0, /**< CRC stripped by hardware */ }, .txmode = { .mq_mode = ETH_MQ_TX_NONE, }, }; //          port_conf.txmode.offloads |= DEV_TX_OFFLOAD_IPV4_CKSUM; port_conf.txmode.offloads |= DEV_TX_OFFLOAD_TCP_CKSUM; ret = rte_eth_dev_configure(0, RX_QUEUE_COUNT, TX_QUEUE_COUNT, &port_conf); if (ret < 0) { rte_exit(EXIT_FAILURE, "Cannot configure device: err=%d\n", ret); } //     for (uint16_t j=0; j<RX_QUEUE_COUNT; ++j) { ret = rte_eth_rx_queue_setup(0, j, 1024, rte_eth_dev_socket_id(0), NULL, g_packet_mbuf_pool); if (ret < 0) { rte_exit(EXIT_FAILURE, "rte_eth_rx_queue_setup:err=%d\n", ret); } } //    struct rte_eth_txconf txconf = { .offloads = port_conf.txmode.offloads, }; for (uint16_t j=0; j<TX_QUEUE_COUNT; ++j) { ret = rte_eth_tx_queue_setup(0, j, 1024, rte_eth_dev_socket_id(0), &txconf); if (ret < 0) { rte_exit(EXIT_FAILURE, "rte_eth_tx_queue_setup:err=%d\n", ret); } } // NIC ret = rte_eth_dev_start(0); if (ret < 0) { rte_exit(EXIT_FAILURE, "rte_eth_dev_start:err=%d\n", ret); } //   lcore_hello(NULL); return 0; } 

في تلك اللحظة ، عندما نقوم من خلال dpdk-setup.py بربط واجهة الشبكة المحددة ببرنامج تشغيل dpdk ، يتوقف وصول واجهة الشبكة هذه إلى kernel. بعد ذلك ، ستسجل بطاقة الشبكة أي حزم تصل إلى هذه الواجهة من خلال DMA في قائمة الانتظار التي قدمناها لها.

وهنا حلقة معالجة الحزم.

 struct rte_mbuf* packets[MAX_PACKETS]; uint16_t rx_current_queue = 0; while (1) { //  unsigned packet_count = rte_eth_rx_burst(0, (++rx_current_queue) % RX_QUEUE_COUNT, packets, MAX_PACKETS); for (unsigned j=0; j<packet_count; ++j) { struct rte_mbuf* m = packets[j]; //   ethernet  struct ether_hdr* eth_header = rte_pktmbuf_mtod(m, struct ether_hdr*); //  IP  if (RTE_ETH_IS_IPV4_HDR(m->packet_type)) { do { //   if (rte_pktmbuf_data_len(m) < sizeof(struct ether_hdr) + sizeof(struct ipv4_hdr) + sizeof(struct tcp_hdr)) { TRACE; break; } struct ipv4_hdr* ip_header = (struct ipv4_hdr*)((char*)eth_header + sizeof(struct ether_hdr)); if ((ip_header->next_proto_id != 0x6) || (ip_header->version_ihl != 0x45)) { TRACE; break; } if (ip_header->dst_addr != MY_IP_ADDRESS) { TRACE; break; } if (rte_pktmbuf_data_len(m) < htons(ip_header->total_length) + sizeof(struct ether_hdr)) { TRACE; break; } if (htons(ip_header->total_length) < sizeof(struct ipv4_hdr) + sizeof(struct tcp_hdr)) { TRACE; break; } struct tcp_hdr* tcp_header = (struct tcp_hdr*)((char*)ip_header + sizeof(struct ipv4_hdr)); size_t tcp_header_size = (tcp_header->data_off >> 4) * 4; if (rte_pktmbuf_data_len(m) < sizeof(struct ether_hdr) + sizeof(struct ipv4_hdr) + tcp_header_size) { TRACE; break; } if (tcp_header->dst_port != 0x5000) { TRACE; break; } size_t data_size = htons(ip_header->total_length) - sizeof(struct ipv4_hdr) - tcp_header_size; void* data = (char*)tcp_header + tcp_header_size; //     hash table struct tcp_key key = { .ip = ip_header->src_addr, .port = tcp_header->src_port }; //    tcp process_tcp(m, tcp_header, &key, data, data_size); } while(0); } else if (eth_header->ether_type == 0x0608) // ARP { //     -       ARP- do { if (rte_pktmbuf_data_len(m) < sizeof(struct arp) + sizeof(struct ether_hdr)) { TRACE; break; } struct arp* arp_packet = (struct arp*)((char*)eth_header + sizeof(struct ether_hdr)); if (arp_packet->opcode != 0x100) { TRACE; break; } if (arp_packet->dst_pr_add != MY_IP_ADDRESS) { TRACE; break; } send_arp_response(arp_packet); } while(0); } else { TRACE; } rte_pktmbuf_free(m); } } 

يتم استخدام الدالة rte_eth_rx_burst لقراءة الحزم من قائمة الانتظار. إذا كان هناك شيء في قائمة الانتظار ، فستقرأ الحزم وتضعها في مصفوفة. إذا لم يكن هناك أي شيء في قائمة الانتظار ، فسيتم إرجاع 0 ، وفي هذه الحالة ، يجب عليك الاتصال به على الفور مرة أخرى. نعم ، هذا النهج "يقضي" وقت وحدة المعالجة المركزية في لا شيء إذا لم يكن هناك حاليًا بيانات على الشبكة ، ولكن إذا أخذنا بالفعل dpdk ، فمن المفترض أن هذا ليس حالتنا. * هام ، الوظيفة غير آمنة لمؤشر الترابط ، لا يمكن قراءتها من قائمة الانتظار نفسها في العمليات المختلفة * بعد معالجة الحزمة ، يجب استدعاء rte_pktmbuf_free. لإرسال حزمة ، يمكنك استخدام دالة rte_eth_tx_burst ، التي تضع rte_mbuf المستلمة من rte_pktmbuf_alloc في قائمة انتظار بطاقة الشبكة.

بعد تفكيك رؤوس الحزمة ، سيكون من الضروري إنشاء جلسة tcp. بروتوكول tcp مليء بالعديد من الحالات الخاصة والمواقف الخاصة ومخاطر الحرمان من الخدمة. يعد تنفيذ برنامج tcp أكثر أو أقل اكتمالًا تدريبًا ممتازًا لمطور متمرس ، ولكن مع ذلك ، لا يتم تضمينه في الإطار الموصوف هنا. في المثال ، يتم تنفيذ tcp فقط بما يكفي للاختبار. تم تنفيذ جدول جلسة استنادًا إلى جدول التجزئة المُزود بـ dpdk ، وضبط وكسر اتصال tcp ، وإرسال واستقبال البيانات دون مراعاة الخسائر وإعادة ترتيب الحزم. يحتوي جدول التجزئة من dpdk على قيود مهمة يمكنك قراءتها ولكن لا يمكنك الكتابة إلى سلاسل رسائل متعددة. المثال مصنوع بسلاسل واحدة ، وهذه المشكلة ليست مهمة هنا ، وفي حالة معالجة حركة المرور على عدة نوى ، يمكنك استخدام RSS وإرسال جدول تجزئة والقيام بذلك دون حظر.

تنفيذ برنامج التعاون الفني
 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) //Documentaion lies!!! { TRACE; if ((tcp_header->tcp_flags & 0x2) != 0) // SYN { TRACE; struct ether_hdr* eth_header = rte_pktmbuf_mtod(m, struct ether_hdr*); if (rte_mempool_get(g_tcp_state_pool, (void**)&state) < 0) { ERROR("tcp state alloc fail"); return; } memcpy(&state->tcp_template, &g_tcp_packet_template, sizeof(g_tcp_packet_template)); memcpy(&state->tcp_template.eth.d_addr, &eth_header->s_addr, 6); state->tcp_template.ip.dst_addr = key->ip; state->tcp_template.tcp.dst_port = key->port; state->remote_seq = htonl(tcp_header->sent_seq); #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wpointer-to-int-cast" state->my_seq_start = (uint32_t)state; // not very secure. #pragma GCC diagnostic pop state->fin_sent = 0; state->http.state = HTTP_START; state->http.request_url_size = 0; //not thread safe! only one core used if (rte_hash_add_key_data(g_clients, key, state) == 0) { struct tcp_hdr* new_tcp_header; struct rte_mbuf* packet = build_packet(state, 12, &new_tcp_header); if (packet != NULL) { new_tcp_header->rx_win = TX_WINDOW_SIZE; new_tcp_header->sent_seq = htonl(state->my_seq_start); state->my_seq_sent = state->my_seq_start+1; ++state->remote_seq; new_tcp_header->recv_ack = htonl(state->remote_seq); new_tcp_header->tcp_flags = 0x12; // mss = 1380, no window scaling uint8_t options[12] = {0x02, 0x04, 0x05, 0x64, 0x03, 0x03, 0x00, 0x01, 0x01, 0x01, 0x01, 0x01}; memcpy((uint8_t*)new_tcp_header + sizeof(struct tcp_hdr), options, 12); new_tcp_header->data_off = 0x80; send_packet(new_tcp_header, packet); } else { ERROR("rte_pktmbuf_alloc, tcp synack"); } } else { ERROR("can't add connection to table"); rte_mempool_put(g_tcp_state_pool, state); } } else { ERROR("lost connection"); } return; } if ((tcp_header->tcp_flags & 0x2) != 0) // SYN retransmit { //not thread safe! only one core used if (rte_hash_del_key(g_clients, key) < 0) { ERROR("can't delete key"); } else { rte_mempool_put(g_tcp_state_pool, state); return process_tcp(m, tcp_header, key, data, data_size); } } if ((tcp_header->tcp_flags & 0x10) != 0) // ACK { TRACE; uint32_t ack_delta = htonl(tcp_header->recv_ack) - state->my_seq_start; uint32_t my_max_ack_delta = state->my_seq_sent - state->my_seq_start; if (ack_delta == 0) { if ((data_size == 0) && (tcp_header->tcp_flags == 0x10)) { ERROR("need to retransmit. not supported"); } } else if (ack_delta <= my_max_ack_delta) { state->my_seq_start += ack_delta; } else { ERROR("ack on unsent seq"); } } if (data_size > 0) { TRACE; uint32_t packet_seq = htonl(tcp_header->sent_seq); if (state->remote_seq == packet_seq) { feed_http(data, data_size, state); state->remote_seq += data_size; } else if (state->remote_seq-1 == packet_seq) // keepalive { struct tcp_hdr* new_tcp_header; struct rte_mbuf* packet = build_packet(state, 0, &new_tcp_header); if (packet != NULL) { new_tcp_header->rx_win = TX_WINDOW_SIZE; new_tcp_header->sent_seq = htonl(state->my_seq_sent); new_tcp_header->recv_ack = htonl(state->remote_seq); send_packet(new_tcp_header, packet); } else { ERROR("rte_pktmbuf_alloc, tcp ack keepalive"); } } else { struct tcp_hdr* new_tcp_header; struct rte_mbuf* packet = build_packet(state, state->http.last_message_size, &new_tcp_header); TRACE; if (packet != NULL) { new_tcp_header->rx_win = TX_WINDOW_SIZE; new_tcp_header->sent_seq = htonl(state->my_seq_sent - state->http.last_message_size); new_tcp_header->recv_ack = htonl(state->remote_seq); memcpy((char*)new_tcp_header+sizeof(struct tcp_hdr), &state->http.last_message, state->http.last_message_size); send_packet(new_tcp_header, packet); } else { ERROR("rte_pktmbuf_alloc, tcp fin ack"); } //ERROR("my bad tcp stack implementation((("); } } if ((tcp_header->tcp_flags & 0x04) != 0) // RST { TRACE; //not thread safe! only one core used if (rte_hash_del_key(g_clients, key) < 0) { ERROR("can't delete key"); } else { rte_mempool_put(g_tcp_state_pool, state); } } else if ((tcp_header->tcp_flags & 0x01) != 0) // FIN { struct tcp_hdr* new_tcp_header; struct rte_mbuf* packet = build_packet(state, 0, &new_tcp_header); TRACE; if (packet != NULL) { new_tcp_header->rx_win = TX_WINDOW_SIZE; new_tcp_header->sent_seq = htonl(state->my_seq_sent); new_tcp_header->recv_ack = htonl(state->remote_seq + 1); if (!state->fin_sent) { TRACE; new_tcp_header->tcp_flags = 0x11; // !@#$ the last ack } send_packet(new_tcp_header, packet); } else { ERROR("rte_pktmbuf_alloc, tcp fin ack"); } //not thread safe! only one core used if (rte_hash_del_key(g_clients, key) < 0) { ERROR("can't delete key"); } else { rte_mempool_put(g_tcp_state_pool, state); } } } 


لن يدعم المحلل http فقط GET لقراءة عناوين URL من هناك وإرجاع html مع عنوان URL المطلوب.

محلل 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; //+1 for FIN tcp_header->tcp_flags = 0x11; state->fin_sent = 1; #endif char* new_data = (char*)tcp_header + sizeof(struct tcp_hdr); memcpy(new_data, g_http_part1, g_http_part1_size); new_data += g_http_part1_size; memcpy(new_data, content_length, content_length_size); new_data += content_length_size; memcpy(new_data, g_http_part2, g_http_part2_size); new_data += g_http_part2_size; memcpy(new_data, http->request_url, http->request_url_size); new_data += http->request_url_size; memcpy(new_data, g_http_part3, g_http_part3_size); memcpy(&http->last_message, (char*)tcp_header+sizeof(struct tcp_hdr), total_data_size); http->last_message_size = total_data_size; send_packet(tcp_header, packet); } else { ERROR("rte_pktmbuf_alloc, tcp data"); } http->state = HTTP_START; http->request_url_size = 0; } else if (*current == '\r') { http->state = HTTP_READ_R1; } else { http->state = HTTP_READ_URL; } break; } default: { ERROR("bad http state"); return; } } if (http->state == HTTP_BAD_STATE) { return; } --remaining_data; ++current; } } 


بعد أن يصبح المثال جاهزًا ، يمكنك مقارنة الأداء بـ nginx. لأن لا يمكنني تجميع حامل حقيقي في المنزل ، لقد استخدمت أمازون EC2. قامت EC2 بتصحيحاتها في الاختبار - اضطررت إلى التخلي عن Connection: إغلاق الطلبات ، لأن في مكان ما بسرعة 300 كيلو بت في الثانية ، بدأت حزم SYN تنخفض بضع ثوانٍ بعد بدء الاختبار. على ما يبدو ، هناك نوع من الحماية ضد الفيضانات SYN ، لذلك تم جعل الطلبات على قيد الحياة. على EC2 ، لا يعمل dpdk على كافة الحالات ، على سبيل المثال على m1.medium لم يعمل. استخدم الحامل نسخة واحدة بحجم r4.8x تكبير مع التطبيق ومثالين حجم r4.8x تكبير لإنشاء تحميل. يتواصلون على واجهات شبكة الفنادق من خلال شبكة VPC فرعية خاصة. حاولت تحميل أدوات مختلفة: ab ، wrk ، h2load ، الحصار. الأكثر راحة كان wrk ، لأنه ab هو مؤشر ترابط واحد وينتج إحصائيات مشوهة إذا كانت هناك أخطاء على الشبكة.

مع وجود الكثير من حركة المرور في EC2 ، يمكن ملاحظة عدد معين من القطرات ، بالنسبة للتطبيقات العادية ، سيكون هذا غير مرئي ، ولكن في حالة ab ، فإن أي إعادة إرسال تستهلك الوقت الكلي و ab ، ونتيجة لذلك تكون البيانات حول متوسط ​​عدد الطلبات في الثانية غير مناسبة. أسباب القطرات هي لغز منفصل يجب التعامل معه ، ومع ذلك ، حقيقة أن هناك مشاكل ليس فقط عند استخدام dpdk ، ولكن أيضًا مع nginx ، تشير إلى أن هذا لا يبدو مثالًا لشيء خاطئ.

لقد أجريت الاختبار على مرحلتين ، ركضت أولاً حطامًا على مثيل واحد ، ثم في 2. إذا كان الأداء الإجمالي من حالتين هو 1 ، فهذا يعني أنني لم أواجه أداء wrk نفسه.

نتيجة اختبار مثال dpdk على r4.8xlarge
إطلاق wrk c 1 مثيل

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

تشغيل wrk من حالتين في نفس الوقت
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 مثل هذه النتائج
إطلاق wrk c 1 مثيل

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


تشغيل wrk من حالتين في نفس الوقت

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


تكوين nginx
بيانات www المستخدم ؛
عمليات العمال
pid /run/nginx.pid ؛
worker_rlimit_nofile 50000 ؛
الأحداث {
العمال_وصلات 10000 ؛
}}
http {
إرسال ملف ؛
tcp_nopush على ؛
tcp_nodelay على ؛
keepalive_timeout 65 ؛
types_hash_max_size 2048 ؛

تشمل /etc/nginx/mime.types ؛
نص افتراضي / نوع عادي ؛

error_log /var/log/nginx/error.log ؛
تم تعطيل الوصول.

الخادم {
الاستماع 80 back_server backlog = 10000 إعادة الإرسال ؛
الموقع / {
إرجاع 200 "answer.padding _____________________________________________________________" ؛
}}
}}
}}

في المجموع ، نرى أنه في كلا المثالين نحصل على حوالي 1 مليون طلب في الثانية ، استخدم nginx فقط 32 وحدة معالجة مركزية لهذا ، و dpdk واحد فقط. ربما يضع EC2 مرة أخرى خنزيرًا و 1 M rps يعد تقييدًا للشبكة ، ولكن حتى لو كان الأمر كذلك ، فإن النتائج ليست مشوهة كثيرًا ، لأن إضافة مثال لتأخير النموذج لـ (int x = 0؛ x <100؛ ++ x) http → request_url [0] = 'a' + (http-> request_url [0]٪ 10) قبل إرسال الحزمة ، قام بالفعل بتقليل rps ، مما يعني تحميل وحدة المعالجة المركزية بالكامل تقريبًا مع عمل مفيد.

في سياق التجارب ، تم اكتشاف لغز واحد ، ما زلت لا أستطيع حله. إذا قمت بتمكين إلغاء تحميل المجموع الاختباري ، أي حساب المجموع الاختباري لعناوين IP و tcp بواسطة بطاقة الشبكة نفسها ، فإن الأداء العام ينخفض ​​، ويحسّن زمن الوصول.
هنا البداية مع تمكين التفريغ

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


وهنا مع الاختباري على وحدة المعالجة المركزية

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


حسنًا ، يمكنني أن أشرح انخفاض الأداء بحقيقة أن بطاقة الشبكة تبطئ ، على الرغم من أن هذا غريب ، إلا أنه يجب أن يتسارع. ولكن لماذا ، مع حساب المجموع الاختباري على خريطة الكمون ، يتبين أنه ثابت تقريبًا يساوي 6 مللي ثانية ، وإذا كنت تعتمد على وحدة المعالجة المركزية ، فإنه يطفو من 2.5 مللي ثانية إلى 817 مللي ثانية؟ سيتم تبسيط المهمة بشكل كبير من خلال حامل غير افتراضي مع اتصال مباشر ، لكن ليس لدي هذا ، للأسف. لا تعمل DPDK نفسها على جميع بطاقات الشبكة ، وقبل استخدامها ، تحتاج إلى التحقق من القائمة .

وأخيرا ، مسح.

Source: https://habr.com/ru/post/ar416651/


All Articles