[Terjemahan] Model threading utusan

Terjemahan artikel: Model threading utusan - https://blog.envoyproxy.io/envoy-threading-model-a8d44b922310

Menurut saya artikel ini cukup menarik, dan karena Envoy paling sering digunakan sebagai bagian dari “istio” atau hanya sebagai “ingress controller” kubernetes, kebanyakan orang tidak memiliki interaksi langsung yang sama dengannya, misalnya dengan tipikal Instalasi Nginx atau Haproxy. Namun, jika ada yang rusak, alangkah baiknya jika Anda memahami cara kerjanya dari dalam. Saya mencoba menerjemahkan sebanyak mungkin teks ke dalam bahasa Rusia, termasuk kata-kata khusus; bagi mereka yang merasa sedih melihatnya, saya meninggalkan aslinya dalam tanda kurung. Selamat datang di kucing.

Dokumentasi teknis tingkat rendah untuk basis kode Envoy saat ini cukup jarang. Untuk mengatasinya, saya berencana membuat serangkaian postingan blog tentang berbagai subsistem Envoy. Karena ini adalah artikel pertama, beri tahu saya pendapat Anda dan apa yang mungkin Anda minati di artikel mendatang.

Salah satu pertanyaan teknis paling umum yang saya terima tentang Envoy adalah menanyakan deskripsi tingkat rendah tentang model threading yang digunakannya. Dalam postingan ini, saya akan menjelaskan bagaimana Envoy memetakan koneksi ke thread, serta sistem Penyimpanan Lokal Thread yang digunakannya secara internal untuk membuat kode lebih paralel dan berkinerja tinggi.

Ikhtisar rangkaian pesan

[Terjemahan] Model threading utusan

Utusan menggunakan tiga jenis aliran berbeda:

  • Utama: Thread ini mengontrol startup dan penghentian proses, semua pemrosesan API XDS (xDiscovery Service), termasuk DNS, pemeriksaan kesehatan, manajemen klaster umum dan runtime, pengaturan ulang statistik, administrasi dan manajemen proses umum - sinyal Linux, hot restart, dll. yang terjadi di thread ini tidak sinkron dan "non-pemblokiran". Secara umum, thread utama mengoordinasikan semua proses fungsionalitas penting yang tidak memerlukan CPU dalam jumlah besar untuk dijalankan. Hal ini memungkinkan sebagian besar kode kontrol ditulis seolah-olah itu adalah thread tunggal.
  • Pekerja: Secara default, Envoy membuat thread pekerja untuk setiap thread perangkat keras di sistem, ini dapat dikontrol menggunakan opsi --concurrency. Setiap thread pekerja menjalankan loop peristiwa "non-pemblokiran", yang bertanggung jawab untuk mendengarkan setiap pendengar; pada saat penulisan (29 Juli 2017) tidak ada sharding dari pendengar, menerima koneksi baru, membuat instance tumpukan filter untuk koneksi, dan memproses semua operasi input/output (IO) selama masa koneksi. Sekali lagi, ini memungkinkan sebagian besar kode penanganan koneksi ditulis seolah-olah itu adalah thread tunggal.
  • Pembils file: Setiap file yang ditulis Envoy, terutama log akses, saat ini memiliki thread pemblokiran independen. Hal ini disebabkan oleh fakta bahwa penulisan ke file di-cache oleh sistem file bahkan saat digunakan O_NONBLOCK terkadang bisa diblokir (menghela nafas). Ketika thread pekerja perlu menulis ke sebuah file, data sebenarnya dipindahkan ke buffer di memori yang pada akhirnya akan dibuang melalui thread siram berkas. Ini adalah salah satu area kode di mana secara teknis semua thread pekerja dapat memblokir kunci yang sama ketika mencoba mengisi buffer memori.

Penanganan koneksi

Seperti yang dibahas secara singkat di atas, semua thread pekerja mendengarkan semua pendengar tanpa sharding apa pun. Dengan demikian, kernel digunakan untuk mengirim soket yang diterima ke thread pekerja dengan baik. Kernel modern umumnya sangat baik dalam hal ini, mereka menggunakan fitur seperti peningkatan prioritas input/output (IO) untuk mencoba mengisi thread dengan pekerjaan sebelum mereka mulai menggunakan thread lain yang juga mendengarkan pada soket yang sama, dan juga tidak menggunakan round robin mengunci (Spinlock) untuk memproses setiap permintaan.
Setelah koneksi diterima di thread pekerja, koneksi tidak akan pernah meninggalkan thread tersebut. Semua pemrosesan koneksi lebih lanjut ditangani sepenuhnya di thread pekerja, termasuk perilaku penerusan apa pun.

