
\chapter{分布式消息网络设计}

\section{概述}

现代软件应用程序通常不是孤立存在的，而是依赖于服务或远程实体提供的信息。在这种分布式架构中，集成至关重要。近年来，消息传递已成为解决分布式系统挑战的主流方案，例如网络不可靠性、生产者与消费者之间的紧密耦合以及应用的异质性。得益于强大的社区支持以及对标准和集成的共同努力，消息代理现已成为许多项目和服务的传输层构建模块，近年来涌现出许多优秀的消息服务，如MQTT、RabbitMQ、RocketMQ和Kafka等。

现代分布式消息网络通常是一种基于分布式系统和消息传递模式的网络架构，支持高可用性、可扩展性和可靠性的通信。这种网络通常由多个节点组成，这些节点可以是物理机器、虚拟机或容器。这些消息网络通常采用发布/订阅模式或点对点模式进行消息传递。在发布/订阅模式中，消息生产者将消息发布到主题（topic），消费者订阅感兴趣的主题并接收相应的消息。在点对点模式中，消息生产者将消息发送到队列，只有一个消费者能够接收并处理该消息。消息网络的核心是消息队列，它采用一种异步通信模式，在多个节点之间传递消息。消息队列通常包括生产者、消费者和代理（broker），其中生产者将消息发送到队列中，消费者从队列中接收消息进行处理，代理作为消息队列的中心节点，负责维护队列、路由消息和确保消息的可靠传递。


尽管应用上述模式的消息框架已经非常成熟，但它们并不适用于本文的使用场景。这些框架通常用于云服务之间的消息通信，而单纯的使用工控PLC通信又显得过于僵硬，不适合构建信息物理系统（CPS）。目前尚无适合涉及大量设备、服务、人和虚拟实体四方消息互动的通信方案。如图\ref{fig-f11}所示，这四方之间的消息传递需求和性能要求各不相同，同时考虑到现实数字孪生系统牵扯的关联设备和网络环境复杂，网络传输协议涉及到HTTP、MQTT、CoAP、XMPP、AMQP、DDS和OPC UA，近场传输协议如4G、5G、NB-loT、LoRa、Sigfox、蓝牙、Wi-Fi、Z-Wave、ZigBee和WirelessHART等\cite{ref18}。因此尝试设计一套新的消息网络以解决数字孪生下的数据困境是非常有必要的。本章将从通讯机制、通讯协议、路由算法、选举算法等描述本平台针对数字孪生数据困境而研究开发的通信框架。


\begin{figure}[h!]
    \centering
    \includegraphics[width=0.8\textwidth]{figure/f11.png}
    \caption{分部性能要求}
    \label{fig-f11}
\end{figure}



\section{通讯机制}

\begin{figure}[h!]
    \centering
    \includegraphics[width=0.8\textwidth]{figure/f3.png}
    \caption{通讯网络}
    \label{fig-f3}
\end{figure}

图\ref{fig-f3}为具体某一集群内的消息节点图，在同一子网下的client节点上线时会进行广播，发现内网节点后进行选举，产生一个子网内核心节点，其余节点连接该节点，核心节点在向上连接，最终链接到核心服务器集群，服务器集群也会选举产生一个核心节点，这个节点为整个网络的核心节点，用来维护节点状态并向下同步状态，整体网络中任意节点下线，皆会触发重新选举或状态更新，核心节点因故障下线时，会导致当前层次子网重新选举，中断服务1s，但是不影响其余层次网络集群功能，最终上线后会同步节点状态，实现整体网络的最终一致性。

对于任意节点来说，其上级节点其实与下级节点一致，在功能逻辑上存在上下级，但是在消息分发上不存在上级，连接的所有节点都是下级或者说同级，会维护一套消息表，记录消息标记和需要发往的目标节点。如节点A刚开始上线时该表为空，当A需要订阅消息时，就广播给周边的节点； B 收到记录下消息类型和目标A以及路径长度1， 在广播给除了A以外的临近节点，同时路径长度加1；每个节点收到同步消息时根据表内数据，没有则添加，有则根据路径长度比较，短则替换目标，长则抛弃。这样，任意节点订阅消息时将会在整个消息网络广播其订阅记录，并在每个节点记录的是最短传播路径。当节点收到消息时，根据消息表内记录，存在标记则转发，不存在则抛弃。


\section{通讯协议}

\subsection{消息帧}

