private async Task <string> FindFreshPackageGuidFileld(EventKeyNames eventKeysSet) { string backServerPrefixGuid = eventKeysSet.BackServerPrefixGuid; string newlyPackageGuid; // этот код до --- можно выделить в отдельный метод, посмотреть остальные применения IDictionary <string, int> packagesList = await _cache.GetHashedAllAsync <int>(backServerPrefixGuid); int packagesListCount = packagesList.Count; if (packagesListCount == 0) // тогда возвращаемся с пустыми руками { return(null); } foreach (var p in packagesList) { var(packageGuid, packageStateInit) = p; if (packageStateInit < 0) { newlyPackageGuid = packageGuid; return(newlyPackageGuid); } _logger.LogInformation(501, "Tasks package field {1} found with value {2}, total fileds {3}.", packageGuid, packageStateInit, packagesListCount); } _logger.LogInformation(511, "The newly created tasks package field was not found in total fileds {0}.", packagesListCount); return(null); }
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 <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 }