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