Implementing Raft protocol by coroutines and Ktor Framework
Andrii Rodionov @AndriiRodionov
Implementing Raft protocol by coroutines and Ktor Framework Andrii - - PowerPoint PPT Presentation
Implementing Raft protocol by coroutines and Ktor Framework Andrii Rodionov @AndriiRodionov About me Devoxx Ukraine organizer KNight Kyiv co-organizer JUG UA Leader Kyiv Kotlin User Group Co-leader Devoxx Ukraine
Andrii Rodionov @AndriiRodionov
30% discount Code: DevoxxUAKotlin
○ servers compute identical copies of the same state
(ZooKeeper, HDFS, …)
○ All servers execute same commands in same order
○ All servers execute same commands in same order
○ All servers execute same commands in same order
○ All servers execute same commands in same order
○ All servers execute same commands in same order
○ Hard to understand ○ Not complete enough for real implementations
○
Raft is a consensus algorithm for managing a replicated log
○
Diego Ongaro and John Ousterhout - Stanford University
○
Docker swarm, Consul, Kudu, RavenDB etc
○ Select one of the servers to act as cluster leader ○ Detect crashes, choose new leader
○ Leader takes commands from clients, appends them to its log ○ Leader replicates its log to other servers (overwriting inconsistencies)
○ Leader executes command in its state machine, returns result to client ○ Leader notifies followers of committed entries in subsequent AppendEntries RPCs ○ Followers execute committed commands in their state machines
○ Leader retries AppendEntries RPCs until they succeed
○ One successful RPC to any majority of servers
service Raft { rpc Vote (RequestVoteRPC) returns (ResponseVoteRPC); rpc Append (RequestAppendEntriesRPC) returns (ResponseAppendEntriesRPC); }
[As a FOLLOWER]
[As a CANDIDATE]
[As a LEADER]
[As a Raft-node]
class RaftServer(... ) : RaftGrpcKt.RaftImplBase(), CoroutineScope { fun vote(request: RequestVoteRPC): Deferred<ResponseVoteRPC> = async { … } fun append(request: RequestAppendEntriesRPC): Deferred<ResponseAppendEntriesRPC> = async { … } }
https://github.com/rouzwawi/grpc-kotlin
val channel = Channel<State>() init { val waitingForHeartbeat = waitingForHeartbeatFromLeaderTimer() launch { channel.consumeEach { when (it) { FOLLOWER -> waitingForHeartbeat.reset() CANDIDATE -> leaderElection() LEADER -> appendRequestAndLeaderHeartbeat() } } } }
class ResettableCountdownTimer(private val action: suspend () -> Unit) { private var timer = startTimer() fun reset() { timer.cancel() timer = startTimer() } private fun startTimer(): Timer { val newTimer = Timer() newTimer.schedule(randomDelay()) { runBlocking { action() } } return newTimer } }
suspend fun <T> retry(delay: Long = 5000, block: suspend () -> T): T { while (true) { try { return block() } catch (e: Exception) { } delay(delay) } }
1 2 3 4 5
val countDownLatch = CountDownLatch(majority)
1 2 3 4 5
val countDownLatch = CountDownLatch(majority) val job = Job() servers.forEach { srv -> launch(parent = job) { val responseVote = retry { srv.vote( … ) } countDownLatch.countDown() ... } } countDownLatch.await(electionTimeout, TimeUnit.SECONDS)
1 2 3 4 5
val countDownLatch = CountDownLatch(majority) val job = Job() servers.forEach { srv -> launch(parent = job) { val responseVote = retry { srv.vote( … ) } countDownLatch.countDown() ... } } countDownLatch.await(electionTimeout, TimeUnit.SECONDS) job.cancelAndJoin()
1 2 3 4 5
val countDownLatch = CountDownLatch(majority) coroutineScope { servers.forEach { launch { val responseVote = retry { it.vote( … ) } countDownLatch.countDown() ... } } countDownLatch.await(electionTimeout, TimeUnit.SECONDS) coroutineContext.cancelChildren() }
1 2 3 4 5
fixedRateTimer(period = 2000) { runBlocking { servers.forEach { launch { try { val response = it.append( … ) ... } catch (e: Exception) { } } } } }
private fun ktorServer() { val server = embeddedServer(Netty, port = 7000) { routing { get("/") { call.respondText("Server $id log ${entries()}", Text.Plain) } get("/cmd/{command}") { appendCommand(call.parameters["command"]) call.respondText("Server $id log ${entries()}", Text.Plain) } } } server.start(wait = false) }