本文介绍了一个很常见的消息推送需求,在系统需要短信、微信、邮件之类的消息推送时,边界如何划分和如何设计技术方案。
1、需求
一个系统,一般会区分多个业务模块,并拆分成不同的业务系统,例如一个商城的架构如下(懒得画,网上找了一张):
里面会有各种服务,每个服务都会有一些消息通知逻辑,例如:
- 用户管理:
- 用户注册成功,需要推送一条站内信,欢迎用户并说明一些商城的介绍之类;
- 用户登录:如果判断此次登录存在风险,比如跟上次登录的IP、地域有变化,要发邮件或短信通知用户及时修改密码;
- 关注微信公众号:要给用户推送微信公众号消息表示欢迎;
- 库存管理:
- 库存不足时,给商家发短信和微信推送,提醒补充库存;
- 支付管理:
- 支付成功:给用户推送微信公众号通知成功;
- 支付失败:给用户推送微信公众号和站内信通知,并提供链接方便用户再次支付;
- 超时未支付:给用户发短信、推送微信公众号和站内信通知,提醒用户及时支付;
- 风控管理:
- 出现可疑操作时,给用户发短信、推送微信公众号通知等等,提示用户风险行为;
- 物流管理:
- 发货通知:推送微信公众号和站内信通知,提醒用户已发货;
2、边界思考
统一的消息服务搭建
基于这些消息通知的需求,为了避免每个模块都进行重复开发,很容易考虑到一个点,就是增加一个消息模块:
- 对外统一对接各种消息服务:
- 不同的邮箱服务,如网易邮箱、谷歌邮箱、微软邮箱等等;
- 微信推送服务,用户关注后记录用户的微信OpenID,以便给用户推送消息;
- 对接短信网关,根据公司需要,可能要对接多家短信服务商,以便故障切换或价格比对;
- 站内信通知:提供公司内部统一的,或整个系统统一的站内信通知能力;
- 其它:如手机移动推送能力、Facebook、Twitter、Instagram等等。
- 对内提供统一的接口,供所有模块调用和进行消息推送
- 通常为每个模块提供一个appId、appSecuret,以便进行接口鉴权;
- 统一接口输入字段一般包括:
- appId:用于识别是哪一个应用要发消息
- msgType:要推送的消息类型,如微信、短信、站内信、邮件
- msgDetail:要推送的消息对象,根据消息类型,定义不同的字段,如:
- 短信只有模板ID和占位符替换内容
- 站内信只有标题和内容
- 邮件有标题、内容、接收人、抄送人、附件、是否HTML邮件等等
- sign:根据上述字段+对应的appSecuret计算得到的签名信息,可以用md5、sha1等算法加盐实现
一般情况下,这个消息模块,会在公司级统一开发一个,这样就不仅仅是单个系统不重复造轮子,
甚至是整个公司的所有产品线,都不需要再重复造轮子了。
具体流程,可以参考我之前写过的一个短信登录的文章,里面画了一个流程图:https://youbl.blog.csdn.net/article/details/127124527
消息服务使用的具体实现
我见过的很多系统,基本到上一步就算模块拆分结束了,就继续每个模块的设计去了。
以支付服务为例,消息推送的流程简述如下:
有经验的程序员,知道这么设计会有技术隐患,消息服务接口出现问题时,会把支付服务搞挂,还会引入MQ消息中间件进行优化,如下图:
注意:这个MQ消息,跟上面说的消息不一样:
- MQ消息:是程序进行事件中转的一种消息机制,一般只提供给下游的程序,不会被用户直接观测到;
- 上面说的消息:指短信、邮件、站内信等,直接触达到用户的消息,是为用户提供信息的消息。
进一步问题发现
系统上线一段时间后,用户量大了,一定会有用户反馈,比如:
- 我不需要支付失败的通知啊,能不能关闭;
- 几块钱的支付成功消息就别推了,能不能超过100块才推消息;
如果只是支付服务,那很简单,在支付服务这边调用API时,做个判断处理就好。
但是加判断的时候,会不会感觉很别扭?一些非核心逻辑,会导致支付服务的代码不断变化?
而且其它的服务,慢慢也要增加类似的逻辑:消息推送开关、推送阈值判断等等。
这些类似的逻辑,因为分布在不同的服务代码里,无法重用。
有没有办法把这些代码合并呢?比如使用SDK的形式是否OK呢?
我们回到需求本身,再思考一下,比如支付服务,给用户发消息,是不是属于这个支付服务的业务范围呢?
支付成功给用户推支付成功的消息,看上去是支付业务。
但是:
支付成功,要给用户发货,我们知道这个发货不属于支付服务的业务范围,而是物流服务的业务,
一般实现是物流服务监听支付成功事件,触发发货事件,
那么推理一下,发消息给用户,也应该不是支付服务的业务。
那么,发消息给用户是消息模块的业务范围吗?很明显不是,消息模块,我们前面的定义,是对接各种消息渠道,减少各个业务对这些渠道的耦合,而消息要不要发,怎么发,明显不应该属于这个消息模块的业务范围。
我们能不能把消息开关、消息改善阈值处理,单独部署一个模块呢?把所有服务的“消息要不要发,怎么发”的逻辑全部合并在这里呢?
答案当然是可以,参考我们上次的餐厅排队发券需求,排队超时发券看上去是排队业务,实际上是营销业务,不应该影响排队服务的正常逻辑,
怎么发消息同样不应该让业务过多的关心,每个业务方,比如支付服务,只关心支付这个动作,并在相应节点抛出事件,就完事了,后续是要发货,还是要发消息,都是外部的业务,不应该让支付服务来操心。
3、最后的设计与实现
统一的消息服务
负责提供站内信通知能力,并负责对接各种消息渠道,如微信、短信、邮件等等,减少各个业务对这些渠道的耦合,具体参考上面的文字描述。
消息服务不仅可以提供API,甚至可以提供全局统一的消息查看界面,可以查看所有的消息类型,消息渠道,推送与否等内容;
我找了一张京东云的消息中心图片参考:
统一的消息配置中心
负责配置每种消息的发送与否,以及每种消息进行发送的阈值(下图没画),配置界面参考:
消息配置中心的主要工作流程如下:
注:这个图把所有业务的消息开关,全部集中在消息配置中心服务,并且因为消息配置中心是一个单独的服务,它拥有独立的数据库设计,而上面的支付服务虽然也使用了消费者,但是因为设计师把它当成支付服务的一部分,一般会直接复用支付服务的数据库,跟支付服务耦合在一起。