示例#1
0
        public async Task Monitor() // _logger = 100
        {
            EventKeyNames eventKeysSet = await _data.FetchAllConstants();


            // на старте проверить наличие ключа с константами
            // в сервисе констант при старте удалять ключ и создавать новый
            // константы можно делить по полям, а можно общим классом
            // а можно и то и другое
            // если все константы классом, то получится надо везде держать модель - одинаковый код
            // на старте фронт сразу запускает два (взять из constant) бэка - чтобы были
            int serverCount = 1;
            // пока запустить руками, потом в контейнерах - вроде как не получится


            // тут можно проверить наличие минимум двух бэк-серверов
            // а можно перенести в цикл ожидания нажатия клавиши

            // здесь можно подписаться на ключ регистрации серверов и при оповещении по подписке, обновлять лист серверов и проверять, что все живые
            // ещё менять флаг разрешения задач
            // это уже функции будущего диспетчера

            IDictionary <string, string> tasksList = await _cache.GetHashedAllAsync <string>(eventKeysSet.EventKeyBackReadiness);

            int tasksListCount = tasksList.Count;

            if (tasksListCount < serverCount)
            {
                // если серверов меньше заданного минимума, сидеть здесь и ждать регистрации нужного количества
                _logger.LogInformation(1001, "Please, start {0} instances of BackgroundTasksQueue server", serverCount);
                // или если есть хотя бы один, то не ждать, а работать?
                // ждать - значит поставить флаг разрешения размещения задач в запрещено, подписка сама оповестит, что произошли изменения
            }

            // при старте, если констант вдруг нет, можно подписаться на стандартный общий ключ получения констант
            // когда константы создадутся, они оповестят по подписке, что константы уже есть и тогда по другому стандартному ключу их взять
            // например, constants:fetch - забирать, constants:await - подписка на ожидание
            // в контейнерах будет непросто разбираться, в каком порядке все должны стартовать, редис первым - уже достижение
            // ещё регистрация сервера в ключе может быть, а сам сервер уже (давно) неживой
            // поэтому в значение поля с номером сервера можно класть не тот же самый номер, а ключ для проверки живости сервера, на который тот будет подписан
            // а лучше просто чистый гуид сервера - они к нему применят разные префиксы для подписки из общих констант
            // скажем, сервер будет подписан на динг:гуид, а фронт, получив гуид сервера, подпишется на донг:гуид и сделает запись на динг
            // после этого сервер, увидев запрос, запишет в ключ донг:гуид и фронт узнает, что сервер живой и общается
            // после чего оба ключа надо сразу же удалить

            // добавить дополнительный ключ с количеством пакетов задач
            // в стартовом ключе в значении указывать задержку -
            // положительная - в секундах,
            // 0 - без задержки,
            // отрицательная - случайная задержка, но не более значения в сек

            // generate random integers from 5 to 10
            Random rand = new();

            rand.Next(5, 11);

            // сделать два сообщения в консоли - подсказки, как запустить эмулятор
            // To start tasks batch enter from Redis console the command - hset subscribeOnFrom tasks:count 30 (where 30 is tasks count - from 10 to 50)


            // тут необходимо очистить ключ EventKeyFrontGivesTask, может быть временно, для отладки
            string eventKeyFrontGivesTask = eventKeysSet.EventKeyFrontGivesTask;

            _logger.LogInformation(1005, "Key eventKeyFrontGivesTask = {0} fetched from constants", eventKeyFrontGivesTask);

            // можно не проверять наличие ключа, а сразу пробовать удалить, там внутри есть своя проверка
            bool isExistEventKeyFrontGivesTask = await _cache.KeyExistsAsync(eventKeyFrontGivesTask);

            if (isExistEventKeyFrontGivesTask)
            {
                bool isDeleteSuccess = await _cache.RemoveAsync(eventKeyFrontGivesTask);

                _logger.LogInformation(1009, "FrontServerEmulation reported - isDeleteSuccess of the key {0} is {1}.", eventKeyFrontGivesTask, isDeleteSuccess);
            }

            // новая версия, теперь это только эмулятор контроллера фронт-сервера

            // множественные контроллеры по каждому запросу (пользователей) создают очередь - каждый создаёт ключ, на который у back-servers подписка, в нём поле со своим номером, а в значении или имя ключа с заданием или само задание
            // дальше бэк-сервера сами разбирают задания
            // эмулятор сейчас выполняет старт многих пакетов (задаётся пользователем при старте), в работе от одного контроллера будет один пакет задач
            // все бэк-сервера подписаны на базовый ключ и получив сообщение по подписке, стараются взять задание - у кого получилось удалить ключ, тот и взял
            // у контроллера остаётся базовый ключ, который он вернёт пользователю и тот потом сможет контролировать ход выполнения задания
            // тогда лишняя сущность диспетчера не нужна, но если задание упадёт, восстановить его будет некому


            // второй вариант - диспетчер собирает всё задания (от множества контроллеров) и ставит у себя в очередь, потом берёт по очереди бэк-сервера и выдаёт им задания
            // named it controllers-dispatcher-queue-back-servers
            // тогда диспетчер по подписке на ключ сервера знает о ходе выполнения и если сообщения прекратятся, но ещё не 100%, сможет перезапустить задачу
            // можно усреднять время выполнения каждого этапа задания и хранить предполагаемое полное время выполнения, а по его истечению принимать какие-то меры


            // первый вариант позволяет его потом дополнить надстройкой диспетчера, которому надо будет следить только за целостностью бэк-серверов и давать команду на восстановление ключа задачи (и, возможно, удаление зависшего сервера)


            // план работы эмулятора
            // ждёт команды с консоли с количеством генерируемых пакетов
            // по получению начинает цикл создания пакетов с задачами
            // первый гуид - главный номер задания, второй - ключ доступа к заданию (или один и тот же номер)
            // при создании пакета сначала создаётся пакет задач в ключе, а потом этот номер создаётся в виде поля в подписном ключе
            // собственно, это пока всё (потом можно добавить случайную задержку между генерацией отдельных пакетов)


            _subscribe.SubscribeOnEventFrom(eventKeysSet);

            while (IsCancellationNotYet())
            {
                var keyStroke = Console.ReadKey();

                if (keyStroke.Key == ConsoleKey.W)
                {
                    _logger.LogInformation("ConsoleKey was received {KeyStroke}.", keyStroke.Key);
                }
            }
        }