Hal ini mempunyai beberapa konsekuensi penting:

  • Semua kumpulan koneksi di Envoy ditetapkan ke thread pekerja. Jadi, meskipun kumpulan koneksi HTTP/2 hanya membuat satu koneksi ke setiap host upstream dalam satu waktu, jika ada empat thread pekerja, akan ada empat koneksi HTTP/2 per host upstream dalam kondisi stabil.
  • Alasan Envoy bekerja dengan cara ini adalah dengan menyimpan semuanya pada satu thread pekerja, hampir semua kode dapat ditulis tanpa pemblokiran dan seolah-olah itu adalah thread tunggal. Desain ini memudahkan penulisan banyak kode dan menskalakan dengan sangat baik ke jumlah thread pekerja yang hampir tidak terbatas.
  • Namun, salah satu kesimpulan utamanya adalah dari sudut pandang kumpulan memori dan efisiensi koneksi, sebenarnya sangat penting untuk mengonfigurasi --concurrency. Memiliki lebih banyak thread pekerja daripada yang diperlukan akan membuang-buang memori, membuat lebih banyak koneksi menganggur, dan mengurangi laju pengumpulan koneksi. Di Lyft, kontainer sespan utusan kami dijalankan dengan konkurensi yang sangat rendah sehingga kinerjanya kurang lebih sama dengan layanan yang ada di sebelahnya. Kami menjalankan Envoy sebagai proxy tepi hanya pada konkurensi maksimum.

Apa yang dimaksud dengan non-pemblokiran?

Istilah "non-pemblokiran" telah digunakan beberapa kali sejauh ini ketika membahas cara kerja thread utama dan thread pekerja. Semua kode ditulis dengan asumsi tidak ada yang diblokir. Namun, hal ini tidak sepenuhnya benar (apa yang tidak sepenuhnya benar?).

Utusan menggunakan beberapa kunci proses yang panjang:

  • Seperti yang telah dibahas, saat menulis log akses, semua thread pekerja memperoleh kunci yang sama sebelum buffer log dalam memori terisi. Waktu penahanan kunci harus sangat rendah, namun ada kemungkinan kunci dapat digugat pada konkurensi tinggi dan throughput tinggi.
  • Envoy menggunakan sistem yang sangat kompleks untuk menangani statistik lokal pada thread. Ini akan menjadi topik postingan terpisah. Namun, saya akan menyebutkan secara singkat bahwa sebagai bagian dari pemrosesan statistik thread secara lokal, terkadang perlu untuk mendapatkan kunci di "penyimpanan statistik" pusat. Penguncian ini seharusnya tidak diperlukan.
  • Thread utama secara berkala perlu berkoordinasi dengan semua thread pekerja. Hal ini dilakukan dengan "menerbitkan" dari thread utama ke thread pekerja, dan terkadang dari thread pekerja kembali ke thread utama. Pengiriman memerlukan kunci agar pesan yang diterbitkan dapat dimasukkan ke dalam antrean untuk pengiriman selanjutnya. Kunci-kunci ini tidak boleh ditentang secara serius, namun secara teknis masih dapat diblokir.
  • Ketika Envoy menulis log ke aliran kesalahan sistem (kesalahan standar), ia memperoleh kunci pada seluruh proses. Secara umum, logging lokal Envoy dianggap buruk dari sudut pandang kinerja, sehingga belum banyak perhatian diberikan untuk memperbaikinya.
  • Ada beberapa kunci acak lainnya, namun tidak satupun yang kritis terhadap kinerja dan tidak boleh ditantang.

Utas penyimpanan lokal

Karena cara Envoy memisahkan tanggung jawab thread utama dari tanggung jawab thread pekerja, terdapat persyaratan bahwa pemrosesan kompleks dapat dilakukan pada thread utama dan kemudian diberikan ke setiap thread pekerja dengan cara yang sangat bersamaan. Bagian ini menjelaskan Penyimpanan Lokal Utusan Utusan (TLS) pada tingkat tinggi. Pada bagian berikutnya saya akan menjelaskan bagaimana hal ini digunakan untuk mengelola sebuah cluster.
[Terjemahan] Model threading utusan

