Как реализовать 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 сообщение всем своим наблюдателям. Таким образом, вы можете реализовать свою собственную логику, то есть воссоздание актера на другом узле. На самом деле это наиболее общий способ, поскольку он не использует никаких дополнительных плагинов или конфигурации, но поведение должно быть описано вами.
Другие вопросы по тегам