示例#2
0
        public async Task Monitor()
        {
            // концепция хищных бэк-серверов, борющихся за получение задач
            // контроллеров же в лесу (на фронте) много и желудей, то есть задач, у них тоже много
            // а несколько (много) серверов могут неспешно выполнять задачи из очереди в бэкграунде

            // собрать все константы в один класс
            //EventKeyNames eventKeysSet = InitialiseEventKeyNames();
            EventKeyNames eventKeysSet = await _data.FetchAllConstants();

            // множественные контроллеры по каждому запросу (пользователей) создают очередь - каждый создаёт ключ, на который у back-servers подписка, в нём поле со своим номером, а в значении или имя ключа с заданием или само задание
            // дальше бэк-сервера сами разбирают задания
            // бэк после старта кладёт в ключ ___ поле со своим сгенерированным guid для учета?
            // все бэк-сервера подписаны на базовый ключ и получив сообщение по подписке, стараются взять задание - у кого получилось удалить ключ, тот и взял

            //string test = ThisBackServerGuid.GetThisBackServerGuid(); // guid from static class
            // получаем уникальный номер этого сервера, сгенерированный при старте экземпляра сервера
            //string backServerGuid = $"{eventKeysSet.PrefixBackServer}:{_guid}"; // Guid.NewGuid()
            //EventId aaa = new EventId(222, "INIT");

            string backServerGuid = _guid ?? throw new ArgumentNullException(nameof(_guid));

            eventKeysSet.BackServerGuid = backServerGuid;
            string backServerPrefixGuid = $"{eventKeysSet.PrefixBackServer}:{backServerGuid}";

            eventKeysSet.BackServerPrefixGuid = backServerPrefixGuid;

            _logger.LogInformation(101, "INIT No: {0} - guid of This Server was fetched in MonitorLoop.", backServerPrefixGuid);

            // в значение можно положить время создания сервера
            // проверить, что там за время на ключах, подумать, нужно ли разное время для разных ключей - скажем, кафе и регистрация серверов - день, пакет задач - час
            // регистрируем поле guid сервера на ключе регистрации серверов, а в значение кладём чистый гуид, без префикса
            await _cache.SetHashedAsync <string>(eventKeysSet.EventKeyBackReadiness, backServerPrefixGuid, backServerGuid, TimeSpan.FromDays(eventKeysSet.EventKeyBackReadinessTimeDays));

            // восстановить время жизни ключа регистрации сервера перед новой охотой - где и как?
            // при завершении сервера успеть удалить своё поле из ключа регистрации серверов - обработать cancellationToken

            // подписываемся на ключ сообщения о появлении свободных задач
            _subscribe.SubscribeOnEventRun(eventKeysSet);

            // слишком сложная цепочка guid
            // оставить в общем ключе задач только поле, известное контроллеру и в значении сразу положить сумму задачу в модели
            // первым полем в модели создать номер guid задачи - прямо в модели?
            // оставляем слишком много guid, но добавляем к ним префиксы, чтобы в логах было понятно, что за guid
            // key EventKeyFrontGivesTask, fields - request:guid (model property - PrefixRequest), values - package:guid (PrefixPackage)
            // key package:guid, fileds - task:guid (PrefixTask), values - models
            // key EventKeyBackReadiness, fields - back(server):guid (PrefixBackServer)
            // key EventKeyBacksTasksProceed, fields - request:guid (PrefixRequest), values - package:guid (PrefixPackage)
            // method to fetch package (returns dictionary) from request:guid

            // можно здесь (в while) ждать появления гуид пакета задач, чтобы подписаться на ход его выполнения
            // а можно подписаться на стандартный ключ появления пакета задач - общего для всех серверов, а потом проверять, что это событие на своём сервере
            // хотелось, чтобы вся подписка происходила из monitorLoop, но тут пока никак не узнать номера пакета
            // а если подписываться там, где становится известен номер, придётся перекрёстно подключать сервисы

            while (IsCancellationNotYet())
            {
                var keyStroke = Console.ReadKey();

                if (keyStroke.Key == ConsoleKey.W)
                {
                    _logger.LogInformation("ConsoleKey was received {KeyStroke}.", keyStroke.Key);
                }
            }

            _logger.LogInformation("MonitorLoop was canceled by Token.");
        }
