0
  • 聊天消息
  • 系统消息
  • 评论与回复
登录后你可以
  • 下载海量资料
  • 学习在线课程
  • 观看技术视频
  • 写文章/发帖/加入社区
会员中心
创作中心

完善资料让更多小伙伴认识你,还能领取20积分哦,立即完善>

3天内不再提示

引入消息队列会多出哪些问题

Linux爱好者 来源:苏三说技术 作者:热爱所以坚持ing 2021-09-23 14:53 次阅读

前言

最近,消息队列(Message Queue ,简称 MQ)越来越火。很多公司在用,很多人在用,其重要性不言而喻。

如果让你回答下面这些问题,你的心中是否有答案了呢?

为什么要用 MQ?

引入 MQ 会多出哪些问题?

如何解决这些问题?

本文将会一一为你解答,这些看似平常却很有意义的问题。

1. 传统模式有哪些痛点

痛点 1

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

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

痛点 2

很多复杂的业务系统,一般都会拆分成多个子系统。以用户下单为例,请求会先通过订单系统,然后分别调用支付系统、库存系统、积分系统和物流系统。

系统之间耦合性太高,如果调用的任何一个子系统出现异常,整个请求都会异常。对系统的稳定性非常不利。

痛点 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 都无法避免。

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

消息生产者产生了重复的消息;

Kafka 和 RocketMQ 的 offset 被回调了;

消息消费者确认失败;

消息消费者确认时超时;

业务系统主动发起重试。

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

3.2 数据一致性问题

很多时候,如果 MQ 的消费者业务处理异常,就会出现数据一致性问题。

举个例子,一个完整的业务流程是,下单成功之后送 100 个积分。下单写库成功了,但是消息消费者在送积分的时候失败了。这样就会造成数据不一致的情况,即该业务流程的部分数据写库了,另外一部分没有写库。

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

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

3.3 消息丢失问题

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

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

生产者产生消息时,由于网络原因发送到 MQ 失败了;

MQ 服务器持久化,存储磁盘时出现异常;

Kafka 和 RocketMQ 的 offset 被回调时,略过了很多消息;

消费者刚读取消息,已经 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两年踩过的一些非比寻常的坑”。

那么消息堆积问题该如何解决呢?这个要看消息是否需要保证顺序。

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

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

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

责任编辑:haq

声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉
  • 服务器
    +关注

    关注

    12

    文章

    9096

    浏览量

    85307
  • 消息
    +关注

    关注

    0

    文章

    29

    浏览量

    12867

原文标题:面霸篇:MQ 的 5 大问题详解

文章出处:【微信号:LinuxHub,微信公众号:Linux爱好者】欢迎添加关注!文章转载请注明出处。

