이 라이브러리는 Swift가 클러스터형 다중 노드 분산 시스템이라는 새로운 공간에서 기반을 마련하도록 돕는 것을 목표로 합니다.
이 라이브러리를 통해 우리는 다양한 클러스터링 사용 사례에 채택할 수 있는 재사용 가능한 런타임 독립적 멤버십 프로토콜 구현을 제공합니다.
클러스터 멤버십 프로토콜은 계산 집약적인 클러스터, 스케줄러, 데이터베이스, 키-값 저장소 등과 같은 분산 시스템의 중요한 구성 요소입니다. 이 패키지의 발표를 통해 우리는 서비스 멤버십을 처리하기 위해 더 이상 외부 서비스에 의존할 필요가 없으므로 이러한 시스템을 더욱 간단하게 구축하는 것을 목표로 합니다. 또한 추가 멤버십 프로토콜에 대해 협력하고 개발할 수 있도록 커뮤니티를 초대하고 싶습니다.
핵심적으로 멤버십 프로토콜은 "나의 (실시간) 동료는 누구입니까?"라는 질문에 대한 답변을 제공해야 합니다. 겉보기에 간단해 보이는 이 작업은 지연되거나 손실된 메시지, 네트워크 파티션, 응답하지 않지만 여전히 "살아 있는" 노드가 매일의 빵과 버터가 되는 분산 시스템에서는 전혀 간단하지 않은 것으로 밝혀졌습니다. 이 질문에 대한 예측 가능하고 신뢰할 수 있는 답변을 제공하는 것이 클러스터 멤버십 프로토콜의 역할입니다.
멤버십 프로토콜을 구현하는 동안 취할 수 있는 다양한 절충안이 있으며, 이는 계속해서 흥미로운 연구 및 개선 분야가 되고 있습니다. 이처럼 클러스터-멤버십 패키지는 단일 구현에 초점을 맞추는 것이 아니라 이 공간에서 다양한 분산 알고리즘을 위한 협업 공간 역할을 하려는 의도입니다.
이 구현의 프로토콜 및 수정 사항에 대한 보다 심층적인 논의를 위해서는 SWIM API 문서와 아래 링크된 관련 문서를 읽어 보시기 바랍니다.
2018 Lifeguard: 보다 정확한 오류 감지를 위한 로컬 건강 인식 문서에 문서화된 몇 가지 주목할만한 프로토콜 확장과 함께 확장 가능한 약하게 일관된 감염 스타일 프로세스 그룹 멤버십 알고리즘("SWIM"이라고도 함).
SWIM은 피어가 주기적으로 다른 노드의 상태 관찰에 대한 정보 비트를 교환하여 결국 정보를 클러스터의 다른 모든 구성원에게 전파하는 가십 프로토콜입니다. 이 분산 알고리즘 범주는 임의 메시지 손실, 네트워크 파티션 및 유사한 문제에 대해 매우 탄력적입니다.
높은 수준에서 SWIM은 다음과 같이 작동합니다.
.ack
가 다시 전송될 것으로 예상하고 해당 피어에 .ping 메시지를 전송하여 이를 수행합니다. 아래 다이어그램에서 A
처음에 B
조사하는 방법을 확인하세요.payload
와 해당 멤버의 멤버십 상태( .alive
, .suspect
등)도 포함됩니다..ack
을 수신하면 피어는 여전히 .alive
로 간주됩니다. 그렇지 않으면 대상 피어가 종료/충돌되었거나 다른 이유로 응답하지 않을 수 있습니다..pingRequest
메시지를 보내 응답하지 않는 피어의 상태에 대해 몇몇 다른 피어에게 묻습니다. 그런 다음 해당 피어에 직접 핑을 보냅니다(프로빙 피어). 아래 다이어그램의 E)..suspect
로 표시됩니다..nack
("부정 승인") 메시지를 사용하여 중개자가 해당 .pingRequest
메시지를 수신했지만 대상이 응답하지 않은 것 같다는 것을 ping 요청 원본에 알립니다. 우리는 이 정보를 사용하여 시간 초과 계산 방법에 영향을 미치는 로컬 상태 승수를 조정합니다. 이에 대해 자세히 알아보려면 API 문서와 Lifeguard 문서를 참조하세요.위의 메커니즘은 오류 감지 메커니즘의 역할을 할 뿐만 아니라 알려진 클러스터 구성원에 대한 정보를 전달하는 가십 메커니즘의 역할도 합니다. 이렇게 하면 구성원은 동료를 모두 미리 나열하지 않고도 결국 동료의 상태에 대해 알 수 있습니다. 그러나 이러한 멤버십 관점은 약하게 일관성이 있다는 점을 지적할 가치가 있습니다. 이는 모든 구성원이 특정 시점에 멤버십에 대해 동일한 정확한 견해를 가지고 있는지 보장(또는 추가 정보 없이 알 수 있는 방법)이 없음을 의미합니다. 그러나 이는 더 높은 수준의 도구와 시스템이 더 강력한 보장을 구축할 수 있는 훌륭한 구성 요소입니다.
오류 감지 메커니즘이 응답하지 않는 노드를 감지하면 결국 .dead로 표시되어 클러스터에서 취소할 수 없게 제거됩니다. 우리 구현에서는 가능한 상태에 .unreachable 상태를 추가하는 선택적 확장을 제공하지만 대부분의 사용자는 이 기능이 필요하다고 생각하지 않으며 기본적으로 비활성화되어 있습니다. 법적 상태 전환에 대한 자세한 내용과 규칙은 SWIM.Status 또는 다음 다이어그램을 참조하세요.
Swift Cluster Membership이 프로토콜을 구현하는 방식은 프로토콜의 " Instances
"를 제공하는 것입니다. 예를 들어, SWIM 구현은 네트워킹 런타임과 인스턴스 자체 사이의 일부 글루 코드에 의해 "구동"되거나 "해석"되어야 하는 런타임 독립적인 SWIM.Instance
에 캡슐화됩니다. 우리는 구현의 이러한 결합 부분을 " Shell
s"라고 부르며, 라이브러리는 UDP를 통해 비동기적으로 모든 메시징을 수행하는 SwiftNIO의 DatagramChannel
사용하여 구현된 SWIMNIOShell
과 함께 제공됩니다. 대체 구현에서는 완전히 다른 전송을 사용하거나 기존의 다른 가십 시스템 등에 SWIM 메시지를 피기백할 수 있습니다.
SWIM 인스턴스에는 측정항목 내보내기(swift-metrics 사용)에 대한 지원도 내장되어 있으며 Swift-log Logger
전달하여 내부 세부정보에 대한 세부정보를 기록하도록 구성할 수 있습니다.
이 라이브러리의 주요 목적은 일종의 프로세스 내 멤버십 서비스가 필요한 다양한 구현에서 SWIM.Instance
구현을 공유하는 것입니다. 사용자 정의 런타임 구현은 프로젝트의 README(https://github.com/apple/swift-cluster-membership/)에 자세히 설명되어 있으므로 다른 전송을 통해 SWIM을 구현하는 데 관심이 있다면 해당 페이지를 살펴보시기 바랍니다.
새로운 전송을 구현하려면 "빈칸 채우기" 연습이 필요합니다.
먼저, 대상 전송을 사용하여 피어 프로토콜(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
// ...
}
이는 일반적으로 메시지를 보내고 해당되는 경우 적절한 콜백을 호출하는 기능으로 일부 연결, 채널 또는 기타 ID를 래핑하는 것을 의미합니다.
그런 다음 피어의 수신 측에서는 해당 메시지 수신을 구현하고 SWIM.Instance
(SWIMProtocol 아래 그룹화됨)에 정의된 해당 on<SomeMessage>(...)
콜백을 모두 호출해야 합니다.
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 문서 를 참조하세요.
저장소에는 간단한 UDP 기반 피어 모니터링 시스템을 활성화하기 위해 SWIM.Instance
를 사용하는 SWIMNIOExample이라는 구현 예제와 엔드투엔드 예제가 포함되어 있습니다. 이를 통해 피어는 SwiftNIO가 구동하는 데이터그램을 보내 SWIM 프로토콜을 사용하여 노드 오류에 대해 서로 험담하고 알릴 수 있습니다.
SWIMNIOExample
구현은 예제로만 제공되며 프로덕션 사용을 염두에 두고 구현되지 않았습니다. 그러나 약간의 노력을 기울이면 일부 사용 사례에서는 확실히 좋은 결과를 얻을 수 있습니다. 클러스터 멤버십 알고리즘, 확장성 벤치마킹 및 SwiftNIO 자체 사용에 대해 더 자세히 알아보고 싶다면 이 모듈을 처음 접해볼 수 있는 훌륭한 모듈입니다. 아마도 모듈이 충분히 성숙되면 예제뿐만 아니라 Swift NIO 기반 클러스터 애플리케이션을 위한 재사용 가능한 구성 요소입니다.
제공된 SWIM 인스턴스와 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 포럼을 통해 연락해 주세요.
경험 보고서, 피드백, 개선 아이디어 및 기여가 크게 권장됩니다! 우리는 귀하의 의견을 기다리겠습니다.
끌어오기 요청 제출 프로세스에 대해 알아보려면 CONTRIBUTING 가이드를 참조하고, 이 라이브러리 작업에 대한 용어 및 기타 유용한 팁은 HANDBOOK을 참조하세요.