在介绍消息队列之前,先看看消息队列在实际业务场景中的简单使用。
流程A在处理时,没有在当前线程同步的处理完,而是直接发送一条消息A1到消息队列中,然后消息队列过了一段时间,这段时间可能是几毫秒、几秒甚至几分钟都有可能,这个消息开始被处理,消息被处理的过程,其实就相当于流程A被处理,这是一个简单的模型,我们套上一个实际的业务场景来看一下。
例如:
业务需要下单成功时,给用户发送短信提示下单成功,如果没有消息队列的话,我们会选择同步调用发短信的接口并等待短信发送成功,正常情况下这看着没什么问题,但这时候我们可以假设一下,在发短信的中间出现的问题,比如短信接口实现出问题了或者说短信调用端超时了,又或者短信发送达到上限了等等情况。这时候我们是选择重新试几次还是放弃发送呢,还是选择把这个信息放入数据库,过一段时间再尝试看看呢?不管怎么样,这里的设计都会变的很复杂,当我们使用了消息队列会变成什么样子呢。
我们假设流程A就是要发短信的这件事情,这时候我们可以把发短信这个操作封装成一条消息发送到消息队列里,消息队列按照一定的顺序处理队列中的消息,某一个时刻开始处理刚收到的这个消息,它会通知一个服务去发送一条短信。顺利的话,刚放进队列的马上会处理,处理时,一次性就发成功了,这样流程就ok了。处理消息时,如果出现什么问题呢?比如刚刚说的短信接口出现问题了、超时了。这时候它可以选择把这个消息重新放到消息队列中等待处理,当然这里也有一些细节需要处理,比如在消息里面指定最少的执行时间、这样如果短信不断的发送失败,尝试的次数会少一些,不至于一直在循环尝试,另外还需要控制一下这里处理消息的速度,用刚才的场景来说,就是控制每秒发送短信的上限。还是有很多需要考虑的。
首先,我们通过消息队列完成了一个异步解耦合的过程,短信发送时,我们保证短信发送到消息队列中就可以了,接着就可以继续做发送短信后面的各种操作,其次很明显的一点,我们的设计变的更简单了,我们不需要在下单的这个业务中过多考虑短信的问题,而是直接把短信发送给队列就可以了。另外我们通过这里面的消息保证了最终一致性,我们通过这样的设计,保证了这条短信肯定会通知到用户,即使当前短信发送有问题,我们也可以通过消息队列来保证短信服务恢复后,将短信发送给用户,只是不那么及时而已。最后一点,我们假设短信发送完成之后,还要发送邮件,有了消息队列,我们就不需要做同步等待了,可以直接做并行处理,下单的核心流程可以更快的结束。这样就很容易增加业务系统的异步处理能力,减少、甚至不可能出现并发现象。
有时候我们在网站上需要输入手机号获取验证码时,有时等了半天验证码还没有收到,算上时间,其实接口已经超时了,这时候有可能后台就是通过消息队列的方式来发送验证码短信,可能这时短信发送出现了问题,或者服务器的网络开了小差,或者消息队列里消息太多了。
消息队列的场景特别多,就拿下单而言,可以在提交订单时,基本操作做完就提示订单已提交。然后发送一条创建订单的消息发送到消息队列中,通过处理消息队列里的消息,去推进订单的状态,处理的过程中可以发送其他消息到消息队列里,随着消息的处理,用户在前端刷新订单状态时,就可以看到订单的实时状态了,有了这种设计,用户就不需要在提交订单时,一直等在那里。再比如我们在携程、去哪里等网站购买机票,其实是从第三方航空公司出票,而跟第三方网站的交互,通常不是在一个系统里进行的,这时候就可以选用消息队列来推进订单的处理了。
1. 业务无关:只做消息分发
一个具有普适性质的消息队列组件,它不需要考虑上层的业务模型,它只要做好消息的分发就可以了 。上层业务的不同模块反而需要依赖消息队列所定义的规范来进行通信。
2. FIFO:先发送先到达
3. 容灾:节点的动态增删和消息的持久化
4.性能:吞吐量提升,系统内部通信效率提高
生产 和 消费 的速度或稳定性不一致
消息队列作为抽象层,可以弥合双方的差异。消息是在两台计算机间传送的数据单位,消息可以是字符串,也可以是嵌入的对象,消息被发送队列中,消息队列是在消息的传输过程中保存消息的一个容器。
当你需要使用消息队列的时候,首先需要考虑它的必要性,可以使用消息队列的场景有很多,最常用的是业务解耦、最终一致性、广播与错峰流控等等。反之如果需要强一致性,关注业务逻辑的处理结果,那么RPC显得更为合适。那么什么是RPC呢?其实就是指远程调用。
业务解耦
解耦是消息队列里要解决的最本质的问题,所谓解耦,简单讲就是一个事务只关心核心的流程,而需要依赖其他系统但不那么重要的事情有通知即可,无需等待结果。基于消息的模型关心的是通知,而不是处理。
最终一致性
最终一致性指的是两个系统的状态保持一致,要么都成功要么都失败。当然了,有个时间的限制,理论上是越快越好,但实际上各种异常的情况下,可能会有一定的延迟来达到最终的一致状态,但最终两系统的最终状态是一样的。所有跨jvm的一致性问题,从技术角度上讲,通用的解决方案有两个 一个是强一致性、一个是最终一致性。强一致性是指分布式事务,最终一致性主要是用记录和补偿的方式来处理,在做所有不确定的事情之前,先把不确定的事情记录下来,然后去做不确定的事情,它的结果通常分三种:成功、失败、不确定(超时等等、可以等价为失败),如果是成功就把记录的事情清理掉,如果是失败或者不确定,我们可以依靠定时任务等方式把所有失败的事情重新做一遍,直到成功为止。最终一致性不是消息队列必备的特性,但确实可以依靠队列来做一致性的事情。需要注意的是,像kafka等消息队列它的设计在设计层面上具有丢消息的可能,比如定时刷盘,如果断电,会丢消息等等。哪怕只丢千分之一的消息,也要用其他手段来保证结果正确。
广播
消息队列的基本功能之一就是进行广播,如果没有消息队列,每当有一个新的业务加入,我们都要调整一下新接口,有了消息队列,我们只需要关心消息是否送达到消息队列,新接入的接口订阅相关的消息,自己去做处理就可以了。
错峰与流控
我们试想一下,上下层对于事情的处理能力是不同的,比如web前端每秒承受上千万的请求并不是神奇的事情,只需要多加点机器,搭建一些LVS负载均衡设备和nginx等即可。但数据库的处理却十分有限,即使使用了分库分表,单机的处理能力任然有限,出于成本的考虑,我们不能奢求数据库的机器数量追上前端,这样的问题同样也出现在系统与系统之间,比如短信系统可能由于短板效应,速度卡在网关上,比如每秒几百次请求,它跟前端的并发量不是一个数量级的,但是用户晚一会收到短信,一般也不会影响多大,如果没有消息队列,两个系统之间通过协商滑动窗口等复杂的方案也不是说不能实现,但是呢,系统的复杂性会成指数型的增长,势必会在上层或者下层做些存储,并且要处理定时阻塞等一系列问题,而且每当处理能力有差异的时候,都需要单独开发一套逻辑来维护这套逻辑,所以利用中间系统(消息队列)来转储通信内容,并在下层系统有能力处理这些消息的时候再处理这些消息,是一套相对比较通用的方式。
总之消息队列不是万能的,对于需要强事务保证,而且延迟很敏感的,RPC远程调用是优于消息队列的。对于一些无关痛痒或者说对于别人重要,但对于自己不是很关心的事情可以用消息队列去做,支持最终一致性的消息队列能够用来处理延迟不那么敏感的分布式事务场景,而且相对与笨重的分布式事务,可能是更优的处理方式,当上下层处理能力存在差距的时候,利用消息队列做通用的“漏斗”,在下层有能力处理的时候再进行分发,同时下层如果有很多系统关心你发出的通知的时候,也果断的使用消息队列来解决。