" />
文章

Java / K8s 线上问题排查

一、如何定位容器或 Pod 的问题来源

1. 使用 Kubernetes 和 Docker 的监控工具

要确定哪个服务或容器可能存在问题,可以利用监控工具来查看资源使用情况,例如:

  • Kubernetes 环境:
  • Prometheus 和 Grafana:Prometheus 可以抓取 Kubernetes 集群中所有 Pod 和节点的资源使用数据,Grafana 可以以可视化图表的形式展示这些数据。这些工具可以帮助你快速找到哪个 Pod 出现了异常的 CPU、内存使用情况。
  • kubectl top 命令:可以查看节点和 Pod 的资源使用情况。
    bash kubectl top pod -n <namespace> kubectl top node
  • Docker 环境:
  • Docker Stats:可以查看所有容器的资源使用情况,帮助你识别哪个容器占用的资源过高。
    bash docker stats

2. Kubernetes 日志与事件查看

  • 查看 Pod 的日志:
  • 通过 kubectl logs 命令查看具体 Pod 的日志,检查是否存在异常的错误信息。
    bash kubectl logs <Pod名> -n <namespace>
  • 查看 Pod 的状态:
  • 使用 kubectl describe pod <Pod名> -n <namespace> 查看 Pod 的事件和状态,检查是否有重启、崩溃等异常情况。

二、具体服务的深入排查

1. CPU 占用高的深入排查

在 Kubernetes 环境下
  1. 定位异常 Pod:
    使用 kubectl top pod 来找到 CPU 占用高的 Pod:
    bash kubectl top pod -n <namespace>
    找到 CPU 占用异常的 Pod。

  2. 进入 Pod 排查线程:
    可以进入该 Pod 内部的容器,进一步查看具体的 Java 进程状态。
    bash kubectl exec -it <Pod名> -n <namespace> -- /bin/bash
    进入容器后,使用 top 或 htop 来确认哪个线程 CPU 占用最高。

  3. 通过 jstack 分析线程栈:
    如果确认某个 Java 线程 CPU 占用过高,可以使用 jstack 打印线程栈来分析。
    bash jstack <Java进程ID> > thread_dump.txt
    使用 top -H -p <Java进程ID> 获取线程 ID,并通过转换为 16 进制来找到具体线程。

比如生成如下的内容

```

"d-m-pool-1731" #1358923 prio=5 os_prio=0 cpu=146.47ms elapsed=1359.17s tid=0x00007f4258025000 nid=0x1d42 waiting on condition [0x00007f421e932000]
java.lang.Thread.State: WAITING (parking)
at jdk.internal.misc.Unsafe.park(java.base@14.0.1/Native Method)
- parking to wait for <0x000000075906e938> (a java.util.concurrent.SynchronousQueue$TransferStack)
at java.util.concurrent.locks.LockSupport.park(java.base@14.0.1/LockSupport.java:211)
at java.util.concurrent.SynchronousQueue$TransferStack.awaitFulfill(java.base@14.0.1/SynchronousQueue.java:460)
at java.util.concurrent.SynchronousQueue$TransferStack.transfer(java.base@14.0.1/SynchronousQueue.java:361)
at java.util.concurrent.SynchronousQueue.take(java.base@14.0.1/SynchronousQueue.java:920)
at java.util.concurrent.ThreadPoolExecutor.getTask(java.base@14.0.1/ThreadPoolExecutor.java:1056)
at java.util.concurrent.ThreadPoolExecutor.runWorker(java.base@14.0.1/ThreadPoolExecutor.java:1116)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(java.base@14.0.1/ThreadPoolExecutor.java:630)
at java.lang.Thread.run(java.base@14.0.1/Thread.java:832)

"Attach Listener" #1370841 daemon prio=9 os_prio=0 cpu=0.68ms elapsed=0.11s tid=0x00007f433c061000 nid=0x4c63 waiting on condition [0x0000000000000000]
java.lang.Thread.State: RUNNABLE

"VM Thread" os_prio=0 cpu=207885.11ms elapsed=168498.05s tid=0x00007f436c060000 nid=0x17 runnable

"VM Periodic Task Thread" os_prio=0 cpu=192715.49ms elapsed=168498.00s tid=0x00007f436c0cb800 nid=0x22 waiting on condition

JNI global refs: 625, weak refs: 24806
```

