JAVA和Nginx 教程大全

网站首页 > 精选教程 正文

「一发入魂」基于Skywalking实现服务链路追踪|监控|告警

wys521 2024-12-02 18:25:26 精选教程 20 ℃ 0 评论

本章内容


Skywalking简介

Skywalking是一款开源的应用性能监控系统,包括链路追踪、日志监控、性能剖析以及告警处理等。

Skywalking支持SpringBoot、SpringCloud、Dubbo等主流框架,代码无侵入,通信方式采用gRPC,性能较好,实现方式为Java Agent(探针)。

Skywalking相比于Sleuth+Zipkin的优势:

  • Skywalking采用字节码增强的技术实现代码无侵入,ZipKin代码侵入性比较高。
  • Skywalking功能比较丰富,报表统计、UI界面更加人性化。

建议优先选择Skywalking。

Skywalking架构

Skywalking总体架构,如图所示:

其中:

  • Agent:负责收集日志数据,并将收集的数据通过gRPC协议传输给OAP服务器。
  • OAP:负责接收Agent发送的Tracing和Metrics信息,对数据进行分析(Analysis Core)后存储到外部存储器(Storage)中,并对外提供查询(Query)服务。
  • UI:负责展示查询的Skywalking信息,如:调用链路、指标信息、性能诊断信息等。
  • Storage:负责数据存储,支持多种存储类型(如:MySQL、Elasticsearch、TiDB等)。

Skywalking执行流程,如图所示:

处理流程:

  • 1)通过探针(Agent)技术对Java应用数据进行采集,并通过gRPC协议将采集信息传输到Skywalking服务端(即:OAP)。
  • 2)Skywalking服务端接收到采集数据后,利用各种插件对采集数据进行分析和逻辑处理(如:JVM相关插件用来处理上报到Skywalking服务端的JVM信息,数据库插件用来分析访问数据库的信息),并将分析、处理后的数据存入数据存储层。
  • 3)SkyWalking数据存储层支持多种主流数据库(如:Elasticsearch、MySQL、H2、TiDB等),推荐使用ElasticSearch。
  • 4)通过Skywalking Rocketbot对采集数据进行多方面展示,如:仪表盘、拓扑图、追踪、性能剖析、日志、告警等。

服务端搭建

下载安装包

Skywalking下载地址:https://skywalking.apache.org/downloads/

如图所示:

本文中选择的版本为:v8.8.0。

完整目录结构如下:

其中:

  • bin:服务端启动脚本。
  • config:配置文件目录。
  • logs:日志目录。
  • oap-libs:OAP服务所需依赖目录。
  • webapp:UI服务目录。

修改配置

OAP服务配置信息在config/application.yml文件中进行配置,修改内容:

cluster:
  # selector默认为:standalone,更新配置中心为:nacos
  selector: ${SW_CLUSTER:nacos}
  standalone:
  # ...
  nacos:
    serviceName: ${SW_SERVICE_NAME:"SkyWalking_OAP_Cluster"}
    hostPort: ${SW_CLUSTER_NACOS_HOST_PORT:localhost:8848}
    # Nacos Configuration namespace
    namespace: ${SW_CLUSTER_NACOS_NAMESPACE:"public"}
    # Nacos auth username
    username: ${SW_CLUSTER_NACOS_USERNAME:""}
    password: ${SW_CLUSTER_NACOS_PASSWORD:""}
    # Nacos auth accessKey
    accessKey: ${SW_CLUSTER_NACOS_ACCESSKEY:""}
    secretKey: ${SW_CLUSTER_NACOS_SECRETKEY:""}

Skywalking UI服务配置信息在webapp/webapp/yml文件中进行配置,修改内容:

server:
  port: 8889

启动服务

进入bin目录,执行:./startup.sh启动OAP服务和Skywalking UI服务。

显示如下信息表示启动成功:

SkyWalking OAP started successfully!
SkyWalking Web Application started successfully!

