Как справиться с устаревшим удаленным akka.net iactorref

Я использую удаленных актеров в akka.net в службе Windows, которая не работает в течение длительного времени. Я получаю IActorRef для удаленного субъекта с помощью ActorSelection и сохраняю этот IActorRef активным в течение длительного периода времени в службе. IActorRef указывает на систему субъектов, работающую в другой службе Windows. Я понимаю, что перезапуск удаленного субъекта не приведет к аннулированию ссылки на удаленного субъекта. Однако вполне возможно, что в какой-то момент удаленная служба Windows может быть перезапущена, и IActorRef в вызывающей службе Windows станет недействительным.

Какова наилучшая практика для решения этой проблемы? Наивным подходом было бы использование ActorSelection для получения нового IActorRef каждый раз, когда я хочу сделать вызов удаленному актору. Очевидно, это было бы неэффективно.

Другой подход может заключаться в том, чтобы просто обернуть каждый вызов, который я делаю для этого IActorRef, в какой-то конверт обработки ошибок, который перехватывает исключения и получает новый IActorRef с использованием выбора актера и повторных попыток? Или конверт может сделать тестовый вызов перед каждым фактическим вызовом, чтобы увидеть, жив ли удаленный актор, и если нет, получить новый ref.

Есть ли лучший способ?


person cfcal    schedule 22.12.2016    source источник


Ответы (2)


Параметр по умолчанию для обнаружения мертвых актеров — Watch их (см. документацию). Когда один актер наблюдает за другим, он получит сообщение Terminated, как только наблюдаемый актер станет мертвым или недоступным.

person Bartosz Sypytkowski    schedule 26.12.2016
comment
Спасибо за ответ. Как это будет работать, когда я отправляю сообщение удаленному актору из фрагмента кода, который не находится внутри самого актора. Например, я получаю IActorRef для удаленного актера, используя выбор актера, и отправляю ему сообщение через Ask(). Поскольку у вызывающей системы нет никаких акторов, у меня не было бы актера для получения сообщений Terminated. Я полагаю, что мог бы создать актера только для этой цели, но есть ли другой способ сделать это без необходимости создавать такого специального актера? - person cfcal; 27.12.2016

Наблюдение за сообщением Terminated предупредит систему о том, что удаленный субъект умер, но возникает вопрос, как именно реагировать на завершение работы удаленного субъекта. Предполагая, что актор получает IActorRef для удаленного актора через свой конструктор, как актор получит новый IActorRef для удаленного актора, когда он снова станет активным. Одним из способов может быть сбой актора и делегирование родительскому актору, который затем получит новый IActorRef для удаленного актора через выбор актера. Проблема с этим, однако, заключается в том, что первоначальный выбор субъекта для удаленного субъекта мог иметь место в коде, не являющемся субъектом, в корне композиции, где обычно происходит внедрение зависимостей. Я полагаю, вы могли бы обойти это, передав делегата фабрики выбора актера, который можно было бы использовать для восстановления удаленного IActorRef. В качестве альтернативы я придумал создать класс-оболочку, реализующий IActorRef, с именем FaultTolerantActorRef.

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

Существует метод Invalidate, который заставит FaultTolerantActorRef сделать новый выбор актера при следующем вызове. Предположительно, это может быть вызвано субъектом в ответ на сообщение Terminated от удаленного субъекта. Даже без вызова Invalidate произойдет новый выбор актера на основе интервала обновления, переданного конструктору.

using Akka.Actor;
using System;
using Akka.Util;
using System.Threading;

namespace JA.AkkaCore
{
    public class FaultTolerantActorRef : IActorRef
    {
        public IActorRef ActorRef
        {
            get
            {
                if (!_valid || DateTime.Now.Ticks > Interlocked.Read(ref _nextRefreshTime))
                    RefreshActorRef();
                return _actorRef;
            }

        }

        public ActorPath Path
        {
            get
            {
                return ActorRef.Path;
            }
        }

        object _lock = new object();
        IActorRef _actorRef;
        volatile bool _valid;
        string _path;
        IActorRefFactory _actorSystem;
        private TimeSpan _requestTimeout;
        private TimeSpan _refreshInterval;
        //private DateTime _nextRefreshTime = DateTime.MinValue;
        private long _nextRefreshTime = DateTime.MinValue.Ticks;

        public FaultTolerantActorRef(IActorRefFactory actorSystem, IActorRef actorRef,
            TimeSpan refreshInterval = default(TimeSpan), TimeSpan requestTimeout = default(TimeSpan))
            : this(actorSystem, actorRef.Path.ToString(), refreshInterval, requestTimeout)
        {
            _actorRef = actorRef;
            _valid = true;
        }
        public FaultTolerantActorRef(IActorRefFactory actorSystem, string actorPath,
            TimeSpan refreshInterval = default(TimeSpan), TimeSpan requestTimeout = default(TimeSpan))
        {
            if (refreshInterval == default(TimeSpan))
                _refreshInterval = TimeSpan.FromSeconds(60);
            else
                _refreshInterval = refreshInterval;
            if (requestTimeout == default(TimeSpan))
                _requestTimeout = TimeSpan.FromSeconds(60);
            else
                _requestTimeout = requestTimeout;
            _actorSystem = actorSystem;
            _valid = false;
            _path = actorPath;
        }
        private void RefreshActorRef()
        {
            lock(_lock)
            {
                if (!_valid || DateTime.Now.Ticks > _nextRefreshTime)
                {
                    _actorRef = _actorSystem.ActorSelectionOne(_path, _requestTimeout);
                    Interlocked.Exchange(ref _nextRefreshTime,DateTime.Now.Ticks + _refreshInterval.Ticks);
                    _valid = true;
                }
            }
        }

        public void Invalidate()
        {
            _valid = false;
        }

        public void Tell(object message, IActorRef sender)
        {
            ActorRef.Tell(message, sender);
        }

        public bool Equals(IActorRef other)
        {
            return ActorRef.Equals(other);
        }

        public int CompareTo(IActorRef other)
        {
            return ActorRef.CompareTo(other);
        }

        public ISurrogate ToSurrogate(ActorSystem system)
        {
            return ActorRef.ToSurrogate(system);
        }

        public int CompareTo(object obj)
        {
            return ActorRef.CompareTo(obj);
        }
    }
}
person cfcal    schedule 29.12.2016