RocketMQ重复消费的解决方案::分布式锁直击面试!

news2025/2/26 6:35:44

文章目录

  • 场景分析
    • 方法的幂等
    • 分布式锁
      • Redis实现分布式锁
        • 抢锁的设计思路
    • 分布式锁案例
  • 直击面试
    • rocketmq什么时候重复消费
    • 消息丢失的问题
      • 消息在哪里丢失
      • 发送端确保发送成功并且配合失败的业务处理
      • 消费端确保消息不丢失
      • rocketmq 主从+同步刷盘

场景分析

在这里插入图片描述
分布式系统架构中,队列是分布式的,生产端是分布式集群,消费端也是分布式集群.

相当于有多个消费端同时监听队列,同时减库存,写入订单.

面试题:如何处理消息重复消费的问题. 重复消费大部分场景,需要解决的.

引入2个概念来解决: 幂等的业务方法,和消息的分布式锁.

方法的幂等

结论: 一个方法的一次业务逻辑调用和N次调用的结果是一致的,我们称这种方法就是幂等.

一旦重复消费,一定要把消费的业务逻辑方法(orderAdd)设计成幂等的.

  • 幂等的方法
    • GET方法: 查询方法,天生幂等.
    • DELETE方法: 删除方法,天生幂等.
    • PUT方法: 修改,并不是天生幂等,需要设计
      • 减少库存:
        • update stock_tbl set stock=stock-#{stock} where id=#{id}(不是幂等)
        • select * from stock_log where order_id=#{orderId}(查询日志,判断是否已经见过库存了),没有数据 update stock_tbl set stock=stock-#{stock} where id=#{id},insert into stock_log (字段) values (订单id,商品减库存信息) (这样设计就幂等了,依然有问题)
    • POST方法: 新增,并不是天生幂等,需要设计
      • 新增订单: insert into order_tbl (order_id,order_item_id,count,user_id) values (各种属性);如果使用唯一属性校验,作用在order_id order_sn(编号).同一张订单,这个字段值是相同(幂等满足,没做幂等不满足)
  • 当前orderAdd方法设计幂等的解决思路(之一)
    • 使用订单id或者订单编号,**userId+商品id(**这个只满足当前我们的案例特点,不满足实际场景.)查询订单,如果已经存在了,库存不减少,订单不增了,购物车不用删除了
@Override
public void orderAdd(OrderAddDTO orderAddDTO) {
    //幂等设计思路: 利用userId和commodityCode 查询,如果已经存在了订单,方法直接执行结束
    //如果结果不存在,减库存,生单,删除购物车
    int count=orderMapper.selectExists(orderAddDTO);
    if (count>0){
        log.debug("订单已经新增了");
        return;
    }
    StockReduceCountDTO countDTO=new StockReduceCountDTO();
    countDTO.setCommodityCode(orderAddDTO.getCommodityCode());
    countDTO.setReduceCount(orderAddDTO.getCount());
    // 利用Dubbo调用stock模块减少库存的业务逻辑层方法实现功能
    stockService.reduceCommodityCount(countDTO);
    // 2.从购物车中删除用户选中的商品(调用Cart模块删除购物车中商品的方法)
    // 利用dubbo调用cart模块删除购物车中商品的方法实现功能
    Order order=new Order();
    BeanUtils.copyProperties(orderAddDTO,order);
    // 下面执行新增 假设insert是幂等的.
    orderMapper.insertOrder(order);
    log.info("新增订单信息为:{}",order);
    cartService.cartDelete(orderAddDTO);
}

@Select("select count(id) from " +
"order_tbl where user_id=#{userId} and commodity_code=#{commodityCode}")
int selectExists(OrderAddDTO orderAddDTO);

分布式锁

当前分布式消费架构
在这里插入图片描述
即使,将方法设计成幂等,这个架构中,消息重复消费

,满足线程安全问题的所有因素

  • 并发/多线程
  • 写操作
  • 共享数据