启动后,注意查看logs文件夹中的启动日志信息,确保启动没有异常。

访问:http://localhost:8889进入Skywalking UI界面。如图所示:

Nacos注册中心信息:

注意:Skywalking多种注册中心,除了Nacos外,还支持Zookeeper、Kubernetes、Consul、Etcd等。

客户端使用

监控服务

示例服务:

  • springboot-nacos-gateway:网关。
  • springboot-nacos-order:订单服务。
  • springboot-nacos-stock:库存服务。

从Skywalking官网下载Java Agent,Java应用启动时,添加如下启动参数(VM Options):

-javaagent:/Users/wuzp/Desktop/learn/agent/skywalking-agent/skywalking-agent.jar
-Dskywalking.agent.service_name=springboot-nacos-gateway
-Dskywalking.collector.backend_service=127.0.0.1:11800

其中:

  • -javaagent:指定Skywalking中agent的(skywalking-agent.jar)路径。
  • -Dskywalking.agent.service_name:指定在skywalking中的服务名称,一般是微服务的spring.application.name。
  • -Dskywalking.collector.backend_service:指定OAP服务绑定的地址,OAP服务默认的端口为11800,因此配置为:127.0.0.1:11800

如图所示:

Skywalking UI展示信息

三个服务启动后,访问http://127.0.0.1:9080/order/query。Skywalking UI显示信息:

仪表盘-Service信息:

仪表盘-Instance信息:

仪表盘-Endpoint信息:

拓扑图:

追踪信息:

统计信息:

Skywalking UI展示信息常见问题:

  • OAP服务启动异常,如:堆内存溢出、元空间溢出等,因此,启动OAP服务后注意查看启动日志。
  • 时间和时区超出范围,注意检查右下角的时间段以及时区是否正确。
  • 数据更新延迟,页面数据刷新频率在右上角处设置,如果发现数据无法及时更新,可以等待指定时长后再查看。

监控方法

添加依赖:

<dependency>
    <groupId>org.apache.skywalking</groupId>
    <artifactId>apm-toolkit-trace</artifactId>
    <version>8.15.0</version>
</dependency>

添加注解:

@Trace
@PostMapping("create")
public String create(){
    log.info("create order traceId:{},spanId:{}",TraceContext.traceId(),TraceContext.spanId());
    log.info("减扣库存");
    stockApi.create();
    log.info("创建订单");
    return "create order success";
}

其中:方法级监控还可以通过@Tag注解获取方法入参和返回值等信息。Skywalking UI展示信息与服务级监控一样,这里就不展示了。

数据库持久化

本文中,数据存储层使用的是默认配置H2,如果需要使用其他存储系统(如:Elasticsearch、MySQL等),切换配置即可。

storage:
  selector: ${SW_STORAGE:h2}

性能剖析

性能剖析是利用方法堆栈快照对方法的执行情况进行分析和汇总,对代码执行速度进行估算。

性能剖析激活时,会对指定线程周期性的进行线程堆栈快照,并将所有的快照进行汇总分析,如果两个连续的快照含有同样的方法栈,则说明此栈中的方法大概率在这个时间间隔内都处于执行状态。通过这种连续快照的时间间隔累加成为估算的方法执行时间。

使用示例

示例方法:

/**
 * @author 南秋同学
 */
@Slf4j
@RestController
@RequestMapping("/order/")
public class OrderController {
    @Resource
    private StockApi stockApi;
    @SneakyThrows
    @GetMapping("query")
    public String query(){
        log.info("query order traceId:{},spanId:{}",TraceContext.traceId(),TraceContext.spanId());
        log.info("查询库存");
        stockApi.query();
        Thread.sleep(1000);
        log.info("订单查询");
        return "query order success";
    }
}

新建任务,如图所示:

其中,最大采样数量为5次,因此,连续请求/order/query接口5次。

注意:端点名称为仪表盘中展示的端点信息,如:GET:/order/query。

