Как обрабатывать начальное состояние в архитектуре, управляемой событиями?

33

В управляемой событиями архитектуре каждый компонент действует только тогда, когда событие отправляется через систему.

Представьте себе гипотетическую машину с педалью тормоза и стоп-сигналом.

  • Тормозные легкие повороты на , когда он получает brake_on событие, и прочь , когда он получает brake_off событие.
  • Педаль тормоза отправляет событие brake_on, когда она нажата, и событие brake_off, когда она отпущена.

Это все хорошо, пока у вас не возникнет ситуация, когда автомобиль включен с уже нажатой педалью тормоза . Поскольку стоп-сигнал никогда не получал событие brake_on , он останется выключенным - явно нежелательная ситуация. Включение стоп-сигнала по умолчанию только полностью изменяет ситуацию.

Что можно сделать, чтобы решить эту «проблему начального состояния»?

РЕДАКТИРОВАТЬ: Спасибо за все ответы. Мой вопрос был не о реальной машине. В автомобилях они решили эту проблему, постоянно отправляя информацию о состоянии, поэтому в этом домене нет проблем с запуском. В моей области программного обеспечения это решение будет использовать много ненужных циклов ЦП.

РЕДАКТИРОВАТЬ 2: В дополнение к ответу @ gbjbaanb , я иду для системы, в которой:

  • гипотетическая педаль тормоза после инициализации отправляет событие со своим состоянием, и
  • гипотетический стоп-сигнал после инициализации посылает событие, запрашивающее событие состояния, из педали тормоза.

В этом решении нет никаких зависимостей между компонентами, нет условий гонки, нет очередей сообщений, которые могут устареть, и нет «главных» компонентов.

Фрэнк Кастерс
источник
2
Первое, что приходит в голову, - это генерировать «синтетическое» событие (вызвать его initialize), которое содержит необходимые данные датчика.
MSS
Разве педаль не должна отправлять событие brake_pedal_on, а фактический тормоз отправляет событие brake_on? Я бы не хотел, чтобы мой стоп-сигнал включался, если тормоз не работал.
BDSL
3
Я упоминал, что это был гипотетический пример? :-) Это сильно упрощает, чтобы вопрос был коротким и конкретным.
Фрэнк Кастерс

Ответы:

32

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

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

Таким образом, педаль тормоза при запуске зажигания получит сообщение о регистрации / проверке от руководства автомобиля и вернет не только сообщение «Я здесь и работаю», но затем проверит свое собственное состояние и отправит сообщения для этого состояния (например, сообщение о нажатой педали).

Тогда проблема становится одной из зависимостей запуска, как если бы стоп-сигнал еще не был зарегистрирован, тогда он не получит сообщение, но это легко решается путем постановки в очередь всех этих сообщений до тех пор, пока базовая система не завершит процедуру запуска, регистрации и проверки. ,

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

gbjbaanb
источник
1
Мне нравится ваш ответ, поскольку он поддерживает все компоненты разъединенными - это была самая важная причина для выбора этой архитектуры. Однако в настоящее время нет настоящего «главного» компонента, который решает, что система находится в «инициализированном» состоянии - все только начинает работать. С проблемой в моем вопросе в результате. Как только мастер решит, что система работает, он может отправить событие «инициализировано системой» всем компонентам, после чего каждый компонент начинает передавать свое состояние. Проблема решена. Спасибо! (Теперь у меня осталась проблема, как решить, инициализирована ли система ...)
Фрэнк Кастерс
Как насчет того, чтобы диспетчер обновлений состояния отслеживал самое последнее обновление, полученное от каждого объекта, и всякий раз, когда получен новый запрос на подписку, отправляет ли он новому подписчику самые последние обновления, полученные из зарегистрированных источников событий?
суперкат
В этом случае вы также должны отслеживать, когда истекают события. Не все события поддаются вечному хранению для любых новых компонентов, которые могут быть зарегистрированы.
Фрэнк Кастерс
@spaceknarf хорошо, в случае, когда «все только начинает работать», вы не можете встроить зависимость в компоненты, поэтому педаль запускается после освещения, вам просто нужно будет запустить их в таком порядке, хотя я представляю, что что-то запускает их, так что бегите они в «правильном» порядке (например, сценарии инициализации запуска linux перед systemd, где служба, запускаемая первой, называется 1.xxx, а вторая - 2.xxx и т. д.).
gbjbaanb
Сценарии с таким порядком хрупки. Он содержит много неявных зависимостей. Вместо этого я подумал, что если у вас есть «главный» компонент, который имеет статически настроенный список компонентов, которые должны запускаться (как упомянуто @Lie Ryan), то он может транслировать событие «ready» после загрузки всех этих компонентов. В ответ на это все компоненты передают свое начальное состояние.
Фрэнк Кастерс
4

Вы можете иметь событие инициализации, которое соответствующим образом устанавливает состояния при загрузке / запуске. Это может быть желательно для простых систем или программ, не включающих в себя несколько аппаратных компонентов, однако для более сложных систем с несколькими физическими компонентами, так как вы рискуете не инициализироваться вообще - если событие «торможения» пропущено или потеряно во время вашего взаимодействия Система (например, система на основе CAN) Вы можете случайно настроить свою систему в обратном направлении, как если бы вы запустили ее с нажатым тормозом. Чем больше у вас может быть контроллеров, например, на автомобиле, тем выше вероятность того, что что-то пропущено.

