mq的那些破事儿,你不好奇吗?(代码片段)

程序员大咖 程序员大咖     2022-12-07     249

关键词:

????????关注后回复 “进群” ,拉你进程序员交流群????????

作者丨因为热爱所以坚持ing

来源丨苏三说技术

前言

最近mq越来越火,很多公司在用,很多人在用,其重要性不言而喻。但是如果我让你回答下面的这些问题:

  1. 我们为什么要用mq?

  2. 引入mq会多哪些问题?

  3. 如何解决这些问题?

你心中是否有答案了呢?本文将会一一为你解答,这些看似平常却很有意义的问题。

1 传统模式有哪些痛点?

1.1 痛点1

有些复杂的业务系统,一次用户请求可能会同步调用N个系统的接口,需要等待所有的接口都返回了,才能真正的获取执行结果。

这种同步接口调用的方式总耗时比较长,非常影响用户的体验,特别是在网络不稳定的情况下,极容易出现接口超时问题。

1.2 痛点2

很多复杂的业务系统,一般都会拆分成多个子系统。我们在这里以用户下单为例,请求会先通过订单系统,然后分别调用:支付系统、库存系统、积分系统 和 物流系统。系统之间耦合性太高,如果调用的任何一个子系统出现异常,整个请求都会异常,对系统的稳定性非常不利。

1.3 痛点3

有时候为了吸引用户,我们会搞一些活动,比如秒杀等。如果用户少还好,不会影响系统的稳定性。但如果用户突增,一时间所有的请求都到数据库,可能会导致数据库无法承受这么大的压力,响应变慢或者直接挂掉。

对于这种突然出现的请求峰值,无法保证系统的稳定性。

2 为什么要用mq?

对于上面传统模式的三类问题,我们使用mq就能轻松解决。

2.1 异步

对于痛点1:同步接口调用导致响应时间长的问题,使用mq之后,将同步调用改成异步,能够显著减少系统响应时间。

系统A作为消息的生产者,在完成本职工作后,就能直接返回结果了。而无需等待消息消费者的返回,它们最终会独立完成所有的业务功能。

这样能避免总耗时比较长,从而影响用户的体验的问题。

2.2 解耦

对于痛点2:子系统间耦合性太大的问题,使用mq之后,我们只需要依赖于mq,避免了各个子系统间的强依赖问题。

订单系统作为消息生产者,保证它自己没有异常即可,不会受到支付系统等业务子系统的异常影响,并且各个消费者业务子系统之间,也互不影响。

这样就把之前复杂的业务子系统的依赖关系,转换为只依赖于mq的简单依赖,从而显著的降低了系统间的耦合度。

2.3 消峰

对于痛点3:由于突然出现的请求峰值,导致系统不稳定的问题。使用mq后,能够起到消峰的作用。

订单系统接收到用户请求之后,将请求直接发送到mq,然后订单消费者从mq中消费消息,做写库操作。如果出现请求峰值的情况,由于消费者的消费能力有限,会按照自己的节奏来消费消息,多的请求不处理,保留在mq的队列中,不会对系统的稳定性造成影响。

3 引入mq会多哪些问题?

引入mq后让我们子系统间耦合性降低了,异步处理机制减少了系统的响应时间,同时能够有效的应对请求峰值问题,提升系统的稳定性。

但是,引入mq同时也会带来一些问题。

3.1 重复消息问题

重复消费问题可以说是mq中普遍存在的问题,不管你用哪种mq都无法避免。

有哪些场景会出现重复的消息呢?

  1. 消息生产者产生了重复的消息

  2. kafka和rocketmq的offset被回调了

  3. 消息消费者确认失败

  4. 消息消费者确认时超时了

  5. 业务系统主动发起重试

如果重复消息不做正确的处理,会对业务造成很大的影响,产生重复的数据,或者导致数据异常,比如会员系统多开通了一个月的会员。

3.2 数据一致性问题

很多时候,如果mq的消费者业务处理异常的话,就会出现数据一致性问题。比如:一个完整的业务流程是,下单成功之后,送100个积分。下单写库了,但是消息消费者在送积分的时候失败了,就会造成数据不一致的情况,即该业务流程的部分数据写库了,另外一部分没有写库。

如果下单和送积分在同一个事务中,要么同时成功,要么同时失败,是不会出现数据一致性问题的。

但由于跨系统调用,为了性能考虑,一般不会使用强一致性的方案,而改成达成最终一致性即可。

