public async Task FrontServerEmulationCreateGuidField(string eventKeyRun, string eventFieldRun, TimeSpan ttl) // not used { string eventGuidFieldRun = Guid.NewGuid().ToString(); // await _cache.SetHashedAsync <string>(eventKeyRun, eventFieldRun, eventGuidFieldRun, ttl); // создаём ключ ("task:run"), на который подписана очередь и в значении передаём имя ключа, содержащего пакет задач _logger.LogInformation("Guid Field {0} for key {1} was created and set.", eventGuidFieldRun, eventKeyRun); }
public void StartWorkItem(string backServerPrefixGuid, string tasksPakageGuidField, string singleTaskGuid, int assignmentTerms) { // Enqueue a background work item _taskQueue.QueueBackgroundWorkItem(async token => { // Simulate loopCount 3-second tasks to complete for each enqueued work item int delayLoop = 0; int loopRemain = assignmentTerms; //var guid = Guid.NewGuid().ToString(); _logger.LogInformation(2101, "Queued Background Task {Guid} is starting.", singleTaskGuid); while (!token.IsCancellationRequested && delayLoop < assignmentTerms) { try { await Task.Delay(TimeSpan.FromSeconds(3), token); } catch (OperationCanceledException) { // Prevent throwing if the Delay is cancelled } // здесь записать в ключ ??? и поле ??? номер текущего цикла и всего циклов, а также время и так далее (потом) // рассмотреть два варианта - ключ - сервер, поле - пакет, а в значении указать номер конкретной задачи и прочее в модели // второй вариант - ключ - пакет, поле - задача, а в значении сразу проценты (int) // ключ - сервер не имеет большого смысла, пакет и так не потеряется, а искать его будут именно по номеру пакета, поэтому пока второй вариант loopRemain--; int multiplier = 10000; int completionTaskPercentage = (delayLoop * multiplier / assignmentTerms) / multiplier; _logger.LogInformation("completionTaskPercentage {0} = delayLoop {1} / assignmentTerms {2}", completionTaskPercentage, delayLoop, assignmentTerms); // обновляем отчёт о прогрессе выполнения задания await _cache.SetHashedAsync(tasksPakageGuidField, singleTaskGuid, completionTaskPercentage); // TimeSpan.FromDays - !!! delayLoop++; _logger.LogInformation("Queued Background Task {Guid} is running. Current Loop = {DelayLoop} / Loop remaining = {3}", singleTaskGuid, delayLoop, loopRemain); } if (delayLoop == assignmentTerms) { bool isDeletedSuccess = await _cache.RemoveHashedAsync(backServerPrefixGuid, singleTaskGuid); //HashExistsAsync _logger.LogInformation("Queued Background Task {Guid} is complete on Server No. {ServerNum} / isDeleteSuccess = {3}.", singleTaskGuid, backServerPrefixGuid, isDeletedSuccess); //int checkDeletedSuccess = await _cache.GetHashedAsync<int>(serverNum, guid); // проверку и сообщение о нём можно убрать после отладки //_logger.LogInformation("Deleted field {Guid} checked on Server No. {ServerNum} / value = {3}.", guid, serverNum, checkDeletedSuccess); } else { bool isDeletedSuccess = await _cache.RemoveHashedAsync(backServerPrefixGuid, singleTaskGuid); _logger.LogInformation("Queued Background Task {Guid} was cancelled on Server No. {ServerNum} / isDeleteSuccess = {3}.", singleTaskGuid, backServerPrefixGuid, isDeletedSuccess); // записать какой-то ключ насчёт неудачи и какую-то информацию о процессе? int checkDeletedSuccess = await _cache.GetHashedAsync <int>(backServerPrefixGuid, singleTaskGuid); } }); }
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."); }
public async Task <bool> FetchKeysOnEventRun(EventKeyNames eventKeysSet) // this Main { string backServerPrefixGuid = eventKeysSet.BackServerPrefixGuid; string eventKeyFrontGivesTask = eventKeysSet.EventKeyFrontGivesTask; string eventKeyBacksTasksProceed = eventKeysSet.EventKeyBacksTasksProceed; _logger.LogInformation(401, "This BackServer {0} started FetchKeysOnEventRun.", backServerPrefixGuid); _logger.LogInformation(40101, "This BackServer fetched eventKeyFrontGivesTask = {0}.", eventKeyFrontGivesTask); _logger.LogInformation(40105, "This BackServer fetched eventKeyBacksTasksProceed = {0}.", eventKeyBacksTasksProceed); // начало главного цикла сразу после срабатывания подписки, условие - пока существует ключ распределения задач // считать пакет полей из ключа, если задач больше одной, бросить кубик // проверить захват задачи, если получилось - выполнять, нет - вернулись на начало главного цикла // выполнение - в отдельном методе, достать по ключу задачи весь пакет // определить, сколько надо процессов - количество задач в пакете разделить на константу, не менее одного и не более константы // запустить процессы в отдельном методе, сложить количество в ключ пакета // достать задачи из пакета и запустить их в очередь // следующим методом висеть и контролировать ход выполнения всех задач - подписаться на их ключи, собирать ход выполнения каждой, суммировать и складывать общий процент в ключ сервера // по окончанию всех задач удалить все процессы? // вернуться на начало главного цикла bool isExistEventKeyFrontGivesTask = true; // нет смысла проверять isDeleteSuccess, достаточно существования ключа задач - есть он, ловим задачи, нет его - возвращаемся while (isExistEventKeyFrontGivesTask) { // проверить существование ключа, может, все задачи давно разобрали и ключ исчез isExistEventKeyFrontGivesTask = await _cache.KeyExistsAsync(eventKeyFrontGivesTask); _logger.LogInformation(402, "isExistEventKeyFrontGivesTask = {1}.", isExistEventKeyFrontGivesTask); if (!isExistEventKeyFrontGivesTask) // если ключа нет, тогда возвращаемся в состояние подписки на ключ кафе и ожидания события по этой подписке { return(false); } // надо true // после сообщения подписки об обновлении ключа, достаём список свободных задач // список получается неполный! - оказывается, потому, что фронт не успеваем залить остальные поля, когда бэк с первым полем уже здесь IDictionary <string, string> tasksList = await _cache.GetHashedAllAsync <string>(eventKeyFrontGivesTask); int tasksListCount = tasksList.Count; _logger.LogInformation(403, "TasksList fetched - tasks count = {1}.", tasksListCount); // временный костыль - 0 - это задач в ключе не осталось - возможно, только что (перед носом) забрали последнюю if (tasksListCount == 0) // тогда возвращаемся в состояние подписки на ключ кафе и ожидания события по этой подписке { return(true); } // выбираем случайное поле пакета задач - скорее всего, первая попытка будет только с одним полем, остальные не успеют положить и будет драка, но на второй попытке уже разойдутся по разным полям (string tasksPackageGuidField, string tasksPackageGuidValue) = tasksList.ElementAt(DiceRoll(tasksListCount)); // проверяем захват задачи - пробуем удалить выбранное поле ключа // в дальнейшем можно вместо Remove использовать RedLock bool isDeleteSuccess = await _cache.RemoveHashedAsync(eventKeyFrontGivesTask, tasksPackageGuidField); // здесь может разорваться цепочка между ключом, который известен контроллеру и ключом пакета задач _logger.LogInformation(411, "This BackServer reported - isDeleteSuccess = {1}.", isDeleteSuccess); if (isDeleteSuccess) { _logger.LogInformation(421, "This BackServer fetched taskPackageKey {1} successfully.", tasksPackageGuidField); // победитель по жизни // следующие две регистрации пока непонятно, зачем нужны - доступ к состоянию пакета задач всё равно по ключу пакета // регистрируем полученную задачу на ключе выполняемых/выполненных задач // поле - исходный ключ пакета (известный контроллеру, по нему он найдёт сервер, выполняющий его задание) // пока что поле задачи в кафе и ключ самой задачи совпадают, поэтому контроллер может напрямую читать состояние пакета задач по известному ему ключу await _cache.SetHashedAsync(eventKeyBacksTasksProceed, tasksPackageGuidField, backServerPrefixGuid, TimeSpan.FromDays(eventKeysSet.EventKeyBackServerAuxiliaryTimeDays)); // lifetime! _logger.LogInformation(431, "Tasks package was registered on key {0} - \n with source package key {1} and original package key {2}.", eventKeyBacksTasksProceed, tasksPackageGuidField, tasksPackageGuidValue); // регистрируем исходный ключ и ключ пакета задач на ключе сервера - чтобы не разорвать цепочку // цепочка уже не актуальна, можно этот ключ использовать для контроля состояния пакета задач // для этого в дальнейшем в значение можно класть общее состояние всех задач пакета в процентах // или не потом, а сейчас класть 0 - тип значения менять нельзя int packageStateInit = -1; // value in percentages, but have set special value for newly created field now await _cache.SetHashedAsync(backServerPrefixGuid, tasksPackageGuidField, packageStateInit, TimeSpan.FromDays(eventKeysSet.EventKeyBackServerAuxiliaryTimeDays)); // lifetime! _logger.LogInformation(441, "This BackServer registered tasks package - \n with source package key {1} and original package key {2}.", tasksPackageGuidField, tasksPackageGuidValue); // тут подписаться (SubscribeOnEventCheck) на ключ пакета задач для контроля выполнения, но будет много событий // каждая задача будет записывать в этот ключ своё состояние каждый цикл - надо ли так делать? // и по завершению выполнения задач хорошо бы удалить процессы // нужен внутрисерверный ключ (константа), где каждый из сервисов (каждого) сервера может узнать номер сервера, на котором запущен - чтобы правильно подписаться на событие // сервера одинаковые и жёлуди у них тоже одинаковые, разница только в номере, который сервер генерирует при своём старте // вот этот номер нужен сервисам, чтобы подписаться на события своего сервера, а не соседнего // складываем задачи во внутреннюю очередь сервера // tasksPakageGuidValue больше не нужно передавать, вместо нее tasksPakageGuidField int taskPackageCount = await TasksFromKeysToQueue(tasksPackageGuidField, tasksPackageGuidValue, backServerPrefixGuid); // здесь подходящее место, чтобы определить количество процессов, выполняющих задачи из пакета - в зависимости от количества задач, но не более максимума из константы // PrefixProcessAdd - префикс ключа (+ backServerGuid) управления добавлением процессов // PrefixProcessCancel - префикс ключа (+ backServerGuid) управления удалением процессов // в значение положить требуемое количество процессов // имя поля должно быть общим для считывания значения // PrefixProcessCount - // не забыть обнулить (или удалить) ключ после считывания и добавления процессов - можно и не удалять, всё равно, пока его не перепишут, он больше никого не интересует // можно в качестве поля использовать гуид пакета задач, но, наверное, это лишние сложности, всё равно процессы общие int addProcessesCount = await AddProcessesToPerformingTasks(eventKeysSet, taskPackageCount); // тут ждать, пока не будут посчитаны всё задачи пакета int completionPercentage = 1; // await CheckingAllTasksCompletion(tasksPakageGuidField); // если проценты не сто, то какая-то задача осталась невыполненной, надо сообщить на подписку диспетчеру (потом) int hundredPercents = 100; // from constants if (completionPercentage < hundredPercents) { await _cache.SetHashedAsync("dispatcherSubscribe:thisServerGuid", "thisTasksPackageKey", completionPercentage); // TimeSpan.FromDays - !!! как-то так } // тут удалить все процессы (потом) int cancelExistingProcesses = await CancelExistingProcesses(eventKeysSet, addProcessesCount, completionPercentage); // выйти из цикла можем только когда не останется задач в ключе кафе } } // пока что сюда никак попасть не может, но надо предусмотреть, что все задачи исчерпались, а никого не поймали // скажем, ключ вообще исчез и ловить больше нечего // теперь сюда попадём, если ключ eventKeyFrontGivesTask исчез и задачу не захватить // надо сделать возврат в исходное состояние ожидания вброса ключа // побочный эффект - можно смело брать последнюю задачу и не опасаться, что ключ eventKeyFrontGivesTask исчезнет _logger.LogInformation(481, "This BackServer cannot catch the task."); // возвращаемся в состояние подписки на ключ кафе и ожидания события по этой подписке _logger.LogInformation(491, "This BackServer goes over to the subscribe event awaiting."); // восстанавливаем условие разрешения обработки подписки return(false); // надо true }