Чтобы учесть это, вы можете заставить логику «включения тормоза» многократно отправлять события «включения тормоза». Возможно, каждые 1/100 секунды или что-то. Ваш код, содержащий мозг, может прослушивать эти события и вызывать «торможение» во время их получения. Через 1 / 10сек после отсутствия сигналов «включение тормоза» запускается внутреннее событие «brake_off».

Разные события будут иметь существенно разные временные требования. В автомобиле ваш стоп-сигнал должен быть намного быстрее, чем, например, контрольный индикатор топлива (где, вероятно, допустима многосекундная задержка ) или другие менее важные системы.

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

В любом случае, с физической системой вы НЕ хотите полагаться на одно событие, которое принимается / обрабатывается правильно. По этой причине подключенные микроконтроллеры в сетевой системе часто имеют тайм-аут "Я жив".

enderland
источник
в физической системе вы бы запустить проводник и использовать бинарную логику: HIGH тормозного подавленный и LOW является тормоз не нажат
трещотка урод
@ratchetfreak есть много возможностей для такого рода вещей. Возможно, с этим справится переключатель. Есть много других системных событий, которые не обрабатываются так просто.
enderland
1

В этом случае я бы не смоделировал тормоз как простой вкл / выкл. Скорее, я бы отправил события "тормозного давления". Например, давление 0 будет означать отключение, а давление 100 будет полностью понижено. Система (узел) будет постоянно посылать события давления разрыва (с определенным интервалом) в контроллер (ы) по мере необходимости.

Когда система была запущена, она начала получать события давления, пока она не была выключена.

Джон Рейнор
источник
1

Если ваше единственное средство передачи информации о состоянии - через события, то у вас проблемы. Вместо этого вы должны быть в состоянии оба:

  1. запросить текущее состояние педали тормоза, и
  2. зарегистрировать события «состояние изменено» с педали тормоза.

Стоп-сигнал можно увидеть как наблюдатель за педалью тормоза. Другими словами, педаль тормоза ничего не знает о стоп-сигнале и может работать без нее. (Это означает, что любое представление о том, что педаль тормоза активно отправляет событие «начальное состояние» на стоп-сигнал, является непродуманным.)

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

Затем уведомления о торможении могут быть реализованы одним из трех способов:

  1. как беспараметрические события «состояние педали тормоза изменилось»
  2. как пара "педаль тормоза теперь нажата" и "педаль тормоза теперь отпущена"
  3. как событие «состояние новой педали разрыва» с параметром «нажата» или «отпущена».

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

Майк Накис
источник
0

В системе, управляемой событиями (которую я в настоящее время использую и люблю), я нахожу важным, чтобы вещи были максимально разделены Итак, имея в виду эту идею, давайте углубимся в.

Важно иметь состояние по умолчанию. Ваш стоп-сигнал будет принимать состояние по умолчанию «выключен», а ваша педаль тормоза будет принимать состояние по умолчанию «вверх». Любые изменения после этого будут событием.

Теперь ответим на ваш вопрос. Представьте, что ваша педаль тормоза инициализируется и нажимается, событие срабатывает, но пока нет стоп-сигналов для получения события. Я обнаружил, что проще всего отделить создание объектов (где слушатели событий будут инициализированы) как отдельный шаг перед инициализацией любой логики. Это предотвратит любые условия гонки, как вы описали.

Мне также неловко использовать два разных события для одного и того же . brake_offи brake_onможет быть упрощен e_brakeс помощью параметра bool on. Вы можете упростить ваши события таким образом, добавив вспомогательные данные.

Thebluefish
источник
0

То, что вам нужно, это трансляция событий и входящие сообщения. Трансляция - это сообщение, которое публикуется для неопределенного числа слушателей. Компонент может подписаться на широковещательные события, поэтому он получает только те события, которые ему интересны. Это обеспечивает разъединение, поскольку отправителю не нужно знать, кто является получателями. Таблица подписки должна быть статически настроена во время установки компонента (а не при инициализации). Входящие - это часть маршрутизатора сообщений, которая действует как буфер для хранения сообщений, когда целевой компонент находится в автономном режиме.

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

  1. Каждое событие трансляции требует имени
  2. в любой момент времени отправитель события трансляции может иметь только одну активную трансляцию с указанным именем.
  3. эффект, вызванный событием, должен быть идемпотентным

Имя вещания необходимо объявить во время установки компонента. Если компонент отправляет вторую трансляцию с тем же именем до того, как получатель обработает предыдущую, новая трансляция перезаписывает предыдущую. Теперь у вас может быть статический лимит размера входящих сообщений, который гарантированно никогда не превысит определенного размера и может быть предварительно рассчитан на основе таблиц подписки.

Наконец, вам также нужен архив трансляции. Архив трансляции - это таблица, в которой хранится последнее событие из каждого имени трансляции. Новые компоненты, которые только что установлены, будут иметь свою папку входящих сообщений, предварительно заполненную сообщениями из широковещательного архива. Как и входящие сообщения, широковещательный архив также может иметь статический размер.

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

Ли Райан
источник