3.3 消息丢失问题

同样消息丢失问题,也是mq中普遍存在的问题,不管你用哪种mq都无法避免。

有哪些场景会出现消息丢失问题呢?

  1. 消息生产者发生消息时,由于网络原因,发生到mq失败了。

  2. mq服务器持久化时,磁盘出现异常

  3. kafka和rocketmq的offset被回调时,略过了很多消息。

  4. 消息消费者刚读取消息,已经ack确认了,但业务还没处理完,服务就被重启了。

导致消息丢失问题的原因挺多的,生产者mq服务器消费者 都有可能产生问题,我在这里就不一一列举了。最终的结果会导致消费者无法正确的处理消息,而导致数据不一致的情况。

3.4 消息顺序问题

有些业务数据是有状态的,比如订单有:下单、支付、完成、退货等状态,如果订单数据作为消息体,就会涉及顺序问题了。如果消费者收到同一个订单的两条消息,第一条消息的状态是下单,第二条消息的状态是支付,这是没问题的。但如果第一条消息的状态是支付,第二条消息的状态是下单就会有问题了,没有下单就先支付了?消息顺序问题是一个非常棘手的问题,比如:

  • kafka同一个partition中能保证顺序,但是不同的partition无法保证顺序。

  • rabbitmq的同一个queue能够保证顺序,但是如果多个消费者同一个queue也会有顺序问题。

如果消费者使用多线程消费消息,也无法保证顺序。

如果消费消息时同一个订单的多条消息中,中间的一条消息出现异常情况,顺序将会被打乱。

还有如果生产者发送到mq中的路由规则,跟消费者不一样,也无法保证顺序。

3.5 消息堆积

如果消息消费者读取消息的速度,能够跟上消息生产者的节奏,那么整套mq机制就能发挥最大作用。但是很多时候,由于某些批处理,或者其他原因,导致消息消费的速度小于生产的速度。这样会直接导致消息堆积问题,从而影响业务功能。

这里以下单开通会员为例,如果消息出现堆积,会导致用户下单之后,很久之后才能变成会员,这种情况肯定会引起大量用户投诉。

3.6 系统复杂度提升

这里说的系统复杂度和系统耦合性是不一样的,比如以前只有:系统A、系统B和系统C 这三个系统,现在引入mq之后,你除了需要关注前面三个系统之外,还需要关注mq服务,需要关注的点越多,系统的复杂度越高。mq的机制需要:生产者、mq服务器、消费者。

有一定的学习成本,需要额外部署mq服务器,而且有些mq比如:rocketmq,功能非常强大,用法有点复杂,如果使用不好,会出现很多问题。有些问题,不像接口调用那么容易排查,从而导致系统的复杂度提升了。

4 如何解决这些问题?

mq是一种趋势,总体来说对我们的系统是利大于弊的,难道因为它会出现一些问题,我们就不用它了?

那么我们要如何解决这些问题呢?

4.1 重复消息问题

不管是由于生产者产生的重复消息,还是由于消费者导致的重复消息,我们都可以在消费者中这个问题。

这就要求消费者在做业务处理时,要做幂等设计,在这里我推荐增加一张消费消息表,来解决mq的这类问题。消费消息表中,使用messageId唯一索引,在处理业务逻辑之前,先根据messageId查询一下该消息有没有处理过,如果已经处理过了则直接返回成功,如果没有处理过,则继续做业务处理。

4.2 数据一致性问题

我们都知道数据一致性分为:

  • 强一致性

  • 弱一致性

  • 最终一致性

而mq为了性能考虑使用的是最终一致性,那么必定会出现数据不一致的问题。这类问题大概率是因为消费者读取消息后,业务逻辑处理失败导致的,这时候可以增加重试机制

重试分为:同步重试异步重试

有些消息量比较小的业务场景,可以采用同步重试,在消费消息时如果处理失败,立刻重试3-5次,如何还是失败,则写入到记录表中。但如果消息量比较大,则不建议使用这种方式,因为如果出现网络异常,可能会导致大量的消息不断重试,影响消息读取速度,造成消息堆积

而消息量比较大的业务场景,建议采用异步重试,在消费者处理失败之后,立刻写入重试表,有个job专门定时重试。

还有一种做法是,如果消费失败,自己给同一个topic发一条消息,在后面的某个时间点,自己又会消费到那条消息,起到了重试的效果。如果对消息顺序要求不高的场景,可以使用这种方式。