如表\ref{tab-c1}所示，消息通讯协议采用了类似于以太网的帧结构，消息帧由前缀、长度、类型、标记、源节点、目标节点、数据组成，其中前缀用于判断消息帧的开始，长度用于判断消息帧的结束，类型用于区分消息类型，标记用于区分消息，源节点用于标识消息来源，目标节点用于标识消息目标，数据用于携带消息内容。
\begin{table}
        \centering
        \caption{通讯帧}
        \begin{tabular}{|l|l|l|l|l|l|l|l|}
            \hline
            \multicolumn{4}{|c|}{prefix} & \multicolumn{4}{c|}{count} \\ 
            \hline 
            typ & \multicolumn{3}{c|}{tag} &  \multicolumn{4}{c|}{source} \\
            \hline
            \multicolumn{4}{|c|}{target} &  \multicolumn{4}{c|}{data} \\
            \hline
            \multicolumn{8}{|c|}{data} \\
            \multicolumn{8}{|c|}{....} \\
            \multicolumn{8}{|c|}{data} \\
            \hline
        \end{tabular}
        \label{tab-c1}
\end{table}

\begin{itemize}
    \item	prefix(8bit): 帧前缀，用于判断消息帧的开始
    \item	count(8bit): 消息长度
    \item	typ(4bit): 消息类型，数字、字符串、二进制、文件地址、json等
    \item	tag(12bit): 消息标记
    \item	source(8bit): 消息来源节点
    \item	target(8bit): 消息目标节点
    \item	payload: 搭载的数据
\end{itemize}

\subsection{消息类型}
在该分布式消息网络中，采取的是发布订阅模式，消息的类型分为两种，一种是广播消息，一种是订阅消息，广播消息是指所有节点都会收到的消息，订阅消息是指只有订阅了该消息的节点才会收到的消息。由于系统内部需要，对部分消息频道进行了保留，设计了不同功能目的的消息类型体系。
\begin{itemize}
    \item   /：公众频道
    \item   /inner：用户保留频道，不会进行广播
    \item   /sys：系统频道，用于系统内部通讯，不会触发任何用户定义的回调函数和事件
    \item   /sys/base/log：系统日志频道，用于系统内部日志记录
    \item   /sys/base/heartbeat：系统心跳频道，用于系统内部心跳检测
    \item   /sys/base/auth：系统认证频道，用于系统内部认证
    \item   /sys/topic/subscribe：系统订阅频道，用于消息订阅
    \item   /sys/topic/cancel：系统取消订阅频道，用于取消消息订阅
    \item   /sys/node/admin：系统节点管理频道，用于节点管理
    \item   /sys/cluster：系统集群频道，用于集群管理
    \item   /sys/cluster/id：用于系统集群内部id交换
    \item   /sys/cluster/level：用于系统集群内部层级交换
    \item   /sys/cluster/info：用于系统集群内部邻居节点信息交换
    \item   /sys/cluster/redirect：用于通知子节点重定向到新的父节点
    \item   /sys/cluster/heartbeat：系统集群心跳频道，用于集群内部心跳检测
\end{itemize}

\subsection{握手协议}
如图\ref{fig-f29}所示，节点握手协议采用了类似于以太网的握手协议，同时加入了权限的认证机制。
\begin{itemize}
    \item	主动节点握手请求：主动节点向节点发送握手请求，并声明自己的id
    \item	被动节点握手响应：被动节点向主动节点声明自身id
    \item	被动节点握手认证：被动节点根据对方id和公钥进行加密，然后发送给主动节点
    \item	主动节点握手确认：主动节点成功使用公钥和自身id解密，确认对方身份，并根据被动节点id和公钥进行加密，发送给被动节点
    \item  被动节点握手确认：被动节点成功使用公钥和自身id解密，确认对方身份，握手成功，发送心跳包

\end{itemize}

\begin{figure}[!ht]
    \centering%
    \includegraphics[width=0.8\textwidth]{figure/f29.png}
        \caption{节点握手协议}
        \label{fig-f29}
\end{figure}

\section{路由算法}

在订阅消息时存在父子订阅需求，对于很多节点来说可能需要订阅的是某个具体的消息，对于某些规则类节点或者虚拟节点来说，订阅的是某个类别的消息，所以设计树状订阅机制，其订阅子树仅会收到子树消息，订阅父节点则会收到下属所有子树消息，这样在进行消息分发时不能简单的通过判断标记想到与否转发，所以采用了trie树算法加速消息分发。

