跳到主要内容

传输能力矩阵

本页说明每种传输方式目前在各个方向上具备的能力,并明确列出缺口。

以代码为准

本页根据实现手动维护,并非自动生成,因此可能落后于实现。 如果本页与你的实例不一致,以实例为准。以下每项说明均根据实现该能力的服务核实,而非来自设计文档; 无法通过这种方式确认的内容会明确标注,不会擅自补全。 夸大平台能力的单元格属于本页的缺陷,请报告。

如何阅读本页​

每项能力只有三种状态,没有第四种:

●完整已实现,没有该方向特有的限制。影响整个传输方式的一般注意事项在备注中说明。
◐部分已实现,但缺少某些具体能力。备注会说明缺少什么;根据该行设计系统前请先阅读。
○无尚未实现。如果这是有意决定而非未完成的工作,备注会说明,两者对规划的意义完全不同。对于写入,备注还会说明仍然发送命令时的结果,因为各行的行为不同。
—不适用该方向对此行没有意义。它不等于“无”,也绝不代表“未知”或“计划中”。

区分 ● 与 ◐ 的规则适用于本页每一行:只要平台可能丢失、截断或拒绝某些内容, 却不告诉任何人,该方向就是 ◐,即使该传输整体上是这里最完整的一种。 ● 仅用于没有这种缺口的方向。这也是 HTTP 接入的订阅能力为 ●,而平台代理不是的唯一原因: 超过租户接入限额时,HTTP 向发布者返回 429;代理路径则在设备已经收到 PUBACK 后丢弃消息,不向发布者传递任何信息。

各方向均从平台的角度命名:

  • 读取:平台向设备请求一个值,并在该次交互中取得响应。
  • 写入:平台设置设备上的值,或要求设备执行操作。
  • 订阅:设备发送读数,而无需每次都收到请求。

设备传输表中没有 —。所有设备传输的三个方向都有意义,因此其中的无始终是对一个实际问题的实际回答。

设备传输​

设备如何接入平台,以及平台如何与设备反向通信。

传输读取写入订阅
MQTT(平台代理)○◐◐
HTTP○○●
MQTT(外部代理)○○◐
Sparkplug B○○◐
LwM2M◐◐◐

MQTT:平台代理​

这是默认路径,也是最完整的路径。代理是 NATS 内置的 MQTT 服务器,无需另外运行代理。

  • 订阅 ◐:设备向自己的事件主题发布消息,代理在任何平台代码看到消息之前就将其持久捕获。 认证有两个独立层次:连接在代理上经过认证,并绑定到该设备自身的主题;事件还携带凭据,在流水线中再次校验。

    唯一的缺口,也是它不是 ● 的原因,是租户超过按读数计量的接入上限时到达的消息,会向代理确认后被丢弃。 代理捕获消息时已经向设备发送 PUBACK,因此发布者不会获知此事;这种传输无法返回 429。 如果设备群可能瞬时超过限额,应根据限额进行容量规划,不能依赖设备收到减速通知,因为它不会收到。

    平台自身拒绝新事件时,设备同样不会收到通知(参见接入路径上的背压)。 代理已向设备确认,因此消息不会被拒绝,而是在捕获流中等待,直到流水线重新接受事件。 如果拒绝持续太久,填满捕获流,最旧的消息会被丢弃。 流水线尚未读取捕获流时,JetStreamDurableStalledBehindStream 会报告此事; 恢复读取后则由 JetStreamDurableLostUnread 报告。

  • 写入 ◐:命令可以送出,但投递仅面向当前在线连接,且没有确认。 发布只会到达那一刻已连接并订阅的设备。代理不会为未连接或未订阅的设备保留消息, 平台也不会获知设备是否收到。系统有意不设 DELIVERED 状态:独立于响应确认投递需要这种传输并不提供的确认机制。 设备答复后命令才算完成,参见响应命令。

  • 读取 ○:不存在由平台发起的请求/响应原语。你可以把读取表达为响应携带数值的命令, 但那是你在设备配置文件上定义的协议词汇,并非传输自身提供的能力。

HTTP​