收藏 人收藏

    评论

    相关推荐

    JavaWeb消息队列使用指南

    在现代的JavaWeb应用中,消息队列(Message Queue)是一种常见的技术,用于异步处理任务、解耦系统组件、提高系统性能和可靠性。 1. 消息队列的基本概念 消息队列是一种应用程序对应
    的头像 发表于 11-25 09:27 135次阅读

    探索字节队列的魔法:多类型支持、函数重载与线程安全

    探索字节队列的魔法:多类型支持、函数重载与线程安全代码难度指数:文章学习重点:参数宏的使用技巧一、引言在嵌入式系统和实时应用中,数据的传输和处理是至关重要的。字节队列(ByteQueue)是一种重要
    的头像 发表于 11-15 01:08 767次阅读
    探索字节<b class='flag-5'>队列</b>的魔法:多类型支持、函数重载与线程安全

    为什么同一个队列引用的全局变量,运行在两个子vi中发现队列数据丢失了

    我创建了一个队列,然后将队列引用做了个全局变量,运行在两个子vi中,一个是只入队列,另一个是只出队列。但我发现,一个字vi数据入队列成功,检
    发表于 11-14 11:47

    在进入OPA847放大器之前加了LC高通滤波,请问这样引入过多噪声吗?

    ,发现如果不滤除直流分量的话,我的互阻增益就很小,所以我在进入放大器之前加了LC高通滤波,请问这样引入过多噪声么?还有别的方法可以消除直流分量吗?
    发表于 09-14 07:17

    嵌入式环形队列与消息队列的实现原理

    嵌入式环形队列,也称为环形缓冲区或循环队列,是一种先进先出(FIFO)的数据结构,用于在固定大小的存储区域中高效地存储和访问数据。其主要特点包括固定大小的数组和两个指针(头指针和尾指针),分别指向队列的起始位置和结束位置。
    的头像 发表于 09-02 15:29 451次阅读

    示波器探头在测试的时候引入什么负载效应

    在进行电子测试时,示波器探头作为一种重要的测量工具,其性能对测量结果的准确性具有重要影响。然而,在使用示波器探头进行测量时,探头本身也引入一定的负载效应,影响测试结果。 一、示波器探头的基本原理
    的头像 发表于 08-09 14:30 394次阅读

    玩转RT-Thread之消息队列的应用

    在嵌入式系统开发中,实时处理串口和ADC数据是一项重要的任务。本文将介绍如何在RT-Thread实时操作系统中,利用消息队列来同时处理来自串口和ADC的数据。通过这种方法,我们能够高效地管理和处理
    的头像 发表于 07-23 08:11 600次阅读
    玩转RT-Thread之消息<b class='flag-5'>队列</b>的应用

    嵌入式实时操作系统中的队列管理与应用

    任务 A 将信息存入队列,任务B以先进先出的方式提取信息。队列通常应足够大,可以承载许多数据,而不仅仅承载单个数据项。因此,它可以充当缓冲或暂存器,为管道提供灵活性。
    发表于 04-30 14:27 557次阅读
    嵌入式实时操作系统中的<b class='flag-5'>队列</b>管理与应用

    Freertos队列项里的字节长度是否可以获取?

    最近刚学Freertos, 看到可以获取Freertos队列长度,但是队列项里的字节长度是否可以获取? 因为项目中队列中会存放不定长字节,需要对队列中的数据分拣,每次分拣的时候遍历所
    发表于 04-29 07:17

    进程间通信的消息队列介绍

    消息队列是一种非常常见的进程间通信方式。
    的头像 发表于 04-08 17:27 305次阅读

    MCU专属队列功能模块之QueueForMcu应用

    当需要从队列头部获取多个数据,但又不希望数据从队列中删除时,可以使用 Queue_Peek_Array 函数来实现,该函数的参数与返回值与 Queue_Pop_Array 完全相同。
    发表于 03-20 11:44 504次阅读
    MCU专属<b class='flag-5'>队列</b>功能模块之QueueForMcu应用

    裸机中环形队列与RTOS中消息队列有何区别呢?

    “环形队列”和“消息队列”在嵌入式领域有应用非常广泛,相信有经验的嵌入式软件工程师对它们都不陌生。
    的头像 发表于 01-26 09:38 707次阅读
    裸机中环形<b class='flag-5'>队列</b>与RTOS中消息<b class='flag-5'>队列</b>有何区别呢?

    labview 队列最前端插入的应用

    LabVIEW是一种用于实时测试、测量和控制系统的高级系统设计软件。它采用了数据流编程方式,提供了一种直观、可视化的方法来构建复杂的测试和测量应用程序。其中一个重要的功能是队列,它可以在软件设计中
    的头像 发表于 01-08 11:45 1177次阅读

    labview队列有什么实际作用

    LabVIEW队列是一种数据结构,常用于解决多任务并发处理的问题。它被广泛应用于科学研究、工程项目和自动化控制等领域。在LabVIEW中,队列提供了一种高效、方便的方式来处理不同任务之间的数据
    的头像 发表于 01-05 16:42 1575次阅读

    半孔板比常规pcb板多出什么流程

    半孔板是一种特殊的PCB板,相比于常规的PCB板,它在制造过程中需要多出一些步骤和流程。在本文中,将介绍半孔板相较于常规PCB板所多出的流程。 首先,半孔板的制造流程与常规PCB板的流程在前期步骤上
    的头像 发表于 12-25 16:13 889次阅读