一个简单问题的复杂排查

开始

2023-08-14 18:30 正在便宜坊和妹妹快乐的吃烤鸭,突然收到一个告警电话,以为是业务告警,没管。

19:00左右的时候,看到群里在艾特我,portal的某个接口挂了,请求超时。

再过了一会,有已经在工位的同事,通过看监控,发现是一个节点挂了,我们线上管控面总共部署了6个副本,挂了一个,而业务的请求正好打在这个节点上,所以请求超时。

快速吃完饭,回公司查bug。

监控现象

当时该节点监控 CPU已打到100%,该节点线程数短短几分钟从几十个增加到1500个。

堆栈频繁GC。

日志现象

前提

管控面在启动的时候,会去启动list watch,管控面去直接调用k8s接口,去watch k8s 内所有应用,监测其状态变化。由于线上集群可以动态添加,所有在添加新集群时,会去调用启动watch的方法,把新集群注册进去,以便于不用重启就可以watch新添加的集群。

 这样就为新的集群创建了一个线程,线程会去执行ApplicationWatcher的run方法。下图:

但是由于此处是死循环,在执行到listNameSpacedCustonObject的时候,抛了异常,由于线上代码在捕获到ApiException的时候未打印日志,因此没找到到底抛出了什么异常。

可以确定的是,死循环抛出异常频繁有异常,堆栈被打满,频繁GC。 

15号看了好久 想不明白为什么线程数会激增,明明代码逻辑只会有一个线程。

深入源码

仔细看了,有一行client.getClient(),点击进去看了下。

 点进build方法,里面有client.getHttpClient,继续:

 在ApiClient中初始化了httpClient,继续看这个builder。

 这个builder里面new 了一个Dispatcher对象。

去看 Dispatcher这个类,发现它里面定义了线程池!

 而initHttpClient中,httpClient = builder.build(),以下是这个build方法,即拿了刚刚创建的Dispatcher对象。

到这里,可以分析出来,线程数激增就是每次都获取client导致的。

在catch到apiExecption时,可以看到做了一个clear client操作,有异常后clear调,下次循环进来又会重新创建,导致在不停的创建http client,每次都会创建线程池。

默认情况下,OkHttpClient使用了两个线程池:

  1. Dispatcher线程池:Dispatcher负责调度和执行请求。使用一个线程池来管理请求的执行。Dispatcher线程池默认最大并发请求数为64。
  2. ConnectionPool线程池:ConnectionPool用于管理HTTP和SPDY连接的复用。使用一个线程池来保持长时间活动的连接,并在需要时提供给请求。连接池线程池的大小默认为5。

异常

那么还剩最后一个问题,为什么会报apiExecption?

仔细检查pfinder发现:

调用k8s的时候执行失败,报401。

跟同事确认过后,因为是新加的集群,还没有新建crd,所以请求时请求不到资源,报错。

反思

重要的异常一定要打印日志!!方便排查。而且关于死循环中使用线程的情况,一定要慎之又慎,一不小心节点就会被打挂掉。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值