每个线程的堆栈信息包括:

  1. 线程名称和编号。
  2. 线程的优先级(prio 和 os_prio)。
  3. CPU 消耗时间。
  4. 线程状态以及线程堆栈信息。

解析:

  • 线程名称:"d-m-pool-1731" 是线程的名称,可能是由线程池创建的某个工作线程。

  • 线程 ID

    :

    • tid(Thread ID):0x00007f4258025000,线程在 JVM 内部的标识符。
    • nid(Native ID):0x1d42,这个是操作系统中线程的 ID。
  • 优先级

    :

    • prio=5:Java 线程优先级。
    • os_prio=0:操作系统级的线程优先级。
  • CPU 消耗:cpu=146.47ms,表明该线程消耗了 146.47 毫秒的 CPU 时间。

  • 线程状态

    :

    java.lang.Thread.State: WAITING (parking)

    • 该线程处于 WAITING 状态,表示它正在等待一些操作完成。例如等待某个锁的释放或等待某个条件。
    • (parking) 表示线程被挂起,这通常是由于等待任务完成或线程池的工作线程没有新的任务。

堆栈信息:

  • 该线程正在等待 SynchronousQueue 的队列操作,这意味着它是在等待任务或其他线程的信号来继续执行。
  • at java.util.concurrent.locks.LockSupport.park(...) 表示线程被挂起,等待一个事件,这种情况常见于线程池中的工作线程等待新的任务。
在 Docker 环境下

使用 docker stats 确定具体 CPU 占用高的容器,然后进入容器进行相似的操作。

2. 服务卡顿问题的排查

使用分布式追踪工具

在微服务环境下,服务卡顿很可能是由于链路上某个微服务的响应速度变慢引起的,使用分布式追踪工具可以非常有效地进行问题定位:
- Jaeger 或 Zipkin:这些工具可以对微服务之间的调用链路进行追踪,帮助发现哪个微服务响应缓慢以及瓶颈所在。

3. 内存占用高的排查

在 Kubernetes 环境下
  1. 使用 kubectl top:
    找到占用内存较高的 Pod。
    bash kubectl top pod -n <namespace>

  2. 生成 heap dump 文件:
    进入目标 Pod 后,生成 heap dump 文件用于进一步分析:
    bash jmap -dump:format=b,file=/tmp/heap_dump.hprof <Java进程ID>
    然后把 heap dump 文件从容器中拷贝出来分析:
    bash kubectl cp <namespace>/<Pod名>:/tmp/heap_dump.hprof ./heap_dump.hprof
    使用 MAT 或其他工具分析 heap dump 文件,检查内存泄漏或大对象分配。

三、集成监控和报警系统

为了实时监控和及时发现 CPU、内存占用异常的问题,推荐在系统中集成监控和报警系统:

  • Prometheus + Grafana:用于监控 Kubernetes 集群的 CPU、内存、网络等各项指标,Grafana 可以进行可视化展示,并设置阈值报警。
  • ELK(Elasticsearch, Logstash, Kibana):用于日志的集中管理和分析,可以方便地查看服务日志,结合报警策略发现异常情况。
  • K8s Horizontal Pod Autoscaler (HPA):在 Kubernetes 中可以使用 HPA 自动扩展 Pod 数量,来应对高 CPU 或内存占用的情况,从而减轻单个 Pod 的压力。

最优方案

使用 arthas,可以使用curl下载到镜像中或者直接dockerfile中加入到打包的镜像

Java 内存占用高查看

jvisualvm命令,可以使用Java自己的控制台查看内存和GC情况

image.png

z

License:  CC BY 4.0