ไลบรารีนี้มีจุดมุ่งหมายเพื่อช่วยให้ Swift สร้างพื้นที่ใหม่: ระบบกระจายหลายโหนดแบบคลัสเตอร์
ด้วยไลบรารีนี้ เราจัดเตรียมการใช้งานโปรโตคอลสมาชิกผู้ไม่เชื่อเรื่องพระเจ้ารันไทม์แบบใช้ซ้ำได้ ซึ่งสามารถนำไปใช้ในกรณีการใช้งานการทำคลัสเตอร์ต่างๆ
โปรโตคอลการเป็นสมาชิกคลัสเตอร์เป็นส่วนสำคัญสำหรับระบบแบบกระจาย เช่น คลัสเตอร์ที่เน้นการประมวลผล ตัวกำหนดเวลา ฐานข้อมูล การจัดเก็บคีย์-ค่า และอื่นๆ ด้วยการประกาศแพ็คเกจนี้ เรามุ่งมั่นที่จะทำให้การสร้างระบบดังกล่าวง่ายขึ้น เนื่องจากระบบไม่จำเป็นต้องพึ่งพาบริการภายนอกเพื่อจัดการสมาชิกบริการอีกต่อไป นอกจากนี้เรายังต้องการเชิญชุมชนให้ร่วมมือและพัฒนาระเบียบการเป็นสมาชิกเพิ่มเติม
โดยพื้นฐานแล้ว ระเบียบการเป็นสมาชิกจำเป็นต้องให้คำตอบสำหรับคำถาม "ใครคือเพื่อน (สด) ของฉัน" งานที่ดูเหมือนเรียบง่ายนี้กลายเป็นว่าไม่ง่ายเลยในระบบแบบกระจาย ซึ่งข้อความที่ล่าช้าหรือสูญหาย พาร์ติชันเครือข่าย และโหนดที่ไม่ตอบสนองแต่ยังคง "มีชีวิต" อยู่เป็นหน้าที่หลักในแต่ละวัน การให้คำตอบที่คาดเดาได้และเชื่อถือได้สำหรับคำถามนี้คือสิ่งที่โปรโตคอลการเป็นสมาชิกคลัสเตอร์ทำ
มีข้อดีหลายอย่างที่สามารถทำได้ในขณะที่ใช้ระเบียบการสำหรับสมาชิก และยังคงเป็นประเด็นที่น่าสนใจสำหรับการวิจัยและการปรับปรุงอย่างต่อเนื่อง ด้วยเหตุนี้ แพ็คเกจสมาชิกคลัสเตอร์จึงไม่ได้มุ่งเน้นที่การใช้งานเพียงครั้งเดียว แต่ทำหน้าที่เป็นพื้นที่การทำงานร่วมกันสำหรับอัลกอริธึมแบบกระจายต่างๆ ในพื้นที่นี้
สำหรับการอภิปรายเชิงลึกเพิ่มเติมเกี่ยวกับโปรโตคอลและการปรับเปลี่ยนในการใช้งานนี้ เราขอแนะนำให้อ่านเอกสารประกอบ SWIM API รวมถึงเอกสารที่เกี่ยวข้องตามลิงก์ด้านล่าง
อัลกอริธึม การเป็นสมาชิกกลุ่มกระบวนการรูปแบบการติดเชื้อที่มีความสม่ำเสมอที่ปรับขนาดได้และปรับขนาดได้ (หรือที่เรียกว่า "SWIM") พร้อมด้วยส่วนขยายโปรโตคอลที่โดดเด่นบางประการดังที่บันทึกไว้ในเอกสาร Lifeguard: Local Health Awareness for More Accurate Failure Detection ปี 2018
SWIM เป็นโปรโตคอลการนินทาที่เพื่อนแลกเปลี่ยนข้อมูลเป็นระยะ ๆ เกี่ยวกับการสังเกตสถานะของโหนดอื่น ๆ และในที่สุดก็จะกระจายข้อมูลไปยังสมาชิกคนอื่น ๆ ทั้งหมดในคลัสเตอร์ อัลกอริธึมแบบกระจายประเภทนี้มีความยืดหยุ่นสูงต่อการสูญเสียข้อความโดยพลการ พาร์ติชันเครือข่าย และปัญหาที่คล้ายกัน
ในระดับสูง SWIM ทำงานในลักษณะนี้:
.ack
จะถูกส่งกลับ ดูว่า A
โพรบ B
เริ่มต้นอย่างไรในแผนภาพด้านล่างpayload
ซุบซิบ ซึ่งเป็นข้อมูล (บางส่วน) เกี่ยวกับสิ่งที่เพื่อนร่วมงานคนอื่นๆ ที่ส่งข้อความทราบ พร้อมด้วยสถานะสมาชิกของพวกเขา ( .alive
, .suspect
ฯลฯ).ack
เพียร์นั้นจะถือว่ายังคงอยู่ . .alive
มิฉะนั้น เพียร์เป้าหมายอาจหยุดทำงาน/ขัดข้อง หรือไม่ตอบสนองด้วยเหตุผลอื่น.pingRequest
ไปยังเพียร์อื่น ๆ ตามจำนวนที่กำหนดค่าไว้ จากนั้นจะส่ง Ping โดยตรงไปยังเพียร์นั้น (ตรวจสอบเพียร์ E ในแผนภาพด้านล่าง).suspect
.nack
("การตอบรับเชิงลบ") เพิ่มเติมในสถานการณ์เพื่อแจ้งที่มาของคำขอ ping ว่าตัวกลางได้รับข้อความ .pingRequest
เหล่านั้นแล้ว อย่างไรก็ตาม ดูเหมือนว่าเป้าหมายจะไม่ตอบสนอง เราใช้ข้อมูลนี้เพื่อปรับตัวคูณสุขภาพในท้องถิ่น ซึ่งส่งผลต่อวิธีคำนวณการหมดเวลา หากต้องการเรียนรู้เพิ่มเติมเกี่ยวกับเรื่องนี้ โปรดดูเอกสาร API และเอกสารของ Lifeguardกลไกข้างต้นไม่เพียงทำหน้าที่เป็นกลไกการตรวจจับความล้มเหลวเท่านั้น แต่ยังเป็นกลไกการนินทาซึ่งนำข้อมูลเกี่ยวกับสมาชิกของคลัสเตอร์ที่รู้จัก วิธีนี้จะทำให้สมาชิกได้เรียนรู้เกี่ยวกับสถานะของเพื่อนของตนในที่สุด แม้ว่าจะไม่ได้ระบุรายชื่อทั้งหมดไว้ล่วงหน้าก็ตาม อย่างไรก็ตาม ควรสังเกตว่ามุมมองของสมาชิกภาพมีความสอดคล้องกันเล็กน้อย ซึ่งหมายความว่าไม่มีการรับประกัน (หรือวิธีการทราบโดยไม่มีข้อมูลเพิ่มเติม) หากสมาชิกทุกคนมีมุมมองที่เหมือนกันต่อการเป็นสมาชิก ณ จุดใดเวลาหนึ่ง อย่างไรก็ตาม มันเป็นองค์ประกอบหลักที่ยอดเยี่ยมสำหรับเครื่องมือและระบบระดับสูงเพื่อสร้างการรับประกันที่แข็งแกร่งยิ่งขึ้น
เมื่อกลไกการตรวจจับความล้มเหลวตรวจพบโหนดที่ไม่ตอบสนอง ในที่สุดโหนดนั้นจะถูกทำเครื่องหมายเป็น .dead ซึ่งส่งผลให้มีการลบออกจากคลัสเตอร์โดยไม่สามารถเพิกถอนได้ การใช้งานของเราเสนอส่วนขยายที่เป็นทางเลือก โดยเพิ่มสถานะ .unreachable ให้กับสถานะที่เป็นไปได้ อย่างไรก็ตาม ผู้ใช้ส่วนใหญ่จะพบว่าไม่จำเป็นและจะถูกปิดใช้งานตามค่าเริ่มต้น สำหรับรายละเอียดและกฎเกณฑ์เกี่ยวกับการเปลี่ยนสถานะทางกฎหมาย โปรดดูที่ SWIM.Status หรือแผนภาพต่อไปนี้:
วิธีที่ Swift Cluster Membership ใช้โปรโตคอลคือการเสนอ " Instances
" ของพวกเขา ตัวอย่างเช่น การใช้งาน SWIM ถูกรวมไว้ใน SWIM แบบไม่เชื่อเรื่องพระเจ้าแบบรันไทม์ อิน SWIM.Instance
ซึ่งจำเป็นต้อง "ขับเคลื่อน" หรือ "ตีความ" ด้วยโค้ดกาวบางส่วนระหว่างรันไทม์ของเครือข่ายและตัวอินสแตนซ์เอง เราเรียกส่วนที่ติดแน่นของการนำไปใช้งานว่า " Shell
s" และไลบรารีจะมาพร้อมกับ SWIMNIOShell
ที่ใช้งานโดยใช้ DatagramChannel
ของ SwiftNIO ซึ่งดำเนินการส่งข้อความทั้งหมดแบบอะซิงโครนัสผ่าน UDP การใช้งานทางเลือกอาจใช้การขนส่งที่แตกต่างกันโดยสิ้นเชิง หรือส่งข้อความว่ายน้ำกลับบนระบบซุบซิบอื่น ๆ ที่มีอยู่ เป็นต้น
นอกจากนี้ อินสแตนซ์ SWIM ยังมีการรองรับในตัวสำหรับการปล่อยตัววัด (โดยใช้ตัววัดแบบรวดเร็ว) และสามารถกำหนดค่าให้บันทึกรายละเอียดเกี่ยวกับรายละเอียดภายในได้โดยการส่ง Logger
บันทึกแบบรวดเร็ว
วัตถุประสงค์หลักของไลบรารีนี้คือการแบ่งปันการใช้งาน SWIM.Instance
ในการใช้งานต่างๆ ซึ่งต้องการบริการสมาชิกที่อยู่ระหว่างดำเนินการบางรูปแบบ การใช้รันไทม์แบบกำหนดเองได้รับการบันทึกไว้ในเชิงลึกใน README ของโปรเจ็กต์ (https://github.com/apple/swift-cluster-membership/) ดังนั้น โปรดดูที่นั่นหากคุณสนใจที่จะนำ SWIM ไปใช้กับการขนส่งที่แตกต่างกัน
การใช้การขนส่งแบบใหม่จะทำให้การฝึก "เติมคำในช่องว่าง" ลดลง:
ขั้นแรก เราต้องใช้โปรโตคอล Peer (https://github.com/apple/swift-cluster-membership/blob/main/Sources/SWIM/Peer.swift) โดยใช้การขนส่งเป้าหมาย:
/// 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
// ...
}
ซึ่งโดยทั่วไปหมายถึงการรวมการเชื่อมต่อ ช่องทาง หรือข้อมูลประจำตัวอื่น ๆ เข้ากับความสามารถในการส่งข้อความและเรียกใช้การโทรกลับที่เหมาะสมเมื่อมี
จากนั้น เมื่อสิ้นสุดการรับเพียร์ เราจะต้องดำเนินการรับข้อความเหล่านั้นและเรียกใช้การเรียกกลับ on<SomeMessage>(...)
ที่เกี่ยวข้องทั้งหมดที่กำหนดไว้ใน SWIM.Instance
(จัดกลุ่มภายใต้ SWIMProtocol)
SWIMProtocol ส่วนหนึ่งแสดงอยู่ด้านล่างเพื่อให้คุณได้รับแนวคิดเกี่ยวกับเรื่องนี้:
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 ]
// ...
}
การเรียกเหล่านี้ทำงานเฉพาะของโปรโตคอล SWIM ทั้งหมดเป็นการภายใน และส่งคืนคำสั่งที่ง่ายต่อการตีความ “คำสั่ง” ไปสู่การใช้งานเกี่ยวกับวิธีการตอบสนองต่อข้อความ ตัวอย่างเช่น เมื่อได้รับข้อความ .pingRequest
คำสั่งที่ส่งคืนอาจสั่งให้เชลล์ส่ง Ping ไปยังบางโหนด คำสั่งนี้จะเตรียมเป้าหมาย การหมดเวลา และข้อมูลเพิ่มเติมที่เหมาะสมทั้งหมด ซึ่งจะทำให้ง่ายต่อการปฏิบัติตามคำสั่งและดำเนินการเรียกอย่างถูกต้อง เช่น:
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
)
}
}
โดยทั่วไปสิ่งนี้จะช่วยให้ "สิ่งที่ต้องทำเมื่อ" ที่ยุ่งยากทั้งหมดถูกห่อหุ้มไว้ภายในอินสแตนซ์โปรโตคอล และเชลล์จะต้องปฏิบัติตามคำแนะนำในการใช้งานเท่านั้น การใช้งานจริงมักจะจำเป็นต้องดำเนินการทำงานพร้อมกันและงานเครือข่ายที่เกี่ยวข้องมากขึ้น เช่น รอลำดับการตอบสนอง และจัดการด้วยวิธีเฉพาะ เป็นต้น อย่างไรก็ตาม เค้าโครงทั่วไปของโปรโตคอลได้รับการควบคุมโดยคำสั่งของอินสแตนซ์
สำหรับเอกสารประกอบโดยละเอียดเกี่ยวกับการเรียกกลับแต่ละรายการ เวลาที่ควรเรียกใช้ และวิธีการทำงานร่วมกันทั้งหมด โปรดดูที่ เอกสารประกอบ API
พื้นที่เก็บข้อมูลประกอบด้วยตัวอย่างตั้งแต่ต้นจนจบและตัวอย่างการใช้งานที่เรียกว่า SWIMNIOExample ซึ่งใช้ SWIM.Instance
เพื่อเปิดใช้งานระบบการตรวจสอบเพียร์ที่ใช้ UDP อย่างง่าย ซึ่งช่วยให้เพื่อนร่วมงานสามารถนินทาและแจ้งเตือนกันเกี่ยวกับความล้มเหลวของโหนดโดยใช้โปรโตคอล SWIM โดยการส่งดาต้าแกรมที่ขับเคลื่อนโดย SwiftNIO
การใช้งาน
SWIMNIOExample
นำเสนอเป็นเพียงตัวอย่างเท่านั้น และไม่ได้ถูกนำมาใช้โดยคำนึงถึงการใช้งานจริง อย่างไรก็ตาม ด้วยความพยายามในระดับหนึ่ง ก็สามารถทำงานได้ดีกับกรณีการใช้งานบางกรณีอย่างแน่นอน หากคุณสนใจที่จะเรียนรู้เพิ่มเติมเกี่ยวกับอัลกอริธึมการเป็นสมาชิกคลัสเตอร์ การเปรียบเทียบความสามารถในการปรับขนาด และการใช้ SwiftNIO เอง นี่เป็นโมดูลที่ยอดเยี่ยมที่จะช่วยให้คุณเริ่มต้นได้ และบางทีเมื่อโมดูลเติบโตเพียงพอแล้ว เรายังสามารถพิจารณาทำให้โมดูลนี้ไม่เพียงเป็นตัวอย่างเท่านั้น แต่ยังเป็น ส่วนประกอบที่นำมาใช้ซ้ำได้สำหรับแอปพลิเคชันคลัสเตอร์ที่ใช้ Swift NIO
ในรูปแบบที่ง่ายที่สุด เมื่อรวม SWIM instance ที่ให้มาและเชลล์ NIO เข้าด้วยกันเพื่อสร้างเซิร์ฟเวอร์แบบธรรมดา เราสามารถฝังตัวจัดการที่ให้มาดังที่แสดงด้านล่างในไปป์ไลน์ของช่อง NIO ทั่วไป:
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 )
ตัวจัดการตัวอย่างสามารถรับและจัดการเหตุการณ์การเปลี่ยนแปลงสมาชิกคลัสเตอร์ 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 ) " ,
] )
}
}
หากคุณสนใจที่จะมีส่วนร่วมและขัดเกลาการใช้งาน SWIMNIO โปรดตรงไปที่ประเด็นปัญหาและรับงานหรือเสนอการปรับปรุงด้วยตนเอง!
โดยทั่วไปเราสนใจที่จะส่งเสริมการอภิปรายและการใช้งานการใช้งานสมาชิกเพิ่มเติมโดยใช้รูปแบบ "อินสแตนซ์" ที่คล้ายกัน
หากคุณสนใจในอัลกอริธึมดังกล่าว และมีโปรโตคอลโปรดที่คุณต้องการนำไปใช้ โปรดอย่าลังเลที่จะติดต่อเราผ่านประเด็นปัญหาหรือฟอรัม Swift
รายงานประสบการณ์ ข้อเสนอแนะ แนวคิดในการปรับปรุง และการมีส่วนร่วมได้รับการสนับสนุนอย่างมาก! เราหวังว่าจะได้รับการติดต่อจากคุณ
โปรดดูคู่มือการมีส่วนร่วมเพื่อเรียนรู้เกี่ยวกับกระบวนการส่งคำขอดึง และดูคู่มือสำหรับคำศัพท์และเคล็ดลับที่เป็นประโยชน์อื่น ๆ สำหรับการทำงานกับไลบรารีนี้