只要解决其中一点,线程安全问题就消失了.

并发多线程–>串行

写操作–> 避免写(不能满足当前案例,必须写)

共享数据–>个体数据(不能满足,重复消费,重复订单是前提)

分布式线程安全问题的解决方案—分布式锁

错误思路: 引入synchronized同步锁,不能解决分布式场景下,多个进程的并发线程安全问题.

概念: 分布式场景下,多进程,多线程并发的抢锁机制. 抢到资源锁,执行业务逻辑,抢不到等待或者放弃执行.能够避免对同一个资源出现并发多线程操作的解决方案.

和synchronized的区别在于 synchronizeds本地锁.管理一个进程中的多线程,分布式锁是管理多个进程中的多线程.

分布式锁当前落地方案: redis setnx命令

Redis实现分布式锁

抢锁的设计思路

  • 目标: 多线程执行业务之前,先判断执行权限,抢锁,抢到锁的才能执行业务,抢不到的不执行.(当前案例中,抢锁,然后执行的业务逻辑是:orderAdd)
    在这里插入图片描述
    抢锁如何执行?: setnx key “”

  • key值如何设计?: 需要结合业务,设计key值(redis中最主要的功能,都关系到key值的设计),抢锁的逻辑中,满足是业务数据,满足重复消费的重复数据.就可以实现这个key值的设计. 消息Id是重复的.

  • 当前业务流程设计缺陷: 如果有一个消费者抢到锁了,执行了业务方法.执行完成后,没有释放锁的机制.如果引入等待重抢的机制,由于抢到锁的没有释放,会导致死锁.
    在这里插入图片描述
    释放锁的逻辑引入
    在这里插入图片描述
    上述整改的流程中避免了死锁问题,但是存在删除失败导致死锁的问题.
    在这里插入图片描述
    所以,要保证del释放没有成功,在redis也一定不会长期保存.
    在这里插入图片描述
    兜底的解决死锁问题.基本不会出现死锁了.
    在这里插入图片描述为了解决误删除的问题,抢锁的时候setnx key value值设计成一个随机数.
    在这里插入图片描述
    随机数两个消费,多个消费者生成相同的可能性极低.

分布式锁案例

@Component
@RocketMQMessageListener(
        topic = "business-order-topic",
        consumerGroup = "${rocketmq.consumer.group}",
        selectorExpression = "orderAdd")
@Slf4j
public class OrderAddConsumerListener implements RocketMQListener<MessageExt> {
    @Autowired
    private IOrderService orderService;
    @Autowired
    private StringRedisTemplate redisTemplate;
    @Override
    public void onMessage(MessageExt msg) {
        //拿到底层消息对象的body
        byte[] body = msg.getBody();
        //尝试先解析成string
        String orderJson=new String(body, StandardCharsets.UTF_8);
        System.out.println(orderJson);
        OrderAddDTO orderAddDTO=
            JSON.toJavaObject(JSON.parseObject(orderJson),OrderAddDTO.class);
        System.out.println(orderAddDTO);
        //1.生成锁的key值,生成当前这把锁的随机数
        //准备锁key
        String msgKeyLock="msg:order:add:"+msg.getMsgId();
        //准备随机数 4 6 8位
        String randCode=new Random().nextInt(9000)+1000+"";
        ValueOperations<String, String> stringOps = redisTemplate.opsForValue();
        try{
            //补充消息消费的抢锁机制
            //2.抢锁 setnx msgKeyLock randCode expire 10s
            Boolean tryLockSuccess = stringOps
                    .setIfAbsent(msgKeyLock, randCode, 10, TimeUnit.SECONDS);
            //3.判断 抢锁成功还是失败
            if(!tryLockSuccess){
                //3.2 失败了 可以等待5秒重新抢锁,也可以直接结束
                //尝试这里使用while编写等待5秒重新抢的逻辑
                log.info("有别人抢锁了,msgKey:{},value:{}",msgKeyLock,randCode);
                return;
            }
            //3.1 成功了 执行orderAdd
            orderService.orderAdd(orderAddDTO);
        }catch (CoolSharkServiceException e){
            //业务异常,说明订单新增业务性失败,比如库存没了
            log.error("库存减少失败,库存触底了:{},异常信息:{}",orderAddDTO,e.getMessage());
        }finally {
            //释放锁 读以下锁的value值,等于当前生成value才释放
            String s = stringOps.get(msgKeyLock);
            if (s!=null && s.equals(randCode)){
                //del msgKeyLock
                redisTemplate.delete(msgKeyLock);
            }
        }
    }
}

