I have a solution with 2 command line projects that creates an akka.net cluster with a seed and client process. The seed starts the cluster and then instantiates a consistent-hash-cluster-router that performs hash mapping on any message that implements my interface "IHasRouting". So any IHasRouting message (from the seed or client) should end up at seed on a routee for the hash of that message.
The projects start fine and the cluster forms without error. Both seed and client instantiate a router. All messages from the seed and client have the same "VolumeId" so they should go to the same route at the seed. BUT Messages from the client node result in a new routee for those messages at the seed!
My understanding of a consistent-hash-cluster-router is that:
- An IActorRef represting it should exit on each node where actors in that node intend to send messages to the router.
- The implentation of the router should be identical on each node and have the same actor name.
- All messages to the router should implement IConsistentHash or the router instance should have a "WithHashMapping()"
- All messages with the same hash will arrive at only one routee and it will always be the same routee
- A routee may take more than one hash
I believe I understand how a consistent-hash-cluster-router should behave and plenty of the DEVs would appear to be using the router type correctly so my implementation must be wrong... Please help! I can provide the full solution if that helps.
The code that creates the router:
system.ActorOf(
new ClusterRouterPool(
local: new ConsistentHashingPool(nrOfInstances: 1)
.WithHashMapping(m => (m as IHasRouting)?.Company?.VolumeId ?? throw new Exception("no routing!")),
settings: new ClusterRouterPoolSettings(
100,
100,
allowLocalRoutees: allowLocalRoutees, //true if the node role is a Seed
useRole: "Seed"))
.Props(Props.Create(() => new CompanyDeliveryActor())), "company-router");
I have a "Company" class that is required for messages to the router. All VolumeIds are the same for this test.
public class Company
{
public readonly Guid CompanyId;
public readonly Guid VolumeId;
public readonly string CompanyName;
public Company(Guid companyId, Guid volumeId, string companyName)
{
this.CompanyId = companyId;
this.VolumeId = volumeId;
this.CompanyName = companyName;
}
}
The IHasRouting interface that is used by the router mapping:
public interface IHasRouting
{
Company Company { get; }
}
An example message class that can be sent to the router:
public class GetTripsMessage : IHasRouting
{
public Company Company { get; private set; }
public GetTripsMessage(Company company)
{
this.Company = company;
}
}
And Finally the CompanyDeliverActor which is instantiated for each routee at the router:
public class CompanyDeliveryActor : ReceiveActor
{
private readonly Dictionary<Guid, IActorRef> companyManagers = new Dictionary<Guid, IActorRef>();
private readonly Guid instanceid = Guid.NewGuid();
public CompanyDeliveryActor()
{
this.Receive<GetTripsMessage>(m => this.RouteCompanyMessage(m, m.Company));
this.Receive<SetTripsMessage>(m => this.RouteCompanyMessage(m, m.Company));
}
private void RouteCompanyMessage(object m, Company company)
{
//placing a watch here shows that this.instanceid is different for messages from the client.
if (!this.companyManagers.TryGetValue(company.CompanyId, out var manager))
{
manager = Context.ActorOf(Props.Create(() => new CompanyManagerActor()));
this.companyManagers[company.CompanyId] = manager;
}
manager.Tell(m, Context.Sender);
}
}
Thanks for any guidance.