4.3 消息丢失问题

不管你是否承认有时候消息真的会丢,即使这种概率非常小,也会对业务有影响。生产者、mq服务器、消费者都有可能会导致消息丢失的问题。

为了解决这个问题,我们可以增加一张消息发送表,当生产者发完消息之后,会往该表中写入一条数据,状态status标记为待确认。如果消费者读取消息之后,调用生产者的api更新该消息的status为已确认。有个job,每隔一段时间检查一次消息发送表,如果5分钟(这个时间可以根据实际情况来定)后还有状态是待确认的消息,则认为该消息已经丢失了,重新发条消息。

这样不管是由于生产者、mq服务器、还是消费者导致的消息丢失问题,job都会重新发消息。

4.4 消息顺序问题

消息顺序问题是我们非常常见的问题,我们以kafka消费订单消息为例。订单有:下单、支付、完成、退货等状态,这些状态是有先后顺序的,如果顺序错了会导致业务异常。

解决这类问题之前,我们先确认一下,消费者是否真的需要知道中间状态,只知道最终状态行不行?

其实很多时候,我真的需要知道的是最终状态,这时可以把流程优化一下:

这种方式可以解决大部分的消息顺序问题。

但如果真的有需要保证消息顺序的需求。订单号路由到不同的partition,同一个订单号的消息,每次到发到同一个partition

4.5 消息堆积

如果消费者消费消息的速度小于生产者生产消息的速度,将会出现消息堆积问题。

那么消息堆积问题该如何解决呢?

这个要看消息是否需要保证顺序。

如果不需要保证顺序,可以读取消息之后用多线程处理业务逻辑。

这样就能增加业务逻辑处理速度,解决消息堆积问题。但是线程池的核心线程数和最大线程数需要合理配置,不然可能会浪费系统资源。

如果需要保证顺序,可以读取消息之后,将消息按照一定的规则分发到多个队列中,然后在队列中用单线程处理。

好了,今天先分享到这来,下期再见。我在这里只是抛砖引玉,其实mq相关的内容还有很多,比如:定时发送、延迟发送、私信队列、事务问题等等,有兴趣的朋友可以找我私聊。

-End-

最近有一些小伙伴,让我帮忙找一些 面试题 资料,于是我翻遍了收藏的 5T 资料后,汇总整理出来,可以说是程序员面试必备!所有资料都整理到网盘了,欢迎下载!

点击????卡片,关注后回复【面试题】即可获取

在看点这里好文分享给更多人↓

《c#爬虫破境之道》:第一境爬虫原理—第五节:数据流处理的那些事儿(代码片段)

为什么说到数据流了呢,因为上一节中介绍了一下异步发送请求。同样,在数据流的处理上,C#也为我们提供几个有用的异步处理方法。而且,爬虫这生物,处理数据流是基础本能,比较重要。本着这个原则,就聊一聊吧。我们... 查看详情

粪菌移植的背后,肠道菌那些你不知道的事儿

近日16岁男孩通过粪菌移植治疗自闭症的新闻刷到了热搜上,估计很多人看到这则消息,惊讶至极,粪菌移植?把别人的粑粑弄进自己肚子里?听起来就超级重口啊!很多人好奇到底是怎么将健康人的粪菌... 查看详情

关于mq那些事(代码片段)

我走过最长的路是你的套路女:二号男嘉宾,假如我们牵手成功后,你会买名牌包包给我吗?男:那你会听话吗?女:会听话。男:听话咱不买!那么消息队列MQ有什么套路呢?(这个话题转换生硬度连我自己都怕!)使用消息... 查看详情

网站重构那些事儿(代码片段)

