Java服务通过动态开关 Profiling 实现关键问题定位-故障定位

作者 观测云 高级技术专家 深圳办公室 黄小龙

简介

Profile 通过收集和分析应用程序运行过程中 CPU、内存和 I/O 相关的数据,可以识别应用程序的性能瓶颈和错误,帮助我们更好地了解程序的运行情况。Profile 是一种非常有价值的技术,通过 Profile 可以实现:

  • 识别性能瓶颈: Profiling 可以帮助发现程序的性能瓶颈,即程序中最耗费时间的部分。通过识别性能瓶颈,以便我们采取措施来优化这部分性能。
  • 优化程序性能: 通过 Profiling 收集的数据,我们可以知道程序的哪些部分需要进行优化。通过优化这些部分,可以提高程序的性能,减少资源的消耗。
  • 排查程序错误: 在应用程序中,有时会出现各种错误。通过 Profiling 分析程序的运行情况,我们可以找到错误的原因,并及时进行修复。

利用**观测云**可以快速收集应用程序 Profile 数据,利用 Profile 火焰图查看器分析 Java 、Python 、 Go 不同语言环境下应用程序运行过程中的动态性能数据,直观 查看每一个方法、类和线程的调用关系和执行效率。通过关联链路,获取链路相关 Span 的关联代码执行片段,实现方法级代码性能追踪,帮助开发人员发现代码优化方向。

收集应用程序 Profile 数据,会对应用程序本身性能造成一定的影响,所以一般情况下不建议在生产环境实时启动 Profile 数据收集,在 Kubernetes 部署的应用程序可以通过动态开关打开 Profile 数据采集,从而实现对应用程序的关键问题定位。


前置条件

安装 DataKit

登录观测云控制台,选择「集成」-「DataKit」-「Kubernetes」,按照所提示的安装步骤下载 datakit.yaml,并配置 DataWay 数据网关地址。

打开 Profile 采集器

修改 datakit.yaml 文件,ConfigMap 加入 Profile 采集配置:

arduino 复制代码
apiVersion: v1
kind: ConfigMap
metadata:
  name: datakit-conf
  namespace: datakit
data:
  profile.conf: |-  
    [[inputs.profile]]
      ## profile Agent endpoints register by version respectively.
      ## Endpoints can be skipped listen by remove them from the list.
      ## Default value set as below. DO NOT MODIFY THESE ENDPOINTS if not necessary.
      endpoints = ["/profiling/v1/input"]
    
      ## set true to enable election, pull mode only
      election = true
    
    ## go pprof config
    ## collect profiling data in pull mode
    #[[inputs.profile.go]]
      ## pprof url
      #url = "http://localhost:6060"
    
      ## pull interval, should be greater or equal than 10s
      #interval = "10s"
    
      ## service name
      #service = "go-demo"
    
      ## app env
      #env = "dev"
    
      ## app version
      #version = "0.0.0"
    
      ## types to pull
      ## values: cpu, goroutine, heap, mutex, block
      #enabled_types = ["cpu","goroutine","heap","mutex","block"]
    
    #[inputs.profile.go.tags]
      # tag1 = "val1"
    
    ## pyroscope config
    #[[inputs.profile.pyroscope]]
      ## listen url
      #url = "0.0.0.0:4040"
    
      ## service name
      #service = "pyroscope-demo"
    
      ## app env
      #env = "dev"
    
      ## app version
      #version = "0.0.0"
    
    #[inputs.profile.pyroscope.tags]
      #tag1 = "val1"

DaemonSet 添加相应的挂载配置:

yaml 复制代码
volumeMounts:
    - mountPath: /usr/local/datakit/conf.d/profile/profile.conf
      name: datakit-conf
      subPath: profile.conf

修改完成之后,安装 datakit.yaml 配置:

复制代码
kubectl apply -f datakit.yaml

应用配置

本文以 Java 应用程序为例,介绍如何动态开关打开 Profile 数据采集。

安装 Datakit Operator

Datakit-Operator 通过 K8s 的 Admission Controller(准入控制器)功能,会向指定的 Pod 注入应用程序启动所需要的 dd-lib 探针文件,这样就不需要应用程序打包镜像时加入对应的探针文件,减少配置从而提高效率。

下载 datakit-operator.yaml文件并执行安装:

vbnet 复制代码
wget https://static.guance.com/datakit-operator/datakit-operator.yaml
kubectl apply -f datakit-operator.yaml

通过以下命令验证所有配置是否安装完成,正常会有 datakit 和 datakit-operator 两种类型的 Pod:

arduino 复制代码
kubectl get pod -n datakit

修改 Dockerfile

修改应用 Dockerfile,暴露启动参数:

注意: java版本要求:java8版本需要高于8u262+,或者使用java11及以上版本。

bash 复制代码
FROM openjdk:8u292

RUN /bin/cp /usr/share/zoneinfo/Asia/Shanghai /etc/localtime
RUN echo 'Asia/Shanghai' >/etc/timezone

ENV jar my-service.jar
ENV workdir /data/app/
RUN mkdir -p ${workdir}
COPY ${jar} ${workdir}
WORKDIR ${workdir}
ENTRYPOINT ["sh", "-ec", "exec java ${JAVA_OPTS} -jar ${jar} ${PARAMS} 2>&1 > /dev/null"]

应用部署 yaml

添加以下环境变量,通过 ENV_PROFILE 环境变量来动态控制 Profile 数据采集:

yaml 复制代码
env:
- name: DD_AGENT_HOST
  valueFrom:
    fieldRef:
      apiVersion: v1
      fieldPath: status.hostIP
- name: ENABLE_PROFILE
  value: "true"
- name: JAVA_OPTS
  value: |-
    -javaagent:/datadog-lib/dd-java-agent.jar -Ddd.service=my-service  -Ddd.profiling.enabled=$(ENABLE_PROFILE) -Ddd.profiling.allocation.enabled=$(ENABLE_PROFILE) -Ddd.env=env -Ddd.agent.port=9529

添加 annotation,位置 spec.template.metadata 下:

vbnet 复制代码
annotations:
  admission.datakit/java-lib.version: ""

生产环境部署时,ENV_PROFILE 环境变量可以设置为 false,需要排查性能问题时,再打开 Profile 开关收集数据,从而实现动态采集 Profile 数据的功能。

完整 yaml 参考:

yaml 复制代码
apiVersion: v1
kind: Service
metadata:
  name: my-service
  namespace: profile
  labels:
    app: my-service
spec:
  selector:
    app: my-service
  ports:
    - protocol: TCP
      port: 9299
      nodePort: 30001
      targetPort: 9299
  type: NodePort
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: my-service
  namespace: profile
  labels:
    app: my-service
spec:
  replicas: 1
  selector:
    matchLabels:
      app: my-service
  template:
    metadata:
      labels:
        app: my-service
      annotations:
        admission.datakit/java-lib.version: ""
    spec:
      containers:
        - name: my-container
          env:
          - name: DD_AGENT_HOST
            valueFrom:
              fieldRef:
                apiVersion: v1
                fieldPath: status.hostIP
          - name: ENABLE_PROFILE
            value: "true"
          - name: JAVA_OPTS
            value: |-
              -javaagent:/datadog-lib/dd-java-agent.jar -Ddd.service=my-service  -Ddd.profiling.enabled=$(ENABLE_PROFILE) -Ddd.profiling.allocation.enabled=$(ENABLE_PROFILE) -Ddd.env=env -Ddd.agent.port=9529
          image: my-service:v1.0
          imagePullPolicy: IfNotPresent
          ports:
          - containerPort: 9299
            protocol: TCP
      restartPolicy: Always

实现效果

相关推荐
码出极致3 分钟前
Redisson分布式缓存与数据一致性保障
后端
用户790349033716 分钟前
springboot集成redisson实现redis分布式锁
后端
陈随易10 分钟前
程序员的新玩具,MoonBit(月兔)编程语言科普
前端·后端·程序员
码出极致16 分钟前
Redisson秒杀系统中的分布式锁应用
后端
Shimiy23 分钟前
第四章 数组
java
间彧23 分钟前
什么是JVM Young GC
java·jvm
xiaok24 分钟前
@Param注解的作用
java·后端
脑袋大大的27 分钟前
钉钉企业应用开发技巧:查询表单实例数据新版SDK指南
java·钉钉·企业应用开发
Sperains32 分钟前
async/await和Synchronous的区别
后端
码出极致35 分钟前
Redisson可重入锁(RLock)的使用与原理
后端