Параллельное Распределение Запросов

#c# #.net #asp.net-core #.net-core #asp.net-web-api

Вопрос:

У меня есть такой сценарий:

  1. У меня есть конечная точка, и эта конечная точка сохранит запросы в списке или очереди в памяти, и она немедленно вернет ответ об успешном выполнении потребителю. Это требование имеет решающее значение, потребитель не должен ждать ответов, он получит ответы от другой конечной точки, если ему это нужно. Таким образом, эта конечная точка должна вернуться как можно быстрее после сохранения сообщения запроса в памяти.
  2. Другой поток будет распространять эти запросы на другие конечные точки и также сохранять ответы в памяти.

Что я делал до сих пор:

Я создал API контроллера для сохранения этих запросов в памяти. Я сохранил их в статическом списке запросов, как показано ниже:

     public static class RequestList
    {
        public static event EventHandler<RequestEventArgs> RequestReceived;

        private static List<DistributionRequest> Requests { get; set; } = new List<DistributionRequest>();

        public static int RequestCount { get => RequestList.Requests.Count; }

        public static DistributionRequest Add(DistributionRequest request)
        {
            request.RequestId = Guid.NewGuid().ToString();
            RequestList.Requests.Add(request);
            OnRequestReceived(new RequestEventArgs { Request = request });
            return request;
        }

        public static bool Remove(DistributionRequest request) => Requests.Remove(request);

        private static void OnRequestReceived(RequestEventArgs e)
        {
            RequestReceived?.Invoke(null, e);
        }
    }

    public class RequestEventArgs : EventArgs
    {
        public DistributionRequest Request { get; set; }
    }
 

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

         private void RequestList_RequestReceived(object sender, RequestEventArgs e)
        {
            _logger.LogInformation($"Request Id: {e.Request.RequestId}, New request received");
            Task.Factory.StartNew(() => Distribute(e.Request));
            _logger.LogInformation($"Request Id: {e.Request.RequestId}, New task created for the new request");
            //await Distribute(e.Request);
        }

        public async Task<bool> Distribute(DistributionRequest request)
        {

            //Some logic running here to send post request to different endpoints 
            //and to save results in memory
        }
 

И вот мой метод контроллера:

         [HttpPost]
        public IActionResult Post([FromForm] DistributionRequest request)
        {
            var response = RequestList.Add(request);
            return Ok(new DistributionResponse { Succeeded = true, RequestId = response.RequestId });
        }
 

Я попробовал этот подход, но он сработал не так, как я ожидал, он должен вернуться в течение миллисекунд, так как я не жду ответов, но, похоже, он чего-то ждет, и после каждого отдельного запроса время ожидания увеличивается, как показано ниже:

введите описание изображения здесь

Что я делаю не так? Или у тебя есть идея получше? Как я могу достичь своей цели?

Комментарии:

1. Составьте его профиль. Это покажет вам, какая функция отнимает все ваше время и, следовательно, в чем проблема.

2. Что-то несвязанное: возможно, вы захотите использовать что-то вроде ConcurrentBag вместо List того, чтобы не сталкиваться с проблемами параллелизма при параллельном удалении и добавлении элементов

Ответ №1:

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

Я сделал это таким образом

введите описание изображения здесь

Контроллер запросов

Точно так же, как это было в вашем примере. Возьмите запрос и добавьте его в список запросов.

 [Route("requests")]
    public class RequestsController : ControllerBase
    {
        private readonly RequestManager _mgr;

        public RequestsController(RequestManager mgr)
        {
            _mgr = mgr;
        }

        [HttpPost]
        public IActionResult AddRequest([FromBody] DistributionRequest request)
        {
            var item = _mgr.Add(request);
            return Accepted(new { Succeeded = true, RequestId = item.RequestId });
        }
    }
 

Менеджер запросов

Управляйте списком запросов и пересылайте их какому-нибудь дистрибьютору.

 public class RequestManager
    {
        private readonly ILogger _logger;
        private readonly RequestDistributor _distributor;

        public IList<DistributionRequest> Requests { get; } = new List<DistributionRequest>();

        public RequestManager(RequestDistributor distributor, ILogger<RequestManager> logger)
        {
            _distributor = distributor;
            _logger = logger;
        }

        public DistributionRequest Add(DistributionRequest request)
        {
            _logger.LogInformation($"Request Id: {request.RequestId}, New request received");
            /// Just add to the list of requests
            Requests.Add(request);
            /// Create and start a new task to distribute the request 
            /// forward it to the distributor.
            /// Be sure to not add "await" here
            Task.Factory.StartNew(() => _distributor.DistributeAsync(request));
            _logger.LogInformation($"Request Id: {request.RequestId}, New task created for the new request");

            return request;
        }
    }
 

Распределитель запросов

Логика распределения может быть реализована здесь

 public class RequestDistributor
    {
        public async Task DistributeAsync(DistributionRequest request)
        {
            /// do your distribution here
            /// currently just a mocked time range
            await Task.Delay(5);
        }
    }
 

Соедините провода

… добавьте все это в свою конфигурацию внедрения зависимостей

 public void ConfigureServices(IServiceCollection services)
        {
            services.AddControllers();
            services.AddSingleton<RequestDistributor>();
            services.AddSingleton<RequestManager>();
        }
 

Тесты

С помощью предоставленных здесь фрагментов кода я получил все запросы обратно менее чем за 10 мс. введите описание изображения здесь

Примечание

Это всего лишь пример, попробуйте всегда добавлять интерфейсы в свои сервисы, чтобы сделать их тестируемыми ;).