Dağıtık sistemler alanında, merkezi bir arıza noktası olmadan düğümler arasında tutarlılık sağlamak en zorlu mimari sorunlardan biridir. Birçok modern uygulama ölçeklendirmeyi basitleştirmek için varsayılan olarak durumsuz tasarımlara yönelse de, liderlik tabloları, oturum depoları veya dağıtık kilitler gibi durumlu mikro servisler sıkı tutarlılık garantileri gerektirir. Anlayış için tasarlanmış ve daha karmaşık Paxos protokolüne pratik bir alternatif sunan Raft uzlaşma algoritması devreye girer. Bu yazıda, özellikle durumlu mikro servislere Raft'ı nasıl uygulayacağımızı; lider seçimi, günlük kopyalama ve güvenlik garantilerine odaklanarak inceleyeceğiz.
Durumlu Servisler İçin Neden Raft?
Durumlu mikro servisler, tüm kopyalar arasında verilerinin tutarlı bir görünümünü korumak zorundadır. İki düğüm çelişkili okumalar sağlarsa, sistem bütünlüğü tehlikeye girer. Raft, uzlaşma sorununu üç alt soruna böler: Lider Seçimi, Günlük Kopyalama ve Güvenlik. Bu modüler yaklaşım, diğer algoritmalarla karşılaştırıldığında hata ayıklamayı ve test etmeyi önemli ölçüde kolaylaştırır. Bir mikro servis mimarisi için bu öngörülebilirlik paha biçilmezdir ve geliştiricilerin arıza modlarını daha büyük bir güvenle değerlendirmesini sağlar.
Temel Bileşenler: Düğümler, Roller ve Dönemler
Bir Raft kümesinde her sunucu üç durumdan birinde bulunabilir: Lider, Takipçi veya Aday. Tüm sunucular Takipçi olarak başlar. Bir Takipçi, belirli bir seçim zaman aşımında Lider'den haber alamazsa, Aday durumuna geçer ve bir seçim başlatır. Küme, en güncel günlüğe sahip olan kişiye dayalı olarak bir Lider seçer. Önemli olan, Raft'ın eski Liderlerden veya Adaylardan gelen bayat istekleri reddetmeye yardımcı olmak için Dönemleri (monoton olarak artan mantıksal saatler) kullanmasıdır.
Lider Seçimini Uygulama
Lider seçimi bir zamanlayıcı tarafından tetiklenir. Canlılığı sağlamak için, seçim zaman aşımı süreleri bölünmüş oylama olasılığını azaltmak amacıyla rastgeleleştirilir. Aşağıda, bir Takipçi'nin Aday'a geçişinin basitleştirilmiş sözde kod örneği verilmiştir:
class RaftNode {
state = "follower"
currentTerm = 0
votedFor = null
electionTimer = new Timer()
function startElection() {
this.state = "candidate"
this.currentTerm += 1
this.votedFor = this.id
var votesReceived = 1
for (var peer in peers) {
if (peer.id != this.id) {
send(peer, {
type: "RequestVote",
term: this.currentTerm,
candidateId: this.id,
lastLogIndex: this.log.length - 1,
lastLogTerm: this.log[this.log.length - 1].term
})
}
}
}
function onMessage(msg) {
if (msg.term > this.currentTerm) {
this.currentTerm = msg.term
this.state = "follower"
this.votedFor = null
}
if (msg.type == "RequestVote") {
if (this.votedFor == null || this.votedFor == msg.candidateId) {
if (isCandidateUpToDate(msg)) {
this.votedFor = msg.candidateId
votesReceived += 1
respond(msg.candidateId, {
type: "VoteResponse",
term: this.currentTerm,
granted: true
})
}
}
}
if (votesReceived > (clusterSize / 2) + 1) {
this.state = "leader"
startHeartbeat()
}
}
}
Günlük Kopyalama ve Güvenlik
Bir Lider seçildikten sonra, günlük girdilerini kopyalamaya başlar. Lider, her girdiye bir Dönem ve bir indeks atar. Girdileri kendi günlüğüne ekler ve tüm Takipçilere AppendEntries RPC'leri gönderir. Bir günlük girdisi, kümenin çoğunluğuna kopyalandığında taahhüt edilmiş (committed) olarak kabul edilir. Lider, ardından Takipçilere durum makinelerine hangi girdilerin güvenle uygulanabileceğini bildirmek için sonraki AppendEntries mesajlarına en yüksek taahhüt edilmiş indeksi dahil eder.
Üretim İçin Pratik Düşünceler
- Ön-Oylar (Pre-Votes): Ağ bölünmeleri sırasında kesintiye uğramayı önlemek için, gerçek bir seçim başlatmadan önce ön-oy mesajları uygulayın. Bu, bir Aday'ın dönemi artırıp mevcut Lider'i kesintiye uğratmadan önce kazanma olasılığının yüksek olduğunu garanti eder.
- Anlık Görüntü Alma (Snapshotting): Günlük büyüdükçe, yeni düğümlere tüm geçmişi göndermek verimsiz hale gelir. Günlüğü sıkılaştırmak ve başlangıç senkronizasyonunu hızlandırmak için durum makinesinin periyodik anlık görüntülerini uygulayın.
- İstemci Zaman Aşımı İşleme: Bir istemci isteği, bir Lider olmayan bir düğüme gönderildiği için başarısız olursa, istemci yeniden denemeli. Failover sırasında kümeyi aşırı yüklemeyi önlemek için üstel geri çekilme (exponential backoff) uygulayın.
Test ve Hata Ayıklama
Uzlaşma algoritmaları, belirsiz doğaları nedeniyle test etmesi meşhur derecede zordur. Ağ bölünmelerini, düğüm arızalarını ve mesaj gecikmelerini simüle etmek için Jepsen çerçevesini kullanmanızı öneririz. Ayrıca, durum makinenizin test edilen tüm koşullar altında tek bir mantıksal düğüm gibi davrandığını doğrulamak için Doğrusallık (Linearizability) denetleyicileri kullanın. Hatalı kodları hata ayıklamak için kodunuzu Dönemler, İndeksler ve Durum Geçişlerinin ayrıntılı günlükleriyle enstrümanlaştırmak kritiktir.
Sonuç
Durumlu mikro servislere Raft'ı uygulamak, yüksek erişilebilir ve tutarlı dağıtık sistemler inşa etmek için sağlam bir temel sağlar. Karmaşıklık önemsiz olmasa da, Raft'ın tasarımı tarafından sunulan açıklık, onu mühendislik ekipleri için pratik bir seçim haline getirir. Lider seçimi, günlük kopyalama ve güvenlik kontrollerini dikkatlice uygulayarak, mikro servislerinizin kısmi arızalar karşısında tutarlı kalmasını sağlayabilirsiniz. Sisteminiz ölçeklendikçe, performansı ve güvenilirliği sürdürmek için ön-oyları ve anlık görüntü almayı benimsemeyi düşünün.