Pustaka ini bertujuan untuk membantu Swift memasuki ruang baru: sistem terdistribusi multi-node yang terkluster.
Dengan perpustakaan ini kami menyediakan implementasi protokol keanggotaan agnostik runtime yang dapat digunakan kembali yang dapat diadopsi dalam berbagai kasus penggunaan pengelompokan.
Protokol keanggotaan cluster adalah blok bangunan penting untuk sistem terdistribusi, seperti cluster intensif komputasi, penjadwal, database, penyimpanan nilai kunci, dan banyak lagi. Dengan diumumkannya paket ini, kami bertujuan untuk membuat pembangunan sistem tersebut menjadi lebih sederhana, karena mereka tidak perlu lagi bergantung pada layanan eksternal untuk menangani keanggotaan layanan. Kami juga ingin mengundang komunitas untuk berkolaborasi dan mengembangkan protokol keanggotaan tambahan.
Pada intinya, protokol keanggotaan perlu memberikan jawaban atas pertanyaan "Siapa rekan saya (yang masih hidup)?". Tugas yang tampaknya sederhana ini ternyata tidak sesederhana itu sama sekali dalam sistem terdistribusi di mana pesan yang tertunda atau hilang, partisi jaringan, dan node yang tidak responsif namun masih "hidup" adalah pekerjaan sehari-hari. Memberikan jawaban yang dapat diprediksi dan dapat diandalkan untuk pertanyaan ini adalah apa yang dilakukan oleh protokol keanggotaan cluster.
Ada berbagai trade-off yang dapat dilakukan ketika menerapkan protokol keanggotaan, dan hal ini terus menjadi bidang penelitian yang menarik dan terus disempurnakan. Dengan demikian, paket keanggotaan klaster bermaksud untuk fokus tidak pada implementasi tunggal, namun berfungsi sebagai ruang kolaborasi untuk berbagai algoritma terdistribusi di ruang ini.
Untuk diskusi lebih mendalam tentang protokol dan modifikasi dalam implementasi ini, kami sarankan untuk membaca Dokumentasi SWIM API, serta makalah terkait yang ditautkan di bawah.
Algoritme Keanggotaan grup proses Infeksi yang Scalable Weakly-consistent (juga dikenal sebagai "SWIM"), bersama dengan beberapa ekstensi protokol penting seperti yang didokumentasikan dalam makalah Lifeguard: Kesadaran Kesehatan Lokal untuk Deteksi Kegagalan yang Lebih Akurat 2018.
SWIM adalah protokol gosip di mana rekan-rekan secara berkala bertukar sedikit informasi tentang pengamatan mereka terhadap status node lain, yang pada akhirnya menyebarkan informasi tersebut ke semua anggota lain dalam sebuah cluster. Kategori algoritme terdistribusi ini sangat tahan terhadap kehilangan pesan yang sewenang-wenang, partisi jaringan, dan masalah serupa.
Pada tingkat tinggi, SWIM bekerja seperti ini:
.ack
dikirim kembali. Lihat bagaimana A
menyelidiki B
pada awalnya pada diagram di bawah.payload
gosip, yang merupakan informasi (sebagian) tentang apa yang diketahui oleh rekan-rekan pengirim pesan lainnya, beserta status keanggotaan mereka ( .alive
, .suspect
, dll.).ack
, rekan tersebut dianggap masih .alive
. Jika tidak, rekan target mungkin telah dihentikan/rusak atau tidak responsif karena alasan lain..pingRequest
ke sejumlah peer lain yang dikonfigurasi, yang kemudian mengeluarkan ping langsung ke peer tersebut (probing peer E pada diagram di bawah)..suspect
,.nack
("pengakuan negatif") tambahan dalam situasi tersebut untuk menginformasikan asal permintaan ping bahwa perantara memang menerima pesan .pingRequest
tersebut, namun target tampaknya tidak merespons. Kami menggunakan informasi ini untuk menyesuaikan Pengganda Kesehatan Lokal, yang memengaruhi cara penghitungan waktu tunggu. Untuk mempelajari lebih lanjut tentang hal ini, lihat dokumen API dan makalah Lifeguard.Mekanisme di atas, tidak hanya berfungsi sebagai mekanisme pendeteksi kegagalan, namun juga sebagai mekanisme gosip, yang membawa informasi tentang anggota cluster yang diketahui. Dengan cara ini para anggota pada akhirnya mengetahui status rekan-rekan mereka, bahkan tanpa harus mencantumkan semuanya terlebih dahulu. Namun perlu diperhatikan bahwa pandangan keanggotaan ini kurang konsisten, yang berarti tidak ada jaminan (atau cara untuk mengetahuinya, tanpa informasi tambahan) jika semua anggota memiliki pandangan yang sama persis mengenai keanggotaan pada suatu waktu tertentu. Namun, hal ini merupakan landasan yang sangat baik bagi alat dan sistem tingkat tinggi untuk membangun jaminan yang lebih kuat.
Setelah mekanisme deteksi kegagalan mendeteksi node yang tidak responsif, node tersebut pada akhirnya ditandai sebagai .dead sehingga node tersebut dihapus dari cluster secara permanen. Penerapan kami menawarkan ekstensi opsional, menambahkan status .unreachable ke status yang memungkinkan, namun sebagian besar pengguna tidak akan menganggapnya perlu dan ini dinonaktifkan secara default. Untuk rincian dan peraturan mengenai peralihan status hukum lihat SWIM.Status atau diagram berikut:
Cara Keanggotaan Cluster Swift mengimplementasikan protokol adalah dengan menawarkan " Instances
" dari protokol tersebut. Misalnya, implementasi SWIM dienkapsulasi dalam instance SWIM.Instance
agnostik runtime yang perlu “digerakkan” atau “ditafsirkan” oleh beberapa kode perekat antara runtime jaringan dan instance itu sendiri. Kami menyebutnya potongan lem implementasi " Shell
s", dan perpustakaan dikirimkan dengan SWIMNIOShell
yang diimplementasikan menggunakan DatagramChannel
SwiftNIO yang melakukan semua pengiriman pesan secara asinkron melalui UDP. Implementasi alternatif dapat menggunakan transportasi yang benar-benar berbeda, atau mendukung pesan SWIM pada sistem gosip lain yang ada, dll.
Instans SWIM juga memiliki dukungan bawaan untuk memancarkan metrik (menggunakan metrik cepat) dan dapat dikonfigurasi untuk mencatat detail tentang detail internal dengan meneruskan Logger
log cepat.
Tujuan utama perpustakaan ini adalah untuk berbagi implementasi SWIM.Instance
di berbagai implementasi yang memerlukan beberapa bentuk layanan keanggotaan dalam proses. Penerapan runtime khusus didokumentasikan secara mendalam di README proyek (https://github.com/apple/swift-cluster-membership/), jadi silakan lihat di sana jika Anda tertarik untuk mengimplementasikan SWIM pada beberapa transportasi yang berbeda.
Penerapan transportasi baru merupakan inti dari latihan “mengisi bagian yang kosong”:
Pertama, seseorang harus mengimplementasikan protokol Peer (https://github.com/apple/swift-cluster-membership/blob/main/Sources/SWIM/Peer.swift) menggunakan transportasi targetnya:
/// SWIM peer which can be initiated contact with, by sending ping or ping request messages.
public protocol SWIMPeer : SWIMAddressablePeer {
/// Perform a probe of this peer by sending a `ping` message.
///
/// <... more docs here - please refer to the API docs for the latest version ...>
func ping (
payload : SWIM . GossipPayload ,
from origin : SWIMPingOriginPeer ,
timeout : DispatchTimeInterval ,
sequenceNumber : SWIM . SequenceNumber
) async throws -> SWIM . PingResponse
// ...
}
Yang biasanya berarti menggabungkan beberapa koneksi, saluran, atau identitas lainnya dengan kemampuan untuk mengirim pesan dan menjalankan panggilan balik yang sesuai bila memungkinkan.
Kemudian, di sisi penerima rekan, kita harus mengimplementasikan penerimaan pesan-pesan itu dan memanggil semua callback on<SomeMessage>(...)
terkait yang ditentukan pada SWIM.Instance
(dikelompokkan dalam SWIMProtocol).
Sepotong Protokol SWIM tercantum di bawah ini untuk memberi Anda gambaran tentangnya:
public protocol SWIMProtocol {
/// MUST be invoked periodically, in intervals of `self.swim.dynamicLHMProtocolInterval`.
///
/// MUST NOT be scheduled using a "repeated" task/timer", as the interval is dynamic and may change as the algorithm proceeds.
/// Implementations should schedule each next tick by handling the returned directive's `scheduleNextTick` case,
/// which includes the appropriate delay to use for the next protocol tick.
///
/// This is the heart of the protocol, as each tick corresponds to a "protocol period" in which:
/// - suspect members are checked if they're overdue and should become `.unreachable` or `.dead`,
/// - decisions are made to `.ping` a random peer for fault detection,
/// - and some internal house keeping is performed.
///
/// Note: This means that effectively all decisions are made in interval sof protocol periods.
/// It would be possible to have a secondary periodic or more ad-hoc interval to speed up
/// some operations, however this is currently not implemented and the protocol follows the fairly
/// standard mode of simply carrying payloads in periodic ping messages.
///
/// - Returns: `SWIM.Instance.PeriodicPingTickDirective` which must be interpreted by a shell implementation
mutating func onPeriodicPingTick ( ) -> [ SWIM . Instance . PeriodicPingTickDirective ]
mutating func onPing ( ... ) -> [ SWIM . Instance . PingDirective ]
mutating func onPingRequest ( ... ) -> [ SWIM . Instance . PingRequestDirective ]
mutating func onPingResponse ( ... ) -> [ SWIM . Instance . PingResponseDirective ]
// ...
}
Panggilan ini melakukan semua tugas khusus protokol SWIM secara internal, dan mengembalikan arahan yang mudah untuk menafsirkan “perintah” ke implementasi tentang bagaimana seharusnya bereaksi terhadap pesan. Misalnya, setelah menerima pesan .pingRequest
, arahan yang dikembalikan mungkin memerintahkan shell untuk mengirim ping ke beberapa node. Arahan menyiapkan semua target, batas waktu, dan informasi tambahan yang sesuai yang membuatnya lebih mudah untuk mengikuti instruksinya dan mengimplementasikan panggilan dengan benar, misalnya seperti ini:
self . swim . onPingRequest (
target : target ,
pingRequestOrigin : pingRequestOrigin ,
payload : payload ,
sequenceNumber : sequenceNumber
) . forEach { directive in
switch directive {
case . gossipProcessed ( let gossipDirective ) :
self . handleGossipPayloadProcessedDirective ( gossipDirective )
case . sendPing ( let target , let payload , let pingRequestOriginPeer , let pingRequestSequenceNumber , let timeout , let sequenceNumber ) :
self . sendPing (
to : target ,
payload : payload ,
pingRequestOrigin : pingRequestOriginPeer ,
pingRequestSequenceNumber : pingRequestSequenceNumber ,
timeout : timeout ,
sequenceNumber : sequenceNumber
)
}
}
Secara umum hal ini memungkinkan semua "apa yang harus dilakukan kapan" yang rumit diringkas dalam contoh protokol, dan Shell hanya perlu mengikuti instruksi yang mengimplementasikannya. Implementasi sebenarnya sering kali perlu melakukan beberapa tugas konkurensi dan jaringan yang lebih terlibat, seperti menunggu serangkaian respons, dan menanganinya dengan cara tertentu, dll. Namun, garis besar protokol diatur oleh arahan instance.
Untuk dokumentasi mendetail tentang masing-masing callback, kapan harus memanggilnya, dan bagaimana semua ini cocok, lihat Dokumentasi API .
Repositori berisi contoh end-to-end dan contoh implementasi yang disebut SWIMNIOExample yang menggunakan SWIM.Instance
untuk mengaktifkan sistem pemantauan rekan sederhana berbasis UDP. Hal ini memungkinkan rekan-rekan untuk bergosip dan saling memberi tahu tentang kegagalan node menggunakan protokol SWIM dengan mengirimkan datagram yang digerakkan oleh SwiftNIO.
Implementasi
SWIMNIOExample
ditawarkan hanya sebagai contoh, dan belum diimplementasikan dengan mempertimbangkan penggunaan produksi, namun dengan sejumlah upaya, implementasi ini pasti dapat berfungsi dengan baik untuk beberapa kasus penggunaan. Jika Anda tertarik untuk mempelajari lebih lanjut tentang algoritme keanggotaan klaster, pembandingan skalabilitas, dan penggunaan SwiftNIO itu sendiri, ini adalah modul yang bagus untuk membantu Anda, dan mungkin setelah modul ini cukup matang, kami dapat mempertimbangkan untuk menjadikannya tidak hanya sebagai contoh, tetapi juga sebuah contoh. komponen yang dapat digunakan kembali untuk aplikasi cluster berbasis Swift NIO.
Dalam bentuknya yang paling sederhana, menggabungkan instance SWIM yang disediakan dan shell NIO untuk membangun server sederhana, seseorang dapat menyematkan penangan yang disediakan seperti yang ditunjukkan di bawah ini, dalam saluran saluran NIO pada umumnya:
let bootstrap = DatagramBootstrap ( group : group )
. channelOption ( ChannelOptions . socketOption ( . so_reuseaddr ) , value : 1 )
. channelInitializer { channel in
channel . pipeline
// first install the SWIM handler, which contains the SWIMNIOShell:
. addHandler ( SWIMNIOHandler ( settings : settings ) ) . flatMap {
// then install some user handler, it will receive SWIM events:
channel . pipeline . addHandler ( SWIMNIOExampleHandler ( ) )
}
}
bootstrap . bind ( host : host , port : port )
Pengendali contoh kemudian dapat menerima dan menangani peristiwa perubahan keanggotaan klaster SWIM:
final class SWIMNIOExampleHandler : ChannelInboundHandler {
public typealias InboundIn = SWIM . MemberStatusChangedEvent
let log = Logger ( label : " SWIMNIOExampleHandler " )
public func channelRead ( context : ChannelHandlerContext , data : NIOAny ) {
let change : SWIM . MemberStatusChangedEvent = self . unwrapInboundIn ( data )
self . log . info ( " Membership status changed: [ ( change . member . node ) ] is now [ ( change . status ) ] " , metadata : [
" swim/member " : " ( change . member . node ) " ,
" swim/member/status " : " ( change . status ) " ,
] )
}
}
Jika Anda tertarik untuk berkontribusi dan menyempurnakan implementasi SWIMNIO, silakan lihat permasalahannya dan ambil tugas atau usulkan sendiri perbaikannya!
Kami umumnya tertarik untuk mendorong diskusi dan penerapan penerapan keanggotaan tambahan menggunakan gaya "Instance" yang serupa.
Jika Anda tertarik dengan algoritme tersebut, dan memiliki protokol favorit yang ingin Anda terapkan, jangan ragu untuk menghubungi kami melalui isu atau forum Swift.
Laporan pengalaman, umpan balik, ide perbaikan, dan kontribusi sangat dianjurkan! Kami menantikan kabar dari Anda.
Silakan merujuk ke panduan KONTRIBUSI untuk mempelajari tentang proses pengiriman permintaan penarikan, dan lihat BUKU PEDOMAN untuk terminologi dan tips berguna lainnya untuk bekerja dengan perpustakaan ini.