问题
I'm creating a sample with Akka.Cluster
with three nodes A, B and C where A is the lighthouse. So far, from the logs, the cluster works fine when there are no actors or when actors are local (created with spawn
and spawnOpt
). I want to create an actor from B and access it from C.
With
let _ = spawne system "r-actor" <@ actorOf (fun msg -> printfn "Received: %s" msg) @> []
I get
2016-08-31 01:59:00.1185|INFO|Akka.Actor.EmptyLocalActorRef|Message String from akka://calculatorSystem/deadLetters to akka://calculatorSystem/user/r-actor was not delivered. 1 dead letters encountered.
Using
let r = FromConfig.Instance :> RouterConfig |> SpawnOption.Router
let _ = spawne system "r-actor" <@ actorOf (fun msg -> printfn "Received: %s" msg) @> [r]
throws the exception
An unhandled exception of type
Akka.Configuration.ConfigurationException
occurred in Akka.dll
Additional information:Configuration problem while creating [akka://calculatorSystem/user/r-actor] with router dispatcher [akka.actor.default-dispatcher] and mailbox and routee dispatcher [akka.actor.default-dispatcher] and mailbox [].
The test function on Node C is
let rec calculate content =
printfn "Processing %s" content
let actor = select "/user/r-actor" system
actor <! content
let text = Console.ReadLine()
if text <> "quit" then
calculate text
calculate "sample1"
HOCON (Node B)
akka {
actor {
provider = "Akka.Cluster.ClusterActorRefProvider, Akka.Cluster"
serializers {
wire = "Akka.Serialization.WireSerializer, Akka.Serialization.Wire"
}
serialization-bindings {
"System.Object" = wire
}
deployment {
/user/add {
router = round-robin-pool
nr-of-instances = 10
cluster {
enabled = on
max-nr-of-instances-per-node = 10
allow-local-routees = off
}
}
/user/r-actor {
router = round-robin-pool
nr-of-instances = 10
cluster {
enabled = on
max-nr-of-instances-per-node = 10
allow-local-routees = off
}
}
}
}
remote {
log-remote-lifecycle-events = DEBUG
log-received-messages = on
helios.tcp {
transport-class = "Akka.Remote.Transport.Helios.HeliosTcpTransport, Akka.Remote"
applied-adapters = []
transport-protocol = tcp
hostname = "127.0.0.1"
port = 0
}
}
loggers = ["Akka.Logger.NLog.NLogLogger,Akka.Logger.NLog"]
cluster {
seed-nodes = [
"akka.tcp://calculatorSystem@127.0.0.1:7001"
]
roles = ["add-service"]
auto-down-unreachable-after = 10s
}
}
How do I create an actor that can be called by another node in the cluster?
回答1:
When defining router configuration, don't use /user
prefix - it's added automatically.
Also if you want to select actor that resides on another node, you need to use full actor path (with node address) - it's due to fact, that there may be different actors living on different nodes with the same path. You must be precise.
Usually address of the node won't be known to you at compile time. There are two ways to extract it:
1. Get addresses from the cluster state
It's easier but you're loosing the ability to react on joining/leaving nodes. You're forced to check it every time, which is slow. This example uses ActorSelection.ResolveOne
to retrieve actual IActorRef
instance.
async {
let members = Cluster.Get(system).State.Members
let actorRefs =
members
|> Seq.filter (fun m -> m.Roles.Contains("expected")) // use roles to filter out nodes that shouldn't be checked
|> Seq.map (fun m ->
let selection = select (m.Address.ToString() + "user/r-actor") system
let actorRef = selection.ResolveOne(timeout) |> Async.AwaitTask)
|> Async.Parallel }
2. Subscribe to cluster events and react on joining/leaving nodes
Here you can react on nodes as they join/leave. It also uses Identify
/ActorIdentity
to receive actual IActorRef
, which is faster option.
let aref =
spawn system "listener"
<| fun mailbox ->
let cluster = Cluster.Get (mailbox.Context.System)
cluster.Subscribe (mailbox.Self, [| typeof<ClusterEvent.IMemberEvent> |])
mailbox.Defer <| fun () -> cluster.Unsubscribe (mailbox.Self)
let rec loop () =
actor {
let! (msg: obj) = mailbox.Receive ()
match msg with
| :? ClusterEvent.MemberUp as up ->
// new node joined the cluster
let selection = select (up.Member.Address.ToString() + "user/r-actor") mailbox
selection <! Identify(null) // request actor under selection to identify itself
| :? ActorIdentity as id when id.Subject <> null ->
// actor has identified itself
id.Subject <! "hello"
| :? ClusterEvent.MemberRemoved as rem ->
// node leaved the cluster, invalidate all actors from that node
| _ -> ()
return! loop () }
loop ()
In case of doubts, I've written a blog post about creating Akka.NET clusters from F#. Maybe you'll find it useful.
来源:https://stackoverflow.com/questions/39239608/how-to-create-an-actor-in-a-clustered-configuration-in-f