记一次线程堵塞(挂起)导致消息队列积压

2023-10-29

1 背景

A服务作为生产者,每天发送上千万的mq消息,每一个消息包含500个用户ids数据。B服务作为消费者,接受MQ消息并通过http调用第三方请求进行业务处理,消费组启用了rabbitmq的多线程消费组,一个实例并发40个mq消费者线程,每个线程一次获取10个消息进行消费。

Mq消费者配置如下:

# mq配置
  rabbitmq:
    connection-timeout: 15000
    cache:
      channel:
        size: 200
    # 消息发送到rabbitmq broker cluster需要回调
    publisher-confirms: true
    # 交换机将消息投递至队列失败时需要回调
    publisher-returns: true
    listener:
      # 手动确认消息已被消费
      simple:
        acknowledge-mode: manual
        # consumer的并发数
        concurrency: 40
        max-concurrency: 50
        # 每个消息者每次取10条
        prefetch: 10

Mq挤压消息如下

2 排查

2.1 复制rabbitmq挤压消息数据进行模拟复现

找出rabbitmq挤压的消息,在本地模拟消费,找出没有进行消息确认的原因,通过rabbitmq控制台的Get messages功能

复制payload的消息进行base64转码,转出来的消息是乱码不完整的,怀疑
是rabbitmq还结合了其他加密处理,放弃这种排查思路

2.2 检查报错日志

rabbitmq的unack消息挤压,那就是消费者没有进行ack确认,怀疑消费者代码有异常导致没能执行到ack的代码。
查询服务器日志,没发现有报错的日志,梳理业务代码,消费者使用了spring aop around机制进行消息确认,所以不管代码有没有报错,按理说都会手动进行mq消息ack确认

2.3 检查服务是否宕机

消费组实例数量符合服务器大小配置,因此服务器应用没有宕机

2.4 检查java线程

使用IBM的TMDA工具进行分析线程堆栈,工具下载地址
TMDA工具下载地址

TMDA工具简介

TMDA分析线程堆栈结果如下

通过分析图,看到大量park线程,确实是符合现状,应用的线程挂起了

3 分析和解决

通过stack深度高到底排序,业务代码存在线程等待情况,具体代码CountDownLatch.await

3.1 结合业务代码分析

通过上图stack提示,找到关联的业务代码

伪代码如下:

