Observability:如何为 Java 应用进行 APM

在我之前的文章 “Solutions:应用程序性能监控/管理(APM)实践”,我已经详细介绍了 APM 以及如何针对 Java 应用进行 APM。在那篇文章中的  Baidu 天气服务由于一些原因现在不能正常工作。在今天的文章中,我将使用 Elastic 官方发布的 Java 例子来做详细的描述。我将一步一步地介绍如何进行设置。

安装

在今天的练习中,我们将使用 docker 来部署 Elastic Stack。首先,我们到如下的地址来下载 docker-compose.yml 文件:

curl -sLO https://raw.githubusercontent.com/elastic/apm-contrib/master/stack/docker-compose.yml

为方便大家阅读,我将该文件贴于下面:

docker-compose.yml

{
  "networks": {
    "default": {
      "name": "apm-integration-testing"
    }
  },
  "services": {
    "apm-server": {
      "cap_add": [
        "CHOWN",
        "DAC_OVERRIDE",
        "SETGID",
        "SETUID"
      ],
      "cap_drop": [
        "ALL"
      ],
      "command": [
        "apm-server",
        "-e",
        "--httpprof",
        ":6060",
        "-E",
        "apm-server.rum.enabled=true",
        "-E",
        "apm-server.rum.event_rate.limit=1000",
        "-E",
        "apm-server.host=0.0.0.0:8200",
        "-E",
        "apm-server.read_timeout=1m",
        "-E",
        "apm-server.shutdown_timeout=2m",
        "-E",
        "apm-server.write_timeout=1m",
        "-E",
        "logging.json=true",
        "-E",
        "logging.metrics.enabled=false",
        "-E",
        "setup.kibana.host=kibana:5601",
        "-E",
        "setup.template.settings.index.number_of_replicas=0",
        "-E",
        "setup.template.settings.index.number_of_shards=1",
        "-E",
        "setup.template.settings.index.refresh_interval=1ms",
        "-E",
        "monitoring.elasticsearch=true",
        "-E",
        "monitoring.enabled=true",
        "-E",
        "apm-server.instrumentation.enabled=true",
        "-E",
        "apm-server.kibana.enabled=true",
        "-E",
        "apm-server.kibana.host=kibana:5601",
        "-E",
        "apm-server.agent.config.cache.expiration=30s",
        "-E",
        "output.elasticsearch.hosts=[\"elasticsearch:9200\"]",
        "-E",
        "output.elasticsearch.enabled=true",
        "-E",
        "output.elasticsearch.pipelines=[{pipeline: 'apm'}]",
        "-E",
        "apm-server.register.ingest.pipeline.enabled=true"
      ],
      "container_name": "localtesting_${STACK_VERSION:-7.5.1}_apm-server",
      "depends_on": {
        "elasticsearch": {
          "condition": "service_healthy"
        },
        "kibana": {
          "condition": "service_healthy"
        }
      },
      "healthcheck": {
        "interval": "10s",
        "retries": 12,
        "test": [
          "CMD",
          "curl",
          "--write-out",
          "'HTTP %{http_code}'",
          "--fail",
          "--silent",
          "--output",
          "/dev/null",
          "http://localhost:8200/"
        ]
      },
      "image": "docker.elastic.co/apm/apm-server:${STACK_VERSION:-7.5.1}",
      "labels": [
        "co.elastic.apm.stack-version=${STACK_VERSION:-7.5.1}"
      ],
      "logging": {
        "driver": "json-file",
        "options": {
          "max-file": "5",
          "max-size": "2m"
        }
      },
      "ports": [
        "127.0.0.1:8200:8200",
        "127.0.0.1:6060:6060"
      ]
    },
    "elasticsearch": {
      "container_name": "localtesting_${STACK_VERSION:-7.5.1}_elasticsearch",
      "environment": [
        "bootstrap.memory_lock=true",
        "cluster.name=docker-cluster",
        "cluster.routing.allocation.disk.threshold_enabled=false",
        "discovery.type=single-node",
        "path.repo=/usr/share/elasticsearch/data/backups",
        "ES_JAVA_OPTS=-XX:UseAVX=2 -Xms1g -Xmx1g",
        "path.data=/usr/share/elasticsearch/data/${STACK_VERSION:-7.5.1}",
        "xpack.security.enabled=false",
        "xpack.license.self_generated.type=trial",
        "xpack.monitoring.collection.enabled=true"
      ],
      "healthcheck": {
        "interval": "20",
        "retries": 10,
        "test": [
          "CMD-SHELL",
          "curl -s http://localhost:9200/_cluster/health | grep -vq '\"status\":\"red\"'"
        ]
      },
      "image": "docker.elastic.co/elasticsearch/elasticsearch:${STACK_VERSION:-7.5.1}",
      "labels": [
        "co.elastic.apm.stack-version=${STACK_VERSION:-7.5.1}"
      ],
      "logging": {
        "driver": "json-file",
        "options": {
          "max-file": "5",
          "max-size": "2m"
        }
      },
      "ports": [
        "127.0.0.1:9200:9200"
      ],
      "ulimits": {
        "memlock": {
          "hard": -1,
          "soft": -1
        }
      },
      "volumes": [
        "esdata:/usr/share/elasticsearch/data"
      ]
    },
    "kibana": {
      "container_name": "localtesting_${STACK_VERSION:-7.5.1}_kibana",
      "depends_on": {
        "elasticsearch": {
          "condition": "service_healthy"
        }
      },
      "environment": {
        "ELASTICSEARCH_URL": "elasticsearch:9200",
        "SERVER_NAME": "kibana.example.org",
        "XPACK_MONITORING_ENABLED": "true",
        "XPACK_XPACK_MAIN_TELEMETRY_ENABLED": "false"
      },
      "healthcheck": {
        "interval": "10s",
        "retries": 20,
        "test": [
          "CMD",
          "curl",
          "--write-out",
          "'HTTP %{http_code}'",
          "--fail",
          "--silent",
          "--output",
          "/dev/null",
          "http://kibana:5601/api/status"
        ]
      },
      "image": "docker.elastic.co/kibana/kibana:${STACK_VERSION:-7.5.1}",
      "labels": [
        "co.elastic.apm.stack-version=${STACK_VERSION:-7.5.1}"
      ],
      "logging": {
        "driver": "json-file",
        "options": {
          "max-file": "5",
          "max-size": "2m"
        }
      },
      "ports": [
        "127.0.0.1:5601:5601"
      ]
    }
  },
  "version": "2.1",
  "volumes": {
    "esdata": {
      "driver": "local"
    },
    "pgdata": {
      "driver": "local"
    }
  }
}

