想象你是家里的家长,孩子每天上学不在身边。你想让他放学后“先把数学作业写完再打游戏”,可孩子当时不在家里,你会怎么办?大多数人会做一件事:在冰箱上贴一张留言条。孩子回家看到留言,照做,然后在你预留的“已完成”栏里打勾。等下次你回来看一眼,就知道作业到底写完没有。 在物联网世界,几十万台设备不可能永远在线。设备可能断电、断网,甚至正在升级固件。这时候,云端依然需要“记住”每台设备应该处于什么状态、现在实际处于什么状态。于是,云平台为每一台设备留了一张“电子留言条”,它叫——设备影子(Device Shadow)。 设备影子是云端维护的一份 JSON 文档,记录设备的“期望状态(desired)”和“上报状态(reported)”,还带版本号、时间戳等元信息。设备离线时,应用把指令写进 desired;设备上线后拉取影子、计算增量 delta、执行动作、上报结果。整个过程就像留言条一贴、一撕、一交作业,解决了离线状态同步的大难题。

岗位关联

理解设备影子,是掌握物联网平台架构的必修课。无论做 IoT 平台研发、边缘计算,还是接入 OTA 与远程配置系统,影子机制都是你绕不开的底层能力——它决定你能否优雅地处理设备离线的“时间差”。

目录
  1. 可视化演示
  2. 影子概念解析
  3. 影子文档结构
  4. 状态上报流程
  5. 期望与增量计算
  6. 版本冲突处理
  7. OTA与远程配置
  8. 工程实践要点
  9. AI 应用场景
  10. Python 演示
  11. 常见误区
  12. 面试题

可视化演示

展示系统处理流水线、实时性能指标曲线及多方案对比分析,包含数据流转、吞吐延迟监控与方案评估。

本节配有交互动效(进入视口后自动加载,约 3KB)。

系统可视化 处理流程:输入→处理→模型→输出 性能监控:实时指标 P=L×T

影子概念解析

冰箱上的留言条,既是家长的“心愿单”,也是孩子回执的“成绩单”。家长把要求写在左栏,孩子把完成情况写在右栏,一份小小的纸条承载了两个人之间的时间差。设备影子做的正是这件事:在云端为每一台物理设备准备一张“虚拟留言条”,让应用和设备不必同时在线也能交换状态。

设备是物理世界的实体,而云端需要它的一个数字化替身。这个替身就叫设备影子(Device Shadow)。不管设备在不在线,应用都可以读取影子的状态;不管应用在不在线,设备也可以把状态写进影子。影子不是设备本身,而是设备与云端之间的“状态缓存层”。

影子的核心是一个 JSON 文档,最关键的是 state 区域:它内部区分 desired(期望状态)与 reported(上报状态)。除此之外,文档还带有 version(版本号)和 metadata(元数据)。版本号是影子的“编号”,每次状态变更都会递增,用来判断新旧的顺序。

家长与孩子的纸条既有要求(desired)也有反馈(reported),还有“第几版”的编号(version)防止拿错旧纸条。影子把这三件事合并成一张可被云和端共同读写的“电子便利贴”,是整个状态同步体系的起点。

Shadow=(statedesired, statereported, version)\text{Shadow} = \left( \text{state}_{\text{desired}},\ \text{state}_{\text{reported}},\ \text{version} \right)
影子就是一个三元组:期望状态 + 上报状态 + 版本号。三者合起来,构成设备在云端脑中留下的“当前印象”。
  1. 期望状态云端希望设备最终达成的配置值,好比留言条上“要做的事”。
    statedesired\text{state}_{\text{desired}}
  2. 上报状态设备最近一次反馈的实际情况,对应孩子核销任务时的“已完成”栏。
    statereported\text{state}_{\text{reported}}
  3. 版本号影子每次被修改都会自动递增的整数,用于识别文档新旧以及解决并发冲突。
    version\text{version}

影子文档结构

冰箱留言条通常分三栏:左边写“你要做的”,右边写“我已做的”,底角写“第几版”。一份标准的影子文档结构也遵循这种“分区 + 编号”的设计,让人一眼就能看出哪些是目标、哪些是事实。