Seperti yang telah dijelaskan, thread utama menangani hampir semua fungsi manajemen dan bidang kendali dalam proses Envoy. Bidang kontrol agak kelebihan beban di sini, tetapi ketika Anda melihatnya dalam proses Envoy itu sendiri dan membandingkannya dengan penerusan yang dilakukan oleh thread pekerja, itu masuk akal. Aturan umumnya adalah proses thread utama melakukan beberapa pekerjaan, dan kemudian perlu memperbarui setiap thread pekerja sesuai dengan hasil pekerjaan tersebut. dalam hal ini, thread pekerja tidak perlu mendapatkan kunci pada setiap akses.

Sistem TLS (Penyimpanan lokal thread) Envoy berfungsi sebagai berikut:

  • Kode yang berjalan di thread utama dapat mengalokasikan slot TLS untuk keseluruhan proses. Meskipun ini diabstraksi, dalam praktiknya ini adalah indeks ke dalam vektor, yang menyediakan akses O(1).
  • Thread utama dapat memasang data sewenang-wenang ke dalam slotnya. Ketika ini selesai, data dipublikasikan ke setiap thread pekerja sebagai peristiwa perulangan normal.
  • Thread pekerja dapat membaca dari slot TLS mereka dan mengambil data lokal thread apa pun yang tersedia di sana.

Meskipun paradigma ini sangat sederhana dan sangat kuat, paradigma ini sangat mirip dengan konsep pemblokiran RCU (Read-Copy-Update). Pada dasarnya, thread pekerja tidak pernah melihat perubahan data apa pun di slot TLS saat pekerjaan sedang berjalan. Perubahan hanya terjadi selama waktu istirahat di antara acara kerja.

Utusan menggunakan ini dalam dua cara berbeda:

  • Dengan menyimpan data yang berbeda pada setiap thread pekerja, data dapat diakses tanpa pemblokiran apa pun.
  • Dengan mempertahankan penunjuk bersama ke data global dalam mode baca-saja di setiap thread pekerja. Dengan demikian, setiap thread pekerja memiliki jumlah referensi data yang tidak dapat dikurangi saat pekerjaan sedang berjalan. Hanya ketika semua pekerja tenang dan mengunggah data bersama yang baru, data lama akan dimusnahkan. Ini identik dengan RCU.

Utas pembaruan klaster

Di bagian ini, saya akan menjelaskan bagaimana TLS (Thread local storage) digunakan untuk mengelola sebuah cluster. Manajemen cluster mencakup xDS API dan/atau pemrosesan DNS, serta pemeriksaan kesehatan.
[Terjemahan] Model threading utusan

Manajemen aliran cluster mencakup komponen dan langkah-langkah berikut:

  1. Cluster Manager adalah komponen dalam Envoy yang mengelola semua upstream klaster yang dikenal, API Cluster Discovery Service (CDS), API Secret Discovery Service (SDS) dan Endpoint Discovery Service (EDS), DNS, dan pemeriksaan kesehatan eksternal yang aktif. Hal ini bertanggung jawab untuk menciptakan tampilan yang "akhirnya konsisten" dari setiap cluster hulu, yang mencakup host yang ditemukan serta status kesehatan.
  2. Pemeriksa kesehatan melakukan pemeriksaan kesehatan aktif dan melaporkan perubahan status kesehatan ke manajer klaster.
  3. CDS (Cluster Discovery Service) / SDS (Secret Discovery Service) / EDS (Endpoint Discovery Service) / DNS dilakukan untuk menentukan keanggotaan cluster. Perubahan status dikembalikan ke manajer cluster.
  4. Setiap thread pekerja terus-menerus mengeksekusi loop peristiwa.
  5. Ketika manajer klaster menentukan bahwa status klaster telah berubah, manajer klaster akan membuat snapshot status klaster hanya-baca dan mengirimkannya ke setiap thread pekerja.
  6. Selama periode tenang berikutnya, thread pekerja akan memperbarui snapshot di slot TLS yang dialokasikan.
  7. Selama peristiwa I/O yang seharusnya menentukan host untuk memuat keseimbangan, penyeimbang beban akan meminta slot TLS (Penyimpanan lokal thread) untuk memperoleh informasi tentang host. Ini tidak memerlukan kunci. Perhatikan juga bahwa TLS juga dapat memicu peristiwa pembaruan sehingga penyeimbang beban dan komponen lainnya dapat menghitung ulang cache, struktur data, dll. Ini di luar cakupan postingan ini, tetapi digunakan di berbagai tempat dalam kode.