trie树又被称为前缀树、字典树是一种用于快速检索的多叉树结构。字典树把字符串看成字符序列，根据字符串中字符序列的先后顺序构造从上到下的树结构，树结构中的每一条边都对应着一个字符。字典树上存储的字符串被视为从根节点到某个节点之间的一条路径，并在终点节点上做个标记"该节点对应词语的结尾"，正因为有终点节点的存在，字典树不仅可以实现简单的存储字符串，还可以实现字符串的映射，只需要将相对应的值悬挂在终点节点上即可。

Trie的核心思想是空间换时间，有如下基本性质：
\begin{itemize}
    \item 	根节点不包含字符，除根节点外每一个节点都只包含一个字符
    \item 	从根节点到某一节点，路径上经过的字符连接起来，为该节点对应的字符串
    \item 	每个节点的所有子节点包含的字符都不相同
\end{itemize}

字典树能够利用字符串中的公共前缀，这样可能会节省内存，利用字符串的公共前缀可以减少查询字符串的时间，能够最大限度的减少无谓的字符串比较，同时在查询的过程中不需要预知待查询字符串的长度，沿着字典树的边进行匹配，查询效率比较高。


\section{分布式选举协议}
\begin{figure}[h!]
    \centering
    \includegraphics[width=0.8\textwidth]{figure/f2.png}
    \caption{选举协议}
    \label{fig-f2}
\end{figure}

每个节点上线时如果有现有的消息网络会直接连入现有的消息网络，当大量节点同时上线或者子网间关键节点下线时会触发节点选举，选举产生一位核心节点代表他们和其他集群进行消息通信，并在这些核心节点中再次选举产生一个核心节点，这样所有的节点会自动的根据网络环境分层出消息层，并在消息传递之间保持最小路径
在分布式选举协议中，一个节点任一时刻处于以下三个状态之一：

\begin{itemize}
    \item	从动
    \item	候选者
    \item	主控
\end{itemize}

如上图所示，可以看出所有节点启动时都是从动状态；在一段时间内如果没有收到来自主控的心跳，从从动切换到候选者，发起选举；如果收到大多数的同意票（含自己的一票）则切换到主控状态；如果发现其他节点比自己更新，则主动切换到从动。

总之，系统中最多只有一个主控节点，如果在一段时间里发现没有主控，则大家通过选举-投票选出主控。主控会不停的给从动发心跳消息，表明自己的存活状态。如果主控节点故障，那么从动节点会转换成候选状态，重新选出主控。


\section{硬实时通信}

在工业控制领域，实时性（Real-Time）是一个关键性的需求。实时系统是一类计算系统，其准确性取决于计算逻辑的正确性以及产生结果的时机。这意味着实时系统需要在预定的时间限制内完成特定任务，否则可能导致系统失效。实时性在工业控制领域的重要性表现在以下几个方面：

\begin{enumerate}
    \item 生产过程监控：实时系统能够实时监测生产过程中的各种关键参数（例如温度、压力和流量），以确保生产过程的连续性和稳定性。如果实时系统无法在预定的时间限制内对异常情况做出响应，可能会导致生产流程中断，甚至发生安全事故。
    \item 设备运行与维护：实时性对于设备的运行和维护具有关键意义。实时系统可以收集和分析设备状态数据，及时发现设备故障，并为操作人员提供故障诊断和处理建议。如果实时系统无法在预定的时间限制内完成这些任务，设备可能会因故障而停机，从而影响生产进度。
    \item 工业自动化与优化：实时性在工业自动化和优化过程中起着至关重要的作用。实时系统可以在短时间内完成大量复杂数学计算任务，为生产过程提供智能决策支持。这有助于提高生产效率、降低成本，并确保产品质量。
    \item 安全与事故防范：在工业领域，实时系统在安全和事故防范方面发挥着重要作用。实时监控和报警功能可以帮助工作人员及时发现潜在的安全隐患，并采取必要的措施防范事故。若实时性要求得不到满足，可能会导致灾难性的后果，如设备损坏、生产中断，甚至人员伤亡。
\end{enumerate}

因此，为满足设备间实时性消息和硬中断响应的需求，需要根据硬件单独编写相应的通信库去支持该性能要求。
在CPU资源调度时，OS主要提供一个多任务(multitasking)的运行环境，以方便应用的开发。在开发某个应用时首先把工作拆解成多个任务(Task/Thread)，每个任务都可以简化成一个简单的无限循环\cite{ref29}\cite{ref28}：