Skywalking UI展示信息,如图所示:

线程堆栈信息,如图所示:

日志监控

在开发过程中,可以通过记录当前调用链路的traceId,根据traceId从Skywalking前端界面中找到当前链路对应的调用记录,对问题进行定位与分析。

使用示例

添加依赖:

<dependency>
    <groupId>org.apache.skywalking</groupId>
    <artifactId>apm-toolkit-logback-1.x</artifactId>
    <version>8.15.0</version>
</dependency>

以logback为例,添加日志配置文件:

<?xml version="1.0" encoding="UTF-8"?>
<configuration scan="true" scanPeriod=" 5 seconds">
    <!-- 控制台打印TraceId -->
    <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
        <encoder class="ch.qos.logback.core.encoder.LayoutWrappingEncoder">
            <layout class="org.apache.skywalking.apm.toolkit.log.logback.v1.x.mdc.TraceIdMDCPatternLogbackLayout">
                <Pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%X{tid}] [%thread] %-5level %logger{36} -%msg%n</Pattern>
            </layout>
        </encoder>
    </appender>
    <!-- 异步打印 -->
    <appender name="ASYNC" class="ch.qos.logback.classic.AsyncAppender">
        <discardingThreshold>0</discardingThreshold>
        <queueSize>1024</queueSize>
        <neverBlock>true</neverBlock>
        <appender-ref ref="STDOUT"/>
    </appender>
    <!-- 将日志信息通过gRPC协议传输到Skywalking -->
    <appender name="grpc-log" class="org.apache.skywalking.apm.toolkit.log.logback.v1.x.log.GRPCLogClientAppender">
        <encoder class="ch.qos.logback.core.encoder.LayoutWrappingEncoder">
            <layout class="org.apache.skywalking.apm.toolkit.log.logback.v1.x.mdc.TraceIdMDCPatternLogbackLayout">
                <Pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%X{tid}] [%thread] %-5level %logger{36} -%msg%n</Pattern>
            </layout>
        </encoder>
    </appender>
    <!-- 日志等级 -->
    <root level="INFO">
        <appender-ref ref="grpc-log" />
        <appender-ref ref="ASYNC"/>
    </root>
</configuration>

注意:如果skywalking-agent与OAP服务分属于不同的服务器,则需要在skywalking-agent的config/agent.config文件中添加如下内容:

# 指定上报日志数据的目标主机的IP
plugin.toolkit.log.grpc.reporter.server_host=${SW_GRPC_LOG_SERVER_HOST:OAP服务地址}
# 指定上报日志数据的目标主机的Port
plugin.toolkit.log.grpc.reporter.server_port=${SW_GRPC_LOG_SERVER_PORT:11800}
# 上报日志数据的最大容量
plugin.toolkit.log.grpc.reporter.max_message_size=${SW_GRPC_LOG_MAX_MESSAGE_SIZE:10485760}
# 上报日志数据超时时长(单位:秒)
plugin.toolkit.log.grpc.reporter.upstream_timeout=${SW_GRPC_LOG_GRPC_UPSTREAM_TIMEOUT:30}

控制台信息,如图所示:

Skywalking UI展示信息,如图所示:

告警处理

Skywalking根据指定时间周期内收集的链路追踪数据与配置的告警规则(如:服务响应时间、服务响应时间百分比)进行判断,如果达到阈值,则发送相应的告警信息。

告警规则在config/alarm-settings.yml文件中定义,主要包含两部分内容:

  • 告警规则(即:触发条件)。
  • 网络钩子(即:触发告警后对应的处理方法)。

告警规则

config/alarm-settings.yml文件中告警规则:

rules:
  # Rule unique name, must be ended with `_rule`.
  service_resp_time_rule:
    metrics-name: service_resp_time
    op: ">"
    threshold: 1000
    period: 10
    count: 3
    silence-period: 5
    message: Response time of service {name} is more than 1000ms in 3 minutes of last 10 minutes.
  service_sla_rule:
    # Metrics value need to be long, double or int
    metrics-name: service_sla
    op: "<"
    threshold: 8000
    # The length of time to evaluate the metrics
    period: 10
    # How many times after the metrics match the condition, will trigger alarm
    count: 2
    # How many times of checks, the alarm keeps silence after alarm triggered, default as same as period.
    silence-period: 3
    message: Successful rate of service {name} is lower than 80% in 2 minutes of last 10 minutes
  service_resp_time_percentile_rule:
    # Metrics value need to be long, double or int
    metrics-name: service_percentile
    op: ">"
    threshold: 1000,1000,1000,1000,1000
    period: 10
    count: 3
    silence-period: 5
    message: Percentile response time of service {name} alarm in 3 minutes of last 10 minutes, due to more than one condition of p50 > 1000, p75 > 1000, p90 > 1000, p95 > 1000, p99 > 1000
  service_instance_resp_time_rule:
    metrics-name: service_instance_resp_time
    op: ">"
    threshold: 1000
    period: 10
    count: 2
    silence-period: 5
    message: Response time of service instance {name} is more than 1000ms in 2 minutes of last 10 minutes
  database_access_resp_time_rule:
    metrics-name: database_access_resp_time
    threshold: 1000
    op: ">"
    period: 10
    count: 2
    message: Response time of database access {name} is more than 1000ms in 2 minutes of last 10 minutes
  endpoint_relation_resp_time_rule:
    metrics-name: endpoint_relation_resp_time
    threshold: 1000
    op: ">"
    period: 10
    count: 2
    message: Response time of endpoint relation {name} is more than 1000ms in 2 minutes of last 10 minutes

其中:

  • Rule name:规则名称,以_rule结尾。如:service_resp_time_rule。
  • Metrics name:oal脚本中的度量名称,目前只支持long、double和int类型。
  • Include names:告警规则生效的服务列表。
  • Exclude names:告警规则排除(即:不生效)的服务列表。
  • Threshold:阈值。
  • OP:操作符,目前支持 >、<、=。
  • Period:校验周期(即:一个时间窗口),单位为分钟。
  • Count:在一个校验周期中,如果Values超过设定的阀值Threshold,且数量达到Count值,则需要发送告警消息。
  • Silence period:沉默周期,即:在一个沉默周期内,相同的告警信息只会被触发一次。
  • message:告警消息。

告警规则(Skywalking-v8.8.0版本):

  • service_resp_time_rule:在一个校验周期(10分钟)内,如果服务平均响应时间超过1秒的次数达到3次,则发送告警消息。
  • service_sla_rule:在一个校验周期(10分钟)内,如果在最近的2分钟内服务的成功率低于80%,则发送告警消息。
  • service_resp_time_percentile_rule:在一个校验周期(10分钟)内,如果最近的3分钟内90%的服务响应时长超过1秒,则发送告警消息。
  • service_instance_resp_time_rule:在一个校验周期(10分钟)内,如果在最近的2分钟内服务实例的平均响应时长超过1秒,则发送告警消息。
  • database_access_resp_time_rule:在一个校验周期(10分钟)内,如果在最近的2分钟内数据库的平均响应时长超过1秒,则发送告警消息。
  • endpoint_relation_resp_time_rule:在一个校验周期(10分钟)内,如果在最近的2分钟内关联端点之间的平均响应时长超过1秒,则发送告警消息。

webhooks

webhook是为告警规则配置的回调处理。即:当触发上述规则告警时,Skywalking会调用配置的webhook。开发人员可以定制回调处理方法,如:发送邮件、微信、钉钉等,通知运维人员进行处理。

Skywalking的告警消息通过HTTP请求进行发送。webhooks规则:

  • 请求类型为POST。
  • 内容格式(Content-Type)为application/json。
  • 参数格式为List<AlarmMessage>的JSON数据。

