目录
- 背景
- 消息推送
- 整体架构
- SDK
- 最终一致性保障
- 消息推送集群
- emqx
- MQTT技术选型
- 原理
- 支撑
- mysql异构一致性保障
- 参考文章
系列总目录
背景
- 将消息推送到pc端,移动端设备以达到提醒用户的目的,比如常用的站内信推送,微信消息推送等。
- 推送服务需要实现各个业务线无缝对接,这就要求拓展性要强,通知方式也需要多样化,比如上述的站内信,微信推送等
- 推送能力要支持高并发
- 推送平台要支持统计监控,可查历史数据
- 需支持,近实时推送,定时推送(比如运营需要在某个时间点推送),合并推送(不重要的消息合并为一条,推送过去)
消息推送
- 本问讨论的是理论上并发量高需要支持的架构,实际业务中会有很多简化,比如数据一致性要求不会那么高,那就会在文章分析的架构基础上进行简化。
整体架构
- 业务侧: 各个业务侧根据需要,接消息推送SDK发送需要推送的消息
- 消息推送SDK: 消息推送SDK会将需要推送的消息发送到MQ, SDK还提供了本地消息表来保证MQ发送的最终一致性
- 消息推送集群: 消费业务侧的MQ消息,并将消息推送到EMXQ集群,或者直接推送到第三方应用
- 消息推送控制台: 提供消息统计,查询等后台能力
- EMQX集群: 实现MQTT协议的集群,推送公司产品移动端,PC端应用的底层实现。公司产品移动端,PC端应用需要实现连接EMQX
- 支撑: 消息推送结论链路追踪Skywalkin; 监控Grafana以便更好的排查问题; 每个应用发送的消息都将会持久化到mysql; 出于运营复杂多样的统计需要,这里会利用DTS监听mysql binlog来进行异构同步到ElasticSearch
SDK
最终一致性保障
- 消息推送的场景分两种,一种是相对不可丢的推送,还有一种类似营销推送是允许小范围的对数据的。对于不同的业务场景,SDK提供了的数据一致性保障。
- mysql本地事务表一致性保障: 业务方执行本地事务时加上sdk提供的mysql本地消息表记录状态,执行发送mq前记录本地消息表并将状态设置为初始状态。mq发送完成时,更新本地消息状态,发送失败也更新本地消息表状态。
- mysql本地事务表捡漏线程: 不断轮训某段时间数据库状态,有问题数据重发送达到最终一致性。
-
未来版本支持更高性能的并发: 有些业务侧对性能要求很高,并且发送消息推送数据不可丢,这时mysql本地事务表就有可能是性能瓶颈。这里可以参考美团到家的架构设计思想: SDK提供同步lua脚本到redis,然后redis调用成功发送消息MQ,如果提交异常则发补偿MQ。捡漏服务监听redis的AOF SDK数据流水,然后进行校验处理。这种方法性能很高,但是实现成本也比较高。目前业务侧的最终一致性保障是通过第一种方案mysql本地事务表。
消息推送集群
- 消费业务侧的MQ消息,并将消息推送到EMXQ集群,或者直接推送到第三方应用。会有推送数据持久化操作,并会将mysql数据异构到es以便运营侧查询。后面文章有提到。
emqx集群
MQTT技术选型
- mqtt几乎目前生产的IoT设备,消息推送相比web socket更合适loT设备的兼容处理,也有很完善的开源集群解决方案。
对比项 | EMQ | RabbitMQ | Mosquitto | ActiveMQ |
---|---|---|---|---|
开源机构 | 杭州映云科技有限公司 | Rabbitmq团队 | Eclipse | Apache |
语言 | Erlang | Erlang | C/C++ | Java |
活跃度 | 很高 | 很高 | 高 | 一般 |
管理界面 | 提供,功能全面 | 提供,功能较全面 | 无 | 无 |
规则引擎 | 支持(多种方式,部分商业化) | 不支持 | 不支持 | 不支持 |
- 综合emqx丰富的文档,以及控制台,优秀的集群功能,最终选择emqx当做mqtt实现。当然emqx也支持客户端连接时身份认证,认证成功后才可以订阅消息
-
规则引擎的支持, 新建规则引擎是能支持对数据清洗筛选处理,然后响应到比如web服务器,比如mysql。进一步对要发布的事情进行处理。
原理
-
EMQ X 分布式的基本功能是将消息转发和投递给各节点上的订阅者,可以采用无中心化架构设计mcast组播方式。也可以采用etcd自动发现
- 实现消息转发投递,即要推送到哪些符合条件的客户端是由几个与之相关的数据结构:订阅表,路由表,主题树相互配合实现。
- 订阅表: 主题 - 订阅者,假设现在有两个broken node1和node2,topic是主题,这里topic类似mq的topic,发送者发送消息到topic, 然后topic再到订阅者。client是客户端
node1:
topic1 -> client1, client2
topic2 -> client3
node2:
topic1 -> client4
- 路由表: 主题 - 节点
topic1 -> node1, node2
topic2 -> node3
topic3 -> node2, node4
- 主题树: 带统配符的主题匹配, eg如下,订阅完成时EMQ X 中会维护如下主题树 (Topic Trie) 和路由表 (Route Table):
客户端 | 节点 | 订阅主题 |
---|---|---|
client1 | node1 | t/+/x, t/+/y |
client2 | node2 | t/# |
client3 | node3 | t/+/x, t/a |
- 消息派发过程
- 例如 client1 向主题 t/a 发布消息,消息在节点间的路由与派发流程:
- client1 发布主题为 t/a 的消息到节点 node1
- node1 通过查询主题树,得知 t/a 可匹配到现有的 t/a、t/# 这两个主题。
- node1 通过查询路由表,得知主题 t/a 只在 node3 上有订阅者,而主题 t/# 只在 node2 上有订阅者。故 node1 将消息转发给 node2 和 node3。
- node2 收到转发来的 t/a 消息后,查询本地订阅表,获取本节点上订阅了 t/# 的订阅者,并把消息投递给他们。
- node3 收到转发来的 t/a 消息后,查询本地订阅表,获取本节点上订阅了 t/a 的订阅者,并把消息投递给他们。
支撑
- 应用发送的消息都会被消息推送集群持久化到mysql,出于运营复杂多样的统计需要mysql数据会被同步到es等异构中间件
mysql异构一致性保障,弱一致性保证方案
-
这里提供了两种方案协同保障数据不丢从而保证数据高可用,数据的更新顺序是通过版本号来控制的,不支持删除操作,删除也是逻辑删除。
- 消息推送集群:业务方mq发送的推送消息消费
- 消息推送集群: 持久化数据到mysql
- 消息推送集群:异构数据mq发送
- 异构服务集群: 消费消息推送的mq,进行异构数据处理
- DTS集群: 监听binlog变动,这个是兜底,监听的变动后发送到异构消息mq集群,集群通过时间戳判断过来的id是否有更新过
-
这里没有使用事务包裹mysql持久化和发mq,因为这种方案有个弊病,发mq消息之后可能被异构数据马上消费,但是这里事务并提交,有点构造函数this逃逸的概念。
mysql异构一致性保障,强一致性保证方案
- 对于一些比如金融的业务场景,需要保证缓存的强一致性处理
-
最常见的思路
-
当缓存读取和DB更新同时时,有可能会造成不一致性,如下图更新操作时,DB更新完之后,比如线程切换之类的导致删除redis有一定的延迟。这时来个查询操作,查到缓存存在返回老缓存。
-
当缓存更新与DB更新并发时,也有可能有问题,查询DB数据后,因为线程切换导致更新操作执行完,这时候查询操作再更新缓存就会造成不一致。
- 强一致性的缓存mysql更新需要加锁处理
-
redis分布式锁保证更新查询一致性