导语:
当一个O2O电商系统到达一定规模之后,就需要考虑系统的可扩展性、松耦合和组件化。一般采用的都是基于时下比较流行SpringCloud和Dubbo的分布式的微服务的架构模式,虽然模块间能够独立部署了,但是模块间的还是强依赖关系,每次改动都需要重新发版上线,产品迭代速度又快,就造成了每次上线心里都唱忐忑。
基于上面这个背景,系统需要做重构,需要解耦,因此我增加了消息总线模块,使用异步消息队列和标准组件来实现了模块间的强耦合和快速的开发迭代。
现有总线设计的问题
一般的消息总线是直接使用MQ来连接上下游的模块。以O2O电商系统为例,一个订单支付完成以后需要发送通知下游的积分模块给用户送积分,优惠券模块给用户送优惠券,微信模块给用户推送微信消息,短信模块给用户发送短信等。上下游直接通过MQ服务器建立起了消息生产和消费的关系。
1.下游消费端必须增加MQ消息消费处理代码,代码会比较复杂,开发者必须掌握MQ消费端开发。
2.当下游有多个消费端时,消费端每个模块需要生成一个消息对列,增加了MQ服务器的压力。
3.当下游多个消费端执行需要有先后关系时,比如优惠券发完了才能推送微信消息,这样就大大增加了系统设计的复杂程度。
优化设计
针对以上几个问题对消息总线系统进行了优化设计,看下图:
通过这种实现实现了一个下几个特点:
1.模块间实现了松耦合的机制,各业务模块间只跟消息总线系统集成,简化开发。
2.消息确保必达,消息到达任何一端都要进行确认,出现网络问题或其他异常能够重试。
3.保证消息处理的幂等,不能因为重复消息导致重复执行相同的操作。
三个基本概念
1.事件消息,如下单事件、注册事件等,事件有唯一的编号,保证任何系统收到。
2.消费操作,如送积分、送优惠券、微信消息推送、短信发送、PUSH推送等等。
3.执行日志,记录每个消费操作执行的结果,失败可以通过定时任务重试。
快速扩展集成
为什么增加独立的消息生产模块?
因为前端业务模块比较多的时候,只能对消息的内容本身形成一个标准,很容易出现问题,增加一个消息生产模块对外提供标准的API接口,前端业务调用按照说明添加参数即可,开发人员也不需要掌握MQ的客户端开发的相关技术。
为什么增加独立的消息消费模块?
因为业务需求变化较快,后端消费模块会经常增加新的模块或停用已有的模块,提供标准的消费接口供消费模块直接实现,能够快速的实现。
保证消息不丢失
消息产生模块如何保证消息不丢失?
保证必须产生前端模块发起消息生产的调用,由消息生产组件再发送消息到MQ,这个时候(2)如果未返回成功或网络超时就重复发送,消息生产模块与MQ之间的调用也是这样,(4)未正常返回或者网络超时(2)是返回未成功,需要重新发送消息。如果(4)返回成功就说明MQ持久化完成了(这里消息采用持久化方式),到此阶段消息发送完成。消息消费模块如何保证消息不丢失?监听MQ队列消息,如果有消息来就存储到db中,并ack给MQ,确定消息收到了,然后根据数据中配置的模块调用后端的模块异步调用后端模块,每一个操作调用都记录到db中,对于因为网络或者异常未执行成功的,由定时任务定期检查db中执行的状态,未成功的调用的消费操作继续调用执行,直到消费成功。幂等性设计
在消息生成模块,因为消息未发送成功重试造成消息的重复,如果消息消费和后端业务不做幂等性设计就会业务数据产生影响,如用户下单完成可能送多次积分或者优惠券。
为了避免相同一个事件消息的重复的消费,就需要每条消息都有一个唯一的MSGID,在消息消费模块保存到db时对消息进行唯一性的验证,保证事件消息不重复。同样,后端的业务模块在实现消费接口时同样要做幂等处理,保证相同的事件消息不重复执行,如果已执行返回成功。
延时消息实现
电商系统中一般在下单多长时间内不支付订单会自动取消,这是一个典型的延时消息,因为我采用的MQ是ArtemisMQ本身天然携带延时消息的功能,所以在这个系统中我采用的是ArtemisMQ的延时消息,到指定时间自动让消息生效,在消息消费模块执行。对于已经成功支付的订单,在支付成功时增加一个删除延时消息的消费操作,减少系统的调用,删除使用的就是消息的MSGID。
如何流量削峰
因为ArtemisMQ本身已经实现了消息的缓冲,在消息消费端通过设定固定的线程数,就能实现消息消费的固定的消费速度,保证消息消费不会受到性能的冲击。
技术架构实现
使用SpringCloud+Dubbo来实现整个架构的搭建,SpringCloud用于外部rest服务的实现,Dubbo用于内部接口的调用的实现。提供消费总线接口模块,提供公共的接口类和实体类供消费生产模块和消息消费模块使用。下图为模块依赖关系图。
消息生成模块采用Dubbo实现,前端业务模块通过Dubbo接口调用消息生成接口即可,根据要求传递对应的参数即可完成。消费操作的实现采用Dubbo的RPC来实现,使用的是Dubbo的可以动态生成消费者的特点,消息消费模块接收到消息后会从数据库中读取关联的消费操作列表,然后循环调用每个消费操作,每一次操作执行都要记录操作日志并记录结果。
通过以上几点消息总线架构基本上设计并实现完成了!你有什么好的建议和意见吗?