线程池使用不规范,连接数被占满,系统卡顿

简介: 线程池使用不规范,连接数被占满,系统卡顿

故障现象

问题影响时间:

钉版:09:50-10:30 11:00-12:00 14:30-15:30 16:00-16:30

ng版:09:40-10:00 11:00-11:30 12:00-12:30 14:30-15:00 16:00-16:30

现象:报销单提单卡顿,数据库连接池耗尽。

恢复情况:16:30 全线恢复访问

故障处理过程

09:03 dyna 告警,连接数不够

image.png

image.png


都是dyna的线程数不够,这次没有重启实例。看起来只是线程数用的比较多

不过目前sizeWarn是40%就开始报警,是不是可以试着调高一些。

09:52 运维在群里提供了dyna 服务的dump 信息,并且第二次重启dyna服务。

10:12 dyna 服务恢复正常

image.png


在此之前,我们一直都认为是dyna 服务的卡顿导致系统的卡顿。


10:58 业务告警群出现响应时间超过500ms 的告警

image.png


11:07 研发查看日志发现是xxx-pro有一台响应时间比较长

image.png


11:08出现dt无法连接数据库告警

image.png


11:15 研发查看数据库会话管理,发现有400多条异常会话。研发查看数据库发现有较多慢sql,从慢sql的数量和执行时间来看不是主要的原因

image.png



11:23 研发查看日志,发现是数据库连接池满了

image.png


11:25 查看链路追踪的应用详情,发现有4台服务器响应时间过高

image.png


11:26 运维重启了这些异常的xxx-pro服务,11:35左右dt-prod恢复正常。

在重启之前志成导出了其中一台异常的xxx-pro服务的stack,

12:00 研发查看了stack文件,发现由http、taskExecutor和smartApproveTaskExecutor这几个线程数加起来的合计线程数达到了接近400个,而数据库的线程最大值只设置了100。猜测可能是因为昨天晚上发版的时候临时加了几个线程池引起数据库线程不足的情况,研发把xxx-pro的数据库连接池最大值调成了200,对xxx-prod和xxx-prod的xxxx-pro分别进行部署,于12:40左右部署完成。

下午14:30左右研发查看xxx-prod数据库会话管理,发现异常会话达到了1K多条,先通过手动杀线程的方式处理

image.png


14:43出现了数据库连接池占满的情况

image.png


对早上导出的线程明细进行了分析,发现等待线程中datahubGrpcService的调用占了大部分

image.png


15:00 对这里的异步线程池策略进行了调整,新增了一个线程池,3点左右分别对dt-prod和ng-prod的xxxx-pro进行部署,部署之后异常会话消除。

private final ExecutorService DATAHUB_SERVICE_POOL =

       new ThreadPoolExecutor(5, 20, 30, TimeUnit.SECONDS, new ArrayBlockingQueue<>(50),

           new NamedThreadFactory("datahub-service-pool"), new ThreadPoolExecutor.CallerRunsPolicy());



过了一会,ng 版提单卡顿,通过链路追踪的功能去定位当前耗时比较长的请求

image.png


15:40 通过日志定位,没有发现什么异常,联系运维同学导出当前机器的堆栈信息

image.png

image.png


通过上面观察,xxx-service-pool 有77个线程在等待,而创建的线程池只有70个,线程开始等待,导致系统卡顿。

注释掉submit 方法中,关于xxx.fetchBillDetailByFormDataCode 方法。半小时后,问题解决。

故障原因

datahubServiceImpl:
    public void asyncFetchBillDetail(String entCode, String userCode, String formDataCode) {
        DATAHUB_SERVICE_POOL.execute(()-> dataHubGrpcService.fetchBillDetail(entCode,userCode,formDataCode));
    }
/**
  * 生成账单明细
  * @param entCode 企业code
  * @param userCode 用户code
  * @param formDataCode 报销单code
 */
public void fetchBillDetail(String entCode,String userCode,String formDataCode) {
    logger.info("{}/{} fetchBillDetail with formDataCode {}",entCode,userCode,formDataCode);
    var request =
        DatahubService.BillDetailFetchV2Request.newBuilder()
        .setEntCode(entCode)
        .setOperator(userCode)
        .setFormDataCode(formDataCode)
        .build();
    callGRpcService(servicePrx -> servicePrx.fetchBillDetailByFormDataCode(request));
}
datahub 的grpc 接口实现类
/**
     * 根据报销单号获取账单明细
     * @param request
     * @param responseObserver
     */
