作用 : 解耦,异步,并行
假设我们有这么一个应用场景,为了完成一个用户注册淘宝的操作,可能需要将用户信息写入到用户库中,然后通知给红包中心给用户发新手红包,然后还需要通知支付宝给用户准备对应的支付宝账号,进行合法性验证,告知sns系统导入新的用户等10步操作。
那么针对这个场景,一个最简单的设计方法就是串行的执行整个流程,如图1所示:

图1 : 用户注册流程 - 串行流程
这种方式的最大问题是,随着后端流程越来越多,每步流程都需要额外的耗费很多时间,从而会导致用户更长的等待延迟。自然的,我们可以采用并行的方式来完成业务,能够极大的减少延迟,如图2所示。

图3 :主流程

图4 : 异步流程

整体流程
解耦 : 消息发送方和消息消费方解耦
异步&并行 : 消息消费之间异步&并行
Notify的设计理念和传统的MQ有些不同,Notify的核心理念是
notify消息领域对象的设计如下图所示,每条消息记录包含messageId(这个就是key)、topic、body(二进制)、header(用户自定义kv集合)、commited(事务状态,是否提交)、Fail target(记录失败的消费者id,消息重试要用到)。


** 对于事务消息,区别之处在于消息生产环节**。生产者先发送half消息,当notify收到half消息后,先进行持久化,在kv存储增加一条half(即commited字段为false)记录,然后ack给生产者,此时并不会触发推送消费者事件。生产者half发送成功后,执行本地事务,事务执行成功后,发送异步commit请求。notify收到commit请求后,将消息的commited字段更新为true,然后就立马执行推送消费者的流程,消费相关逻辑和普通消息无异。

注意点 : notify事务消息,第5步commit是异步的吗? commit之后第6步 notify才写KV,如果写失败了,生产者的本地事务还是会提交的。这样的话怎么保证消息会发出去呢?是因为会定期扫描没有commit的数据,然后回调生产者的接口询问数据是否需要comit吗?
1 第5步commit是异步的
2 会扫描未commit的消息回查生产者,达到最终一致
从07年到现在,notify采用的kv存储模块经历过多次改造。最早notify的消息存储是采用本地文件存储的,参考了ActiveMQ的kaha实现了单机kv存储引擎,在07年12月上线,支撑直冲业务,日均500w消息。然而考虑到交易消息重要性,消息要保证不能丢失,第一个版本的单机存储引擎没有经过多年生产环境的验证,不够成熟,另外也没有多副本机制,无法满足交易对消息可靠性的严苛需求。到了第二个版本,notify开始采用当时最稳定的关系型数据库oracle来做消息存储,在08年3月份发布,支撑核心交易链路,日均1000w消息。随后集团开始启动去O战略,数据库存储需要全面从oracle迁到mysql,为了弥补mysql和oracle的性能和稳定性差异,notify实现了多点高可用mysql存储集群,全部迁移到mysql存储,10年4月完成。由于多点mysql在存储层具备了无状态横向扩容的能力,在稳定性和性能方面许久未遇到瓶颈,所以一直使用了很多年。

图5 : Notify系统组成结构