MQTT 是一种基于发布订阅模型的即时通讯协议,由于轻巧,开源,易用,耗能少,支持 QOS/遗言(WILL)等特性,正被广泛应用于物联网和移动互联网。
消息队列 MQ 提供了对 MQTT 协议的支持,完全兼容 MQTT 标准协议,但是在使用 MQ MQTT 时,对比标准协议,需要注意两点:
1. 父级 Topic 需要提前创建
根据标准 MQTT 协议,Topic 存在多级,且拥有动态的特性(不需要用户提前定义和创建),但是使用过消息队列 MQ 的用户都知道,MQ Topic是需要通过 MQ 控制台提前创建的,这是因为 MQ MQTT是队列(消息持久化),且 MQ 不仅支持 MQTT 协议,还支持 TCP, HTTP 协议的接入,为了能将不同方式接入的消息互通,比如:用 MQTT 方式发送的消息,用 TCP 方式可以收到;用 HTTP 方式发送的消息,用 MQTT 方式也能收到,故 MQ MQTT 做了以下变通:
定义第一级 Topic 为父 Topic,使用前,需要在 MQ 控制台创建父 Topic,子级 Topic 无需创建,直接在代码中使用。
2. MQ MQTT 支持 p2p,且不需要显示订阅
这一点弥补了标准 MQTT 协议不支持 p2p 的遗憾。而且通过 TCP/HTTP 接入的客户端都可以收发 p2p 消息。
MQTT Topic的动态特性给设计者们带来了很多发挥空间,但是有些地方需要注意:
1)名称不能带/和空格
2)短小精干,简洁明了
3)使用 UTF-8,避免不可见字符
4)在一些特殊场景里,考虑将唯一设备号嵌入到 Topic 名称,便于辨识消息发送者
比如,只有 client1 可以发布消息到client1/status, 且不能发布到client2/status,那么当你收到client1/status时,可以肯定就是client1发送的消息。
5)谨慎使用通配符#
比如,订阅 news/sport/#, 那么你就会收到下面所有这些 Topic 的消息
news/sport
news/sport/player1
news/sport/player2
news/sport/player1/score
不可预计的消息数量,会对接收端(移动端,嵌入式设备)造成负担。
6)业务可延伸
订阅关系需要事先建立,所以发送端和接收端都需要提前意识到 Topic 的存在,这就要求我们合理设计 Topic 的结构,在增加新业务需求时,适当增加子级 Topic 即可,而不用修改,甚至推翻 Topic 原先的结构。
比如,订阅李娜的新闻,可以设计成订阅 news/lina, 但是文体娱乐圈里有很多和李娜重名的,后续业务需要,用户想订阅网球李娜的新闻时,整个 Topic 结构将被迫改变,所以,在业务设计之初,尽可能在延伸性上多考虑一些。
这样设计较之前就更稳妥些:
news/sport/tennis/lina
news/sport/tennis/lina/ranking
news/sport/tennis/lina/score/australianopen
下面结合 MQ MQTT 特点,提供一个社交 IM 场景下的实现雏形,欢迎大家讨论。
准备工作:在 MQ 控制台创建父级 Topic 和 GroupID
每个设备接入 MQ MQTT 服务时,都要提供 Client ID,这是每个客户端的唯一标识,要求全局唯一。
Client ID 由两部分组成,组织形式为 GroupID@@@DeviceID。
GroupID: 需要在 MQ 控制台申请创建,用于指定一组逻辑功能完全一致的节点共用组名,代表一类相同功能的设备。
DeviceID: 每个设备独一无二的标识,由业务方自己指定,无需在 MQ 控制台创建。
我们在 MQ 控制台创建父级 Topic:IMS 和 GroupID:GID_IMS,且假设
用户A使用设备GID_IMS@@@DeviceID_A
用户B使用设备GID_IMS@@@DeviceID_B
用户C使用设备GID_IMS@@@DeviceID_C
场景1,用户A请求加用户B为好友,并进行一对一聊天
方案一,使用 p2p 消息。
优点是无需双方提前订阅(其实 p2p 功能也是通过订阅来实现的,只是用户在使用时无感知),缺点是如果DeviceID_B同时收到多个好友申请(见2),如何分辨是谁发送的请求呢?
从消息 Topic 是无法得知的,只能通过解析 message body 来辨识。
注意,p2p 消息,二级 Topic 必须是 p2p 字样,三级Topic是目标设备的 Client ID。
方案二,给每一个用户设计一个 Inbox,每个用户在客户端登录时都要订阅自己的 Inbox。
优点是每个用户收到消息时,可以通过解析 Topic 知道是谁要加他为好友,也能知道谁通过了他的好友添加请求。
当 USER B 收到消息 IMS/UserB/Inbox/Add/GFReq/UserA (见4),就知道是来自于 USER A 的好友添加请求。
当 USER A 收到消息 IMS/UserA/Inbox/Add/GFResp/UserB (见6),就知道是来自于 USER B 的反馈。
场景2,用户A关注好友B动态,B更新朋友圈,A收到更新
用户 A 想关注好友 B 的朋友圈动态,可以订阅 IMS/UserB (见1)。
好友 B 有好东东想晒一下,可以发布 IMS/UserB (见2),发布之后,所有订阅 IMS/UserB 的好友都可以看见这条分享(MQ MQTT 会负责将消息发送给所有订阅的好友)。
如果 A 不想看 B 的朋友圈动态了,那么就取消订阅 IMS/UserB (见4)。
如果是 B 不想让 A 看见自己晒的东东,该怎么办呢?
也可以利用 Inbox。
用户 A 登录时订阅 IMS/UserA/Inbox/Update/# (见2)
用户 B 发送一条消息到 IMS/UserA/Inbox/Update/UserB/Unsub (见3),A 在收到这条消息后,取消订阅 IMS/UserB (见5),在此之后,B 在朋友圈所有的分享,好友 A 都是看不到的。
当然,A 在收到 B 的取消订阅请求(见4)后,是弹窗通知到 A 本人(让 A 知晓被 B 拉黑)还是静默(A 被默默拉进 B 的黑名单),就完全由应用设计来决定了。
场景3,用户A邀请B,C好友进行群聊(B,C非好友关系)
群聊创建者 A 首先要订阅 IMS/Group123/# (见1)。
邀请用户 B, C 加入群聊,可以使用 p2p 消息(见2,4)。
用户 B, C 同意加入群聊,订阅 IMS/Group123/# (见6,7)。
加入群聊组中的成员 B 想要发言,可以携带自己的身份信息发布到 IMS/Group123/UserB (见8), 这样一来,所有订阅 IMS/Group123/# 的成员都能看到 B 的这条发言了。
场景4,逢年过节,给多个好友发送祝福信息(群发消息,不是群聊噢)
可以逐一给好友发送 p2p 消息,或者发送消息到各好友的 Inbox 中。
这种方式的缺点是需要多次 Publish,有几个好友,就需要 publish 几次(见4,6)。
不想 publish 多次,该怎么办呢?
Sorry,目前确实没有更好的办法。
其实我们希望可以创建一个虚拟 Group,好友们不用显示订阅这个 Group,由系统根据业务配置动态创建订阅关系。完成的效果是,好友们能收到发送给这个 Group 的消息,但是不会意识到这个 Group 的存在,因为客户端没有发起过订阅虚拟 Group 的动作(原理类似 p2p 订阅)。
目前 MQ MQTT 还不支持这样的功能,我们期待后续它可以提供这样功能的 Plugin,方便业务端扩展更多功能。
场景5,商业推广,系统给不同种类/定位的用户推送消息
想给不同地域的用户推送系统的新年祝福,消息的内容根据用户地域不同而变化。
正如之前的群发场景一样,使用 p2p 或者 Inbox 固然可以做到,但是整个系统,那么多用户,逐一发送并不是理想的方式。
而目前 MQ MQTT 又不支持虚拟 Group,如何完成这个功能呢?
业务设计之初,预留一个给系统广播使用的 Topic, 比如 IMS/System,每一个用户除了订阅自己的 Inbox,好友之外,也要根据自己的属地订阅系统预留的 Topic (见1,2)。
如:
北京用户订阅 IMS/System/Beijing
杭州用户订阅 IMS/System/Hangzhou
系统发送一条消息到 IMS/System/Beijing(见3), 所有北京用户都会收到(见4),系统发送一条消息到 IMS/System/Hangzhou(见5), 所有杭州用户都会收到这条消息(见6)。
用户登录客户端时,业务应用就需要预先判断其属地,并订阅相应的预留 Topic。
这种方案其实不够灵活,因为当后续扩展新业务功能时,程序需要升级(订阅新的 Topic 来满足新业务),但就目前来说,这也不失为一种可行的解决方案了。
轻量级,够灵活,开源易用,让您轻松,快速打造出一款即时通信应用APP, 心动了没?