Dengan menggunakan prosedur di atas, Envoy dapat memproses setiap permintaan tanpa pemblokiran apa pun (kecuali seperti yang dijelaskan sebelumnya). Selain kompleksitas kode TLS itu sendiri, sebagian besar kode tidak perlu memahami cara kerja multithreading dan dapat ditulis dalam single-threaded. Hal ini membuat sebagian besar kode lebih mudah ditulis selain kinerjanya yang unggul.

Subsistem lain yang menggunakan TLS

TLS (Penyimpanan lokal thread) dan RCU (Read Copy Update) banyak digunakan di Envoy.

Contoh penggunaan:

  • Mekanisme untuk mengubah fungsionalitas selama eksekusi: Daftar fungsionalitas yang diaktifkan saat ini dihitung di thread utama. Setiap thread pekerja kemudian diberikan snapshot read-only menggunakan semantik RCU.
  • Mengganti tabel rute: Untuk tabel rute yang disediakan oleh RDS (Route Discovery Service), tabel rute dibuat di thread utama. Snapshot read-only selanjutnya akan diberikan ke setiap thread pekerja menggunakan semantik RCU (Read Copy Update). Hal ini membuat perubahan tabel rute menjadi efisien secara atom.
  • Cache tajuk HTTP: Ternyata, menghitung header HTTP untuk setiap permintaan (saat menjalankan ~25K+ RPS per core) cukup mahal. Envoy menghitung header secara terpusat kira-kira setiap setengah detik dan memberikannya kepada setiap pekerja melalui TLS dan RCU.

Ada kasus lain, namun contoh sebelumnya seharusnya memberikan pemahaman yang baik tentang kegunaan TLS.

Jebakan kinerja yang diketahui

Meskipun kinerja Envoy cukup baik secara keseluruhan, ada beberapa area penting yang memerlukan perhatian ketika digunakan dengan konkurensi dan throughput yang sangat tinggi:

  • Seperti yang dijelaskan dalam artikel ini, saat ini semua thread pekerja memperoleh kunci saat menulis ke buffer memori log akses. Pada konkurensi tinggi dan throughput tinggi, Anda perlu mengelompokkan log akses untuk setiap thread pekerja dengan mengorbankan pengiriman yang tidak sesuai pesanan saat menulis ke file akhir. Alternatifnya, Anda dapat membuat log akses terpisah untuk setiap thread pekerja.
  • Meskipun statistiknya sangat optimal, pada konkurensi dan throughput yang sangat tinggi kemungkinan besar akan terjadi perselisihan kecil pada statistik individual. Solusi untuk masalah ini adalah penghitung per thread pekerja dengan pengaturan ulang penghitung pusat secara berkala. Hal ini akan dibahas pada postingan berikutnya.
  • Arsitektur saat ini tidak akan berfungsi dengan baik jika Envoy diterapkan dalam skenario di mana terdapat sangat sedikit koneksi yang memerlukan sumber daya pemrosesan yang signifikan. Tidak ada jaminan bahwa koneksi akan didistribusikan secara merata di antara thread pekerja. Hal ini dapat diatasi dengan menerapkan penyeimbangan koneksi pekerja, yang memungkinkan pertukaran koneksi antar thread pekerja.

Kesimpulan

Model threading Envoy dirancang untuk memberikan kemudahan pemrograman dan paralelisme besar-besaran dengan mengorbankan memori dan koneksi yang berpotensi boros jika tidak dikonfigurasi dengan benar. Model ini memungkinkannya bekerja dengan sangat baik pada jumlah thread dan throughput yang sangat tinggi.
Seperti yang saya sebutkan secara singkat di Twitter, desainnya juga dapat berjalan di atas tumpukan jaringan mode pengguna penuh seperti DPDK (Data Plane Development Kit), yang dapat mengakibatkan server konvensional menangani jutaan permintaan per detik dengan pemrosesan L7 penuh. Akan sangat menarik untuk melihat apa yang akan dibangun dalam beberapa tahun ke depan.
Satu komentar singkat terakhir: Saya telah ditanya berkali-kali mengapa kami memilih C++ untuk Envoy. Alasannya tetap karena ini masih satu-satunya bahasa kelas industri yang banyak digunakan di mana arsitektur yang dijelaskan dalam postingan ini dapat dibangun. C++ jelas tidak cocok untuk semua atau bahkan banyak proyek, namun untuk kasus penggunaan tertentu, C++ masih menjadi satu-satunya alat untuk menyelesaikan pekerjaan.

Tautan ke kode

Tautan ke file dengan antarmuka dan implementasi header dibahas dalam posting ini:

Sumber: www.habr.com

Tambah komentar