当业务系统的规模扩大时,也会增加系统架构的复杂度,在架构设计时对系统进行分层与解耦能够避免多个组件之间的性能不足、负载高、任务处理堆栈长及组件故障等风险。
系统进行纵向分层可以实现前后端的分离,如前端展示层、前端逻辑层、后端逻辑层、后端数据库层等,层与层之间通过API调用或函数调用的方式进行请求与响应;系统进行横向分层时按照不同业务类型等因素来划分,如新零售场景中的商品展示、订单处理、支付系统、物流系统等。
横向为技术方案、纵向为业务场景,如下图所示。
图 纵向业务场景与横向技术方案分离
@ 架构坏味道
系统组件紧耦合导致扩展难度大、组件可用性低,以及生产者和消费者互相等待会导致效率低等问题。
@ 最佳实践
将系统组件按照功能拆分,通过消息队列进行解耦,实现异步处理。
生产-消费原理
系统中的组件互相调用要避免紧耦合,背后的原理就是生产-消费原理。生产者持续生产商品,消费者消费商品,如果是紧耦合关系,消费者每生产一个商品就要串行等待消费者来消费,两者的生产和消费效率必须保持高度一致,如果生产速度快而消费者还没消费完,会造成生产者等待;如果生产速度慢而消费者已经完成任务处理,会造成消费者等待。解耦之后,生产者无须等待消费者消费,将生产的商品放到缓冲区中,消费者按照自己的节奏来消费商品,不受生产者的效率影响,只有缓冲区没有商品时才会影响消费者,生产-消费原理如图6-5所示。
图 生产-消费原理
生产-消费原理说明的是因生产者与消费者处理任务的时长不同、处理任务的能力不同等原因导致的生产过剩或消费过剩的现象,该原理并不复杂,却能够形象地表明解耦系统组件的必要性,可通过解耦来降低生产者和消费者之间的耦合/依赖关系。
实现异步解耦
为了避免业务逻辑链过长,避免任何一个环节出现故障、超时、错误都会影响整个业务逻辑的准确性,可把长链转换成多个功能职责单一的任务,不同任务之间相互调用并互相隔离,从而实现系统组件上的解耦。例如用户上传图片的功能,用户上传照片、对图片大小进行裁剪、对图片进行压缩、添加水印、将图片存储到对象存储中、将MetaData写入MongoDB,这些都可以封装为功能职责单一的逻辑。
解耦使得组件之间不再互相依赖,使得任何一层方便扩展。当对图片进行裁剪、压缩、添加水印等处理操作而引发服务器负载高时可分别单独扩展,任何一步操作相互独立,无须等待。每个处理任务的功能单一化,先加载数据,再按照预定算法进行处理,然后将数据存储,即可完成单个处理任务。
如下图所示,业务系统(生产者)的请求在消息队列中排队,并按照FIFO(First In FirstOut)的原则将消息推送给消费者,消息队列在其中起到了前后端请求解耦、缓存消息的作用。
图 通过消息队列实现异步解耦
实现削峰填谷
根据生产-消费原理,解耦后的系统组件可以实现削峰填谷的效果,如图6-7所示,负责生产的组件无须关注消费者的状态,当有任务时,生产者先处理任务,之后将任务传送到消息队列中,生产者继续生产任务。消费者按照任务优先级处理或按序处理,在消息队列中可能会有堆积的任务,消费者根据消息顺序持续处理,实现业务的削峰填谷。因为已经与生产者实现了解耦,所以不会影响生产者对前端请求的响应。
当前端流量有高并发情况时,会给后端服务节点及系统带来瞬时的高负载,当系统没有足够的防护措施时,会导致系统崩溃或服务器宕机,此时可通过消息队列解耦实现缓存前端请求的效果,实现削峰填谷,后端服务节点与系统可以保持在稳定负载下处理任务。
图 通过消息队列实现削峰填谷
订阅型、队列型消息队列
消息队列分为订阅型和队列型,这两种类型的消息队列分别有不同的特性和使用场景。
订阅型消息队列
一个或多个消息生产者在一个主题中发布消息,一至多个订阅者均会收到相同的订阅消息,每个订阅者互相独立。订阅型消息队列用于处理互不相关的任务,比如用户上传图片后的图片裁剪、图片MetaData信息抽取和存储,它们之间并没有依赖关系。
队列型消息队列
消息生产者将消息放置到消息通道中,并且消息之间有先后顺序,消息消费者按照先进先出(First In First Out,FIFO)原则来获取数据。消息仅用于消费一次,即消费一次后会在消息队列中移除该消息。适合串联进行的任务,比如为图片添加水印、图片压缩等,这些任务之间有先后顺序,不可并行。
在队列型消息队列中,消息有先后顺序,消费者按照顺序消费数据,不过存在以下几种可能,消息队列中的消息顺序和真正需要处理的顺序不一致,如消费者按照Step1、Step2、Step3的顺序来处理,但是消息队列中关于图片image0001的Step3的消息早于Step2,这就需要消费者能够按照业务顺序来选择消息,将未消费的Step3消息设置为未消费状态,并在一段时间后重试读取和处理Step3消息。如果消费者读取消息后就标记为“已读”或“删除”,则可能会在处理消息失败时丢失该消息,消费者处理完一个消息任务时应向消息队列发送“消息消费成功”的响应,这时在消息队列中将该条消息标记为“完成”,避免对该条消息进行重复处理。
常见的开源版本的消息队列有RabbitMQ、RocketMQ、Kafka等,云服务商也提供了基于这些开源版本的消息队列进行封装的产品,云平台版本与开源版本互相兼容,支持大部分功能和命令。