Artikel ini akan memperkenalkan dan menganalisis reka bentuk dan pelaksanaan modul Raft Log dalam Raft etcd, bermula daripada log dalam algoritma konsensus Raft. Matlamatnya adalah untuk membantu pembaca lebih memahami pelaksanaan Rakit etcd dan menyediakan pendekatan yang mungkin untuk melaksanakan senario yang serupa.
Algoritma konsensus Raft pada dasarnya ialah mesin keadaan yang direplikasi, dengan matlamat untuk mereplikasi satu siri log dengan cara yang sama merentas kluster pelayan. Log ini membolehkan pelayan dalam kelompok mencapai keadaan yang konsisten.
Dalam konteks ini, log merujuk kepada Log Rakit. Setiap nod dalam kluster mempunyai Log Rakit sendiri, yang terdiri daripada satu siri entri log. Entri log biasanya mengandungi tiga medan:
Adalah penting untuk ambil perhatian bahawa indeks Log Rakit bermula pada 1, dan hanya nod ketua boleh mencipta dan mereplikasi Log Rakit kepada nod pengikut.
Apabila entri log disimpan secara berterusan pada majoriti nod dalam kelompok (cth., 2/3, 3/5, 4/7), ia dianggap komit.
Apabila entri log digunakan pada mesin keadaan, ia dianggap digunakan.
etcd raft ialah perpustakaan algoritma Raft yang ditulis dalam Go, digunakan secara meluas dalam sistem seperti etcd, Kubernetes, CockroachDB dan lain-lain.
Ciri utama rakit etcd ialah ia hanya melaksanakan bahagian teras algoritma Raft. Pengguna mesti melaksanakan penghantaran rangkaian, storan cakera dan komponen lain yang terlibat dalam proses Raft itu sendiri (walaupun etcd menyediakan pelaksanaan lalai).
Berinteraksi dengan perpustakaan rakit etcd agak mudah: ia memberitahu anda data yang perlu diteruskan dan mesej yang perlu dihantar ke nod lain. Tanggungjawab anda adalah untuk mengendalikan penyimpanan dan proses penghantaran rangkaian dan memaklumkannya dengan sewajarnya. Ia tidak membimbangkan dirinya dengan butiran cara anda melaksanakan operasi ini; ia hanya memproses data yang anda serahkan dan, berdasarkan algoritma Raft, memberitahu anda langkah seterusnya.
Dalam pelaksanaan kod rakit etcd, model interaksi ini digabungkan dengan lancar dengan ciri saluran unik Go, menjadikan perpustakaan rakit etcd benar-benar tersendiri.
Dalam etcd raft, pelaksanaan utama Raft Log terletak dalam fail log.go dan log_unstable.go, dengan struktur utama ialah raftLog dan tidak stabil. Struktur yang tidak stabil juga merupakan medan dalam raftLog.
etcd raft menguruskan log dalam algoritma dengan menyelaraskan raftLog dan tidak stabil.
Untuk memudahkan perbincangan, artikel ini akan menumpukan hanya pada logik pemprosesan entri log, tanpa menangani pengendalian syot kilat dalam rakit etcd.
type raftLog struct { storage Storage unstable unstable committed uint64 applying uint64 applied uint64 }
Bidang teras raftLog:
type unstable struct { entries []pb.Entry offset uint64 offsetInProgress uint64 }
Bidang teras tidak stabil:
Medan teras dalam raftLog adalah mudah dan boleh dikaitkan dengan pelaksanaan dalam kertas Raft. Walau bagaimanapun, medan dalam tidak stabil mungkin kelihatan lebih abstrak. Contoh berikut bertujuan untuk membantu menjelaskan konsep ini.
Anggapkan kami sudah mempunyai 5 entri log kekal dalam Log Rakit kami. Kini, kami mempunyai 3 entri log yang disimpan dalam tidak stabil, dan 3 entri log ini sedang diteruskan. Keadaannya adalah seperti di bawah:
offset=6 menunjukkan bahawa entri log pada kedudukan 0, 1, dan 2 dalam tidak stabil. entri sepadan dengan kedudukan 6 (0 6), 7 (1 6), dan 8 (2 6) dalam Log Rakit sebenar. Dengan offsetInProgress=9, kita tahu bahawa unstable.entry[:9-6], yang merangkumi tiga entri log pada kedudukan 0, 1 dan 2, semuanya berterusan.
Sebab offset dan offsetInProgress digunakan dalam tidak stabil ialah unstable tidak menyimpan semua entri Log Rakit.
Memandangkan kami hanya menumpukan pada logik pemprosesan Log Rakit, "bila hendak berinteraksi" di sini merujuk kepada bila rakit etcd akan melepasi entri log yang perlu diteruskan oleh pengguna.
etcd raft berinteraksi dengan pengguna terutamanya melalui kaedah dalam antara muka Nod. Kaedah Sedia mengembalikan saluran yang membolehkan pengguna menerima data atau arahan daripada rakit etcd.
type raftLog struct { storage Storage unstable unstable committed uint64 applying uint64 applied uint64 }
Struktur Sedia yang diterima daripada saluran ini mengandungi entri log yang memerlukan pemprosesan, mesej yang harus dihantar ke nod lain, keadaan semasa nod dan banyak lagi.
Untuk perbincangan kami tentang Raft Log, kami hanya perlu menumpukan pada medan Entri dan CommittedEntries:
type unstable struct { entries []pb.Entry offset uint64 offsetInProgress uint64 }
Selepas memproses log, mesej dan data lain yang dihantar melalui Sedia, kami boleh memanggil kaedah Advance dalam antara muka Nod untuk memaklumkan rakit etcd bahawa kami telah menyelesaikan arahannya, membenarkannya menerima dan memproses Sedia seterusnya.
etcd raft menawarkan pilihan AsyncStorageWrites, yang boleh meningkatkan prestasi nod sedikit sebanyak. Walau bagaimanapun, kami tidak mempertimbangkan pilihan ini di sini.
Di sisi pengguna, tumpuan adalah pada pengendalian data dalam struktur Sedia yang diterima. Di bahagian rakit etcd, tumpuan adalah pada menentukan masa untuk menghantar struct Sedia kepada pengguna dan tindakan yang perlu diambil selepas itu.
Saya telah meringkaskan kaedah utama yang terlibat dalam proses ini dalam rajah berikut, yang menunjukkan urutan umum panggilan kaedah (perhatikan bahawa ini hanya mewakili anggaran tertib panggilan):
Anda dapat melihat bahawa keseluruhan proses adalah gelung. Di sini, kami akan menggariskan fungsi umum kaedah ini dan dalam analisis aliran tulis seterusnya, kami akan menyelidiki cara kaedah ini beroperasi pada medan teras raftLog dan tidak stabil.
Terdapat dua perkara penting untuk dipertimbangkan di sini:
1. Kekal ≠ Komited
Seperti yang ditakrifkan pada mulanya, kemasukan log dianggap komited hanya apabila ia telah diteruskan oleh majoriti nod dalam kelompok Raft. Jadi, walaupun kami meneruskan Penyertaan yang dikembalikan oleh rakit etcd melalui Sedia, penyertaan ini belum lagi boleh ditanda sebagai komited.
Namun, apabila kami memanggil kaedah Advance untuk memaklumkan etcd raft bahawa kami telah menyelesaikan langkah kegigihan, etcd raft akan menilai status kegigihan merentas nod lain dalam kelompok dan menandakan beberapa entri log sebagai komited. Entri ini kemudiannya diberikan kepada kami melalui medan CommittedEntries pada struct Ready supaya kami boleh menggunakannya pada mesin keadaan.
Oleh itu, apabila menggunakan rakit etcd, masa untuk menandakan penyertaan sebagai komited diuruskan secara dalaman, dan pengguna hanya perlu memenuhi prasyarat kegigihan.
Secara dalaman, komitmen dicapai dengan memanggil kaedah raftLog.commitTo, yang mengemas kini raftLog.committed, sepadan dengan commitIndex dalam kertas Raft.
2. Komited ≠ Memohon
Selepas kaedah raftLog.commitTo dipanggil dalam etcd raft, entri log sehingga indeks raft.committed dianggap komited. Walau bagaimanapun, entri dengan indeks dalam julat lastApplied < index <= committedIndex belum lagi digunakan pada mesin keadaan. etcd raft akan mengembalikan entri komited tetapi tidak digunakan ini dalam medan CommittedEntries of Ready, membolehkan kami menggunakannya pada mesin keadaan. Sebaik sahaja kami memanggil Advance, etcd raft akan menandakan penyertaan ini sebagai digunakan.
Masa untuk menandakan entri seperti yang digunakan juga dikendalikan secara dalaman dalam rakit etcd; pengguna hanya perlu menggunakan penyertaan komited daripada Sedia ke mesin keadaan.
Satu lagi perkara halus ialah, dalam Raft, hanya Pemimpin boleh melakukan entri, tetapi semua nod boleh menggunakannya.
Di sini, kami akan menyambungkan semua konsep yang dibincangkan sebelum ini dengan menganalisis aliran rakit etcd semasa ia mengendalikan permintaan tulis.
Untuk membincangkan senario yang lebih umum, kami akan mulakan dengan Log Rakit yang telah pun melakukan dan menggunakan tiga entri log.
Dalam ilustrasi, hijau mewakili medan raftLog dan entri log yang disimpan dalam Storan, manakala merah mewakili medan tidak stabil dan entri log tidak kekal disimpan dalam entri.
Memandangkan kami telah melakukan dan menggunakan tiga entri log, kedua-duanya komited dan memohon ditetapkan kepada 3. Medan memohon memegang indeks entri log tertinggi daripada aplikasi sebelumnya, yang juga 3 dalam kes ini.
Pada ketika ini, tiada permintaan telah dimulakan, jadi tidak stabil.entri kosong. Indeks log seterusnya dalam Log Rakit ialah 4, menjadikan offset 4. Memandangkan tiada log sedang diteruskan, offsetInProgress juga ditetapkan kepada 4.
Sekarang, kami memulakan permintaan untuk menambahkan dua entri log pada Log Rakit.
Seperti yang ditunjukkan dalam ilustrasi, entri log yang dilampirkan disimpan dalam entri tidak stabil. Pada peringkat ini, tiada perubahan dibuat pada nilai indeks yang direkodkan dalam medan teras.
Ingat kaedah HasReady? HasReady menyemak sama ada terdapat entri log yang tidak berterusan dan, jika ya, mengembalikan benar.
Logik untuk menentukan kehadiran entri log tidak berterusan adalah berdasarkan sama ada panjang unstable.entry[offsetInProgress-offset:] adalah lebih besar daripada 0. Jelas sekali, dalam kes kami:
type raftLog struct { storage Storage unstable unstable committed uint64 applying uint64 applied uint64 }
menunjukkan bahawa terdapat dua entri log yang tidak berterusan, jadi HasReady kembali benar.
Tujuan readyWithoutAccept adalah untuk mencipta struct Ready untuk dikembalikan kepada pengguna. Memandangkan kami mempunyai dua entri log yang tidak berterusan, readyWithoutAccept akan memasukkan dua entri log ini dalam medan Entri bagi Ready yang dikembalikan.
acceptReady dipanggil selepas struct Ready diserahkan kepada pengguna.
acceptReady mengemas kini indeks entri log yang sedang dalam proses dikekalkan kepada 6, bermakna entri log dalam julat [4, 6) kini ditandakan sebagai berterusan.
Selepas pengguna meneruskan Entri dalam Sedia, mereka memanggil Node.Advance untuk memberitahu rakit etcd. Kemudian, etcd raft boleh melaksanakan "panggilan balik" yang dibuat dalam acceptReady.
"Panggil balik" ini mengosongkan entri log yang telah berterusan dalam unstable.enries, kemudian set offset kepada Storage.LastIndex 1, iaitu 6.
Kami menganggap bahawa kedua-dua entri log ini telah dikekalkan oleh majoriti nod dalam gugusan Raft, jadi kami boleh menandakan kedua-dua entri log ini sebagai komited.
Melanjutkan dengan gelung kami, HasReady mengesan kehadiran entri log yang dilakukan tetapi belum digunakan, jadi ia kembali benar.
readyWithoutAccept mengembalikan entri log Sedia yang mengandungi (4, 5) yang dilakukan tetapi belum digunakan pada mesin keadaan.
Penyertaan ini dikira sebagai rendah, tinggi := menggunakan 1, komited 1, dalam selang kiri-buka, kanan-tutup.
acceptReady kemudian tandakan entri log [4, 5] dikembalikan dalam Ready sebagai digunakan pada mesin keadaan.
Selepas pengguna memanggil Node.Advance, etcd raft melaksanakan "panggilan balik" dan kemas kini digunakan pada 5, menunjukkan bahawa entri log pada indeks 5 dan lebih awal semuanya telah digunakan pada mesin keadaan.
Ini melengkapkan aliran pemprosesan untuk permintaan tulis. Keadaan akhir adalah seperti yang ditunjukkan di bawah, yang boleh dibandingkan dengan keadaan awal.
Kami bermula dengan gambaran keseluruhan Raft Log, memperoleh pemahaman tentang konsep asasnya, diikuti dengan pandangan awal pada pelaksanaan rakit etcd. Kami kemudiannya mendalami modul teras Raft Log dalam etcd raft dan mempertimbangkan soalan penting. Akhirnya, kami mengikat semuanya bersama-sama melalui analisis lengkap aliran permintaan tulis.
Saya harap pendekatan ini membantu anda memperoleh pemahaman yang jelas tentang pelaksanaan rakit dll dan mengembangkan pandangan anda sendiri tentang Log Rakit.
Itu menyimpulkan artikel ini. Jika terdapat sebarang kesilapan atau pertanyaan, sila hubungi melalui mesej peribadi atau tinggalkan komen.
BTW, raft-foiver ialah versi ringkas rakit etcd yang saya laksanakan, mengekalkan semua logik teras Raft dan dioptimumkan mengikut proses dalam kertas Raft. Saya akan mengeluarkan siaran berasingan yang memperkenalkan perpustakaan ini pada masa hadapan. Jika anda berminat, sila hubungi Star, Fork atau PR!
Atas ialah kandungan terperinci Memahami Pelaksanaan Rakit etcd: Penyelaman Mendalam ke Log Rakit. Untuk maklumat lanjut, sila ikut artikel berkaitan lain di laman web China PHP!