Upgrade to Pro — share decks privately, control downloads, hide ads and more …

[SnowOne 2023] Александр Нозик: Асинхронная система сбора данных — сделай сам!

jugnsk
March 17, 2023

[SnowOne 2023] Александр Нозик: Асинхронная система сбора данных — сделай сам!

Системы сбора данных и управления оборудованием (SCADA) давно уже не являются какой-то экзотикой. Любое крупное производство использует их. Не говоря уже о всяких "умных" домах. Но интересный факт заключается в том, что большинство таких систем (как минимум, открытых) разработаны лет 20 назад, и на данный момент "идейно отсталые".

В докладе мы разберем архитектуру работы систем сбора данных разной степени устарелости и обсудим, как мы сделали полностью асинхронную систему сбора данных (Controls-kt) на реактивных потоках (корутинах), и какие в этом есть плюсы и минус.

jugnsk

March 17, 2023
Tweet

More Decks by jugnsk

Other Decks in Programming

Transcript

  1. Асинхронная система сбора данных.
    Сделай сам!
    Александр Нозик, МФТИ

    View Slide

  2. Обо мне
    • Директор Центра Научного Программирования.
    • К. ф.–м. н. по физике частиц.
    • Преподаватель МФТИ.
    • (Со-)руководитель московского KUG.
    • https://sciprog.center/people/Nozik
    • https://twitter.com/noraltavir
    • https://t.me/noraltavir
    2

    View Slide

  3. Что такое система сбора
    данных?
    3/16/2023 Controls-kt 3

    View Slide

  4. Кто все эти люди?
    • SCADA - Supervisory Control And Data Acquisition
    • АСУ ТП - Автоматизированная система управления
    технологическим процессом
    • САУ - Системы автоматического управления
    • DCS - Distributed control system
    • HMI – Human-Machine Interface
    • PLC - Programmable logic controller (что оно тут делает?)
    3/16/2023 Controls-kt 4

    View Slide

  5. Какие они (не)бывают
    Шина
    Асинхронная
    Клиент-сервер
    Синхронная
    3/16/2023 Controls-kt 5
    DOOCS
    TANGO controls
    Монолитная
    Распределенная
    EPICS
    LabView
    Много закрытых
    HMI
    WinCC

    View Slide

  6. Задачи, которые они (не)решают
    • Чтение и запись свойств устройства
    • Обнаружение устройств в сети
    • Распределение прав доступа к устройствам
    • Централизованное хранение данных
    • Локальное хранение данных
    • Панели управления
    • База данных конфигураций
    • Интеграция с другими системами
    Все озабочены этим
    3/16/2023 Controls-kt 6
    И никто этим

    View Slide

  7. И в чем проблема?
    • Системы строятся вокруг протоколов. Как только протоколы перестают
    поддерживаться, все становится плохо.
    • Протоколы имеют ограниченную реализацию на разных языках
    программирования.
    • Системы обнаружения сервисов и распределенные базы данных
    требуют сложной настройки.
    • Инструментарий для создания серверов устройств очень сложный
    (часто система поддерживает только «свое» железо).
    • В результате запуск простенького эксперимента с десятком датчиков
    требует профессиональной команды и года работы!
    3/16/2023 Controls-kt 7

    View Slide

  8. Сделай сам!
    https://github.com/SciProgCentre/controls.kt
    3/16/2023 Controls-kt 8
    Photo by Rick Mason on Unsplash

    View Slide

  9. Сервер устройства
    3/16/2023 Controls-kt 9

    View Slide

  10. Что такое сервер устройства
    Device property
    Device property
    Device command
    Read from port
    Synchronization
    Device property
    • Устройство состоит из свойств
    (типизированных).
    • Свойство может быть доступно
    на чтение и запись.
    • Свойство может быть связано с
    физическим состоянием
    прибора.
    • Возможно наличие команд.
    3/16/2023 Controls-kt 10

    View Slide

  11. Интерфейс устройства
    public interface Device : Closeable, ContextAware, CoroutineScope {
    public val meta: Meta
    public val propertyDescriptors: Collection
    public val actionDescriptors: Collection
    public suspend fun readProperty(propertyName: String): Meta
    public fun getProperty(propertyName: String): Meta?
    public suspend fun invalidate(propertyName: String)
    public suspend fun writeProperty(propertyName: String, value: Meta)
    public val messageFlow: Flow
    public suspend fun execute(action: String, argument: Meta? = null): Meta?
    public suspend fun open(): Unit
    override fun close(): Unit
    }
    Параметры устройства
    Дескрипторы свойств
    Дескрипторы действий
    Асинхронное чтение (физического) свойства
    Взять текущее (логическое) значение свойства
    Сброс логического значения
    Запись (физического) значения
    Подписка (многоразовая) на события
    3/16/2023 Controls-kt 11

    View Slide

  12. Стоп, стоп… назад
    Какие такие физические и
    логические свойства?
    Что такое Meta?
    Почему там один и тот же
    тип.
    3/16/2023 Controls-kt 12

    View Slide

  13. Логический уровень свойства
    • Делаем запросы с той
    частотой, с которой удобно
    прибору.
    • Храним последнее состояние в
    виде логического значения.
    • Посылаем сигнал только когда
    логическое значение
    поменялось (экономим
    события).
    3/16/2023 Controls-kt 13

    View Slide

  14. Что за Meta?
    Meta - дерево значений.
    • Q: Почему не типизированный
    объект?
    A: Все равно придется
    обезтипливать при
    сериализазции.
    • Q: Почему не JSON?
    A: JSON для текстового
    представления. А тут в памяти.
    Meta root
    A
    B
    Value
    C List value
    D[ index1
    D[ index2
    Value
    Value
    3/16/2023 Controls-kt 14

    View Slide

  15. Добавим уюта
    https://github.com/SciProgCentre/controls.kt/blob/dev/demo/all-things/src/main/kotlin/space/kscience/controls/demo/DemoDevice.kt
    class DemoDevice(context: Context, meta: Meta) :
    DeviceBySpec(DemoDevice, context, meta) {
    private var timeScaleState = 5000.0
    private var sinScaleState = 1.0
    private var cosScaleState = 1.0
    companion object : DeviceSpec() {
    // register virtual properties based on actual object state
    val timeScale by mutableProperty(MetaConverter.double, DemoDevice::timeScaleState) {
    metaDescriptor {
    type(ValueType.NUMBER)
    }
    info = "Real to virtual time scale"
    }
    val sinScale by mutableProperty(MetaConverter.double, DemoDevice::sinScaleState)
    val cosScale by mutableProperty(MetaConverter.double, DemoDevice::cosScaleState)
    Состояние виртуального прибора
    или обращение к физическому состоянию
    3/16/2023 Controls-kt 15

    View Slide

  16. Добавим уюта
    Спецификация устройства,
    общая для всех устройств этого типа.
    Не хранит состояния.
    3/16/2023 Controls-kt 16
    https://github.com/SciProgCentre/controls.kt/blob/dev/demo/all-things/src/main/kotlin/space/kscience/controls/demo/DemoDevice.kt
    class DemoDevice(context: Context, meta: Meta) :
    DeviceBySpec(DemoDevice, context, meta) {
    private var timeScaleState = 5000.0
    private var sinScaleState = 1.0
    private var cosScaleState = 1.0
    companion object : DeviceSpec() {
    // register virtual properties based on actual object state
    val timeScale by mutableProperty(MetaConverter.double, DemoDevice::timeScaleState) {
    metaDescriptor {
    type(ValueType.NUMBER)
    }
    info = "Real to virtual time scale"
    }
    val sinScale by mutableProperty(MetaConverter.double, DemoDevice::sinScaleState)
    val cosScale by mutableProperty(MetaConverter.double, DemoDevice::cosScaleState)

    View Slide

  17. Добавим уюта
    https://github.com/SciProgCentre/controls.kt/blob/dev/demo/all-things/src/main/kotlin/space/kscience/controls/demo/DemoDevice.kt
    class DemoDevice(context: Context, meta: Meta) :
    DeviceBySpec(DemoDevice, context, meta) {
    private var timeScaleState = 5000.0
    private var sinScaleState = 1.0
    private var cosScaleState = 1.0
    companion object : DeviceSpec() {
    // register virtual properties based on actual object state
    val timeScale by mutableProperty(MetaConverter.double, DemoDevice::timeScaleState) {
    metaDescriptor {
    type(ValueType.NUMBER)
    }
    info = "Real to virtual time scale"
    }
    val sinScale by mutableProperty(MetaConverter.double, DemoDevice::sinScaleState)
    val cosScale by mutableProperty(MetaConverter.double, DemoDevice::cosScaleState)
    Регистрация изменяемых свойств. Хранение состояния в экземпляре
    3/16/2023 Controls-kt 17

    View Slide

  18. Добавим уюта
    val sin by doubleProperty {
    val time = Instant.now()
    kotlin.math.sin(time.toEpochMilli().toDouble() / timeScaleState) * sinScaleState
    }
    val cos by doubleProperty {
    val time = Instant.now()
    kotlin.math.cos(time.toEpochMilli().toDouble() / timeScaleState) * sinScaleState
    }
    override suspend fun DemoDevice.onOpen() {
    doRecurring(50.milliseconds) {
    sin.read()
    cos.read()
    }
    }
    Чтение «физического» свойства
    3/16/2023 Controls-kt 18
    https://github.com/SciProgCentre/controls.kt/blob/dev/demo/all-things/src/main/kotlin/space/kscience/controls/demo/DemoDevice.kt
    Похожая концепция используется в Plotly.kt:
    https://www.youtube.com/live/8F0e_JaoUBU

    View Slide

  19. Добавим уюта
    val sin by doubleProperty {
    val time = Instant.now()
    kotlin.math.sin(time.toEpochMilli().toDouble() / timeScaleState) * sinScaleState
    }
    val cos by doubleProperty {
    val time = Instant.now()
    kotlin.math.cos(time.toEpochMilli().toDouble() / timeScaleState) * sinScaleState
    }
    override suspend fun DemoDevice.onOpen() {
    doRecurring(50.milliseconds) {
    sin.read()
    cos.read()
    }
    }
    Автоматически читаем свойство с той скоростью,
    с которой комфортно устройству.
    3/16/2023 Controls-kt 19
    https://github.com/SciProgCentre/controls.kt/blob/dev/demo/all-things/src/main/kotlin/space/kscience/controls/demo/DemoDevice.kt

    View Slide

  20. Запись свойств
    button("Submit") {
    useMaxWidth = true
    action {
    controller.device?.run {
    launch {
    timeScale.write(timeScaleSlider.value)
    sinScale.write(xScaleSlider.value)
    cosScale.write(yScaleSlider.value)
    }
    }
    }
    }
    Входим в контекст устройства
    В контексте устройства используем
    типо-безопасный дескриптор для
    записи значения.
    public suspend fun WritableDevicePropertySpec.write(value: T) {
    invalidate(name)
    write(self, value)
    //perform asynchronous read and update after write
    launch {
    read()
    }
    }
    3/16/2023 Controls-kt 20

    View Slide

  21. Работа с поротом
    3/16/2023 Controls-kt 21
    private val portDelegate = lazy {
    val ports = context.request(Ports)
    ports.buildPort(meta["port"] ?: error("Port is not defined in device configuration")).synchronous()
    }
    private val port: SynchronousPort by portDelegate
    private val responsePattern: Regex by lazy {
    ("@${address}ACK(.*);FF").toRegex()
    }
    private suspend fun talk(requestContent: String): String? = withTimeoutOrNull(5000) {
    val answer = port.respondStringWithDelimiter(String.format("@%s%s;FF", address, requestContent), ";FF")
    responsePattern.matchEntire(answer)?.groups?.get(1)?.value
    ?: error("Message $answer does not match $responsePattern")
    }

    View Slide

  22. Моделирование приборов
    • Создание модели прибора –
    важный этап разработки
    прибора, его отладки и
    поддержки.
    • Встраивание модели прибора
    в рабочую систему – важный
    этап отладки всей системы.
    3/16/2023 Controls-kt 22
    Photo by Atish Sewmangel on Unsplash

    View Slide

  23. Сервера устройств: выводы
    • Сервер устройства состоит из двух частей: спецификация и
    экземпляр.
    • Спецификация содержит описания свойств.
    • Экземпляр хранит логические свойства.
    • Используем делегаты в Kotlin для того, чтобы объявлять и сразу
    регистрировать свойства.
    • Делаем сервер устройства в 50 строк.
    3/16/2023 Controls-kt 23

    View Slide

  24. Шина
    3/16/2023 Controls-kt 24
    Photo by Taylor Vick on Unsplash

    View Slide

  25. Шина? Какая шина?
    • Большинство SCADA систем не
    используют шину данных.
    • Используются синхронные P2P
    запросы.
    • Клиент отличается от сервера.
    • SCADA система предоставляет
    сервисы для обнаружения
    устройств.
    Deadlock
    Bottleneck
    3/16/2023 Controls-kt 25

    View Slide

  26. Асинхронная шина
    • Есть единый «сервер» -
    (распределенная) шина и
    множество «клиентов» с
    двусторонней коммуникацией.
    • Сообщения отправляются
    когда хочет клиент, а не когда
    спросили.
    Event bus
    send
    send
    send
    send
    send
    subscribe
    3/16/2023 Controls-kt 26

    View Slide

  27. Что лучше?
    P2P
    • Синхронные запросы с
    гарантией доставки и
    гарантией времени отклика.
    • Плохо масштабируется.
    • Нужен сервис обнаружения
    устройств.
    • Каждое сообщение
    доставляется только адресату.
    Шина
    • Асинхронные события. Нет (в
    общем случае) гарантий
    доставки.
    • Хорошо масштабируется.
    • Не нужен сервис обнаружения
    устройств.
    • Сообщения доставляются
    всем, кто подписан.
    3/16/2023 Controls-kt 27

    View Slide

  28. It’s Magix
    public interface MagixEndpoint {
    public fun subscribe(
    filter: MagixMessageFilter= MagixMessageFilter.ALL,
    ): Flow
    public suspend fun broadcast(
    message: MagixMessage,
    )
    public fun close()
    }
    Подписка на события
    Отправка событий
    3/16/2023 Controls-kt 28

    View Slide

  29. Magix server
    val magixFlow = MutableSharedFlow(
    replay = buffer,
    extraBufferCapacity = buffer,
    onBufferOverflow = BufferOverflow.DROP_OLDEST
    )
    Волшебная штучка:
    {
    "id": 1235,
    "origin": "waltz",
    "format": "dataforge",
    "target": "192.168.111.132:8882",
    "payload":{
    "type": "property.set",
    "targetDevice":"my-device",
    "property": "a",
    "value": 11,
    "comment": "pretty please!"
    }
    }
    Пример сообщения:
    https://github.com/waltz-controls/rfc
    Спецификация тут:
    3/16/2023 Controls-kt 29

    View Slide

  30. Реализация для RSocket
    RSocketRequestHandler(coroutineContext) {
    //handler for request/stream
    requestStream { request: Payload ->
    val filter = magixJson.decodeFromString(
    MagixMessageFilter.serializer(),
    request.data.readText()
    )
    magixFlow.filter(filter).map { message ->
    val string = magixJson.encodeToString(MagixMessage.serializer(), message)
    buildPayload { data(string) }
    }
    }
    //single send
    fireAndForget { request: Payload ->
    val message = magixJson.decodeFromString(MagixMessage.serializer(), request.data.readText())
    magixFlow.emit(message)
    }
    }
    3/16/2023 Controls-kt 30

    View Slide

  31. Реализация для RSocket
    RSocketRequestHandler(coroutineContext) {
    //handler for request/stream
    requestStream { request: Payload ->
    val filter = magixJson.decodeFromString(
    MagixMessageFilter.serializer(),
    request.data.readText()
    )
    magixFlow.filter(filter).map { message ->
    val string = magixJson.encodeToString(MagixMessage.serializer(), message)
    buildPayload { data(string) }
    }
    }
    //single send
    fireAndForget { request: Payload ->
    val message = magixJson.decodeFromString(MagixMessage.serializer(), request.data.readText())
    magixFlow.emit(message)
    }
    }
    3/16/2023 Controls-kt 31
    public fun subscribe(
    filter: MagixMessageFilter = MagixMessageFilter.ALL,
    ): Flow

    View Slide

  32. Реализация для RSocket
    RSocketRequestHandler(coroutineContext) {
    //handler for request/stream
    requestStream { request: Payload ->
    val filter = magixJson.decodeFromString(
    MagixMessageFilter.serializer(),
    request.data.readText()
    )
    magixFlow.filter(filter).map { message ->
    val string = magixJson.encodeToString(MagixMessage.serializer(), message)
    buildPayload { data(string) }
    }
    }
    //single send
    fireAndForget { request: Payload ->
    val message = magixJson.decodeFromString(MagixMessage.serializer(), request.data.readText())
    magixFlow.emit(message)
    }
    }
    3/16/2023 Controls-kt 32
    public suspend fun broadcast(
    message: MagixMessage,
    )

    View Slide

  33. Протокол
    коммуникации
    3/16/2023 Controls-kt 33

    View Slide

  34. “Биологическое” разнообразие
    • EPICS
    • Sun ONC (DOOCS)
    • CORBA (TANGO controls)
    • OPC-UA
    • Protobuf
    • HTTP/SSE
    • WebSocket
    • ZMQ
    • …
    3/16/2023 Controls-kt 34

    View Slide

  35. Почему протокол – это сложно?
    • RPC протокол с безопасным типом требует схемы.
    • Схему надо передать всем участникам коммуникации.
    • Протокол надо поддержать на всех языках программирования, на
    которых написаны сервера устройств.
    • Если библиотека, обеспечивающая протокол «протухла», надо
    поддерживать ее самостоятельно.
    3/16/2023 Controls-kt 35

    View Slide

  36. А почему один протокол?
    Message flow
    ZMQ
    Endpoint
    RSocket
    Endpoint
    HTTP
    Endpoint
    • Общая шина позволяет
    реализовывать подключения по
    разным протоколам.
    • Использование не-типизированных
    наполнений сообщений позволяет
    не думать о схеме.
    • Конвертация протоколов не
    бесплатная, но очень дешевая.
    3/16/2023 Controls-kt 36

    View Slide

  37. Собираем все
    вместе
    3/16/2023 Controls-kt 37

    View Slide

  38. Система как конструктор
    context.launch {
    device = deviceManager.install("demo", DemoDevice)
    //starting magix event loop
    magixServer = startMagixServer(
    RSocketMagixFlowPlugin(), //TCP rsocket support
    ZmqMagixFlowPlugin() //ZMQ support
    )
    //Launch device client and connect it to the server
    val deviceEndpoint = MagixEndpoint.rSocketWithTcp("localhost")
    deviceManager.connectToMagix(deviceEndpoint)
    //connect visualization to a magix endpoint
    val visualEndpoint = MagixEndpoint.rSocketWithWebSockets("localhost")
    visualizer = visualEndpoint.startDemoDeviceServer()
    //serve devices as OPC-UA namespace
    opcUaServer.startup()
    opcUaServer.serveDevices(deviceManager)
    }
    https://ideas.lego.com/projects/b383b238-
    c159-41e4-b4b9-7354240a890e
    3/16/2023 Controls-kt 38

    View Slide

  39. Система как конструктор
    context.launch {
    device = deviceManager.install("demo", DemoDevice)
    //starting magix event loop
    magixServer = startMagixServer(
    RSocketMagixFlowPlugin(), //TCP rsocket support
    ZmqMagixFlowPlugin() //ZMQ support
    )
    //Launch device client and connect it to the server
    val deviceEndpoint = MagixEndpoint.rSocketWithTcp("localhost")
    deviceManager.connectToMagix(deviceEndpoint)
    //connect visualization to a magix endpoint
    val visualEndpoint = MagixEndpoint.rSocketWithWebSockets("localhost")
    visualizer = visualEndpoint.startDemoDeviceServer()
    //serve devices as OPC-UA namespace
    opcUaServer.startup()
    opcUaServer.serveDevices(deviceManager)
    }
    https://ideas.lego.com/projects/b383b238-
    c159-41e4-b4b9-7354240a890e
    3/16/2023 Controls-kt 39

    View Slide

  40. Система как конструктор
    context.launch {
    device = deviceManager.install("demo", DemoDevice)
    //starting magix event loop
    magixServer = startMagixServer(
    RSocketMagixFlowPlugin(), //TCP rsocket support
    ZmqMagixFlowPlugin() //ZMQ support
    )
    //Launch device client and connect it to the server
    val deviceEndpoint = MagixEndpoint.rSocketWithTcp("localhost")
    deviceManager.connectToMagix(deviceEndpoint)
    //connect visualization to a magix endpoint
    val visualEndpoint = MagixEndpoint.rSocketWithWebSockets("localhost")
    visualizer = visualEndpoint.startDemoDeviceServer()
    //serve devices as OPC-UA namespace
    opcUaServer.startup()
    opcUaServer.serveDevices(deviceManager)
    }
    https://ideas.lego.com/projects/b383b238-
    c159-41e4-b4b9-7354240a890e
    3/16/2023 Controls-kt 40

    View Slide

  41. Система как конструктор
    context.launch {
    device = deviceManager.install("demo", DemoDevice)
    //starting magix event loop
    magixServer = startMagixServer(
    RSocketMagixFlowPlugin(), //TCP rsocket support
    ZmqMagixFlowPlugin() //ZMQ support
    )
    //Launch device client and connect it to the server
    val deviceEndpoint = MagixEndpoint.rSocketWithTcp("localhost")
    deviceManager.connectToMagix(deviceEndpoint)
    //connect visualization to a magix endpoint
    val visualEndpoint = MagixEndpoint.rSocketWithWebSockets("localhost")
    visualizer = visualEndpoint.startDemoDeviceServer()
    //serve devices as OPC-UA namespace
    opcUaServer.startup()
    opcUaServer.serveDevices(deviceManager)
    }
    https://ideas.lego.com/projects/b383b238-
    c159-41e4-b4b9-7354240a890e
    3/16/2023 Controls-kt 41

    View Slide

  42. Система как конструктор
    context.launch {
    device = deviceManager.install("demo", DemoDevice)
    //starting magix event loop
    magixServer = startMagixServer(
    RSocketMagixFlowPlugin(), //TCP rsocket support
    ZmqMagixFlowPlugin() //ZMQ support
    )
    //Launch device client and connect it to the server
    val deviceEndpoint = MagixEndpoint.rSocketWithTcp("localhost")
    deviceManager.connectToMagix(deviceEndpoint)
    //connect visualization to a magix endpoint
    val visualEndpoint = MagixEndpoint.rSocketWithWebSockets("localhost")
    visualizer = visualEndpoint.startDemoDeviceServer()
    //serve devices as OPC-UA namespace
    opcUaServer.startup()
    opcUaServer.serveDevices(deviceManager)
    }
    https://ideas.lego.com/projects/b383b238-
    c159-41e4-b4b9-7354240a890e
    3/16/2023 Controls-kt 42

    View Slide

  43. Система как конструктор
    context.launch {
    device = deviceManager.install("demo", DemoDevice)
    //starting magix event loop
    magixServer = startMagixServer(
    RSocketMagixFlowPlugin(), //TCP rsocket support
    ZmqMagixFlowPlugin() //ZMQ support
    )
    //Launch device client and connect it to the server
    val deviceEndpoint = MagixEndpoint.rSocketWithTcp("localhost")
    deviceManager.connectToMagix(deviceEndpoint)
    //connect visualization to a magix endpoint
    val visualEndpoint = MagixEndpoint.rSocketWithWebSockets("localhost")
    visualizer = visualEndpoint.startDemoDeviceServer()
    //serve devices as OPC-UA namespace
    opcUaServer.startup()
    opcUaServer.serveDevices(deviceManager)
    }
    https://ideas.lego.com/projects/b383b238-
    c159-41e4-b4b9-7354240a890e
    3/16/2023 Controls-kt 43

    View Slide

  44. Система как конструктор
    3/16/2023 Controls-kt 44

    View Slide

  45. Конвертер форматов
    public fun CoroutineScope.launchMagixConverter(
    endpoint: MagixEndpoint,
    filter: MagixMessageFilter,
    outputFormat: String,
    newOrigin: String? = null,
    transformer: suspend (JsonElement) -> JsonElement,
    ): Job = endpoint.subscribe(filter).onEach { message->
    val newPayload = transformer(message.payload)
    val transformed: MagixMessage = MagixMessage(
    outputFormat,
    newPayload,
    newOrigin ?: message.origin,
    message.target,
    message.id,
    message.parentId,
    message.user
    )
    endpoint.broadcast(transformed)
    }.launchIn(this)
    Можно встроить конвертер в
    передатчик.
    Можно встроить конвертер в
    потребитель.
    Можно прицепить конвертер к шине
    и просто переводить все сообщения.
    3/16/2023 Controls-kt 45

    View Slide

  46. Асинхронный анализ данных
    Агрегатор Анализатор
    Данные Результат анализа
    Срез по времени
    3/16/2023 Controls-kt 46

    View Slide

  47. Проблемы
    3/16/2023 Controls-kt 47

    View Slide

  48. Синхронный запрос
    3/16/2023 Controls-kt 48

    View Slide

  49. Гарантии доставки
    Проблема
    • Асинхронная система (в отличие
    от синхронной) не дает гарантий
    доставки сообщений.
    • Сервисы могут самопроизвольно
    подключаться и отключаться от
    шины.
    • Или вовсе не поддерживать
    определенные типы сообщений.
    Решения
    • Использовать шину с гарантиями
    доставки (например Apache
    Kafka).
    • Использовать сервис для
    проверки подключения
    (watchdog).
    • Регулярно подавать признаки
    жизни (heartbeat).
    3/16/2023 Controls-kt 49

    View Slide

  50. Много сообщений
    Проблема
    • В худшем случае на одно
    сообщение от источника N
    пересылок (где N – количество
    сервисов).
    • То есть общее количество
    пересылаемых сообщений 𝑁2
    Решения
    • Использовать фильтр на
    стороне шины.
    • Использовать распределенную
    шину (если не знаешь что
    делать, бери железо
    посильнее).
    • Можно сегментировать шину…
    3/16/2023 Controls-kt 50

    View Slide

  51. Большие бинарные данные
    Проблема
    • Шина не годится для передачи
    больших бинарных данных.
    Решения
    • Передавать по шине только
    сигнал о появлении файла.
    • Запрос файла делать
    напрямую к серверу
    устройства по другому
    протоколу.
    3/16/2023 Controls-kt 51

    View Slide

  52. В остатке
    3/16/2023 Controls-kt 52

    View Slide

  53. Выводы
    • Мир систем сбора данных большой и дивный (но не новый).
    • Хороших общепринятых решений там нет.
    • Те, что есть в основном синхронные.
    • Мы сделали конструктор на основе асинхронной шины.
    • И вроде получилось (сейчас стадия MVP).
    • Есть еще хранение, про него не успел рассказать.
    • И OPC-UA.
    3/16/2023 Controls-kt 53

    View Slide

  54. Ссылки
    3/16/2023 Controls-kt 54
    • Сам проект: https://github.com/SciProgCentre/controls.kt
    • TANGO: https://www.tango-controls.org/
    • EPICS: https://epics.anl.gov/
    • Визуализация на Walz: https://github.com/waltz-controls/waltz
    • Обсудить: https://t.me/SciProgCentre

    View Slide