利用消息队列MQTT,打造一款属于自己的IM社交软件
MQTT 是一种基于发布订阅模型的即时通讯协议,由于轻巧,开源,易用,耗能少,支持 QOS/遗言(WILL)等特性,正被广泛应用于物联网和移动互联网。
消息队列 MQ 提供了对 MQTT 协议的支持,完全兼容 MQTT 标准协议,但是在使用 MQ MQTT 时,对比标准协议,需要注意两点:
- 父级 Topic 需要提前创建
根据标准 MQTT 协议,Topic 存在多级,且拥有动态的特性(不需要用户提前定义和创建),但是使用过消息队列 MQ 的用户都知道,MQ Topic是需要通过 MQ 控制台提前创建的,这是因为 MQ MQTT是队列(消息持久化),且 MQ 不仅支持 MQTT 协议,还支持 TCP, HTTP 协议的接入,为了能将不同方式接入的消息互通,比如:用 MQTT 方式发送的消息,用 TCP 方式可以收到;用 HTTP 方式发送的消息,用 MQTT 方式也能收到,故 MQ MQTT 做了以下变通:
定义第一级 Topic 为父 Topic,使用前,需要在 MQ 控制台创建父 Topic,子级 Topic 无需创建,直接在代码中使用。
- 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 的反馈。
image.png
场景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 的黑名单),就完全由应用设计来决定了。
image.png
场景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)。
image.png
不想 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)。
image.png
用户登录客户端时,业务应用就需要预先判断其属地,并订阅相应的预留 Topic。
这种方案其实不够灵活,因为当后续扩展新业务功能时,程序需要升级(订阅新的 Topic 来满足新业务),但就目前来说,这也不失为一种可行的解决方案了。
轻量级,够灵活,开源易用,让您轻松,快速打造出一款即时通信应用APP, 心动了没?
原文链接:https://yq.aliyun.com/articles/68390
原链接在国内看不了图片