十二张图,一脚踹开消息队列的大门
消息队列つ登场
大家好,我是老三,是一个电商公司的程序员,负责订单系统的开发。
掉了不少头发之后,我完成了用户订单支付的开发。
订单支付的业务是这样的。用户支付完成之后,我需要更新订单状态,这一部分是在本系统完成的。接下来,我要调用库存系统,减库存,好了,剩下的就是库存系统的事情了。
开发、联调、测试、上线,我的小日子变得清闲起来,每天就是在群里吹牛打屁。
可是没过两天,产品妹子,找过来了,她说,她想加个功能,用户完成订单支付以后,要增加用户的积分。
没问题,so easy,噼里啪啦,我两天就做完了,无非是调用一下会员系统。
这天,正和沙雕群友斗图的时候,产品妹子过来,他说要接入消息系统,好,搞!
又过两天,她说要添加营销系统,行吧,干!
又过两天,她说要搞推荐系统,嗯……,来吧!
又过两天……
于是系统就变成了这个样子:
就这样,我过上了暗无天日的日子,我要维护和若干个系统的对接,每次他们发布新版本,我都要跟着值班。
我要迭代,也要改和几个系统的对接代码。
周一,营销系统;
周二,库存系统;
……
这天,眼圈发黑的我正在和下游服务撕巴的时候,突然忍不住两腿战战,她来了,产品妹(女)子(王)来了——她是我不能拒绝的女人。
脆弱的眼泪流了满面,我的猿生一片灰暗……
没想到,代救星出现了,我的好朋友傲天过来了,拿鼻孔看着我。
“你个 Loser,竟不知道用消息队列,怪不得天天被人欺负,哼!”
一语惊醒梦中人,为什么不用消息队列啊?
于是我引入消息队列,对系统进行了重构。
这下好了,我只管更新订单状态
,剩下的丢给消息队列,你们这些下游自己去消息队列消费消息就好了,别来缠着我了。
……
引入消息队列之后,又是一个闲适的下午。
我没有在群聊里扯扯,因为我退群了。
前几天,我受到了前所未有的伤害——
我在群里嘲讽一个老哥,技术真菜,连消息队列都不会!
老哥反手就发出他和女朋友的合照,“单身狗,技术好又怎么样,连个女朋友都没有!”
我瞬间 san 值狂掉!
“程序员单身,不算单身……new 个对象的事,能算单身么?”接连着便是什么难懂的话,什么“没有妹子”,什么“哲学”之类,引得众人都哄笑起来,群里充满了快活的空气。
于是,这个下午我盯着空空如也的需求单发呆,公司真的没有妹子么?……
好了,冗长的前奏结束了,接下来该进入正文了😂。
消息队列つ用途
在上面的前言中,我们已经了解了消息队列最重要的一个用途:
解耦
通过消息队列,降低系统间的耦合,避免过多的调用。
就好像公司的行政小姐姐要通知一件事情,她通常会是在群里发一个公告,然后我们扣 1 就行了。要是一个个通知,她要通知几十上百次。
异步
还是上面我们提到的订单支付,支付之后,我们要扣减库存、增加积分、发送消息等等,这样一来这个链路就长了,链路一长,响应时间就变长了。引入消息队列,除了更新订单状态
,其它的都可以异步去做,这样一来就来,就能更快地响应我们的上游。
为什么不用多线程之类的方式做异步呢?——
嗨,只用多线程做异步,不是还得写代码去调那一堆下游吗,所以这又回到了解耦这个问题上。
削峰
消息队列同样可以用来削峰。
用一个比喻,一条河流,假如它的下游能容纳的水量是有限的,为了防止洪水冲垮堤坝,我们应该怎么办呢?
我们可以在上游修建一个水库,洪峰来的时候,我们先把水给蓄起来,闸口里只放出下游能承受地住的水量。
放在我们的系统,也是一个道理。比如秒杀系统,平时流量很低,但是要做秒杀活动,秒杀的时候流量疯狂怼进来,你的服务器,Redis,MySQL 各自的承受能力都不一样,直接全部流量照单全收肯定有问题啊,严重点可能直接打挂了。
所以一样,我们可以把请求放到队列里面,只放出我们下游服务能处理的流量,这样就能抗住短时间的大流量了。
除了这三大用途之外,还可以利用队列本身的顺序性,来满足消息必须按顺序投递的场景;利用队列 + 定时任务来实现消息的延时消费 ……
消息队列つ本质
过气老北鼻马老师有三招——接
、化
、发
。
消息队列核心有三板斧:发
、存
、消费
。
生产者先将消息投递一个叫做「队列」的容器中,然后再从这个容器中取出消息,最后再转发给消费者[1]。
上面这个图便是消息队列最原始的模型,它包含了消息队列中的一两个关键词消息
和队列队列
:
消息:就是要传输的数据,可以是最简单的文本字符串,也可以是自定义的复杂格式。
队列:大家应该再熟悉不过了,是一种先进先出数据结构。它是存放消息的容器,消息从队尾入队,从队头出队,入队即发消息的过程,出队即收消息的过程。
所以消息队列的本质就是把要传输的数据放在队列中。
围绕着这个本质:
把数据放到消息队列的角色就是
生产者
把数据从队列中取出的就是
消费者
消息队列つ模型
[1]我们上面已经了解了消息队列模型的本质,随着应用场景的变化,消息队列的模型逐渐发生了一些演进。
就好像我们的文字通讯,最开始单对单地发消息,后来可以群发,再后来,可以拉一个群聊。
队列模型
最初的消息队列就是上一节讲的原始模型,它是一个严格意义上的队列(Queue)。消息按照什么顺序写进去,就按照什么顺序读出来。不过,队列没有 “读” 这个操作,读就是出队,从队头中 “删除” 这个消息。
如果有多个生产者往同一个队列里面发送消息,这个队列中可以消费到的消息,就是这些生产者生产的所有消息的合集。消息的顺序就是这些生产者发送消息的自然顺序。
如果有多个消费者接收同一个队列的消息,这些消费者之间实际上是竞争的关系,每个消费者只能收到队列中的一部分消息,也就是说任何一条消息只能被其中的一个消费者收到。
发布 - 订阅模型
如果需要将一份消息数据分发给多个消费者,并且每个消费者都要求收到全量的消息。很显然,队列模型无法满足这个需求。
一个可行的方案是:为每个消费者创建一个单独的队列,让生产者发送多份。这种做法比较笨,而且同一份数据会被复制多份,也很浪费空间。
为了解决这个问题,就演化出了另外一种消息模型:发布-订阅模型。
在发布 - 订阅模型中,消息的发送方称为发布者(Publisher),消息的接收方称为订阅者(Subscriber),服务端存放消息的容器称为主题(Topic)。发布者将消息发送到主题中,订阅者在接收消息之前需要先“订阅主题”。“订阅”在这里既是一个动作,同时还可以认为是主题在消费时的一个逻辑副本,每份订阅中,订阅者都可以接收到主题的所有消息。
仔细对比下它和 “队列模式” 的异同:生产者就是发布者,队列就是主题,消费者就是订阅者,无本质区别。唯一的不同点在于:一份消息数据是否可以被多次消费。
消息队列つ代价
天下没有白吃的午餐——这句话放在系统设计中同样适用。引入了消息队列,解决了一些问题,但同时也增加了系统的复杂性和维护成本。[5]
高可用
前面说,我们引入了消息队列,降低了系统间的耦合,但是,我们对消息队列的依赖也变重了,想想,要是消息队列挂了,那我们下游就全凉了。
所以,我们消息队列的部署必须保证高可用,至少是主/从
,一般都得集群/分布式
。
消息丢失问题
我们将数据写到消息队列上,下游服务没来得及取消息队列的数据,突然挂了。如果没有做任何的措施,我们的数据就丢了。
那可怎么办呢?
消息队列中的数据得想办法存在别的地方,这样才尽可能减少数据的丢失。
问题又来了,存在哪呢?
磁盘?
数据库?
Redis?
分布式文件系统?
同步存储还是异步存储?
重复消费问题
要是我们网络延迟或者什么原因,导致下游重复消费怎么办?
我们这个问题是在消息队列解决?还是在下游服务解决?
还有其它的顺序消息等等问题。
这些问题都是选型时候需要充分考虑的。
消息队列つ选择
目前在市面上比较主流的主要有四大消息中间件:Kafka、ActiveMQ、RabbitMQ、RocketMQ 。
它们的对比我整理了一张图:
消息队列つ终焉
好了,到这我们就了解了消息队列的一些基础的知识。
门外汉,这个门,你入了吗?我反正是在里面打滚了。
<br>
"简单的事情重复做,重复的事情认真做,认真的事情有创造性地做!"——
我是三分恶,一个能文能武的全栈开发。
点赞
、关注
不迷路,咱们下期见!
<hr><big><b>参考:</b></big>
[1]. 《吃透MQ系列》核心基础全在这里了
[2]. 《进大厂系列》系列-消息队列基础
[3]. 知乎问答:消息队列怎么能通俗点解释?
[4].系统学习消息队列分享(四) 消息模型:主题和队列有什么区别?
[5]. 什么是消息队列?
版权声明: 本文为 InfoQ 作者【三分恶】的原创文章。
原文链接:【http://xie.infoq.cn/article/d6f6563a3ab65950868d0f2dc】。文章转载请联系作者。
评论