Mengapa kinerja penerusan http2 lebih rendah dari http1 ketika diuji pada server proxy? – ahfuzhang – Taman Blog
9 mins read

Mengapa kinerja penerusan http2 lebih rendah dari http1 ketika diuji pada server proxy? – ahfuzhang – Taman Blog

Penulis: Zhang Fuchun (ahfuzhang), harap sebutkan penulis dan tautan kutipan saat mencetak ulang, terima kasih!


Ketika saya menguji perbedaan kinerja antara server Http2 dan server Http 1.1, data pengujian tertinggi adalah http2 3,6 kali lebih cepat daripada http1.
Ketika Carter menguji kinerja server proxy apsix, data yang didapatnya adalah kinerja http2 hanya 80% dari http1.
Kami biasa mempertanyakan data satu sama lain dan menganggapnya luar biasa!
Maksud saya: protokol biner harus lebih cepat dari protokol teks, tidak ada alasan mengapa data yang diuji pada server proxy akan menyebabkan http2 lebih lambat dari http 1.

Hari ini saya akhirnya mengetahui alasannya. Faktanya, kami berdua benar!
Kesimpulannya adalah: pada server proxy, kinerja penerusan http2 akan lebih rendah dibandingkan http1.
Alasan utama perbedaan ini adalah splice() Panggilan sistem, yaitu, dilakukan di server proxy零拷贝kuncinya.

(Nanti jawaban ChatGPT akan dikutip secara panjang lebar)

splice () adalah panggilan sistem di Linux (dilihat melalui splice (2) di C), digunakan untuk mentransfer data antara dua deskriptor file, dan mencoba mengambil jalur nol-salinan untuk mengurangi partisipasi buffer mode pengguna.

Penggunaan paling umum: file ↔ pipa, pipa ↔ soket, yang dapat digunakan untuk “mengirim” data dari file disk langsung ke koneksi jaringan. Tidak perlu read() untuk mode pengguna dan kemudian write() kembali ke kernel.

masalah apa yang dipecahkan

Tulisan tradisional:

read(file_fd, user_buf, n);
write(sock_fd, user_buf, n);

Kekurangan: Data akan disalin antara mode pengguna kernel ↔ (dapat merusak cache CPU dan menempati bandwidth memori).

Tujuan dari splice () adalah untuk memindahkan data di kernel sebanyak mungkin (seperti dari cache halaman/buffer pipa langsung ke buffer soket) dan mengurangi satu atau lebih salinan.

Bentuk dasar (semantik)

ssize_t splice(int fd_in,  loff_t *off_in,
               int fd_out, loff_t *off_out,
               size_t len, unsigned int flags);
•	fd_in / fd_out:输入/输出 fd
•	off_in / off_out:
•	传 NULL 表示使用 fd 当前偏移并推进(像普通读写那样)
•	非 NULL 则用你提供的偏移(类似 pread/pwrite 的味道),并且不一定修改 fd 的文件偏移(具体取决于对象类型)
•	len:最多搬运多少字节
•	flags:控制阻塞/移动行为(例如 SPLICE_F_NONBLOCK 等)

Nilai kembalian: Jumlah byte yang sebenarnya ditransfer; 0 biasanya menunjukkan EOF; -1 menunjukkan kesalahan (errno menunjukkan alasan).

Kombinasi umum

  1. file → pipa → soket (“kirim file” berkinerja tinggi)
    • sambungan(file_fd → pipe_write_end)
    • sambungan (pipe_read_end → sock_fd)
    Jenis kombinasi ini sering digunakan untuk mencapai “efek seperti sendfile”, tetapi sambungan lebih serbaguna (terutama cocok untuk skenario di mana Anda juga perlu melakukan pemrosesan mode kernel lainnya di tengah).

  2. pipa ↔ pipa

Digunakan untuk mentransfer data antar pipeline (seperti menyalin dengan tee()).

Pembatasan berlaku (sangat penting)
• Tidak didukung oleh fd apa pun. sambungan memerlukan dukungan implementasi operasi file (VFS/driver/tumpukan protokol).
• Di Linux, pipa sering kali merupakan “stasiun transit” dari sebuah sambungan: banyak skenario memerlukan setidaknya salah satu ujung fd_in atau fd_out untuk menjadi sebuah pipa (atau ujung dari buffer pipa digunakan sebagai pembawa di kernel).
• Jika sumber datanya adalah file biasa, biasanya berasal dari cache halaman (sehingga Anda bisa mendapatkan lebih sedikit atau sama sekali tidak ada salinannya).

