面霸篇:MQ 的 5 大关键问题详解

最近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 消息堆积

如果消费者消费消息的速度小于生产者生产消息的速度,将会出现消息堆积问题。其实这类问题产生的原因很多,如果你想进一步了解,可以看看我的另一篇文章《我用kafka两年踩过的一些非比寻常的坑》。

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

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

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

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

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

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


往期推荐

批处理框架 Spring Batch 这么强,你会用吗?


Mybatis中SQL注入攻击的3种方式,真是防不胜防!


再有人问你MySql的隔离级别,直接把这篇文章发给他!


本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/544684.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

C# Winform 窗体美化(八、Icon)

八、Icon 之前 Winform 项目也有在 Icon 上遇到些问题(这里的 Icon 指的是 .ico 类型的文件),比如刚开始不知道怎么让自己的程序 Icon 和其他软件一样可以放大,还有放大之后在音量合成器中会出现比较奇葩的效果之类的问题&#x…

Java LocalDate类| 带示例的getEra()方法

LocalDate类的getEra()方法 (LocalDate Class getEra() method) getEra() method is available in java.time package. getEra()方法在java.time包中可用。 getEra() method is used to get the era applicable for this LocalDate object. getEra()方法用于获取适用于此LocalD…

MyBatis 的执行流程,学废了!

作者:双子孤狼来源:blog.csdn.net/zwx900102/article/details/108455514MyBatis可能很多人都一直在用,但是MyBatis的SQL执行流程可能并不是所有人都清楚了,那么既然进来了,通读本文你将收获如下:1、Mapper接…

C# Winform 窗体美化(九、嵌入窗体)

九、嵌入窗体 还是关于 Winform 窗体的一些操作问题,这次是研究了一个嵌入窗体,这次学习纯属偶然,项目中确实没遇到过这种需求。就是把别人的程序嵌入到自己的程序中,就像这样: 这里我嵌入了测试显示器的程序 [外链图…

Mac 流程图

https://www.lucidchart.com/pages/signup?utm_expid39895073-174.qKyHpBEbQS26y86OArD-rQ.1 https://www.processon.com/

setname_Python线程类| setName()方法与示例

setnamePython Thread.setName()方法 (Python Thread.setName() Method) Thread.setName() method is an inbuilt method of the Thread class of the threading module in Python. It uses a Thread object and sets the name of the thread. Thread.setName()方法是Python中线…

SpringBoot 优雅的参数效验!

引言 不知道大家平时的业务开发过程中 controller 层的参数校验都是怎么写的?是否也存在下面这样的直接判断?public String add(UserVO userVO) {if(userVO.getAge() null){return "年龄不能为空";}if(userVO.getAge() > 120){return &quo…

NTFS Change Journal(USN Journal)详解

写在前面 最近又用了一下usn日志来获取所有文件列表,在分多次加载文件列表的时候发现有文件丢失的情况,后来发现一篇文章比较详细的讲了usn。 用cmd来读取usn日志 如图: 以下是转载内容: 还是那个文件监控的应用,…

Java即时类| hashCode()方法与示例

即时类hashCode()方法 (Instant Class hashCode() method) hashCode() method is available in java.time package. hashCode()方法在java.time包中可用。 hashCode() method is used to get the hash code value for this Instant. hashCode()方法用于获取此Instant的哈希码值…

系统起动时加载的过程

sof_getdjval转载于:https://blog.51cto.com/bks2015/1660178

绝,Java 中创建对象的 5 种方法!

我们日常生活中会创建很多对象,但是这个对象和你理解的那么对象不一样,因为作者不是女娲,不能造人。作者只是程序员,他只能在 Java 中创建对象。那么我问你一个问题,你知道 Java 中如何创建对象吗?这个问题…

C# Winform 窗体美化(十、自定义窗体)

十、自定义窗体 写在前面 最近在做 winform 应用程序,需要自定义一种窗口的样式,所以就随便搞了一个简单的窗口。 效果图 有两种样式,界面如下: 无标题: 有标题: 关键词 1、黑色描边边框 对于…

几种在shell命令行中过滤adb logcat输出的方法

几种在shell命令行中过滤adb logcat输出的方法 分类标签: LogCat ADB 我们在Android开发中总能看到程序的log日志内容充满了屏幕,而真正对开发者有意义的信息被淹没在洪流之中,让开发者无所适从,严重影响开发效率。本文就具体介绍几种在sh…

duration java_Java Duration类| toHours()方法与示例

duration javaDuration Class toHours()方法 (Duration Class toHours() method) toHours() method is available in java.time package. toHours()方法在java.time包中可用。 toHours() method is used to convert this Duration into the number of hours. toHours()方法用于…

SpringBoot时间格式化的5种方法!

作者 | 王磊来源 | Java中文社群(ID:javacn666)转载请联系授权(微信ID:GG_Stone)在我们日常工作中,时间格式化是一件经常遇到的事儿,所以本文我们就来盘点一下 Spring Boot 中时间格…

C#文件加密和解密

下载 CSDN下载:https://download.csdn.net/download/myinc/9913318 Github:GitHub 如果没有积分,也可以关注我获取哟~【文件加密】 // * 最近看了一下加密算法,对加密文件突然很感兴趣,就研究了一下:…

zabbix server 迁移步骤

zabbix server 迁移步骤: 1.在新机器上安装同版本的zabbix server软件和zabbix agent软件。 2.同步zabbix_server.conf配置文件。 3.同步/usr/lib/zabbix/{alertscripts,externalscripts}里面的程序。 4.我们这里有安装使用oneproxy,需要同步oneproxy软件…

stl reserve_vector :: reserve()函数以及C ++ STL中的示例

stl reserveC vector :: reserve()函数 (C vector::reserve() function) vector::reserve() is a library function of "vector" header, which is used to request change in vector allocation. Refer to example to understand in details. vector :: reserve()是…

SpringBoot 如何统一后端返回格式?老鸟们都是这样玩的!

大家好,我是磊哥。今天我们来聊一聊在基于SpringBoot前后端分离开发模式下,如何友好的返回统一的标准格式以及如何优雅的处理全局异常。首先我们来看看为什么要返回统一的标准格式?为什么要对SpringBoot返回统一的标准格式在默认情况下&#…

SysinternalsSuite工具

写在前面(下载) 下载地址 简介 sysinternals 的网站创立于1996年由Mark russinovich和布赖科格斯韦尔主办其先进的系统工具和技术资料微软于2006年7月收购sysinternals公司 . 不管你是一个IT高级工作者还是一个开发者,你都会发现sysintern…