示例#3
0
        private async Task BackgroundProcessing(CancellationToken stoppingToken)
        {
            EventKeyNames eventKeysSet = await _data.FetchAllConstants();

            //string backServerGuid = _guid ?? throw new ArgumentNullException(nameof(_guid));
            //eventKeysSet.BackServerGuid = backServerGuid;
            //string backServerPrefixGuid = $"{eventKeysSet.PrefixBackServer}:{backServerGuid}";
            //eventKeysSet.BackServerPrefixGuid = backServerPrefixGuid;

            //string eventKey = "task:add";
            string cancelKey             = "task:del";
            int    createdProcessesCount = 0;
            string backServerGuid        = $"{eventKeysSet.PrefixBackServer}:{_guid}"; // backserver:(this server guid)

            _logger.LogInformation(1101, "INIT No: {0} - guid of This Server was fetched in QueuedHostedService.", backServerGuid);
            // создать ключ для подписки из констант
            string prefixProcessAdd   = eventKeysSet.PrefixProcessAdd; // process:add
            string eventKeyProcessAdd = $"{prefixProcessAdd}:{_guid}"; // process:add:(this server guid)
            // поле-пустышка, но одинаковое с тем, что создаётся в основном методе - чтобы достать значение
            string eventFieldBack = eventKeysSet.EventFieldBack;

            _logger.LogInformation(1103, "Processes creation on This Server was subscribed on key {0} / field {1}.", eventKeyProcessAdd, eventFieldBack);
            // подписка на ключ добавления бэкграунд процессов(поле без разницы), в значении можно было бы ставить количество необходимых процессов
            // типовая блокировка множественной подписки до специального разрешения повторной подписки
            bool flagToBlockEventAdd = true;

            _keyEvents.Subscribe(eventKeyProcessAdd, async(string key, KeyEvent cmd) =>
            {
                if (cmd == KeyEvent.HashSet && flagToBlockEventAdd)
                {
                    // временная защёлка, чтобы подписка выполнялась один раз
                    flagToBlockEventAdd = false;
                    _logger.LogInformation(1111, "Received key {0} with command {1}", eventKeyProcessAdd, cmd);
                    // название поля тоже можно создать здесь и передать в метод
                    // ещё лучше - достать нужное значение заранее и передать только его, тогда метод будет синхронный (наверное)
                    // не лучше
                    // лучше
                    int requiredProcessesCount = await _cache.GetHashedAsync <int>(eventKeyProcessAdd, eventFieldBack);
                    if (requiredProcessesCount > 0)
                    {
                        createdProcessesCount = await AddProcessesToPerformingTasks(stoppingToken, requiredProcessesCount);
                        _logger.LogInformation(1131, "AddProcessesToPerformingTasks created processes count {0}", createdProcessesCount);

                        if (createdProcessesCount > 0)
                        {
                            flagToBlockEventAdd = true;
                        }
                    }
                    // если вызвали с неправильным значением в ключе, подписка навсегда останется заблокированной, где-то тут ее надо разблокировать
                }
            });

            string eventKeyCommand = $"Key {eventKeyProcessAdd}, HashSet command";

            _logger.LogInformation(1311, "You subscribed on event - {EventKey}.", eventKeyCommand);

            _keyEvents.Subscribe(cancelKey, (string key, KeyEvent cmd) =>
            {
                if (cmd == KeyEvent.HashSet)
                {
                    _logger.LogInformation("key {0} - command {1}", key, cmd);
                    if (createdProcessesCount > 0)
                    {
                        // останавливаем процесс
                        var cts = completingTasksProcesses[createdProcessesCount - 1].CancellationTaskToken;
                        cts.Cancel();

                        completingTasksProcesses.RemoveAt(createdProcessesCount - 1);
                        createdProcessesCount--;
                        _logger.LogInformation("One Task for Background Processes was removed, total count left {Count}", createdProcessesCount);
                    }
                    else
                    {
                        _logger.LogInformation("Task for Background Processes cannot be removed for some reason, total count is {Count}", createdProcessesCount);
                    }
                }
            });

            List <Task> processingTask = completingTasksProcesses.Select(t => t.ProcessingTask).ToList();

            await Task.WhenAll(processingTask);

            _logger.LogInformation("All Background Processes were finished, total count was {Count}", processingTask.Count);
        }