\begin{lstlisting}[
    language={C},
    caption={基本调度过程},
    label={code-c-sample},
]
void MyTask (void)
{
    while (1) {
        Wait for an event to occur;  
        Perform task operation;  
    }
}
\end{lstlisting}

如上面代码所示，任务(Task)都是等待event，然后处理事务。任何一个任务得以运行，都是因为它收到了一个Event，这个Event可能是一个中断、也可能是超时到期、还有可能是其他任务发出的IPC信号，继续追查发出IPC信号的任务最后的源头Event肯定是一个外部设备硬件中断或者是内部的Timer中断。中断引起了Event传递，形成了逐个运行多个任务的链条(Chain)。一个系统内部会存在很多条这种链条。

对实时系统来说，不仅仅要求OS能提供多任务环境，更要求任务能在极短的时间之内响应外部的中断事件。

对于终端节点来说硬实时通讯较好实现，利用时钟定时中断即可强制切换到高级别消息发送，但是终端节点一般作为信息发送和执行节点，不具备逻辑处理功能，在整体通讯延迟受最慢一级也就是逻辑处理层一般也是主机节点影响最大，为处理此问题，对消息进行分级，高优先级消息会先触发中断响应，并切换到执行状态,且不可被抢占。

\begin{figure}[h!]
    \centering
    \includegraphics[width=0.8\textwidth]{figure/f30.png}
    \caption{测试设备图}
    \label{fig-f30}
\end{figure}

在上述协议下可以实现在场景内部的毫秒级硬中断响应通信需要，对于已有的不同终端，根据通信协议和线材不同可连入已有的物理节点进行消息转发，同时目前已开发了c/rust通信依赖库可供开发使用，直接在设备内嵌入通信逻辑。用户可以选择是外接设备还是内嵌代码接入到整个数字孪生平台，该物联终端接口可同样应用于自定义仿真算法和逻辑算法节点通信接入。如图\ref{fig-f30}所示，在raspberry4/esp32/esp8266/intel jetson nano/thinkerboardS/STM32等不同芯片平台和不同实时非实时系统上通过了通信库的基础通信功能测试，在esp32/raspberry/stm32上通过了硬中断通信功能测试。


\section{性能分析}

以下性能测试皆使用 2.4 GHz 八核Intel Core i9 作为测试芯片，并进行相关内核性能调优后得到的测试结果。

\begin{figure}[h!]
    \centering
    \includegraphics[width=0.8\textwidth]{figure/f4.png}
    \caption{消息编码速率分布}
    \label{fig-f4}
\end{figure}

\begin{figure}[h!]
    \centering
    \includegraphics[width=0.8\textwidth]{figure/f5.png}
    \caption{消息编码速率拟合曲线}
    \label{fig-f5}
\end{figure}

\begin{table}
    \centering
    \caption{消息编码}
    \label{tab-c2}
    \begin{tabular}{cccc}
        \toprule
        编码时间 & 下限 & 估值 & 上限 \\
        \midrule
        回归斜率 & 296.52 ns & 298.26 ns & 300.04 ns \\
        拟合度 & 0.9228055 & 0.9263008 & 0.9225949 \\
        均值 & 295.37 ns & 297.35 ns & 299.41ns \\
        样本标准偏差 & 8.6420 ns & 10.325 ns & 11.813 ns \\
        中值 & 293.45 ns & 295.80 ns & 297.40 ns\\
        绝对中位差 & 6.1489 ns & 8.4741 ns & 11.089 ns\\
        \bottomrule
    \end{tabular}
\end{table}

由图\ref{fig-f4}和表\ref{tab-c2}可知，消息分发网络中消息编码时间在300ns左右，设备负载对消息编码速率分布有较大影响.
针对消息分发速率，使用wrk 工具对系统进行压力测试，得到图\ref{fig-f6}

\begin{figure}[h!]
    \centering
    \includegraphics[width=0.8\textwidth]{figure/f6.png}
    \caption{消息分发速率压测结果}
    \label{fig-f6}
\end{figure}

由图\ref{fig-f6}可以看出，消息分发在未绑定任何响应函数的情况下单机可以达到每秒157651条，在未读取数据库情况下消息分发延迟6.33ms，以上数据目前满足数字孪生平台要求。
