Кластерный непротиворечивый хеш-пул, создающий новый маршрут для того же отображения

У меня есть решение с 2 проектами командной строки, которое создает кластер akka.net с начальным и клиентским процессами. Начальное число запускает кластер, а затем создает экземпляр-hash-cluster-router, который выполняет отображение хеша для любого сообщения, которое реализует мой интерфейс "IHasRouting". Таким образом, любое сообщение IHasRouting (от семени или клиента) должно заканчиваться на семени в маршрутизаторе для хэша этого сообщения.

Проекты запускаются нормально и кластер создается без ошибок. И seed, и клиент создают экземпляр маршрутизатора. Все сообщения из семени и клиента имеют одинаковый "VolumeId", поэтому они должны идти по тому же маршруту в семени. НО Сообщения от клиентского узла приводят к новому маршруту для этих сообщений в начальном порядке!

Мое понимание согласованного хеш-кластера-маршрутизатора таково:

  • IActorRef, представляющий его, должен завершиться на каждом узле, где участники этого узла намереваются отправлять сообщения на маршрутизатор.
  • Внедрение маршрутизатора должно быть идентичным на каждом узле и иметь одинаковое имя актера.
  • Все сообщения к маршрутизатору должны реализовывать IConsistentHash или экземпляр маршрутизатора должен иметь "WithHashMapping()"
  • Все сообщения с одним и тем же хешем будут поступать только на один адрес, и он всегда будет одним и тем же.
  • Рут может занять более одного хэша

Я полагаю, что понимаю, как должен вести себя кластер-маршрутизатор с последовательным хешем, и многие разработчики, по-видимому, правильно используют тип маршрутизатора, поэтому моя реализация должна быть неправильной... Пожалуйста, помогите! Я могу предоставить полное решение, если это поможет.

Код, который создает маршрутизатор:

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");

У меня есть класс "Компания", который требуется для сообщений на маршрутизатор. Все VolumeIds одинаковы для этого теста.

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;
    }
}

Интерфейс IHasRouting, который используется сопоставлением маршрутизатора:

public interface IHasRouting
{
    Company Company { get; }
}

Пример класса сообщений, который можно отправить на маршрутизатор:

public class GetTripsMessage : IHasRouting
{
    public Company Company { get; private set; }
    public GetTripsMessage(Company company)
    {
        this.Company = company;
    }
}

И, наконец, CompanyDeliverActor, который создается для каждого маршрутизатора на маршрутизаторе:

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);
    }
}

Спасибо за любое руководство.

2 ответа

Когда ты звонишь ActorOf вы на самом деле создаете новый экземпляр субъекта на текущем узле кластера. В случае маршрутизаторов пула, создание нового маршрутизатора также создаст новый пул участников-маршрутизаторов, которые могут быть отправлены через другие узлы. Это также означает, что вы звоните ActorOf два раза (один на клиентском узле, один на начальном узле), в результате вы получите два отдельных пула актеров.

  1. Самый простой способ решить эту проблему - просто создать экземпляр пула маршрутизаторов только один раз - позволить ему отправлять пул маршрутов по узлам кластера - и пересылать все ваши запросы через узел, который служит точкой входа в ваш кластер. Вы можете настроить другие узлы как аварийное переключение для повторного выполнения этой задачи в узле входа.
  2. Еще одна вещь, которую нужно сделать, - это использовать распределенный паб / суб или сегментирование кластера в зависимости от того, для чего вы используете маршрутизацию сообщений кластера.

После расследования я согласен с тем, что мое понимание кластерного согласованного хеш-маршрутизатора неверно. Теперь я понимаю, что мне нужно сообщить IActorRef, который представляет маршрутизатор, остальной части кластера. Это может быть достигнуто через ClusterSinglton или (возможно) распределенный паб-механизм некоторого типа. Механизм должен справиться с ситуацией, когда узел или субъект становятся "потерянными", создается новый актер (с новыми маршрутами), и новый субъект сообщается всем тем в кластере, на которые была указана предыдущая ссылка.

Я исследую подход "Кластерное разделение" и опубликую еще один вопрос, касающийся технических трудностей, которые возникают у меня с этим подходом.

Другие вопросы по тегам