使用相同 JSON 事件正文的 POST 端点,简单且单向。

  • 订阅 ●:POST /{instanceId}/{tenant}/events 返回:

    • 事件存入平台入站流后返回 202;
    • 无法解码的正文或语法无效的租户返回 400;
    • 路径中的实例 ID 错误时返回 404;
    • 租户超过按读数计量的接入上限时返回 429。此检查先于背压,因此超过自身限额的租户会收到 429,绝不会收到背压的 503;
    • 平台施加背压时返回带 Retry-After 响应头的 503:平台拒绝新事件,防止它们挤掉消费者尚未处理的事件(参见接入路径上的背压);
    • 事件无法交给流时返回不带 Retry-After 的 503。

    收到 503 时应重试:这是唯一能够这样告知你数据可能尚未落地的传输。 带 Retry-After 的 503 表明事件肯定未存储,等待指定时间后重新发送。 没有该响应头的 503 表明发布失败;如果失败发生在流已经存储事件之后,重试会重复存储, 除非事件携带 altId 和 occurredTime(参见服务质量)。 429 同样意味着事件未被接受,它携带 Retry-After 响应头,应退避后重试。 202、400 和 404 是该次请求的终结响应。

  • 写入 ○ / 读取 ○:**完全没有下行链路。**仅通过 HTTP 接入平台的设备无法接收命令。 这更多是集成方式本身的形态,而非等待修复的缺口:需要接收命令的设备也应建立 MQTT 连接。

    向仅使用 HTTP 的设备发送命令,不会被拒绝,而是最终过期。 平台不会为通过 HTTP 到达的设备生成传输名称(设备记录的来源是操作员给事件源指定的 ID), 因此识别不可投递传输的门控无法识别这种情况。 命令会被接受,发布到没有设备订阅的位置,标记为 SENT,最后成为 TIMEOUT。 本页只有 Sparkplug 在此列为 ○ 时,会迅速得到 FAILED。

  • 接入监听器终止的是明文 HTTP,本身没有传输认证;设备凭据放在事件正文中。 需要 TLS 时,由服务前面的组件提供。

MQTT:外部、由操作员管理的代理​

平台也可以作为客户端连接你已运行的代理,从中接入数据。

  • 订阅 ◐:可以工作,但有四项不足,离开实验环境后都很重要:

    • 连接使用明文,没有 TLS;
    • 不提供代理凭据;
    • 实际效果是至多一次;
    • 超过限额或平台施加背压时被拒绝的消息会被丢弃,不向发布者发送任何信息。

    除非确实需要读取现有代理,否则优先使用平台代理。

    为自己的代理选择 QoS 时,至多一次这一点很重要。 平台以 QoS 1 订阅,因此丢失不是订阅造成的。会话不持久化,解码交接也位于内存中, 所以平台已从代理取走、但尚未发布的消息,会在进程重启时丢失。 提高你这一侧的 QoS 不会改变这一点,平台也不会对不属于自己的代理作持久性保证。

    代理重启或连接中断后,平台会自行重连并重新订阅。 如果代理在启动或重连后拒绝订阅,例如 ACL 变更禁止了主题,整个 event-sources 服务会因错误而停止, 而不是保持连接却不接入任何数据。这会影响平台代理和 HTTP 接入,不只是该来源: 服务会重启并持续失败,直到代理重新允许订阅。

    每个来源由一个 Pod 读取,并使用它自己的客户端 ID。 平台以 devicechain:<instance>:<source>:<pod> 连接代理,因此两个实例、两个来源或两个 event-sources Pod 不会共用会话。代理为每个客户端 ID 保留一个会话,第二个连接到来时会断开旧连接; 旧版使用统一共享 ID,曾因此丢失消息。无论运行多少个 event-sources Pod, 同一来源同一时间都只有一个读取者,因为这条路径无法区分“同一消息交给两个 Pod”与“两条消息”。 其他 Pod 不为该来源建立连接,只待命,但仍提供其余服务。 如果代理的 ACL、允许列表或客户端 ID 规则按 ID 匹配,请允许以 devicechain: 开头的 ID。 此 ID 超过 23 个字符,即 MQTT 要求代理接受的最短上限;严格执行该上限的代理会拒绝它。

    • **交接。**正常停止的 Pod 会释放来源,另一个 Pod 约两秒内连接。 突然丢失时(节点故障、SIGKILL、内存不足终止),来源约 30 秒加重连时间无人读取。 会话不持久化,所以这两个间隙中代理交付的消息不会被保留。
    • 接管来源却无法连接的 Pod 会记录原因、释放来源,并每 15 秒重试。 它不会停止服务。如果代理拒绝订阅,服务会按上述方式停止。 服务启动时负责读取来源的 Pod 遇到这两种错误仍会使启动失败,与此前相同。
    • **扩容不会分摊负载。**增加 event-sources Pod 增加的是待命者,不是外部来源的读取者。
    • 监控。devicechain_eventsources_external_mqtt_owner{source} 在读取来源的 Pod 上为 1,在其他 Pod 上为 0。 当某来源两分钟内没有 Pod 读取,或被多个 Pod 读取时,ExternalMqttSourceNotReadByOnePod 告警会触发。
    • **重叠。**仍连接着但失去来源所有权的 Pod 会丢弃继续收到的消息,并计入 devicechain_eventsources_total_msg_not_owner{source},而非存储。 两个 Pod 都可能存储同一消息的窗口只占一秒的一小部分,不是整个 30 秒租约期。
  • 写入 ○ / 读取 ○:此集成仅用于接入。向这种设备发送命令的行为与上述 HTTP 完全相同:发布、SENT,然后 TIMEOUT。