直击面试

rocketmq什么时候重复消费

  • 在broker做扩展的时候,消息队列的消息,做扩展的时候,原本存储在原队列的消息,会进行rebalance重平衡.
  • 消费开始阶段
    在这里插入图片描述消费者consumer1 所在group1 绑定队列,push消费模式,使得消费者接受到了queue1 queue2的6条消息.消费过程,成功执行,即将返回确认.
    在这里插入图片描述
    总结:
  • 消费者并发消费的逻辑,同一组消费者绑定分布式队列,推送批量的消息
  • 在某个消费者还没有来得及消费,或者没来得及返回确认给rocketmq,队列发生了扩容缩容
  • rocketmq会对队列中所有的消息做rebalance重平衡(消息重新分配给不同队列),消费者绑定也充平衡
  • 导致已经推送的但是未返回确认的消息,被发送给不同消费者多次.

消息丢失的问题

rocketmq kafka rabbitmq activemq都是队列.只要谈到其中一个.

  1. 重复消费的问题(方法必须设计成幂等,一旦设计成幂等,可能造成线程安全隐患,所以引入分布式锁)
  2. 消息丢失如何处理.

面试题:消息丢失如何处理.

消息在哪里丢失

  • 发送没成功,没有解决不成功的业务逻辑
  • rocketmq保存的时候,断电,宕机,丢失消息(运行的时候,消息存储在内存)
  • 消费端丢失消息(没有成功处理消息,就直接返回success,并不是所有的消费逻辑都是先消费,再确认的,如果关注的是消费速度,不关注成功或者是否丢失,就可以这样处理)

发送端确保发送成功并且配合失败的业务处理

同步发送,接收发送结果,SEND_OK才结束.

客户端代码底层都有默认重试(retry 3 times).发送重试都失败了.

处理发送失败的逻辑.

  1. 发送到备用/失败的队列
  2. 记录日志,将消息来源,目标和消息内容,详细记录,等待监控系统,维护人员来直接处理

消费端确保消息不丢失

一定是先消息费,在确认,消费失败,返回失败(rocketmq消费点位保持原有位置不变,同一个消费者组,会重新拿到消息)

rocketmq 主从+同步刷盘

在这里插入图片描述
同步刷盘(消息数据可靠性保证): 如果持久化内存消息数据到磁盘失败,发送结果没有成功.

异步刷盘: 只要内存接收到了生产端的消息数据,数据是否持久化到磁盘,都会给生产端发送成功接收信息.

主从的双机热备: broker可以配置主从,考虑数据可靠性,和性能,一般主master做同步刷盘,slave做异步刷盘.(都同步刷盘,100%保证消息只要到达rocketmq就不会丢失,但是性能不能保证.)

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

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

相关文章

go-zero学习 第六章 分布式事务dtm

go-zero学习 第六章 分布式事务dtm 1 参考文档2 官方示例3 go-zero使用dtm参考代码3.1 go-zero支持dtm 代码操作步骤※3.2 gozerodtm 代码操作步骤 4 注意事项4.1 grpc接口地址※4.2 动态调用过程4.3 dtm的回滚补偿4.4 barrier的空补偿、悬挂等4.5 barrier在rpc中本地事务 1 参…

多媒体工作中用到的小工具