向Litten致谢!濯去旧见,以来新意距写上一篇博客足足有两个月了,实在是没有脸继续拖下去,毕竟马上就是要入职实习的人,还是要开始找回文字的感觉,而不是整天守望着防御塔和水晶了(话说你见过敌方的... 查看详情

阿里,京东,淘宝,美团,那些你不知道的事儿

B2B即businesstobusiness,2是to的谐音,即企业与企业之间,商家与商家之间,通过互联网进行产品、服务及信息的交换。交易的供需双方都是商家(或企业、公司)。如:阿里巴巴就是这种模式的一家公司。 B2C即businesstocustomer,... 查看详情

分布式系统的那些事儿-mq时代的通信

之前在讲RPC通信的各种好处,特别好用,但是RPC并不是万能的,也并不是适用于各种场景的,因为他是同步的;现如今很多场景下的调用都是异步的,系统A调用B后,并不需要知道B的结果,而且对B的结果无所谓,甚至你B挂了都... 查看详情

promise的那些事儿(代码片段)

在JavaScript中,异步操作非常多见,然而在Promise之前,我们是在类似以下的做法中处理多重异步回调,每一层里都要调另一个异步函数,形成了所谓的“回调地狱”,Promis是为了解决回调地狱的方案,是一种链式操作。什么是Promis... 查看详情

注解的那些事儿|注解的使用(代码片段)

...章首发于【博客园-陈树义】,点击跳转到原文《注解的那些事儿(三)|注解的使用》学会了如何定义自定义注解,那还要会用起来才行。其实自定义注解使用也非常简单,像我们上篇文章定义的一个Sweet注解。public@interfaceSweetSt... 查看详情

fastdfs运维友好那些事儿(代码片段)

FastDFS运维友好那些事儿本篇文章转载于FastDFS作者余庆大佬的FastDFS分享与交流公众号。FastDFS运维友好那些事儿(一)FastDFS运维友好那些事儿(二)最近有人在FastDFSQQ技术交流群里爆料,说网上有人吐槽FastDFS... 查看详情

诊断协议那些事儿(代码片段)

诊断协议那些事儿本专栏将以ISO14229、15765-2为基础深入介绍诊断那些事儿,从故障定位到软件刷写……重点掌握各个服务的功能、报文格式,为后续功能开发打下基础!提示:可参考目录索引进行学习一、UDS是什... 查看详情

ios单元测试的那些事儿(代码片段)

iOS单元测试的那些事儿作为客户端开发,很多时候我们过多的关注于功能的测试,而忽略标准的单元测试。其实,单元测试是保障项目稳定性的最有效且成本最低的测试方式。越偏向底层服务的代码,越需要使用单元测试来对可... 查看详情

c的那些事儿(代码片段)

...学习、编程的经历中收获的一些经验,我将其命题为C的那些事儿,记录C语言的编程哲学。二、正文auto关键字显然是摆设这个关键字只对创建符号表入口的编译器设计者有意义。它的意思是“在进入程序块时自动进行内存分配“... 查看详情

关于数据标注行业有些你不知道的事儿(代码片段)

一位在觉醒向量做数据标注的员工说:正如别人评论的那样,我们的工作的确很像数字世界的建筑工人,但是我们在扮演着很重要的角色,因为没有我们,AI这栋摩天大楼就无法建成。大数据作为一个术语而言,其历史可能并不... 查看详情

字符串的那些事儿(代码片段)

一、字符串替换二、字符串排序三、删除字符串中的子串一、字符串替换描述:实现字符串"Goodmorning!"到"Goodevening!"的替换。代码实现:#include<stdio.h>char*MyReplace(char*s1,char*s2,intpos)//自定义的替换函数 inti,j; ... 查看详情

并发编程的那些事儿(代码片段)

1、生产者和消费者模型  作用:用于解耦。  原理:生产者将生产的数据放到缓存区,消费者从缓存区直接取。借助队列来实现该模型(队列就是缓存区)  队列是安全的,自带锁机制。  q=Queue(num)  num为队列的最... 查看详情

util.promisify的那些事儿(代码片段)

util.promisify是在node.js8.x版本中新增的一个工具,用于将老式的Errorfirstcallback转换为Promise对象,让老项目改造变得更为轻松。在官方推出这个工具之前,民间已经有很多类似的工具了,比如es6-promisify、thenify、bluebird.promisify。以及... 查看详情

文件处理那些事儿~(代码片段)

  当你把一个东西放在一个不稳定的桌子上,当桌子坏掉的时候,你的东西就会坏掉;当你把东西放在一个稳定的桌子上,东西便会把这个东西永久保存下来。  电脑也是一样,如果你把一个程序的变量写入内存中,当你关... 查看详情

handler使用的那些事儿(代码片段)

   对任何有开发经验的童鞋,对Handler肯定是如雷贯耳,使用也必定非常熟练,当现在我还是想对其来个梳理,总结下常见的疑问: 1.常听人说mHandler.obtainMessage(what,obj).sendToTarget()要比Messagemsg=newMessage࿱... 查看详情