Sparkplug B​

适用于已经在自己的代理上使用 Sparkplug 的现有设备群。

  • 订阅 ◐:解码 NBIRTH/NDATA/DBIRTH/DDATA,包括别名表和序列跟踪, BIRTH/DEATH 提供权威在线状态,而非根据超时推断。它不是 ● 的原因是:只有数值指标会成为测量值。 解码时会跳过布尔、字符串、字节数组、DataSet 或 Template 指标,不记录,也不提示。 如果设备群的重要信号是布尔值,例如运行标志、故障位,你会看到设备权威地在线,却没有上报任何数据。

    平台施加背压期间(参见接入路径上的背压), 读数会被丢弃并计数,不重试。BIRTH 和 DEATH 仍被接受,因此在线状态保持正确。

  • **写入 ○:有意不在范围内,而非尚未完成。**没有 Sparkplug 命令出口(DCMD),也不是等待开发: Sparkplug 设备群位于客户自己的 MQTT 基础设施中,没有组件把平台命令流桥接过去。 发给 Sparkplug 设备的命令会以不可投递的 FAILED 结束,有两项限定:

    • **结果通常很快,但与入队不同步。**命令入队会立即尝试为该设备派发。 如果这是设备唯一排队的命令,在线状态门控会立即令其失败。 如果已有其他排队命令,该次尝试会让出,结果在下一轮投递扫描中产生;扫描默认每 30 秒进行一次,可配置为 5 到 300 秒。
    • **必须配置在线状态门控。**门控根据设备传输报告的状态扣留或拒绝命令, 需要跨服务密钥和 device-state 端点。缺少任一项时,门控关闭,并在启动时记录; 命令会像其他命令一样派发,最终成为 TIMEOUT。
  • 读取 ○:原因相同。

平台确实在 Sparkplug 上发布的内容:永远没有 DCMD,也没有可通过命令 API 触发的 NCMD。 唯一发送的 NCMD 是内部的 Node Control/Rebirth,由 Host Application 自身的会话跟踪发出, 用于修复序列缺口,使用 QoS 0,不保留。

Host Application 会在 spBv1.0/STATE/{host_id} 发布自己的 STATE 消息,使用 QoS 1 并保留: 连接时、正常停止时,以及异常终止时作为 Last-Will 发布。 这是 Sparkplug Host 的 birth/death 协议约定,边缘节点会读取它。

代理 ACL 必须允许平台发布 STATE

配置代理 ACL 时,请允许平台客户端在 spBv1.0/STATE/{host_id} 发布。 如果拒绝,Host 会话从第一次连接起就无法正确工作。

Sparkplug 设备身份在代理上确立,而非逐设备认证

Sparkplug 设备的身份来自它发布所用的主题,因此逐事件设备认证模式不能阻止代理上的一个发布者 在同一租户内冒用另一个设备的身份。跨租户不会发生这种情况:租户由消息到达时的代理连接固定, 绝不由消息内的内容决定。租户内部的隔离由你的代理通过每客户端凭据和主题权限实施。 将共享代理指向某租户前,应先规划好这些设置。

LwM2M​

适用于通过 CoAP/UDP 和 DTLS 通信的受限设备。

  • 读取 ◐:通过设备命令实现,并返回响应正文。限制是超过 8 KiB 的正文会被截断, 但响应仍被报告为成功。“有大小上限”不足以描述其行为:内容没有被拒绝,结果也没有部分返回标记, 因此大型资源读取看起来像是完整返回。

  • 写入 ◐:一次只能写一个标量资源。不支持写入对象实例、在一次操作中写多个资源或部分更新。 值的大小上限为 8 KiB。

  • 订阅 ◐:Observe 可用,但只解码 SenML-JSON 通知,其中也只有数值资源会成为测量值。 以布尔值为主的对象完全不会产生遥测数据,与 Sparkplug 的限制相同。

    请提前评估:符合规范但仅支持 LwM2M 1.0 的客户端无法生成 SenML,因此会正确拒绝 Observe。 这种设备仍可注册、驱动在线状态、接受命令,但完全不报告遥测数据。 后续对旧 TLV 格式的解码支持将补上这一缺口。

    可观察对象限于内置且不可配置的允许列表,每个注册最多 32 个观察。 观察不能在领导者故障转移后保留:在线状态会被重建,遥测则只有在各设备续注册时才会重新建立。

    平台施加背压期间(参见接入路径上的背压), 通知被丢弃并计数,由下一次通知取代。注册和断开连接仍被接受,因此在线状态保持正确。

  • 发给休眠设备的命令会被持久保留,在设备下次报到时排空,并记录为 PARKED。 这是唯一一种在传输已经尝试投递之后,仍为设备保留命令而不是丢失命令的情况。 它并非平台唯一的扣留机制:所有传输,包括代理自身报告设备在线状态的 MQTT, 在传输明确声明设备不在线时,都在发布前把命令扣留为 HELD,在线状态恢复后释放。 参见向不在线设备发送命令。

