Как реализовать Failover в кластере Akka.NET с помощью API Akka.FSharp?
Как реализовать Failover в кластере Akka.NET с помощью API Akka.FSharp?
У меня есть следующий узел кластера, который служит в качестве семени:
open Akka
open Akka.FSharp
open Akka.Cluster
open System
open System.Configuration
let systemName = "script-cluster"
let nodeName = sprintf "cluster-node-%s" Environment.MachineName
let akkaConfig = Configuration.parse("""akka {
actor {
provider = "Akka.Cluster.ClusterActorRefProvider, Akka.Cluster"
}
remote {
log-remote-lifecycle-events = off
helios.tcp {
hostname = "127.0.0.1"
port = 2551
}
}
cluster {
roles = ["seed"] # custom node roles
seed-nodes = ["akka.tcp://script-cluster@127.0.0.1:2551"]
# when node cannot be reached within 10 sec, mark is as down
auto-down-unreachable-after = 10s
}
}""")
let actorSystem = akkaConfig |> System.create systemName
let clusterHostActor =
spawn actorSystem nodeName (fun (inbox: Actor<ClusterEvent.IClusterDomainEvent>) ->
let cluster = Cluster.Get actorSystem
cluster.Subscribe(inbox.Self, [| typeof<ClusterEvent.IClusterDomainEvent> |])
inbox.Defer(fun () -> cluster.Unsubscribe(inbox.Self))
let rec messageLoop () =
actor {
let! message = inbox.Receive()
// TODO: Handle messages
match message with
| :? ClusterEvent.MemberJoined as event -> printfn "Member %s Joined the Cluster at %O" event.Member.Address.Host DateTime.Now
| :? ClusterEvent.MemberLeft as event -> printfn "Member %s Left the Cluster at %O" event.Member.Address.Host DateTime.Now
| other -> printfn "Cluster Received event %O at %O" other DateTime.Now
return! messageLoop()
}
messageLoop())
Затем у меня есть произвольный узел, который может умереть:
open Akka
open Akka.FSharp
open Akka.Cluster
open System
open System.Configuration
let systemName = "script-cluster"
let nodeName = sprintf "cluster-node-%s" Environment.MachineName
let akkaConfig = Configuration.parse("""akka {
actor {
provider = "Akka.Cluster.ClusterActorRefProvider, Akka.Cluster"
}
remote {
log-remote-lifecycle-events = off
helios.tcp {
hostname = "127.0.0.1"
port = 0
}
}
cluster {
roles = ["role-a"] # custom node roles
seed-nodes = ["akka.tcp://script-cluster@127.0.0.1:2551"]
# when node cannot be reached within 10 sec, mark is as down
auto-down-unreachable-after = 10s
}
}""")
let actorSystem = akkaConfig |> System.create systemName
let listenerRef =
spawn actorSystem "temp2"
<| fun mailbox ->
let cluster = Cluster.Get (mailbox.Context.System)
cluster.Subscribe (mailbox.Self, [| typeof<ClusterEvent.IMemberEvent>|])
mailbox.Defer <| fun () -> cluster.Unsubscribe (mailbox.Self)
printfn "Created an actor on node [%A] with roles [%s]" cluster.SelfAddress (String.Join(",", cluster.SelfRoles))
let rec seed () =
actor {
let! (msg: obj) = mailbox.Receive ()
match msg with
| :? ClusterEvent.MemberRemoved as actor -> printfn "Actor removed %A" msg
| :? ClusterEvent.IMemberEvent -> printfn "Cluster event %A" msg
| _ -> printfn "Received: %A" msg
return! seed () }
seed ()
Какова рекомендуемая практика для отработки отказа в кластере?
В частности, есть ли пример кода того, как кластер должен вести себя, когда один из его узлов больше не доступен?
- Должен ли мой узел кластера ускорить замену или есть другое поведение?
- Есть ли конфигурация, которая автоматически обрабатывает это, что я могу просто установить без необходимости писать код?
- Какой код мне нужно реализовать и где?
1 ответ
Прежде всего, лучше полагаться на события MemberUp и MemberRemoved (оба реализуют интерфейс ClusterEvent.IMemberEvent, так что подпишитесь на него), так как они помечают фазы, когда процедура присоединения / выхода из узла завершена. Связанные и оставленные события не обязательно гарантируют, что узел полностью работоспособен в сигнальный момент времени.
Что касается сценария отработки отказа:
- Автоматическое вращение замен может быть сделано через плагин Akka.Cluster.Sharding (прочитайте статьи 1 и 2, чтобы получить больше информации о том, как это работает). В Akka.FSharp для него нет эквивалента, но вместо этого вы можете использовать плагин https://www.nuget.org/packages/Akkling.Cluster.Sharding/: см. Пример кода.
- Другим способом является создание замещающих акторов заранее на каждом из узлов. Вы можете направлять им сообщения, используя кластерные маршрутизаторы или распределенную публикацию / подписку. Это, однако, более справедливо в ситуации, когда у вас есть сценарии без сохранения состояния, так что каждый актер может в любой момент получить работу другого актера. Это более общее решение для распределения работы между многими участниками, живущими на разных узлах.
- Вы также можете установить наблюдателей за обработчиками. Используя функцию монитора, вы можете приказать своему актеру присматривать за другим актором (независимо от того, где он живет). В случае отказа узла информация о умирающем актере будет отправлена в виде
Terminated
сообщение всем своим наблюдателям. Таким образом, вы можете реализовать свою собственную логику, то есть воссоздание актера на другом узле. На самом деле это наиболее общий способ, поскольку он не использует никаких дополнительных плагинов или конфигурации, но поведение должно быть описано вами.