在 docker-compose.yml 所处的同一个目录下,我们创建一个如下的文件 .env:

$ pwd
/Users/liuxg/data/apm-docker
$ ls -al
total 24
drwxr-xr-x    4 liuxg  staff   128 Jul  1 11:25 .
drwxr-xr-x  136 liuxg  staff  4352 Jul  1 11:16 ..
-rw-r--r--    1 liuxg  staff    21 Jul  1 11:25 .env
-rw-r--r--    1 liuxg  staff  5391 Jul  1 11:16 docker-compose.yml
$ cat .env
STACK_VERSION=7.13.2

在上面的 .env 文件中,我们修改变量 STACK_VERSION 为我们所喜欢的 Elastic Stack 版本。

接下来,我们使用如下的命令来启动 Elastic Stack:

docker-compose up

这样我们就完成了 Elasticsearch,Kibana 及 APM Server 的安装:

Java Demo 应用

在这一节,我们来下载 Elastic 官方推荐的 Java Demo 应用来进行展示。我们首先来创建一个目录 demos,并在该目录下打入如下的命令:

git clone https://github.com/elastic/opbeans-java

这是一个 Java 编写的 Spring boot 应用。你需要安装 Java 的编译环境以及 Maven 来对它进行编译。等代码下载完毕后,我们进行 opbeans-java 目录,并打入如下的命令来进行编译:

cd opbeans-java/opbeans
mvn package

在上面,我们可以看到一个叫做 opbeans-0.0.1-SNAPSHOT.jar 的 jar 文件以及被成功生成,并在 target 目录下:

$ ls target/opbeans-0.0.1-SNAPSHOT.jar
target/opbeans-0.0.1-SNAPSHOT.jar

这样我们就完成了对 Java 应用的编译工作。

将 APM 数据流式传送到 Elastic Stack

接下来,我将描述如何把 APM 的数据传送至 Elastic Stack。首先,我们打开 Kibana:

选择上面的 APM:

由于我们已经安装好 Elastic Stack 了,所以我们直接跳过前面的部分来直接配置 Java APM agent。我们首先点击上面的 Maven Central 链接来下载 agent jar,并把相应的文件保存于 opbean-java/opbean 目录下:

我们也可以直接使用如下的命令来获得:

wget -O elastic-apm-agent-1.24.0.jar https://search.maven.org/remotecontent\?filepath\=co/elastic/apm/elastic-apm-agent/1.24.0/elastic-apm-agent-1.24.0.jar

上面的命令适合于在云上的部署。这样在我们的目录中会发现一个新下载的 elastic-apm-agent-1.24.0.jar 文件:

$ pwd
/Users/liuxg/demos/apm/opbeans-java/opbeans
$ ls
elastic-apm-agent-1.24.0.jar pom.xml
mvnw                         src
mvnw.cmd                     target

我们接下来按照 Kibana 中所提示的那样:

我们在 terminal 中输入如下的命令:

java -javaagent:./elastic-apm-agent-1.24.0.jar \
     -Delastic.apm.service_name=opbeans-java \
     -Delastic.apm.server_urls=http://localhost:8200 \
     -Delastic.apm.secret_token= \
     -Delastic.apm.environment=production \
     -Delastic.apm.application_packages=co.elastic.apm.opbeans \
     -jar ./target/opbeans-0.0.1-SNAPSHOT.jar

一旦我们成功运行,我们可以看到如下的画面:

从上面的过程中,我们可以看出来,我们并没有对我们的 Java 应用做任何的修改。我们只是通过在命令行中添加一下参数从而使得 Java agents 能够收集 APM 信息。这种操作我们称之为 instrument,也即插庄。

上面启动 Java 应用的方式,我们称之为手动配置。我们其实可以通过一种叫做自动配置的方式来对 Java 应用进行监控。详细描述可以参阅文章 “设置 Elastic APM Java 代理 - 自动设置”。

在上面,我们成功地启动了 Java Spring Boot 应用。我们带自己的浏览器,并输入地址:http://localhost:8080/

我们在上面的界面中进行一些操作从能够生产一些请求。我们将在后面的 APM 应用中来分析这些请求的性能。

我们再回到 Kibana 的界面:

点击上面的  Check APM Server status 按钮。我们可以看到我们已经成功地收到数据了。

点击上面的 Launch APM 或者 直接启动 APM 应用我们就可以查看到这个 Spring Boot 的应用性能。

为了能够在下面的分析中看到一些错误的信息,我们故意在 Spring Boot 的应用中访问不存在的地址:http://localhost:8080/is-it-coffee-time

可视化 APM 数据

我们重新回到上面的 APM 应用中:

在上面,我们可以看到有两个 Services。上面显示了一些粗略的信息。我们点击上面的 opbeans-java 链接。我们可以看到这个应用的 APM overview 情况。

从上面,我们可以看到各种指标信息。比如针对 Transanctions 来说,我们看到对我们影响最大的一个 API 接口是 APIRestController#orders,而且它还有一些错误:

点击上面的链接:

从上面,我们可以看出来有一个 transactoin 所花的时间较长。其中的原因是因为有一个错误。我们点击上面的 span:

我们可以看到更为详细的信息:

我们点击上面的 Transactions 链接:

我们点击上面的 Erros:

点击上面的 Broken pipe:

我们可以看到更为详细的错误信息。

我们点击 JVM:

点击上面的 liuxg 链接:

我们可以看到 JVM 的运行状况。

我们点击 Service Map:

我们可以看到 opbeans-java 访问 h2 数据库。点击上面的 Service Details。它会带我们回到 Service overview 页面:

评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值