多线程场景下由于线程池使用不当引发的血案

背景分析:我们在开发某些类似用户运营侧的需求的时候,往往需要调用多个其他团队的接口来完成数据的整理,再处理业务,如果我们每个接口都同步串行调用,那么总体的响应时长往往是不可接受的,就需要考虑多线程异步的方式来并行调用,加快处理速度,但是使用多线程,尤其线程池使用不当,却会带来严重的后果。

问题再现:
我们先看看结果,由于某次需求需要紧急上线,开发时间和联调自测时间都很紧张,产品提出了需要根据试点城市处理某些业务的需求,下游的团队为我们提供了多个接口去满足试点城市业务的接口,其实接口就是通过某些参数的是否传递来实现的。
再开发的时候,为了能够并行的使用线程池调用多个接口,加快处理速度,就没有区分试点城市,统一处理,结果在生产环境做实验,下游接口无法支撑,超时严重。需要紧急临时改方案,于是,加上了试点城市的区分逻辑,减轻下游接口的压力。问题就出现在这里,看下代码:

CompletableFuture<Map<Key, Value>> future = asyncMethod(params);
        try {
            resultMap = future.get();
        } catch (InterruptedException | ExecutionException e) {
            log.error("查询**接口发生异常", e);
        }

private CompletableFuture<Map<Key, Value>> asyncMethod(RequestVo requestVo) {
        CompletableFuture<Map<Key, Value>> future = CompletableFuture
                .supplyAsync(() -> api.getInfo(requestVo.getParam()), executor)
                .exceptionally(e -> {
                    log.error("查询**接口发生异常", e);
                    return new HashMap();
                });
        return future;
    }

代码的变量名称进行了改写,改成非试点城市调用的逻辑,本来可以直接简单的写成同步调用,因为非试点的逻辑只需要调这一个接口就可以了,但当时紧急上线,直接把试点的代码段拷贝过来上线了。
上线后,当时没有什么问题,等到白天高峰期,观察这台服务的监控,cpu的使用率和内存的使用率快速增长,导致的严重后果就是,上游的接口获取不到关键信息频繁超时,甚至服务的可用性短时间内下降了10个百分点。
于是当时检查代码发现逻辑没问题,下游接口也没有大量超时,就采取了第一步措施,增加pot节点,扩大了线程池的配置:

@Configuration
public class MyTPC {
    @Autowired
    private ThreadPoolTaskExecutor e;

    public @PostConstruct void init() {
        ex.setMaxPoolSize(512);
        e.setCorePoolSize(512);
    }
}

从原来的256省到了512,然后再次上线,问题短时间内得到了缓解,但是再观察1小时,内存和cpu使用率还是在不断增长,于是确定代码有问题。仔细回顾发现了上面的问题代码段,改成了同步接口后,重新部署,效果立竿见影,上游服务也没有再频繁超时了。
这里我配置的核心线程数等于最大线程数,没有配置等待任务队列和拒绝策略,都是使用的默认配置,需要回顾一下线程池的执行逻辑:
处理流程

  1. 当一个任务被提交到线程池时,首先查看线程池的核心线程是否都在执行任务,否就选择一条线程执行任务,是就执行第二步。
  2. 查看核心线程池是否已满,不满就创建一条线程执行任务,否则执行第三步。
  3. 查看任务队列是否已满,不满就将任务存储在任务队列中,否则执行第四步。
  4. 查看线程池是否已满,不满就创建一条线程执行任务,否则就按照策略处理无法执行的任务。

由于服务的并发量较大,核心线程数很快就占满了,后面的请求逐步放到阻塞队列,默认阻塞队列的最大值是Integer.MAX_VALUE,这里的问题在于很多的请求被搁置在了阻塞队列等待,导致超时严重,影响了上游的业务,由于快速创建线程的开销以及存储了大量请求到阻塞队列,cpu和内存的使用率都快速增长,虽然扩容后能短时缓解,但终究不是解决问题的办法。

解决问题的方案:
解决方案其实大家都心知肚明了,就是把异步调用改成最简单的同步调用,只要下游服务不严重超时,我的上游服务的请求也能够得到保障的,因为本身计算逻辑很简单。
问题解决后,擦了一把冷汗,虚惊一场,我们作为程序员,一定要对代码有一种敬畏之心,不可大意失荆州。

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值