命令
DeviceChain 的命令是双向且持久化的。发出的命令会根据设备能力契约验证并存储。只有设备确实能够接收时才派发,并持续跟踪,直到设备报告结果或命令生存时间(TTL)耗尽。这里没有发出后就不再关注的命令。
发出命令的方法见发送命令。
能力契约
设备配置文件可以声明设备接受的命令。每条命令都有带类型的参数模式:名称、数据类型、是否必填、最小/最大值和枚举。这些声明使配置文件成为契约,而不是标签。
命令入队时,根据已发布配置文件版本验证,而不是草稿。有三种结果:
| 配置文件…… | 结果 |
|---|---|
| 没有声明命令 | 接受任何命令。声明命令集合是可选的,因此尚未采用它的配置文件保持原有行为。 |
| 声明了命令,且键匹配其中一个 | 按该命令参数模式验证载荷。未知参数、错误类型、超范围值和缺失必填参数全部被拒绝。未声明参数的命令接受任何格式正确的 JSON 载荷。 |
| 声明了命令,但键没有匹配项 | 拒绝。不能向设备发送其能力契约不包含的命令。 |
命令键精确匹配,包括大小写。大小写错误的键意味着错误的控制操作,这正是验证要阻止的情况。
验证有意读取已发布快照。已经编写但尚未发布的定义还没有传达给任何下游,强制执行它会拒绝设备实际上接受的命令。发布配置文件,才能让新命令生效。
你也可以读取已发布命令集合。平台通过与入队检查相同的查询,从配置文件已发布版本解析设备当前接受的命令。这里没有设备自行报告的内容。控制台使用该列表直接提供命令:已声明命令选择器,以及根据选中命令参数模式构建的带类型表单,而不是自由文本框。没有声明命令的配置文件仍显示自由文本表单,与平台接受范围一致。已编写但未发布的命令在选择器旁列为不可用,因此缺失命令会被理解为“尚未发布”,而不是功能缺失。
命令生命周期
发出的命令会持久化,并通过一组状态跟踪。
以下状态表示命令尚未完成:
QUEUED:已接受并验证,等待首次派发判断。它确实是过渡状态,命令不会长期停留在此。HELD:平台明确知道设备不在,因此有意保留命令。这是离线设备群积压所在的状态,可以持续数天。保留的命令仍属于处理中,可取消;TTL 到期记录EXPIRED而非TIMEOUT,因为命令从未发出。设备返回时,它重新进入QUEUED。SENT:已发布到设备自己的命令主题,等待响应。应理解为向平台认为在线的设备派发,而不是设备已收到的证明。设备在在线检查与发布之间掉线,仍会进入此状态。持续数分钟没有结果的命令,可能已没有任何组件持有;参见平台失去命令跟踪时。PARKED:命令已发布,但传输发现设备没有可用连接,平台仍持有它。这用于休眠设备,命令在下次唤醒时投递。与HELD一样,它仍属于处理中,可取消;TTL 到期记录EXPIRED而非TIMEOUT,因为命令从未到达设备。参见注册不等于可达。LwM2M 设备仍连接时也可能出现PARKED:设备队列已满、刚重连,或前面的命令无法确认。此类命令稍后按顺序投递,无需等待唤醒。
以下是终态,进入后不再迁移:
-
SUCCESSFUL/FAILED:设备报告了结果。平台自身无法完成命令时也记录FAILED:- 设备传输完全无法承载命令;
- 设备已回答,但所有尝试后平台仍无法记录答案;
- 平台无法向设备发布命令,并已停止重试。
每种情况下,命令的
error字段都会说明原因,避免将平台侧FAILED误认为设备报告故障。 -
TIMEOUT:已派发,但设备从未回答。 -
EXPIRED:尚未到达设备,TTL 就已耗尽。 -
CANCELLED:运维人员或租户取消了命令。
EXPIRED 和 TIMEOUT 回答不同问题,混淆会导致排查方向错误。EXPIRED 表示命令未到达设备,连续出现说明投递未成功;TIMEOUT 表示命令已发出但没有回复,指向设备。
向不在线设备发送命令
通过 MQTT,命令只到达当时已连接并订阅的设备。消息代理不会保留命令等待设备稍后领取。不检查时,向缺席设备发布的命令会丢失,平台无法知道:记录为已发送、没有回复,一周后变成 TIMEOUT。这会形成永久记录,归咎于从未收到命令的设备。
因此平台在发布前检查。传输报告设备未连接时,命令进入 HELD,不发布。设备返回时,命令回到 QUEUED,通常在重连后一两秒内,因为拥有连接的传输会直接报告。
定期扫描也根据平台当前对设备的认识,重新检查保留命令,因此即使通知丢失也能释放积压。两条路径都依赖平台知道设备已返回。在线状态记录变化才真正解除保留,通知只是加快执行。
有四项限制:
- 检查需要报告连接的传输。 对只传递数据的设备,“近期没有事件”不是无法接收的证据。每小时上报的设备每小时静默 59 分钟,但始终可达。这些命令仍按原方式派发。
- 这是检查,不是队列。 设备在检查与发布之间断开,命令仍会丢失。检查消除的是平台事先能知道的情况。为休眠设备维护自身队列的传输单独处理,参见注册不等于可达。
- Sparkplug 设备完全不投递命令。 该路径尚未构建:Sparkplug 节点位于你的 MQTT 基础设施,而非平台消息代理,没有桥接。发给它的命令立即记录
FAILED并说明原因,而不是等待设备返回,因为返回也无法解决。 - 保留可以比引发它的传输活得更久。 只有报告设备缺席的传输可以报告其返回。如果该传输停止运行,积压会等待各命令自行过期。将设备改回推断在线状态可以释放命令,因为保留依据是设备被明确报告缺席,而推断状态的设备并非如此。
对于已知间歇连接的设备,连续 TIMEOUT 仍应优先理解为连接时间问题,而不是固件问题;不过这种连续情况现在应少得多。
注册不等于可达
在线状态检查询问设备是否已注册。对于有意休眠的设备,例如队列模式 LwM2M 设备,这与当前是否可达不同。设备已注册,因此检查通过、命令被发布,但传输发现没有可用连接,命令无法到达。
这与上面的情况相同,只是低一层,以前结果也相同。命令停在 SENT,而该状态也表示“平台已交给设备”,因此一个状态有两种相反含义。一周后记录变成 TIMEOUT,归咎于从未获得命令的设备。
现在这类命令记录 PARKED:已发布、没有接收者,仍由平台负责在设备下次唤醒时投递。由于平台仍持有:
- TTL 到期记录
EXPIRED而非TIMEOUT。 命令没有到达设备,记录准确说明,不归咎于设备未回答。 - 可以取消单条命令或整个设备群操作中的命令,取消后不会在下次唤醒时发出。但这不保证操作从未执行。退回可能是设备连接失效前已收到的发布的重试,因此取消应理解为“不会再次投递”,不是“从未执行”。过去,取消设备群操作会把仍被平台持有的暂存命令报告为已发送、无法撤回。
- 计入租户未投递命令上限,与
QUEUED和HELD相同,因为平台仍承担这项工作。
PARKED 不总表示设备休眠。LwM2M 适配器在设备仍连接时,也会因该设备队列已满、刚重连且等待命令优先,或前面的命令无法与 command-delivery 确认而退回命令。这些命令也记录 PARKED,稍后分批按顺序投递给已连接设备,而不是等下次唤醒。
平台失去命令跟踪时
退回机制只在仍有组件持有命令时有效。命令也可能进入 SENT 后,没有任何组件继续处理:
- 发布它的 Pod 在记录结果前退出;
- 传输用完重试次数后放弃;
- 实例没有运行执行退回的组件。
设备和命令本身都没问题。命令已经没有所有者,而 SENT 的退出都需要所有者,除了将它记录为 TIMEOUT 的 TTL。
这与 PARKED 要消除的错误标记相同,只是来自另一条路径,平台采用相同方式解决。后台扫描查找停在 SENT、没有结果且时间超过平台可能仍在处理的命令。阈值从消息层重试预算和运维人员可配置的最慢投递扫描推导,目前约十分钟,不是随意指定。可以安全重新启用的命令进入 PARKED,与其他暂存命令一样在设备下次唤醒时投递。
两项限制是有意的:
- 只适用于 LwM2M 设备。
PARKED断言命令没有到达任何设备,普通 MQTT 设备无法作出这种断言。MQTT 命令实时投递给当时连接者,无法区分“没有到达”和“到达但答案丢失”,因此这些设备的行为不变。 - 重新启用接受答案丢失时可能执行两次。 没有记录结果,不等于从未执行:设备可能已执行,但报告丢失。重新启用仍是更好的选择,因为替代方案是把平台确实未投递的命令必然记录为
TIMEOUT,归咎于无过错设备。这与退回机制相同,都是至少一次保证:应理解为“会被投递”,不是“此前未执行”。
延迟到达的投递不同,不会导致第二次执行。长时间 LwM2M 故障后,重新启用命令的原始投递仍可能出现,例如故障切换后重新投递,或适配器恢复后首次读取。LwM2M 命令到达设备前,适配器会与平台确认持有投递仍是命令当前投递。平台已重新启用或重新发送过的旧投递会被丢弃,不执行。确认会将 sentTime 移到实际向设备发送的时刻。
租户可以保留多少积压
未投递命令积压只有三种消退方式:
- 设备返回或唤醒;
- 命令 TTL 到期,记录
EXPIRED; - 有人取消,记录
CANCELLED。
设备群持续离线且无人干预时,积压保留到 TTL 到期。因此它按租户限制,每个层级都有真实数值:任何设置都不表示“无限制”。 无界积压会让租户触发运维人员看不到的持久存储增长。
限制沿级联解析:有租户覆盖值就使用它,否则使用层级值,再否则使用平台默认 10,000。已达到限制的租户下一条命令会被拒绝,代码为 HELD_CEILING_EXCEEDED。
计数包含所有 QUEUED、HELD 和 PARKED 命令,不只是因设备缺席被保留的命令,因此即使整个设备群在线,也可能仅因入队处理中数量被拒绝。
排队命令在一个处理周期内排空,因此约贡献一个周期的入队数量,虽小但不为零;接近上限并高速发送的租户会遇到这一限制。
一部分上限为投递机制预留
不是全部上限都供你使用。其中一部分——默认 20%,即平台默认 10,000 中的 2,000——为平台自身命令投递保留,只有平台可以使用。所有代表你发出命令的入口都受剩余额度限制:控制台、SDK、dcctl 和自有集成一视同仁。
预留源于设备群操作可能造成的情况。“重启所有泵”是一项合法请求,却能立即占满上限。之后,自动化规则尝试发送的每条命令都会被拒绝,直到积压排空;离线设备群可能等待数天。预留让设备群操作进行期间,告警驱动自动化仍能工作。
发送一条或一万条命令时都同样适用。批次接受到与单条命令循环相同的限制,没有绕过方式,也没有哪种形式更有优势。参见一条命令,多台设备。
拒绝信息指出实际适用的限制。预留限制生效时,还会说明预留多少,让看到上限 10,000 却在 8,000 被拒绝的调用方能够区分两个数字。预留是运维设置,不是租户设置,不能逐租户提高或降低。
重试上限拒绝
HELD_CEILING_EXCEEDED 是入队路径唯一的临时拒绝。其他拒绝说明请求在下一次尝试仍然错误;这一项会随着积压投递自行解除。只有这个代码值得重试,其余应该提示用户处理。参见发送命令。
谁报告结果
只有设备可以报告成功。SUCCESSFUL 和设备报告的 FAILED 是只能由设备产生的两种结果。其他终态由平台自行写入:
TIMEOUT、EXPIRED或CANCELLED;- 传输完全无法承载命令导致的
FAILED; - 设备答案已到达,却无法记录到命令导致的
FAILED; - 平台无法发布且停止重试导致的
FAILED。
最后两种都不是 TIMEOUT,理由从相反方向一致:一种设备确实回答了,另一种设备根本没收到内容。两者使用 TIMEOUT 都会让你去检查正常硬件。
- 答案丢失时,命令
error字段说明情况,设备报告结果无法恢复。如果重要,请再次询问设备。 - 命令无法发布时,完全没有内容到达设备,修复原因后可以再次发出命令。
报告结果是设备一侧的契约,参见响应命令。从不响应的设备会让命令保持 SENT,直到 TTL 将其变为 TIMEOUT。例外是 LwM2M,数分钟没有结果的命令会重新启用,等待下次唤醒,详见平台失去命令跟踪时。
每条命令都有 TTL:通过 expiresAt 指定,或使用平台默认七天。如果设备不报告结果,而一周超过命令有效用途,请自行设置。
取消记录为 CANCELLED。直到最近,取消和 TTL 到期共用 EXPIRED,因此更改前取消的命令仍显示 EXPIRED。历史数据中两者都有,且没有记录哪些 EXPIRED 来自取消,因此之后无法区分。
每台设备在只属于自己的主题接收命令,并只被授权访问该主题。设备无法观察租户内发给其他设备的命令。
投递顺序不是执行顺序
平台按入队顺序投递同一设备的命令,但设备是否按顺序执行由设备决定。一次处理一条命令的设备会按顺序执行,同时运行多个处理器的设备则不会,除非把命令分组。例如,.NET SDK 的 MaxConcurrentCommands 设置会放弃不同命令间的执行顺序,除非它们共享同一通道。顺序本身有意义的序列,例如先写固件再执行,必须在设备侧分组,因为平台无法让同时运行两条命令的设备串行执行它们。
一条命令,多台设备
命令批次将同一命令分发到多台设备,记录为单个操作。可以显式指定设备,也可以从实体组解析,每台设备收到相同命令键和载荷。发出方法见向设备群发送命令。
上述行为仍逐设备适用:每条命令按设备能力契约验证,设备不在线时保留,跟踪相同生命周期,并使用相同 TTL。批次不改变单条命令的行为,而是改变平台对整个操作记录什么。
记录正是关键。被平台拒绝的设备根本没有命令行,没有表示“想创建但未创建”的状态。没有批次记录,拒绝就不留痕迹,晚上发送设备群操作、早晨回来的运维人员将没有内容可查。记录保留目标解析出多少设备、实际入队多少,以及哪些被拒绝和原因。
批次与单条命令循环有三项区别,都不只是便利:
- 分组目标在发出时冻结。 批次解析分组已发布成员关系,绝不使用草稿选择器,并记录解析所用分组版本。之后编辑分组不能改变已发出的内容,审计仍能回答批次发出时分组意味着什么。
- 部分分发是明确决定,不是默认行为。 真实设备群中某些设备无法接受命令,调用方必须声明是否接受这种情况。不接受时,整个批次被拒绝,不创建任何内容,包括记录,因为没有操作发生。接受时,可接收设备获得命令,其余记录为拒绝。
- 可以作为单个操作取消。 撤销循环需要分别取消每条命令,并仍知道所有发出时使用的令牌。
记录上的数量——解析设备数、接受设备数——描述的是批次发出时刻,不是现在。命令行不会永远存在,实时计数可能低于创建时真实数量,却没有拒绝说明缺口。回答“排队的 5,000 条已有多少发出”等现在时问题时,应搜索批次创建的命令,而不是重新读取批次。
拒绝以两种方式保存,大规模分发需要两者:
- 单独设备列表,限制长度,避免一次设备群操作保存无界数据块;
- 完整的按代码汇总数量,绝不截断。
汇总让记录可以自行核对:即使设备名称样本较短,解析设备总数仍始终等于接受数加上所有拒绝数。
取消批次只停止尚未发出的命令
取消批次将 QUEUED、HELD 或 PARKED 命令移到 CANCELLED。已经 SENT 的命令不变,收到命令的设备仍会执行。
SENT 是唯一边界:平台取消仍持有的内容,不干预已经发到线上的内容。SENT 命令已向认为在线的设备派发,取消无法撤回,只会让平台停止等待设备答案,将真实结果替换为“运维人员取消”的记录。整个设备群会执行,响应却被丢弃,记录说操作已取消。
取消单条命令使用完全相同边界:取消 QUEUED、HELD 和 PARKED,SENT 原样返回,而不是改成 CANCELLED。参见取消单条命令。
LwM2M 设备多一道停止检查。适配器在执行前立即与平台确认命令。批次取消时,已发布但尚未到达设备的命令会在此停止,并记录 CANCELLED。批次取消结果仍将它计为已发送,因为取消时它确实处于该状态。
批次取消绝不拒绝。因为部分设备已执行而拒绝启动“刹车”,会让其余设备仍受命执行,这是最坏结果。因此,它始终生效,并报告拦住多少命令。批次记录标记取消时间和该次捕获数量。首次标记优先:第二次取消不会覆盖第一次记录。