// new一个CompletableFuture
public CompletableFuture<Integer> httpCall(String tokenData){
        CompletableFuture<Integer> completableFuture = CompletableFuture.supplyAsync(() -> {
            long time = 3000L;
            try {
                Thread.sleep(time);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            return Integer.parseInt(tokenData);
        });
        return completableFuture;
    }

httpCall(tokenData).whenCompleteAsync((returnValue, ex)->{
		// do business
		// ex.getMessage()
		// 其中ex对象为空,使用ex.getMessage()报了空指针,导致没能执行如下的countDown
		countDownLatch.countDown();
	})

消费者服务通过http调用第三方服务,为了提高并发,使用了多线程,每一组(数十个为一组)http请求批量调用完成后再把请求响应结果异步存入数据库,
主线程使用了countDownLatch.await进行等待,
其中whenCompleteAsync方法存在空指针问题,导致没能执行如下的countDown方法。

这里有人会问, 上面错误日志检查步骤,不是说日志没有空指针异常吗?
对,子线程报了空指针,因为CompletableFuture执行每次都是new 一个新的CompletableFuture对象,并把结果作为下一个CompletableFuture执行的入参,
通过伪代码可以发现,执行whenCompleteAsync后,没有新的CompletableFuture方法执行,所以异常没有抛出来,使得排查变得困难

3.2 解决

因为存在whenCompleteAsync报错的情况,添加多一个新的异常捕获处理方法,捕获异常也进行countDown的操作。

代码如下:

    httpCall(tokenData).whenCompleteAsync((returnValue, ex)->{
        // do business
        // ex.getMessage()
        // 其中ex对象为空,使用ex.getMessage()报了空指针,导致没能执行如下的countDown
        countDownLatch.countDown();
    }).exceptionally(e ->{
        log.info("exceptionally捕获到异常,tokenData={}, e={}", tokenData, e.getMessage());
        countDownLatch.countDown();
        return null;
    });

4 结论

  • 熟练CompletableFuture的使用,要看源码的实现(实现原理cas + 多个future采用入stack,每次把前一个future的结果作为参数传入下一个future去执行)

  • 使用多线程需要考虑异常、超时等情况

  • 熟练使用jvm stack分析工具

5 文章参考

CompletableFuture流程图

CompletableFuture参考文章如下

CompletableFuture 原理浅析

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

记一次线程堵塞(挂起)导致消息队列积压 的相关文章

随机推荐

  • 百度又发布一个神器!网友直呼好家伙

    目标检测作为计算机视觉领域的顶梁柱 不仅可以独立完成车辆 商品 缺陷检测等任务 也是人脸识别 视频分析 以图搜图等复合技术的核心模块 在自动驾驶 工业视觉 安防交通等领域的商业价值有目共睹 正因如此 YOLOv5 YOLOX PP YOLO
  • 你了解这些算法吗?SHA256、RIPEMD-160、DES、AES、RSA、ECC

    一 HASH算法 哈希散列算法和哈希摘要算法都叫做哈希算法 1 概念 把一段任意长度的数据变成均匀分布固定长度的数据 反之不可以 Hash不可逆 在任何电脑 手机 或者笔算Hash值都是一样的 y Hash x 已知x可以得到y 反之不可以
  • 【ElasticSearch系列连载】7. 关于ES数据读写那点事儿

    1 对文档建索引 1 1 自定义文档ID 如果数据本身有自己的唯一标记 那么在建立索引时可以使用id来指定文档的id 如下 使用curl在your index索引下写入一个id 1001的文档 curl H Content Type app
  • 归并排序 笔试面试手写代码常考

    归并排序是将两个或者两个以上的有序序列进行合并的一种排序算法 采用了分治的思想 它的主要思路是将序列分为两个子序列 对于两个最终有序的子序列进行合并 得到有序的整体序列 如何保证子序列有序呢 对子序列采用同样的方式进行划分 当子序列长度为1
  • 一文看懂人工智能芯片的产业生态及竞争格局

    近日 国内人工智能芯片公司寒武纪科技 Cambricon 获得了一亿美元A轮融资 是目前国内人工智能芯片领域初创公司所获得的最高融资记录 如果要说这桩融资对人工智能领域的最直接意义 或许是让人工智能芯片逐渐走入了更多人的视野 深度学习不仅在
  • linux centos 系统提示No space left on device错误 centos清理硬盘空间

    一 问题描述 线上的一个centos系统 硬盘满了 通过以下方式清理后 启动程序还是会提示No space left on device错误 具体请看解决方法 这里讲下如何清理硬盘 1 查看系统磁盘是否已满 df h 看哪个目录use到10
  • LeetCode每日刷题:两个数组的交集

    题目 给你两个整数数组 nums1 和 nums2 请你以数组形式返回两数组的交集 返回结果中每个元素出现的次数 应与元素在两个数组中都出现的次数一致 如果出现次数不一致 则考虑取较小值 可以不考虑输出结果的顺序 解题思路 双指针 排序 先
  • 单向散列函数介绍

    一 点睛 单向散列函数有一个输入和一个输出 其中输入称为消息 输出称为散列值 单向散列函数可以根据消息的内容计算出散列值 而散列值就可以被用来检查消息的完整性 单向散列函数根据消息的内容计算出散列值 这里的消息不一定是人类能够读懂的文字 也
  • Yolov5s/Yolov8s网络结构图

    一 网络模型配置 Yolov5s Parameters nc 1 number of classes depth multiple 0 33 model depth multiple width multiple 0 50 layer ch
  • 【AntDB数据库】AntDB数据库价值优势

    AntDB数据库的技术优势 Oracle语法兼容 AntDB与Oracle数据库高度兼容 使得企业现有的基于Oracle数据库开发的应用程序无需做任何修改或只做少量的修改便可以运行在AntDB平台之上 由此降低了程序迁移的风险 减少了重写应
  • Spring Data Jpa之JAP注解+多表设计

    1 JPA注解的使用 package com example jpademo entity import javax persistence Entity 标记该类是一个实体类 指定表名 当实体类名称与表名一致时 可以省略不写 Table
  • Find Peak Element

    A peak element is an element that is greater than its neighbors Given an input array where num i num i 1 find a peak ele
  • ThinkPHP5配置redis缓存和Redis的CURD操作

    一 连接redis use think Cache 转自 http www zzuyxg top article 444 html 转自 https blog csdn net qq 37462176 article details 794
  • C++中, 结构体 vector 使用 sort排序

    qvector 结构体排序 c 实现成绩排序 C 中 结构体 vector 使用 sort排序 QVector容器内元素排序和去重简单用法 sort vector unique erase QVector排序 qvector 结构体排序 c
  • 类的初始化和实例化的区别

    类的初始化 是完成程序执行前的准备工作 在这个阶段 静态的 变量 方法 代码块 会被执行 同时在会开辟一块存储空间用来存放静态的数据 初始化只在类加载的时候执行一次 类的实例化 是指创建一个对象的过程 这个过程中会在堆中开辟内存 将一些非静
  • IC常用知识4-静态功耗和动态功耗

    文章目录 1 简介 2 静态功耗 3 动态功耗 3 1 开关功耗 3 2 短路功耗 4 低功耗设计 4 1 RTL级 4 2 门级电路 5 相关题目 1 简介 CMOS电路功耗主要由动态功耗和静态功耗组成 动态功耗又分为开关功耗 短路功耗两
  • 将 List 转换为 String

    将 List 转换为 String的几种方式 1 使用toString 方法将 List 转换为 String 2 使用Java 8 Streams Collectors api和String join 方法将带有逗号分隔符或自定义分隔符的
  • 过去式加ed的发音_小学英语规则动词的过去式总结汇总

    学习导航 点击文字 课本视频 课本音频 单词听写 句子听写 课本听写 单词卡片 微课 课本剧 活动手册 活 答案 评价手册 评 答案 拓展阅读 第1课堂 Mini课堂 课堂在线 同步歌曲 英文儿歌 课堂跟踪 53天天练 英文故事 英文绘本
  • 力扣:130. 被围绕的区域(深度优先算法)

    给你一个 m x n 的矩阵 board 由若干字符 X 和 O 找到所有被 X 围绕的区域 并将这些区域里所有的 O 用 X 填充 示例 1 输入 board X X X X X O O X X X O X X O X X 输出 X X
  • 记一次线程堵塞(挂起)导致消息队列积压

    1 背景 A服务作为生产者 每天发送上千万的mq消息 每一个消息包含500个用户ids数据 B服务作为消费者 接受MQ消息并通过http调用第三方请求进行业务处理 消费组启用了rabbitmq的多线程消费组 一个实例并发40个mq消费者线程