@Override
public void fetchBillDetailByFormDataCode(DatahubService.BillDetailFetchV2Request request,
                                          StreamObserver<Empty> responseObserver) {
    try {
        List<DatahubBill> datahubBills =
            billService.listBillByFormDataCode(request.getEntCode(), request.getFormDataCode());
        if(CollectionUtils.isEmpty(datahubBills)){
            return;
        }
        List<String> dhBillCodes = datahubBills.stream().map(DatahubBill::getCode).collect(Collectors.toList());
        billService.fetchBillDetail(request.getEntCode(),request.getOperator(),dhBillCodes);
        responseObserver.onNext(null);
        responseObserver.onCompleted();
    } catch (Exception e) {
        logger.warn("exception when fetchBillDetail {}",e);
        responseObserver.onError(Status.UNKNOWN.withDescription(e.getMessage()).withCause(e).asRuntimeException());
    }
}

问题代码在第13行,如果没有关联月结账单,直接就return,没有关闭grpc 请求的流。

反思与改进方案

代码层面:

  1. 开发的时候没有考虑周全。为了让代码逻辑收敛、和对接口client 端友好,临时把两个接口合并成一个,在合并两个请求的时候按照通常的逻辑,直接return,没有注意到流的关闭。
  2. 自测的时候没有发现问题。在本地写自测用例,没有通过grpc 接口,重现不了。合到uat自己测试了几次,因为有线程池的存在,自己没有复现出来。
  3. 问题的定位分析。之前定位分析问题的时候,把自己的思路局限在了日志、ARMS 还有数据库的健康状态。这个问题涉及到线程的阻塞,很通过上面的方式直接定位到。还需要通过堆栈信息找到根本原因。
  4. 异步调用没有加链路监控,通过链路追踪无法排查

对策:

  1. 在方案设计的时候周全一点,不一定要用线程池做异步,还可以用mq。

 2. taskExecutor 类的滥用,目前系统中有超过100处的使用,后续可以根据具体的场景,判断是否需要使用单独的连接池。

 3. 对于异步调用, 增加日志链路监控。在项目中封装一个tracableTread 类,继承这个类,异步线程也可以实现链路追踪。

 4. grpc 远程调用的设置超时时间,根据服务选择合适的超时时间。修改AbstractBaseGRpcService,用户可以自定义超时的时间。

 5. grpc 服务端,需要统一下grpc服务端的写法规范。这里后续跟基础架构部的同事沟通下,最好是能够封装到service-common 中。

相关实践学习
实时数据及离线数据上云方案
本实验通过使用CANAL、DataHub、DataWorks、MaxCompute服务,实现数据上云,解决了数据孤岛问题,同时把数据迁移到云计算平台,对后续数据的计算和应用提供了第一步开山之路。
相关文章
|
3月前
|
Linux 调度 数据库
Linux下的系统编程——线程同步(十三)
Linux下的系统编程——线程同步(十三)
52 0
Linux下的系统编程——线程同步(十三)
|
4月前
|
消息中间件 缓存 Java
根据实际开发经验(订单管理系统),谈谈多线程开发的好处
根据实际开发经验(订单管理系统),谈谈多线程开发的好处
41 0
|
2月前
|
安全 数据处理 C++
【Qt 底层之事件驱动系统】深入理解 Qt 事件机制:主事件循环与工作线程的交互探究,包括 QML 的视角
【Qt 底层之事件驱动系统】深入理解 Qt 事件机制:主事件循环与工作线程的交互探究,包括 QML 的视角
126 3
|
7月前
|
存储 Linux 调度
Linux系统编程 多线程基础
Linux系统编程 多线程基础
30 0
|
2月前
|
资源调度 算法 Linux
Linux进程/线程的调度机制介绍:详细解析Linux系统中进程/线程的调度优先级规则
Linux进程/线程的调度机制介绍:详细解析Linux系统中进程/线程的调度优先级规则
129 0
|
2月前
|
存储 安全 数据管理
Linux系统编程教程之Linux线程函数的使用:讲解Linux线程函数
Linux系统编程教程之Linux线程函数的使用:讲解Linux线程函数
19 1
|
2月前
|
存储 算法 Linux
【Linux 系统标准 进程资源】Linux 创建一个最基本的进程所需的资源分析,以及线程资源与之的差异
【Linux 系统标准 进程资源】Linux 创建一个最基本的进程所需的资源分析,以及线程资源与之的差异
136 0
|
2月前
|
算法 Linux 调度
Linux 线程介绍:介绍Linux系统中线程的基本概念、创建和调度机制
Linux 线程介绍:介绍Linux系统中线程的基本概念、创建和调度机制
26 0
|
2月前
|
消息中间件 存储 安全
Linux 进程和线程介绍:介绍Linux系统中进程和线程的基本概念、执行方式和相互关系
Linux 进程和线程介绍:介绍Linux系统中进程和线程的基本概念、执行方式和相互关系
35 1
Linux 进程和线程介绍:介绍Linux系统中进程和线程的基本概念、执行方式和相互关系
|
2月前
|
编译器 API C语言
C/C++ 线程超详细讲解(系统性学习day10)
C/C++ 线程超详细讲解(系统性学习day10)

相关实验场景

更多