安利下嵌入式多媒体用到的各种小工具 下面是同事们推荐的 以下是对这些软件的简要介绍: ocenaudio:ocenaudio是一款跨平台的音频编辑软件,它提供了直观的界面和丰富的功能,可以帮助用户进行音频剪辑、修复、转码等操作。 MKVToolNix:MKVToolNix是一款开源的多媒体容器格…

web-vim信息泄露

&#xff08;1&#xff09;知识补充 vim 交换文件名 在使用vim时会创建临时缓存文件&#xff0c;关闭vim时缓存文件则会被删除&#xff0c;当vim异常退出后&#xff0c;因为未处理缓存文件&#xff0c;导致可以通过缓存文件恢复原始文件内容   以 index.php 为例&#xff1…

Redis一主二从三哨兵模式

文章目录 Redis一主二从三哨兵模式环境配置实践配置主从配置哨兵模式 测试主从复制测试模拟master宕机恢复master Redis一主二从三哨兵模式 当你使用Redis作为主从复制的架构&#xff0c;并且希望在出现主节点故障时自动进行故障转移时&#xff0c;适用于一主而从三哨兵模式。…

android 面试题目之handler消息机制

Handler消息机制是Android里面很基础的东西&#xff0c;基本上属于必考题 一般会从如下几个方面来考查 实现原理&#xff0c;Handler/Message/MessageQueue/Looper 几个类的实现流程&#xff0c;Handler导致的内存泄露怎么处理主线程的Looper是什么时候创建的&#xff1b;如果…

3. Spring 更简单的读取和存储对象(五大类注解 方法注解)

目录 1. 存储 Bean 对象 1.1 配置扫描路径 1.2 添加注解存储 Bean 对象 1.2.1 Controller&#xff08;控制器存储&#xff09; 1.2.2 Service&#xff08;服务存储&#xff09; 1.2.3 Repository&#xff08;仓库存储&#xff09; 1.2.4 Component&#xff08;组件存储&…

C语言学习笔记 Ubuntu系统下部署gcc编译工具-01

在22.04版本 ubuntu系统下&#xff1a; 1.进行apt工具包更新 sudo apt-get update 2.安装gcc工具 sudo apt-get install -y gcc 3.创建一下文件&#xff0c;并编译它输出相应的内容 touch hello.c 4.使用gcc编译c源程序并运行 格式&#xff1a;gcc 源文件名 -o 生成的执行文…

AcWing 1210. 连号区间数

输入样例1&#xff1a; 4 3 2 4 1输出样例1&#xff1a; 7输入样例2&#xff1a; 5 3 4 2 5 1输出样例2&#xff1a; 9样例解释 第一个用例中&#xff0c;有 77 个连号区间分别是&#xff1a;[1,1],[1,2],[1,3],[1,4],[2,2],[3,3],[4,4][1,1],[1,2],[1,3],[1,4],[2,2],[3,3…

记一次vscode配置CMake编译task的坑

事情经过是这样的&#xff0c;博主在一个项目中需要使用交叉编译链进行项目编译&#xff0c;但是在CMake中有一个自定义的编译选项&#xff0c;在vscode中配置task任务后&#xff0c;编译发现终端报静态库.a文件格式错误&#xff0c;如下图所示&#xff1a; 但是如果在CMakeLis…

python与深度学习(七):CNN和fashion_mnist

目录 1. 说明2. fashion_mnist实战2.1 导入相关库2.2 加载数据2.3 数据预处理2.4 数据处理2.5 构建网络模型2.6 模型编译2.7 模型训练2.8 模型保存2.9 模型评价2.10 模型测试2.11 模型训练结果的可视化 3. fashion_mnist的CNN模型可视化结果图4. 完整代码 1. 说明 本篇文章是C…

Install the Chinese input method on Linux

Open terminal and input: sudo -i apt install fcitx fcitx-googlepinyinWait for it to finish. Search fcitx: "设置"-->"输入法": Finally, we get the following result&#xff1a; Ctrl Space&#xff1a;Switch the input method. The test …