打开影子文档,顶层通常包含四个字段:state、metadata、version、timestamp。其中 state 是主体,内部有两个子对象:desired 和 reported。metadata 记录每个状态属性的更新时间戳,方便审计“这条期望是什么时候写的”;version 是文档级别的整数版本号;timestamp 则是整个文档最后一次修改的时间。

文档中的值支持 JSON 的常见类型:字符串、数字、布尔值、对象、数组。更新时采用增量补丁(JSON Merge Patch)语义,而不是整篇替换:只修改涉及的路径,其他字段保持不变。这样既能减少网络流量,也能避免并发更新时误伤无关字段。

你能从留言条角落的编号判断纸条新旧,云端同样从 version 判断影子文档新旧。而 metadata 则像是纸条上每一行的“便签日期”,帮助追溯每条期望是什么时候贴上去的。结构清晰,是状态同步不乱套的地基。

Shadow={state, metadata, version, timestamp}\text{Shadow} = \left\{ \text{state},\ \text{metadata},\ \text{version},\ \text{timestamp} \right\}
文档四要素:状态区、元数据区、版本号、时间戳。就像留言条有“要求栏”“反馈栏”“编号角”“贴条时间”四个部分,缺一不可。
  1. 状态区影子文档的核心分区,内部进一步拆分为期望态与上报态两个对象。
    state={desired, reported}\text{state} = \{ \text{desired},\ \text{reported} \}
  2. 元数据区记录每个状态属性最近一次更新的时间点,便于定位“谁在什么时候动过它”。
    metadata={p↦tp}\text{metadata} = \{ p \mapsto t_p \}
  3. 版本号文档每次变更自动加一,充当并发控制的“接力棒”,后写者必须持有最新编号。
    version∈Z≥0\text{version} \in \mathbb{Z}_{\ge 0}

状态上报流程

孩子放学回到家,会先在“我已做到”那一栏里打勾,再补上日期。设备也是一样:上线后,它把实际执行结果写入 reported 区域,云端收到后做一个增量合并,再对外发布“影子已更新”的通知。

在主流 IoT 平台中,设备通过 MQTT 主题上报状态,例如 $aws/things/{device}/shadow/update。设备上发送的 JSON 会被合并进云端影子文档:同一路径的值直接覆盖,不同路径则保留原值。这个合并逻辑是“增量式”的,类似于 Git 的 merge,而不是把整个文件删掉重写。

合并完成后,云端会做两件事:第一,把影子的 version 自动加一;第二,向订阅了更新主题的客户端推送新的文档或摘要。这样,应用端能实时感知设备的最新状态,设备端也能确认自己的上报是否被接受。

孩子不会把整张留言条擦掉重写,只把自己完成的项目划掉,这就是增量合并。设备的每次上报也只携带“变化的部分”,云端负责把新旧信息拼在一起,形成一份完整且最新的“家庭作业登记表”。

R(t+1)=R(t)∪ΔRreport,v(t+1)=v(t)+1R^{(t+1)} = R^{(t)} \cup \Delta R_{\text{report}}, \quad \displaystyle v^{(t+1)} = v^{(t)} + 1
合并规则:把本次上报的增量字段并入既有 reported 集合,同路径覆盖旧值;随后版本号加一,让每次变化都可被订阅方感知。
  1. 当前上报集表示 t 时刻云端保存的设备上报状态集合,即“上次听到孩子说了什么”。
    R(t)R^{(t)}
  2. 增量合并新上报的字段集合与旧集合取并集,重复路径用新值覆盖旧值。
    R(t)∪ΔRreportR^{(t)} \cup \Delta R_{\text{report}}
  3. 版本自增合并成功后云端把版本号加一,所有监听者都能借此识别“又更新了一版”。
    v(t+1)=v(t)+1\displaystyle v^{(t+1)} = v^{(t)} + 1

期望与增量计算

家长写下心愿单之后,孩子回家要先逐条检查“哪些还没做到”:数学写完没有?英语听力听了没有?把没做的挑出来,这一串“待办差距”就是 delta。在设备影子里,delta 是期望状态与上报状态之间的差值。

应用端通过更新接口写入 desired 后,云端会立刻计算 delta:只要期望中存在、而 reported 中不存在,或者两者值不相同的属性,就会被收集到一个增量对象里。这个对象会被推送到设备订阅的 delta 主题,设备收到后就知道“现在需要执行什么”。

