设计分析

​ 目前主流的web应用服务, 一般都是采用无状态的Restful API 接口, 所用操作都基于HTTP请求完成. 一些外围设备(比如手机、电器、机顶盒等等)与API接口交互获取数据, 都是主动请求形式. 换言之, 所有后台的修改(修改配置、开关服务等等), 终端都只能在下一次请求时进行更新, 存在一定有时差性.

那么现在这种终端主动请求的方式不能完全满足业务发展需求, 迫切需要一种可靠的实时的消息推送服务.

对于该消息服务,需要尽可能满足以下条件:

  • 网络资源消耗低, 机台工作环境低带宽
  • 轻量级易集成, 代码改造小,尽量以模块形式
  • 支持跨平台, 需要支持Android、IOS等等

基于上述要求, MQTT这种轻量级的物联网协议消息服务可满足.

关于MQTT

MQTT是一个物联网传输协议,它被设计用于轻量级的发布/订阅式消息传输,旨在为低带宽和不稳定的网络环境中的物联网设备提供可靠的网络服务.MQTT是专门针对物联网开发的轻量级传输协议.MQTT协议针对低带宽网络,低计算能力的设备,做了特殊的优化,使得其能适应各种物联网应用场景.目前MQTT拥有各种平台和设备上的客户端,已经形成了初步的生态系统.

​ MQTT的设计思想是开源、可靠、轻巧、简单,MQTT的传输格式非常精小,最小的数据包只有2个比特,且无应用消息头.MQTT可以保证消息的可靠性,它包括三种不同的服务质量(最多只传一次、最少被传一次、一次且只传一次),如果客户端意外掉线,可以使用“遗愿”发布一条消息,同时支持持久订阅.MQTT在物联网以及移动应用中的优势有:

  • 可靠传输.MQTT可以保证消息可靠安全的传输,并可以与企业应用简易集成.
  • 消息推送.支持消息实时通知、丰富的推送内容、灵活的Pub-Sub以及消息存储和过滤.
  • 低带宽、低耗能、低成本.占用移动应用程序带宽小,并且带宽利用率高,耗电量较少.

设计说明

本文设计是针对一种终端为无人售货机的推送,所以围绕着配置管理价格更新库存更新这些常见服务.

同理无论终端手机机顶盒或者共享单车等等一些具有物联性的设备, 都可以根本改设计思路来进行改良, 本文仅仅做抛砖引玉.

设计时围绕以下几点进行:

  • 中立性
    • MQTT服务, 属于终端与服务端的中间件(类似现实生活中的邮局)
  • 无角色
    • 无论终端或者服务器都可以是消费者生产者 (任何人或者单位都是邮寄和收件)
  • 非匿名
    • MQTT服务使用非匿名模式配合访问控制列表(ACL), 即只有提供认证的设备对权限内的TOPIC进行读/写
  • 可控性
    • 多设备接入时, 消息可以精准推送到某一设备或某一组设备(多个)

特别是可控性 部分, 这是设计的关键.

每个终端设备都有唯一设备号(device_id), 多种设备可能属于不同的用户或者部门(operator_id).

像设计API一样设计topic, 那么可以实现出公有私有两种topic

ACL配置如下

1
2
3
4
5
6
7
user device
pattern read /devices/%c
pattern read /operators/#

user server
pattern write /devices/#
pattern write /operators/#

终端设备使用device用户, 登录时device_id作为mqtt 会话中的client_id, 使用该用户具有两个主题的读取权限.

服务端使用server用户, 使用该用户具有两个主题的写入权限.

代理选型

​ 目前支持MQTT协议的消息代理比较多,包括Mosquitto, RabbitMQ等等.参考性能分析MQTT Broker Performance ,最终决定选型MOSQUITTO.

  • 性能优秀
  • 支持多Broker桥接
  • 开源代码(SDK支持C/Python/Go/Java/Nodejs 等等)
  • 丰富的官方文档支持

架构拓扑

1
2
3
4
5
Device->MQTT: subscribe topic
Note right of Server: call the APIs
Server-->MQTT: public message
MQTT-->Device: receive message
Note left of Device: logical processing

使用说明

​ 设备通过MQTT客户端PAHO-MQTT发起连接消息代理, 根据配置指定账号密码作为认证, 成功连接后分别订阅/devices/<device_id>/operators/<operator_id> 两个topic.其中设备topic为当前机台私有,仅收到发给自己的消息, 运营商topic为设备共有, 同一运营商下的所有机台都会收到相同消息.设备对公私有topic都只具有读取权限.

以更新商品价格为例, 设计推送私有消息例子:

1
2
3
4
5
6
7
8
{
"uuid": "0a71b58182324abcb8453bbed2340d2b",
"method": "device.product.put",
"response": {
"id": "78bfa0d163114ffb9458ee11015e5c0f",
"price": 200
}
}

  • uuid 本次消息的唯一ID, 服务器与终端都可以记录保存.
  • method 像API一样, topic是统一的接口网关, 那么这里就是具体接口描述
  • response 本次消息的内容

由于消息跨平台, 我们不希望消息以明文的方式传输, 于是进行base64编码, 这样可以保证消息全部为可见字符, 避免了在不同语言环境下因为字符ACSII引起的各种问题.

那么消息例子进化为

1
eyJyZXNwb25zZSI6IHsicHJpY2UiOiAyMDAsICJpZCI6ICI3OGJmYTBkMTYzMTE0ZmZiOTQ1OGVlMTEwMTVlNWMwZiJ9LCAidXVpZCI6ICIwYTcxYjU4MTgyMzI0YWJjYjg0NTNiYmVkMjM0MGQyYiIsICJtZXRob2QiOiAiZGV2aWNlLnByb2R1Y3QucHV0In0=

总结

像设计Restful API风格 一样设计 TOPIC