当前位置: 首页 > 科技观察

高并发系统为什么要使用消息队列?这次我彻底明白了!

时间:2023-03-22 01:36:19 科技观察

作者个人研发在高并发场景下提供了一个简单、稳定、可扩展的延迟消息队列框架,具有精准的定时任务和延迟队列处理功能。开源半年多以来,已成功为十几家中小企业提供精准定时调度解决方案,经受住了生产环境的考验。为了造福更多的童鞋,这里提供一个开源的框架地址:https://github.com/sunshinelyz/mykit-delay上面写着很多高并发系统都会用到消息队列中间件,那么问题来了,为什么在高并发系统中会使用消息队列中间件?如果你立志要成为高级架构师,你有没有想过这个问题?本文汇集了众多技术高手的编程思想,由冰川汇集整理,在此,感谢那些为技术发展原则默默付出的前辈们!场景分析现在假设这样一个场景,用户下单成功后需要给用户发送一条短信。如果没有消息队列,我们??会选择同步调用短信接口,等待短信发送成功。现在假设短信接口实现有问题,或者短时间内短信发送量达到上限。这个时候,你是应该选择重试几次还是放弃发送呢?这里的设计会很复杂。如果使用消息队列,我们??选择将发送短信的操作封装成一条消息发送到消息队列中。消息队列通知服务发送文本消息。即使出现以上问题,你也可以选择将消息放回消息队列等待。处理。消息队列的好处通过上面的例子,我们可以看出消息队列完成了一个异步解耦的过程。发送短信时,我们只需要保证短信成功发送到消息队列,就可以做其他事情了;,设计变得更简单。在下单的场景下,发送短信我们不需要考虑太多,交给消息队列管理即可。发送短信可能会有延迟,但保证最终一致性。消息队列特性与业务无关,只是分发消息。先进先出,先到先得。容灾:节点动态增删、消息持久化。性能:提高吞吐量,提高系统内部通信效率。为什么高并发系统要使用消息队列?(1)业务解耦成功完成了一个异步解耦过程。发短信的时候一定要把它放到消息队列里,然后做下面的事情。事务只关心必要的过程。当需要依赖其他东西但又不是那么重要时,只需要通知即可,不需要等待结果。每个成员不必受其他成员的影响,可以更加独立,只通过一个简单的容器连接。对于我们的订单系统来说,在订单最终支付成功之后,可能需要给用户发送短信积分,其实这并不是我们系统的核心流程。如果外部系统速度慢(比如短信网关速度不行),主进程的时间就会长很多,用户肯定不想之前点击支付几分钟看到结果。那么我们只需要通知短信系统“我们的支付成功”就可以了,不用等待处理完成。(2)最终一致性主要通过记录和补偿来处理;在做所有不确定的事情之前,先把事情记录下来,然后再做不确定的事情,其结果通常分为三种:成功、失败或不确定;如果成功了,我们就可以清理记录的东西了。对于失败和不确定的情况,我们可以使用定时任务,将所有失败的事情重新做一遍,直到成功为止。保证了最终的一致性,通过将任务存储在队列中,保证最终会被执行。最终一致性是指两个系统的状态是一致的,要么都成功,要么都失败。当然,是有时间限制的。理论上是越早越好,但在实践中,在各种异常情况下,到达最终一致状态可能会有一定的延迟,但两个系统的最终状态是相同的。业界有一些消息队列是为了“最终一致性”,比如Notify(阿里巴巴)、QMQ(去哪儿)等,最初是为交易系统中的高可靠通知而设计的。使用银行的转账流程来了解最终一致性,转账要求非常简单。如果系统A扣钱成功,那么系统B一定加钱成功。不然的话,一起滚回去,当什么都没发生过一样。但是这个过程中有很多可能出现的意外:A扣钱成功,但是调用B的加钱接口失败。A扣钱成功,虽然调用B的加钱接口成功,但是网络异常导致获取最终结果时超时。A扣钱成功,B加钱失败,A想回滚扣的钱,但是A的机器宕机了。可见,要完成这件看似简单的事情,真的没有那么容易。从技术角度来看,所有跨JVM一致性问题的通用解决方案是:强一致性、分布式事务,但实现起来难度太大,成本太高。最终一致性主要采用“记录”和“补偿”两种方式。在做所有不确定的事情之前,先把事情记录下来,然后再做不确定的事情,结果可能是:成功,失败或者不确定,“不确定”(比如超时等)可以等同于失败。如果你成功了,你可以清理记录的东西。对于失败和不确定的事情,可以依靠定时任务等方法,把失败的事情全部重新做一遍,直到成功。回到刚才的例子,当A扣钱成功时,系统会在库中记录给B的“通知”(为了保证最高的可靠性,可以通知B系统加钱扣钱成功.事件在本地事务中维护),如果通知成功则删除记录。如果通知失败或者不确定,会使用定时任务补偿通知我们,直到我们更新状态为正确的状态。消息可能会重复,注意消息的重复性和幂等性。(3)如果没有消息队列进行广播,每次访问新的服务我们都需要连接一个新的接口;有了消息队列,我们??只需要知道消息是否发送到消息队列,新访问的接口订阅相关消息,自己处理即可。(4)错峰和流量控制使用消息队列转储两个系统的通信内容,并在下游系统有能力处理这些消息时处理这些消息。试想一下,上下游对事物的处理能力是不同的。举个例子,Web前端能承受每秒几千万次的请求并不是什么神奇的事情。只需要多加一点机器,然后搭建一些LVS负载均衡设备和Nginx即可。但是,数据库的处理能力是非常有限的。即使用SSD来分库分表,单机的处理能力也还是万级水平。出于成本考虑,我们不能指望数据库机器数量赶上前端。这种问题也存在于系统之间。比如短信系统的速度可能会因为短板效应卡在网关上(每秒几百个请求),跟前端并发不是一个数量级。但是,如果用户在晚上半分钟左右收到一条短信,一般不会有什么大问题。如果没有消息队列,两个系统之间实现协商、滑动窗口等复杂的解决方案也不是没有可能。但是系统的复杂度呈指数增长,存储必须做上游或者下游,还要处理时序、拥塞等一系列问题。而每当处理能力出现差距时,就需要单独开发一套逻辑来维护这个逻辑。因此,使用中间系统转储两个系统的通信内容,然后在下游系统有能力处理这些消息时,再处理这些消息是一种比较常见的方式。总结总而言之,消息队列不是万能的。对于那些需要强大的事务保证并且对延迟敏感的,RPC优于消息队列。对于一些无关紧要的事情,或者对别人来说很重要但自己却没那么关心的事情,可以使用消息队列来做。支持最终一致性的消息队列可以用来处理对延迟不太敏感的“分布式事务”场景,可能是比大容量分布式事务更好的处理方式。当上下游系统的处理能力存在差距时,利用消息队列做一个共同的“漏斗”。当下游有能力处理时,再进行分发。如果下游有很多系统关心你系统的通知,果断使用消息队列。本文转载自微信公众号“银禾科技”,可通过以下二维码关注。转载本文请联系冰川科技公众号。