举个例子:影子 desired 中写着 {"color": "red", "brightness": 80},而 reported 只有 {"color": "red"},那么 delta 就是 {"brightness": 80}。如果想把 reported 里的某个属性删掉,就把 desired 中对应属性的值显式设为 null,这相当于在留言条上写“这项作废”。

孩子把没完成的“英语听力”挑出来做,做完之后划掉,心愿单就被清空了。设备消费完 delta 并上报新状态后,应用再把 desired 中已满足的项清除,整套“留言—执行—销项”的闭环就完成了。

Δ={(p,v)  |  (p,v)∈D, ∄(p,v′)∈R 且 v′=v}\Delta = \left\{ (p, v) \;\middle|\; (p, v) \in D,\ \nexists (p, v') \in R\ \text{且}\ v' = v \right\}
delta 的含义:期望 D 中那些在上报 R 里找不到相同键值的键路径集合。每条 delta 都是一条“待办指令”。
  1. 键路径代表一个 JSON 路径,如 color、brightness 或 nested.switch.state,是定位状态字段的“门牌号”。
    pp
  2. 值匹配条件期望 D 中路径 p 对应的值 v,在上报 R 中找不到相同值,就构成差异项。
    (p,v)∈D,(p,v)∉R(p, v) \in D,\quad (p, v) \notin R
  3. 增量集合所有满足条件的键值对聚在一起,形成设备要消费的 delta 增量。
    Δ={(p,v)∣(p,v)∈D, (p,v)∉R}\Delta = \{ (p, v) \mid (p, v) \in D,\ (p, v) \notin R \}

版本冲突处理

妈妈刚贴了“买牛奶”,爸爸又贴了“买面包”,孩子撕纸条时按编号最新的来。可如果两个人同时贴了同一编号的纸条,家里就乱了。设备影子面临同样的问题:两个应用同时写入,后写的可能无声覆盖先写的,导致指令丢失。

影子用 version 字段实现乐观锁(optimistic concurrency control)。写入时必须带上你此前读取到的版本号,云端会把它和当前版本比对:一致,才接受你的写入;不一致,就拒绝并返回版本冲突错误。这就像你在纸条上必须写“在上一版 v3 基础上修改”,如果云端已经 v4,你的修改会被弹回。

冲突解决的标准流程是:先重新拉取最新的影子文档,把本地业务逻辑与云端最新状态合并,再带着新的 version 发起重试。千万不能把旧版本请求原封不动地重发,那样会永远失败。这种“先读后写”的模式,保证了多应用同时操作影子时的最终一致性。

编号领先的那张留言条才是“最新命令”,拿着旧编号写的新内容会被冰箱弹回。版本冲突不是 bug,而是保护机制:它阻止了并发写入造成的“静默覆盖”,让每个应用都必须在最新状态上协同工作。

accepted  ⟺  vreq=vcloud,vcloud′=vcloud+1\text{accepted} \iff v_{\text{req}} = v_{\text{cloud}}, \quad \displaystyle v_{\text{cloud}}' = v_{\text{cloud}} + 1
写入生效条件:请求携带的版本与云端当前版本相等才接受;接受后云端版本立即自增,旧版本请求后续自然失效。
  1. 请求版本客户端在发起写入时必须携带它此前读取到的影子版本号,作为“我基于哪一版修改”的凭证。
    vreqv_{\text{req}}
  2. 版本校验云端把请求版本与当前版本做比较,两者相等才视为基于最新版本的合法写入。
    vreq=vcloudv_{\text{req}} = v_{\text{cloud}}
  3. 版本推进接受写入后云端版本加一,并发到达的旧版本请求会因版本不匹配而被拒绝。
    vcloud′=vcloud+1\displaystyle v_{\text{cloud}}' = v_{\text{cloud}} + 1

OTA与远程配置

家长不仅要下达“完成作业”,还要告诉孩子“去某网址下载参考书”。留言条写不下整本书,只能写下载地址、版本号和校验值。OTA 升级也是如此:影子不传输固件文件本身,而是传输“升级指令 + 固件 URL + 目标版本”这些元信息。

OTA 升级的典型流程是:平台把目标固件版本、下载地址等信息写入 desired;设备上线后拉取影子,发现 delta 中有新升级指令,便去下载固件、校验校验值、执行升级;完成后设备把新版本号写入 reported;云端再清除对应 desired。整个过程不需要应用和设备同时在线,完美适配工厂、户外等弱网场景。

远程配置下发同理:智能门锁的温度阈值、共享单车的计费规则、工业传感器的采样频率,都可以通过影子动态调整。灰度升级时,平台按批次给设备写入 desired,每批升级完成后统计健康度,达标后再放量下一批,避免一次推送导致全线故障。

先在几个小区试点,反馈良好再全城推广,避免一次“贴遍全城”出大乱子。影子在 OTA 与远程配置中扮演的角色,就是一张可持续分批下达指令、且能逐个确认回执的“云端指挥留言条”。

θj=Nok,jNbatch,j,继续放量  ⟺  θj≥θmin⁡\theta_j = \cfrac{N_{\text{ok},j}}{N_{\text{batch},j}}, \quad \text{继续放量} \iff \theta_j \ge \theta_{\min}
灰度健康度:当前批次成功升级设备数与批次设备总数的比值,不低于设定的阈值才允许推进下一批。
  1. 批次成功数第 j 批中完成升级且重启后健康检查通过的设备数量,是升级效果的“实报数”。
    Nok,jN_{\text{ok},j}
  2. 批次总数第 j 批计划升级的设备总数,构成健康度计算的分母。
    Nbatch,jN_{\text{batch},j}
  3. 阈值判断成功比例达到最低门槛,才继续滚动到下一批,否则暂停并回滚。
    θj≥θmin⁡\theta_j \ge \theta_{\min}

工程实践要点

留言条不能写满一整篇 A4 纸,也不能每秒钟都重写一次,否则冰箱门都关不住。设备影子同样有平台配额限制:体积、更新频率、错误码都得讲究分寸。

首先是影子大小限制。经典 AWS IoT 影子限制单份文档 8KB,新版提升到 128KB,但这仍然意味着你不能把历史遥测数据全塞进去。高频、海量的传感器读数应该走消息管道(如 MQTT topic),低频、有状态的配置才放进影子。其次是更新频率控制,平台一般对每秒更新次数有配额,超过会返回节流错误,需要做指数退避重试。

错误码处理也需要分类对待:400 表示请求格式非法;403 表示权限不足;409 表示版本冲突;429 表示请求过频。业务上必须区分“可重试”和“不可重试”的错误。安全方面,影子应通过 IAM 策略或设备证书做细粒度访问控制,比如只允许特定应用读某设备影子的 desired,只允许该设备写自己的 reported。

冰箱门上的留言纸要克制、贴得规整,一家人的信息才不会乱成一锅粥。影子也遵循同样的方法论:内容精炼、频率克制、权限分明,才能在大规模设备接入时保持稳定。

Tsync≈1fupd,∣Shadow∣≤Lmax⁡T_{\text{sync}} \approx \cfrac{1}{f_{\text{upd}}}, \quad \left| \text{Shadow} \right| \le L_{\max}
同步周期约等于更新频率的倒数,频率越高延迟越低但配额压力越大;影子体积必须小于平台上限,否则直接被拒绝。
  1. 同步周期完成一次影子状态同步大致需要的时间,与更新频率成反比关系。
    Tsync≈1fupd\displaystyle T_{\text{sync}} \approx \cfrac{1}{f_{\text{upd}}}
  2. 影子体积影子 JSON 序列化后的字节数,必须控制在平台允许的最大值之内。
    ∣Shadow∣≤Lmax⁡\left| \text{Shadow} \right| \le L_{\max}
  3. 频率上限每秒允许更新的次数受配额约束,超限会收到 429 节流错误。
    fupd≤fmax⁡f_{\text{upd}} \le f_{\max}

AI 应用场景

智能家居远程配置

阶段:物联网设备配置下发与状态管理

解决的问题:用户出门在外想把家里空调设为 26℃ 并开启除湿,但空调处于离线状态。传统 MQTT 发布指令没有缓存,消息发出即丢失,用户回家发现空调纹丝未动。AI 驱动的家庭中枢叠加影子后,离线指令被暂存为 desired,设备联网自动执行。

具体案例:某智能家居 App 将目标温度写入设备影子 desired;空调重启联网后,IoT 平台推送 delta,空调执行并上报 reported,用户 App 实时显示“已生效”。家中哪怕断网数小时,配置也不会丢。

工业设备 OTA 升级

阶段:OTA 升级管理与固件部署

解决的问题:工厂注塑机分布在多个车间,网络时断时续,直接推送固件包极易中途失败。影子机制把“目标版本 + 固件 URL + 校验值”作为期望状态下发,设备自行决定何时拉取与升级,天然适配弱网环境。

具体案例:某智能制造园区用影子驱动注塑机升级:平台按批次写入 desired,设备上线后拉取 delta 下载固件,升级完成后上报新版本号。平台根据 reported 统计成功率,健康度达标才推进下一批,最终完成全园区的无感升级。

共享出行车队状态同步

阶段:设备监控与调度策略下发

解决的问题:共享单车进入地下车库后无信号,用户扫码开锁的指令无法送达,调度策略也无法更新。影子可以作为“离线命令缓存”,把开锁授权、停车区域、价格策略暂存到 desired,等单车回网后立即同步执行。

具体案例:某共享出行平台在单车离线期间写入“调整夜间调度价”的期望状态,单车驶出地库联网后自动拉取 delta 并生效。运营人员无需等待每一辆车在线,就能批量完成策略更新。

Python 演示

python106 行
# -*- coding: utf-8 -*-"""Device Shadow 模拟器:演示 desired/reported/delta/version 同步闭环"""import json  class DeviceShadow:    def __init__(self, name=""):        self.name = name        self._reported = {}        self._desired = {}        self._version_num = 0        self._meta = {}     def _bump_version(self):        self._version_num += 1        return self._version_num     def get_shadow(self):        return {            "state": {"desired": self._desired, "reported": self._reported},            "version": self._version_num,            "metadata": self._meta,        }     def update_reported(self, values):        for key, val in values.items():            self._reported[key] = val            self._meta.setdefault("reported", {})[key] = self._version_num        self._bump_version()     def set_desired(self, values):        for key, val in values.items():            self._desired[key] = val            self._meta.setdefault("desired", {})[key] = self._version_num        self._bump_version()     def clear_desired(self, keys):        for key in keys:            if key in self._desired:                del self._desired[key]     def compute_delta(self):        delta = {}        for key, goal in self._desired.items():            if goal is None:                delta[key] = None            elif self._reported.get(key) != goal:                delta[key] = goal        return delta     def pull_and_apply(self, device):        delta = self.compute_delta()        deleted = []        for key, val in delta.items():            if val is None:                device.remove_state(key)                deleted.append(key)            else:                device.apply_state(key, val)                self.update_reported({key: val})        if deleted:            for key in deleted:                self._reported.pop(key, None)            self._bump_version()        return delta  class FakeDevice:    def __init__(self, name):        self.name = name        self.state = {}     def apply_state(self, key, val):        self.state[key] = val        print(f"[{self.name}] 执行 delta: {key} = {val}")     def remove_state(self, key):        if key in self.state:            del self.state[key]            print(f"[{self.name}] 删除字段: {key}")  def main():    shadow = DeviceShadow("bulb-001")    bulb = FakeDevice("bulb-001")     print("1. 设备离线, 云端写入 desired 期望...")    shadow.set_desired({"color": "red", "brightness": 80})     print("2. 设备上线, 拉取影子并消费 delta...")    delta = shadow.pull_and_apply(bulb)    print("   本次 delta:", json.dumps(delta, ensure_ascii=False))    shadow.clear_desired(list(delta.keys()))     print("3. 同步后的影子文档:")    print(json.dumps(shadow.get_shadow(), ensure_ascii=False, indent=2))     print("4. 设置 color 为 null, 触发删除语义...")    shadow.set_desired({"color": None})    delta = shadow.pull_and_apply(bulb)    shadow.clear_desired(list(delta.keys()))    print(json.dumps(shadow.get_shadow(), ensure_ascii=False, indent=2))  if __name__ == "__main__":    main()
目的:用约百行代码模拟设备影子的完整闭环——离线写入期望、上线消费 delta、执行后上报、null 删除字段。运行后可以直观看到 desired 与 reported 逐步收敛的全过程。
  1. shadow.set_desired({"color": "red", "brightness": 80})模拟设备离线时云端写入期望状态,对应影子中的 desired 区域。
  2. delta = shadow.pull_and_apply(bulb)设备上线后拉取影子并消费 delta,一次完成“差距计算 + 指令执行 + 状态上报”。
  3. shadow.clear_desired(list(delta.keys()))完成同步后清除已满足的期望状态,保持文档精简。
  4. self.update_reported({key: val})设备执行 delta 后把实际结果写入 reported,让云端知道“孩子做到了”。
  5. if goal is None: delta[key] = Nonedesired 中显式置 null 的值触发删除语义,对应“留言条上写作废”。
  6. self._bump_version()每一次版本自增,都是乐观锁生效的前提,让并发写入有据可依。
  7. device.remove_state(key)模拟物理设备根据 delta 中的 null 指令删除自身对应字段。

常见误区

误区我以为影子文档里 reported 就是设备此刻的真实状态,每次打开仪表盘看到的都是当前值,设备断电瞬间影子也会跟着更新。

正解影子只是云端缓存,reported 是设备“最近一次成功上报”的状态。设备断网、重启、升级固件时,影子可能长时间滞后。它就像冰箱上最后一张留言条的日期,不代表孩子此刻正在做什么。要实时数据,仍需靠 MQTT 消息等高 QoS 直读通道。

为何常见:看到文档里写着“state”就误以为是实时数据库快照,忽略了设备影子存在的首要价值恰恰是应对离线场景。

误区设备不再需要某个配置项,我就在 desired 里把那个属性设为 `""`,觉得空字符串等于“没有”,云端应该自动删掉它。

正解置空字符串仍然是一个有效值,并不代表删除。主流 IoT 平台的影子协议要求把 desired 中目标属性显式置为 null,平台才会生成删除型 delta。空字符串会被当作一个新配置下发,导致设备收到一个空值配置而非删除命令。

为何常见:直觉上“空 = 没有”,但 JSON 中只有 null 才是显式的“不存在”标记,两种语义在不同平台上差异极大。

误区设备离线时我只要在云端把配置写进 desired,它上线后应该会自动搞定一切,影子自己会完成同步、执行、上报全套流程。

正解desired 只是一张留言条,真正的执行需要设备固件支持:上线后主动拉取影子、计算并消费 delta、执行动作、回写 reported,最后清除 desired。如果设备端没有实现这些逻辑,留言永远只是留言。影子是云和端协作的协议,不是云端魔法。

为何常见:把“云端存储”误当成“云端执行”,忽略了设备才是动作的执行者,影子只是状态传递的载体。

误区更新影子返回 409 版本冲突,我就写个 for 循环把同一份请求发五次,想着总有一次能撞上成功。

正解如果重发的仍是旧版本号,云端会持续拒绝,因为乐观锁的规则就是“版本必须匹配”。正确做法是先重新拉取最新影子,把本地业务状态与最新文档合并,再携带新 version 提交。盲目重试只会加剧并发挤兑。

为何常见:把 409 当成了网络抖动,没有意识到它是乐观锁主动拦截“基于过期版本的写入”,是数据保护机制而非临时故障。

误区把传感器所有历史数据、复杂对象全塞进影子,再每 100ms 更新一次,数据越全面越实时就越“高级”。

正解影子有明确的大小上限(如 8KB)和更新速率配额。超大文档会让弱网设备的拉取变得缓慢;高频更新会触发 429 节流,产生大量失败重试和费用。正确的分工是:高频遥测走消息管道,低频有状态的配置走影子。

为何常见:把影子当成万能存储,没有理解它本质上是“低频状态缓存”,与消息队列承载的海量数据流是两个不同职责。

面试题

Q1. 设备影子的核心价值是什么?

单选题 · easy

  • A.离线缓存状态指令← 正确答案
  • B.替代业务数据库
  • C.云端执行计算任务
  • D.承载视频实时流

答案:A。设备影子在云端缓存设备的期望与上报状态,应用和设备不必同时在线即可交换信息,这正是“留言条”式设计的核心价值。选项 B 将影子误用作数据仓库,C、D 均超出影子的能力边界。

Q2. 影子文档中 reported 字段表示什么?

单选题 · easy

  • A.云端期望达到目标
  • B.设备上报最近实际状态← 正确答案
  • C.系统随机生成的占位值
  • D.用户自定义的备注字段

答案:B。reported 是设备实际执行后上报的状态,对应留言条上孩子填写的“已完成”栏;desired 才是云端期望值。C 和 D 在标准影子文档中不存在。

Q3. delta 增量由哪两部分对比得出?

单选题 · easy

  • A.D 区与 R 区对比生成← 正确答案
  • B.v 与 t 新旧比较得到
  • C.MQTT 与 HTTP 协议对比
  • D.设备ID与密钥散列对比

答案:A。delta 由期望状态 desired(D 区)与上报状态 reported(R 区)对比得出,期望存在但上报缺失或值不同的属性进入增量。版本 v 和时间戳 t 不参与 delta 计算,B、C、D 均为干扰项。

Q4. 设备上报状态成功后,影子版本号如何变化?

单选题 · mid

  • A.保持不变原样返回
  • B.每次自动递增一← 正确答案
  • C.更新瞬间重置为零
  • D.随机正负增减

答案:B。每次影子文档被修改,version 都会自动加一。版本单调递增是乐观锁的基础,帮助客户端判断自己是否持有最新状态。重置或随机都会破坏冲突检测,A 选项违背每次变更必增版本的规则。

Q5. 影子更新返回版本冲突时,设备端应优先怎么做?

单选题 · mid

  • A.拉取影子合并后重试← 正确答案
  • B.强制覆盖云端版本
  • C.删除设备影子文档
  • D.等待后重新上报

答案:A。冲突说明本地版本过期,正确做法是重新拉取最新影子,合并业务状态后携带新版本重试。B 和 C 会被云端拒绝或造成数据丢失;D 仅等待不换版本,依然会继续冲突。

Q6. 清除 desired 中某属性的正确方式是?

单选题 · mid

  • A.将该属性置为 null← 正确答案
  • B.删除整个影子 JSON
  • C.属性值改为空串
  • D.将版本号清零即可

答案:A。在标准影子协议中,将 desired 中的目标属性显式置为 null 才表示删除,平台会生成删除型 delta。空字符串仍是有效值而非删除;删除整个文档影响其他字段;版本号清零会破坏乐观锁机制。

Q7. 下列哪个场景最适合使用设备影子?

单选题 · mid

  • A.离线时下发期望配置← 正确答案
  • B.传输视频实时监控画面
  • C.高并发存储银行流水
  • D.断点续传超大固件包

答案:A。影子专为“离线缓存与状态同步”设计,适合低频配置指令。视频流需要高吞吐消息通道,银行流水需要数据库,超大固件包应走对象存储+专用下载通道,均不适合塞进影子。

Q8. metadata 字段在影子文档中主要记录什么?

单选题 · hard

  • A.记录属性更新时间戳← 正确答案
  • B.存储设备硬件序列号
  • C.保存固件下载地址
  • D.存放文档加密密钥

答案:A。metadata 记录每个状态属性最近一次更新的时间戳,用于审计和追踪。硬件序列号属于设备注册信息,固件地址属于 OTA 指令内容,加密密钥应由安全服务管理,都不属于影子的元数据职责。

Q9. 以下哪些原因会导致影子更新请求被拒绝?

多选题 · hard

  • A.请求版本号不匹配← 正确答案
  • B.影子文档大小超限← 正确答案
  • C.访问权限配置不足← 正确答案
  • D.设备风扇转速过高

答案:ABC。版本不匹配触发 409 冲突,文档超限触发 400 类错误,权限不足触发 403。设备风扇转速是物理层遥测数据,与影子文档的写入资格无关,属于干扰项。

Q10. 关于设备影子与 delta 机制,下列说法正确的是?

多选题 · extreme

  • A.设备上线会同步 delta← 正确答案
  • B.期望与上报差异即 delta← 正确答案
  • C.影子仅由设备独立创建
  • D.版本号支撑乐观锁控制← 正确答案

答案:ABD。设备上线后拉取影子并消费 delta,A 正确;delta 正是 desired 与 reported 的差异,B 正确;影子可由云端或设备端更新,并非仅设备创建,C 错误;版本号用于乐观锁并发控制,D 正确。综合考验对影子闭环的整体理解。