Terkait dengan sendfile()
• sendfile(): Lebih “pribadi”, biasanya jalur cepat ke file → soket.
• splice (): lebih “umum” dan dapat dipindahkan antar jenis FD lainnya. Penggunaan yang umum adalah menggunakan pipa sebagai jembatan untuk menyambung ke file → soket link zero-copy.

Ya, Anda tahu dengan sangat akurat: selama aliran byte akhirnya “dilihat/diproses” dalam mode pengguna, copy_to_user tidak dapat dilewati. splice(socket → pipe) hanya mengubah langkah “baca buffer pengguna dari soket” menjadi “baca pipa kernel dari soket”, sehingga tidak dapat menghapus salinan “kernel → mode pengguna”; Anda tetap akan menyalinnya saat membaca (pipefd, userbuf, …) nanti.

Lebih tepatnya:
• Rute tradisional (konsumsi mode pengguna)
recv()/baca(soket, penggunabuf)
→ Soket kernel menerima antrian → Mode pengguna userbuf (satu copy_to_user)
• jalur sambungan (mode pengguna masih perlu digunakan)
splice(socket → pipe) (pindahkan/ganti halaman di kernel untuk menghindari copy_to_user)
Kemudian baca (pipa → userbuf)
→ pipa → mode pengguna userbuf (masih copy_to_user)

Oleh karena itu: dengan tujuan “mendapatkan muatan dalam mode pengguna”, sambungan tidak akan mengurangi jumlah total salinan menjadi 0. Apakah mungkin untuk menguranginya:
• Mengurangi satu “salinan/pergerakan buffer perantara yang redundan” (misalnya, Anda awalnya membaca untuk pengguna terlebih dahulu, lalu menulis ke fd lain)
• Atau mengurangi overhead CPU (dalam beberapa skenario, menggantung langsung dari soket ke pipa, lalu menyambung ke target fd)

  1. Penerusan murni (tidak ada penguraian data)

socket_in → pipa → socket_out / file
Anda tidak menyentuh data dalam mode pengguna, Anda hanya meneruskan/melepaskannya. Biasanya: membalikkan proxy, L7 tetapi tidak terbuka, merekam aliran ke file, dll.

  1. Campuran “hanya sedikit kepala + banyak badan”

Misalnya, HTTP: Anda hanya menerima sebagian kecil header dalam mode pengguna untuk mengambil keputusan; body menggunakan sambungan untuk penerusan tanpa salinan.
Tipe ini dapat mengurangi jumlah total copy_to_user (karena badan besar tidak masuk ke mode pengguna).

Apakah Anda benar-benar khawatir: Dapatkah jumlah salinan dalam komunikasi TCP dikurangi?
• Lapisan aplikasi harus menangani payload (decoding, kompresi, JSON, protobuf…):
Setidaknya satu kernel → salinan pengguna tidak dapat dihindari (memberikan byte pada proses).
• Lapisan aplikasi tidak memproses payload, ia hanya melanjutkan:
Dapat masuk ke mode pengguna 0 kali (sambungan/kirim offload terkait NIC akan dibahas sendiri).

Tidak sepenuhnya benar, tetapi intuisi Anda mengambil setengahnya: kunci untuk menentukan apakah “lanjutkan mode kernel seperti L4” dimungkinkan bukanlah HTTP / 1 vs HTTP / 2, tetapi apakah proxy memahami semantik protokol dan apakah itu “mengakhiri koneksi dan membangun kembali koneksi.” Karena HTTP/2 memiliki multiplexing + kontrol aliran + kompresi header, lebih mudah untuk “memaksa” Anda ke mode pengguna L7, tetapi HTTP/1 tidak dapat melanjutkan mode kernel secara alami.

Bagilah menjadi beberapa bentuk agen dan Anda akan memiliki gambaran yang lebih jelas:

  1. Benar·Proksi L4 (penyeimbangan beban TCP/Layer 4)
    • Ini tidak muncul di HTTP, hanya meneruskan aliran byte: klien TCP ↔ TCP backend.
    • Hal ini sama untuk HTTP / 1 dan HTTP / 2: keduanya dapat melakukan penerusan yang “ramah kernel” (seperti jalur splice / zero-copy, bahkan TPROXY / IPVS di kernel, dll.).
    • Harga: Anda tidak dapat melakukan kemampuan L7 seperti perutean URL / Host / Header, otentikasi, dan pembatasan kecepatan.

✅ Kesimpulan: HTTP/2 juga bisa meneruskan L4, tapi sekarang Anda belum tahu itu HTTP/2.

  1. L7 generasi terbalik HTTP/1 (tipe yang biasa digunakan di Nginx/Messenger)
    • Anda harus membaca baris permintaan/header, Anda ingin merutekannya, Anda dapat mengubah header, Anda dapat melakukan cache, mengompres, mencoba lagi, dll.
    • Ini memiliki mode pengguna L7, bukan “mode kernel L4”.