JSON数据示例:

[{
    "scopeId": 1,
    "scope": "SERVICE_INSTANCE",
    "name": "serviceA",
    "id0": 100,
    "id1": 0,
    "ruleName": "service_resp_time_rule",
    "alarmMessage": "alarmMessage test",
    "startTime": 1560524171000
}]

其中:

  • scopeId、scope:所有可用的Scope,在org.apache.skywalking.oap.server.core.source.DefaultScopeDefine中声明。
  • name:目标Scope实体名称。
  • id0:Scope实体ID。
  • id1:预留字段,暂未使用。
  • ruleName:告警规则名称。
  • alarmMessage:告警消息。
  • startTime:告警时间,格式为时间戳。

使用示例

添加依赖

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-mail</artifactId>
</dependency>

1)定义消息接收对象

/**
 * @author 南秋同学
 */
@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class AlarmMessage {
    private int scopeId;
    private String scope;
    private String name;
    private String id0;
    private String id1;
    private String ruleName;
    private String alarmMessage;
    private long startTime;
}

2)定义接收Skywalking告警通知方法

/**
 * @author 南秋同学
 * 告警服务
 */
@Slf4j
@RestController
@RequestMapping("/alarm/")
public class AlarmController {
    @Resource
    private JavaMailSender sender;

    @Value("${spring.mail.username}")
    private String from;

    /**
     * 接收Skywalking的告警通知并发送告警信息至邮箱
     * @param alarmMessages 告警消息
     */
    @PostMapping("receive")
    public void receive(@RequestBody List<AlarmMessage> alarmMessages){
        SimpleMailMessage message = new SimpleMailMessage();
        // 发送者邮箱
        message.setFrom(from);
        // 接收者邮箱
        message.setTo(from);
        // 主题
        message.setSubject("Skywalking告警邮件");
        String content = getContent(alarmMessages);
        // 邮件内容
        message.setText(content);
        sender.send(message);
        log.info("告警邮件已发送");
    }

    private String getContent(List<AlarmMessage> alarmMessages) {
        StringBuilder sb = new StringBuilder();
        for (AlarmMessage alarmMessage : alarmMessages) {
            sb.append("scopeId: ").append(alarmMessage.getScopeId())
                    .append("\nscope: ").append(alarmMessage.getScope())
                    .append("\n目标Scope实体名称: ").append(alarmMessage.getName())
                    .append("\nScope实体ID: ").append(alarmMessage.getId0())
                    .append("\nid1: ").append(alarmMessage.getId1())
                    .append("\n告警规则名称: ").append(alarmMessage.getRuleName())
                    .append("\n告警消息内容: ").append(alarmMessage.getAlarmMessage())
                    .append("\n告警时间: ").append(alarmMessage.getStartTime())
                    .append("\n\n---------------\n\n");
        }
        return sb.toString();
    }
}

3)配置邮件信息

spring: 
  mail:
    host: smtp.126.com
    #发送者邮箱账号
    username: xxxxxx@126.com
    #发送者密码
    password: xxxxxx
    default-encoding: utf-8
    #端口号465
    port: 465
    protocol: smtp
    properties:
      mail:
        debug:
          false
        smtp:
          socketFactory:
            class: javax.net.ssl.SSLSocketFactory

4)配置webhooks(config/alarm-settings.yml)

webhooks:
  - http://127.0.0.1:9080/alarm/receive/

【阅读推荐】

更多精彩内容,如:

  • Redis系列
  • 数据结构与算法系列
  • Nacos系列
  • MySQL系列
  • JVM系列
  • Kafka系列

请移步【南秋同学】个人主页进行查阅。内容持续更新中......

【作者简介】

一枚热爱技术和生活的老贝比,专注于Java领域,关注【南秋同学】带你一起学习成长~

Tags:

本文暂时没有评论,来添加一个吧(●'◡'●)

欢迎 发表评论:

最近发表
标签列表