设备影子与物模型
物模型
物模型(Thing Specification Language)是为产品定义的数据模型,用于描述产品的功能。物模型将设备在云端从属性、服务和事件三个维度,分别描述了该实体是什么、能做什么、可以对外提供哪些信息。定义了物模型的这三个维度,即完成了产品功能的定义。
设备影子
设备影子(Device Shadow)是指在物联网设备与云端之间建立的一种虚拟设备模型,它保存了物联网设备的最新状态和控制信息,并提供了远程访问这些信息的能力。
简单来说,设备影子保存了设备即时状态。应用程序可对影子状态进行获取和设置。设备在线时,可直接获取到平台下发的设置指令;设备离线后,再次上线时可以主动查询影子进行配置。
主要目的:
- 缓存设备状态:应用程序可以方便地查看设备状态,即使设备下线。
- 离线操作:设备影子允许应用程序和其他设备对设备状态进行操作,即使设备处于离线状态,等设备上线时主动获取期望状态进行更新。
- 解耦:设备影子充当了设备和应用程序之间的缓冲层,使得应用程序无需直接与设备进行通信,从而降低了设备和应用程序之间的耦合度。
控制模型
数据结构
数据以 JSON 文档形式存储,主要包含以下属性:
- state
- desired 设备预期状态
应用程序设置的设备预期状态,可用于远程控制设备。 - reported 设备报告状态
记录了设备当前的属性和状态信息。应用程序读取文档的该部分以确定设备的上次报告状态。
- desired 设备预期状态
- metadata 元数据
存储 state 部分中每个属性更新的时间戳。 - version 文档版本
文档每次更新时,此版本号都会递增。用于确保正在更新的文档为最新版本。更新时版本号需要设置成大于影子系统的版本号,否则会更新失败。如果不进行校验版本号,则不要带上该字段。
1 | { |
存储引擎
占用空间:以一个设备有 30 个属性为例,大约需要 4.2K 空间,30万个设备则需要 1.2G, 1百万个设备则需要 4G
对比了 Redis、MongoDB、COS
- MongoDB:需要引入新技术栈,有学习、维护成本
- COS:费用不低,心跳机制每天读写请求量好几亿
考虑到目前现有的业务以及对存储引擎的读写操作并发要求不低、使用成本,决定使用 Redis 进行存储。
数据流
下面以摄像机设备为例,说明设备、设备影子以及应用程序之间的通信,主要介绍设备主动上报状态、应用程序(SDK)改变设备状态、设备主动获取影子内容,和设备、应用程序主动删除影子属性。
设备主动上报
设备登录在线时,主动上报设备状态到影子。应用程序主动获取设备影子状态。
- 设备通过 Topic 上报最新状态到设备影子。如果携带 version ,影子系统则判断根据版本号决定是否更新,否则直接覆盖更新。
- 设备影子文件更新后,设备影子会返回结果给设备,即发送消息到设备订阅的 Topic 。
- 如果更新失败,设备则需要重新上报。
应用程序改变设备状态
设备在线时,应用程序更新设备期望状态到影子,设备影子将指令下发至设备,如果设备下线,则当设备上线时主动获取影子信息,根据期望状态进行更新。
- 应用程序通过 HTTP 或者 MQTT 更改期望状态,开启人脸检测
- 平台设备影子文件更新
- 影子文件更新成功后,下发指令给设备(需要重试机制),设备订阅 Topic 获取期望状态进行更新。
- 设备在线:根据上面的 desired 部分的值,将人脸检测进行开启。(设备可根据时间戳决定是否更新)
- 设备不在线:上线时主动获取设备影子期望状态进行更新
- 设备更新成功后,使用 Topic 上报最新状态到影子。影子更新成功后则返回结果给设备。(流程参考 3.5.1 设备上报状态)
- 设备上报完最新状态后,影子系统可自动清空 desired
1 | { |
设备主动获取影子内容
若应用程序发送指令时,设备离线。设备再次上线后,将主动获取设备影子内容。
- 主动发送以下消息到 Topic 中,请求获取设备影子中保存的最新状态。这里设备影子将只返回设备需要更新的期望状态。
- 当设备影子收到消息后,发送最新状态到 Topic 。
- 设备根据返回的数据决定是否更新设备当前状态,一旦更新设备自身状态,需要进行上报状态至设备影子,(流程参考 3.5.1 设备上报状态)
- 设备影子接收上报的最新状态后更新 JSON 文档,并返回设备更新结果。
设备主动删除影子属性
设备或应用需要删除影子则需要发送请求至设备影子,例如在重置设备时。
- 设备发送删除命令到 Topic,将需要删除的属性值设置为 null 。
- 设备影子接收到命令后,更新影子 JSON 文件,然后会返回结果给设备
版本控制
以下客户端指的是 SDK 或设备
设备影子服务支持在每个更新消息中进行版本控制。这意味着,每次更新影子时,JSON 文档的版本将会增加。这可以确保两件事情:
- 如果客户端尝试使用旧版本号覆盖影子,则会更新失败。需要先重新获取影子信息,然后才能更新数据。
- 如果消息的版本比客户端存储的版本低,客户端则可决定不对该消息执行操作。
客户端可以在影子文档中不包含版本以绕过版本匹配。
为了减少因版本冲突更新失败的问题,在版本校验失败时进一步校验属性的更新时间戳,如果时间戳符合更新条件(即时间戳大于修改该属性
时间戳时),则忽略版本冲突问题。