Redis追本溯源(三)内核:线程模型、网络IO模型、过期策略与淘汰机制、持久化

文章目录 一、Redis线程模型演化1.Redis4.0之前2.Redis4.0之后单线程、多线程对比3.redis 6.0之后 二、Redis的网络IO模型1.基于事件驱动的Reactor模型2.什么是事件驱动&#xff0c;事件驱动的Reactor模型和Java中的AIO有什么区别3.异步非阻塞底层实现原理 三、Redis过期策略1.…

印刷和数字设计的页面布局软件 QuarkXPress 2023 Crack

QuarkXPress 2023 用于印刷 和数字设计的页面布局软件&#xff0c;使用 QuarkXPress 释放您的创造力并最大限度地提高生产力 图形设计和桌面出版流程早就应该进行创新和颠覆&#xff0c;所以 QuarkXPress 就来了。自 1987 年首次亮相市场以来&#xff0c;成千上万的创意专业人士…

RocketMQ教程-(5)-功能特性-事务消息

事务消息为 Apache RocketMQ 中的高级特性消息&#xff0c;本文为您介绍事务消息的应用场景、功能原理、使用限制、使用方法和使用建议。 事务消息为 Apache RocketMQ 中的高级特性消息&#xff0c;本文为您介绍事务消息的应用场景、功能原理、使用限制、使用方法和使用建议。…

FFmpeg AVFilter的原理(三)- filter是如何被驱动的

1、下面是一个avfilter的graph 上图是ffmpeg中doc/examples中filtering_video.c案例的示意图。 本章节主要查看avfilter中的数据是怎么进入的&#xff0c;然后又是怎么出来的。 主要考察两个函数&#xff1a; av_buffersrc_add_frame_flags&#xff08;&#xff09;av_buffers…

gcc编译的时候出现错误,可以用core查看错误信息

比如说我们有文件main.c,threadpool.c,threadpool.h main.c和threadpool.c都用了threadpool.h,也就是#include "threadpool.h" &#xff08;1&#xff09;如果我们直接使用gcc main.c -o a.out -lpthread会报如下的错 我们需要进行动态库链接 gcc -c threadpool.c -…

驱动开发 day3 (模块化驱动启动led,蜂鸣器,风扇,震动马达)

模块化驱动启动led,蜂鸣器,风扇,震动马达并加上Makefile 封装模块化驱动&#xff0c;可自由安装卸载驱动&#xff0c;便于驱动更新(附图) 1.安装模块驱动同时初始化各个设备并使能 2.该驱动会自动创建驱动节点. 3.通过c函数程序输入控制各个设备 4.卸载模块驱动 //编译驱动…

裂缝二维检测:裂缝类型判断

裂缝类型选择 裂缝类型有很多种&#xff0c;这里我们判断类型的目的是要搞明白是否有必要检测裂缝的长度。在本文中&#xff0c;需要判断的裂缝类型共有四种&#xff1a;横向裂缝、纵向裂缝、斜裂缝、网状裂缝。 环境搭建 上一节骨架图提取部分&#xff0c;我们已经安装了sk…

Linux centos安装openoffice在线预览

前言&#xff1a;由于项目里需要用到word、excel等文件的在线预览&#xff0c;所有选择了openoffice 1、下载openoffice Apache OpenOffice - Official Download 大家自行选择需要安装的版本&#xff0c;楼主由于之前在其他服务器安装过&#xff0c;选择了之前用过的版本&am…

4.Docker数据管理和容器互联

文章目录 Docker数据管理数据卷&#xff08;容器与宿主机之间数据共享&#xff09;数据卷容器&#xff08;容器与容器之间数据共享&#xff09;容器互联 Docker数据管理 数据卷&#xff08;容器与宿主机之间数据共享&#xff09; 数据卷是一个供容器使用的特殊目录&#xff0…