LwM2M 操作详情​

操作备注
Read◐CoAP GET;超过 8 KiB 的响应被无声截断,仍报告成功
Write◐单个标量资源,仅支持替换
Execute●可以带参数或不带参数
Observe◐仅 SenML-JSON;仅数值资源;固定对象允许列表;每个注册最多 32 个
Discover○未实现
Create○未实现
Delete○未实现
Write-Attributes○未实现,无法通过平台设置通知阈值范围
Bootstrap○未实现;Bootstrap 服务器在规划中

设备认证在 DTLS 握手时逐设备进行,使用预共享密钥。X.509 和原始公钥凭据在规划中。

出站连接器​

规则触发时平台向哪里发送数据。连接器是单向接收端,没有设备方向, 所以读取和订阅标为 —,而不是“无”。

连接器读取写入订阅备注
httpCall webhook—●—仅 POST;非 POST 方法会被拒绝
publish → MQTT—●—QoS 0/1/2;用户名和密钥;tcp、mqtt、ssl、tls、mqtts、ws、wss URL;没有 TLS 设置,见下文
publish → Kafka—●—TLS;SASL PLAIN、SCRAM-SHA-256、SCRAM-SHA-512
publish → AWS SNS—●—仅支持每租户静态凭据
publish → AWS SQS—●—仅支持每租户静态凭据
publish → Google Pub/Sub—○—不可用:写入时即被拒绝,见下文

Kafka 有真正的 tls 开关;MQTT 连接器配置则没有任何 TLS 字段,并拒绝未知键,所以无法填写这些配置。 只有代理 URL 为 ssl://、tls://、mqtts:// 或 wss:// 时才隐式启用 TLS。 连接随后根据公共信任库和 URL 中的主机名进行验证,无法提供 CA、客户端证书或验证设置。

两个 AWS 连接器有意要求静态访问密钥,不会退回使用所在 Pod 的环境 IAM 身份。 这种隔离保证不会借用平台自身的云身份来替租户调用。

每个连接器目标都会在连接建立时检查。如果解析到私有地址或云元数据地址,就会被拒绝, 除非操作员明确允许了该地址;参见连接器可以向哪里发送。

Google Pub/Sub 连接器会被拒绝,而不是被接受后永不发送

gcp_pubsub 是可识别的连接器类型,但本版本没有投递实现。创建该类型的连接器、将连接器更新为该类型或发布它,都会被拒绝,错误代码为 UNSUPPORTED。 早期版本已存储的该类型连接器,如果有 publish 指向它,仍会因不受支持而进入死信;它可识别,但不能执行,不会无声丢弃。 将来发布此能力时,它会像 AWS 连接器一样使用存储在连接器上的凭据认证,而不会使用 Pod 身份。

与连接器不同,通知渠道 通过 SMTP 和 webhook 联系人,而不是系统。

尚不可用​

明确列出以下能力,是因为从平台外部看来,“没有列在上面”和“询问后明确答复不支持”看起来一样, 但只有前者可能值得等待。

WebSocket 设备接入尚不可用。简介 中列为计划能力。
LwM2M 之外的 CoAP尚不可用。CoAP 只能通过 LwM2M 接入平台。
使用原始 NATS 作为设备传输尚不可用。设备凭据授权的是 MQTT 连接,没有 NATS 原生设备客户端。
Sparkplug 命令出口(DCMD)尚不可用,且有意不在范围内,而非等待开发;参见上文。
工业现场总线协议:OPC-UA、Modbus、BACnet不作为平台传输提供,项目发布的任何组件都不会这些协议。受支持的形态是在工厂网络中部署能使用现场总线的本地网关,通过 MQTT 或 HTTP 转发;协议转换由你提供。项目确实发布 dc-edge-agent,但它做的是这项工作的另一半:在本地终止设备 MQTT 路径,WAN 中断时持久暂存,并仅通过 MQTT 转发。它自身不会任何现场总线协议。