Namun, HTTP/1 memiliki poin “mudah dioptimalkan”:
• Demarkasi header/body jelas, dan biasanya satu koneksi melayani satu permintaan pada waktu yang sama (walaupun live, biasanya serial, dan pipa jarang digunakan).
• Jadi Anda dapat melakukan “hanya membaca sejumlah kecil header dalam mode pengguna, dan kemudian menggunakan sambungan untuk melewati badan” (badan besar sangat populer saat mengunggah/mengunduh).

✅ Kesimpulan: HTTP/1 tidak “biasanya dapat melewati mode kernel L4”, lebih mudah untuk melakukan “melewati setengah L7 + setengah nol”.

  1. L7 generasi terbalik HTTP/2 (perutean tingkat aliran, multiplexing)
    • Frame dari beberapa aliran disisipkan dalam koneksi TCP yang sama.
    • Anda perlu memecah frame, menjadwalkan, memelihara jendela kontrol aliran, dan menangani HPACK melalui aliran.
    • Ujung depan dan belakang biasanya merupakan dua sambungan independen (satu H2 di sisi klien, dan satu lagi H2 atau H1 di ujung belakang). Tabel dinamis stream id/window/HPACK tidak dapat “diteruskan” secara utuh.

Hal ini membuatnya sangat sulit untuk “menyambung” muatan besar sebagai byte yang berurutan, karena Anda harus terlebih dahulu mengekstrak bingkai DATA yang terkait dengan aliran tertentu dalam mode pengguna dan kemudian mengirimkannya ke backend (dan itu juga harus diatur ulang/dibatasi kecepatan/diprioritaskan).

✅ Kesimpulan: Ketika HTTP/2 dibalik ke L7, itu hanya bisa dalam mode pengguna (atau dengan kata lain, harus ada “titik penghentian protokol” di mode pengguna/lapisan implementasi).

Kalimat yang lebih akurat
• HTTP / 1: Lebih mudah untuk membuat pass-through “body” untuk mode kernel (splice / sendfile), dan mode pengguna hanya menangani beberapa bidang kendali kecil (header).
• HTTP / 2: Selama Anda ingin melakukan L7 (routing by stream / header), sulit untuk mendapatkan passthrough mode kernel full body, karena framing / mux / flow-control memaksa Anda untuk berpartisipasi dalam distribusi data.
• Namun HTTP/2 masih dapat melakukan penerusan L4 murni (Anda hanya kehilangan semua kemampuan L7).

  • Fitur protokol http 1.1: Ada perbedaan yang jelas antara header dan body. Panjang tubuhnya sangat jelas. Hanya ada satu respons permintaan pada koneksi TCP pada satu waktu – oleh karena itu, dalam skenario server proxy, http 1 dapat dengan mudah dioptimalkan menggunakan mekanisme zero-copy seperti splice.
    • Khusus untuk konten yang lebih panjang seperti bagian tubuh, pertukaran data dapat dilakukan langsung dalam keadaan kernel. Tidak perlu menyalin data ke negara pengguna lalu mengirimkannya ke luar negara pengguna.
  • http 2 memiliki banyak aliran dalam koneksi tcp, setiap aliran meluas SETTINGS_MAX_FRAME_SIZE Kemudian akan dibagi menjadi beberapa frame – ini menentukan apakah sulit untuk mencapai optimasi zero-copy melalui koneksi di http2, atau ada lebih sedikit skenario di mana zero-copy yang sebenarnya dapat dicapai.
  • Untuk server:
    • Protokol biner seperti http 2 sebenarnya lebih cepat daripada http 1, terutama dalam skenario seperti server api/server rcp dengan paket data kecil dan permintaan yang sering. Tidak heran http2 3,6 kali lebih cepat dari http1.
    • Jika pengunduhan gambar/pengunduhan file/transmisi data memiliki throughput yang besar, lebih baik memilih http1

Berita Terkini

Berita Terbaru

Daftar Terbaru

News

Berita Terbaru

Flash News

RuangJP

Pemilu

Berita Terkini

Prediksi Bola

Togel Deposit Pulsa

Technology

Otomotif

Berita Terbaru

Slot Demo Gratis Tanpa Potongan 2025

Slot yang lagi gacor

Teknologi

Berita terkini

Berita Pemilu

Berita Teknologi

Hiburan

master Slote

Berita Terkini

Pendidikan

Resep

Jasa Backlink

One Piece Terbaru