springCloud二-SkyWalking3-性能剖析-⽇志上传-告警管理-接入飞书

提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档

文章目录

  • 前言
  • [1. 性能剖析](#1. 性能剖析)
  • [2. ⽇志上传](#2. ⽇志上传)
    • [2.1 ⽇志配置](#2.1 ⽇志配置)
  • [3. 告警管理](#3. 告警管理)
    • [3.1 告警规则](#3.1 告警规则)
    • [3.2 Webhook](#3.2 Webhook)
      • [3.2.1 SkyWalking 的 WebHook 实现](#3.2.1 SkyWalking 的 WebHook 实现)
        • [3.2.1.1 告警触发与推送](#3.2.1.1 告警触发与推送)
        • [3.2.1.2 WebHook配置](#3.2.1.2 WebHook配置)
        • [3.2.1.3 典型应⽤场景](#3.2.1.3 典型应⽤场景)
      • [3.2.2 WebHook实践](#3.2.2 WebHook实践)
        • [3.2.2.1 配置WebHook](#3.2.2.1 配置WebHook)
        • [3.2.2.2 接⼝开发](#3.2.2.2 接⼝开发)
      • [3.2.3 配置126网易邮件告警](#3.2.3 配置126网易邮件告警)
  • [3.3 接入飞书](#3.3 接入飞书)
  • 总结

前言

1. 性能剖析

性能剖析功能是⼀种针对分布式系统代码级性能的动态分析技术, SkyWalking 提供了追踪分析的功能,可以查看请求调⽤链中具体⽅法或者代码块的执⾏耗时, ⽤于快速识别⾼耗时⽅法(如慢SQL查询等), 定位服务调⽤链(Trace)中具体 Span(单个操作节点)的性能问题

就是分析每一行代码的消耗时间

点击trace profing

点击新建任务

选择query9

点击新建任务

发现第一行耗时最长

点击这一行,然后点击分析


我们找到这里,说明是这个执行了2000ms

还知道是35行代码耗时

com.bite.order.controller.OrderController.queryOrder:35

果然是35行

这样我们就知道是哪里代码慢了

2. ⽇志上传

SkyWalking不仅⽀持链路追踪, 还可以集成⽇志数据, 帮助⽤⼾在⼀个平台上统⼀查看⽇志和追踪信息, 基于 Trace ID 实现⽇志与请求链路的⾃动关联,快速定位故障上下⽂.

java 复制代码
    public OrderInfo queryOrder(Integer orderId) {
        log.info("queryOrder,orderId:{}",orderId);
        return orderMapper.selectById(orderId);
    }

问题是什么呢,问题就是如果是多线程的话,打印日志的顺序就可能不是按照一个线程打印一堆了,而是按照一种日志打印一堆---》比较混乱

我们只需要在trace这里点击查看日志

就是把控制台的日志上传到SkyWalking中

配置参考

java 复制代码
        <dependency>
            <groupId>org.apache.skywalking</groupId>
            <artifactId>apm-toolkit-logback-1.x</artifactId>
            <version>9.4.0</version>
        </dependency>

2.1 ⽇志配置

我们现在的id与traceId无关,没有关联起来

但是SkyWalking是有traceId的

所以要配置TraceId,还有上报到SkyWalking中

添加 logback-spring.xml ⽂件

• Logback 框架默认加载的配置⽂件名为 logback.xml , 该名称适⽤于⾮ Spring Boot 应⽤.

• Spring Boot默认会识别logback-spring.xml, 这是Spring Boot推荐的⽅式, 优先级⾼于默认logback.xml, ⽀持 Spring 扩展特性(如 ${spring.profiles.active})

• 若同时存在 logback.xml 和 logback-spring.xml , Spring Boot 优先加载 logback-spring.xml

java 复制代码
<?xml version="1.0" encoding="UTF-8"?>
<configuration scan="true" scanPeriod="60 seconds" debug="false">

    
    
</configuration>

大框架

在日志中打印traceId

set %tid in Pattern section of logback.xml

java 复制代码
    <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.TraceIdPatternLogbackLayout">
                <Pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%tid] [%thread] %-5level %logger{36} -%msg%n</Pattern>
            </layout>
        </encoder>
    </appender>

appender 是负责日志的输出的核心组件,把日志内容输出到指定的目标

ConsoleAppender表示输出到控制台

Pattern表示输出的格式

其中%tid就是traceId

java 复制代码
        <root level="INFO">
            <appender-ref ref="STDOUT"/>
        </root>

这个是定义STDOUT日志输出的级别

这个TID就是traceID

然后是日志上报到SkyWalking中

grpc上报日志到SkyWalking中

java 复制代码
    <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>

GRPCLogClientAppender就是上报日志到SkyWalking

java 复制代码
    <root level="INFO">
        <appender-ref ref="STDOUT"/>
        <appender-ref ref="grpc-log"/>
    </root>

这样就可以了

java 复制代码
<?xml version="1.0" encoding="UTF-8"?>
<configuration scan="true" scanPeriod="60 seconds" debug="false">

    <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.TraceIdPatternLogbackLayout">
                <Pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%tid] [%thread] %-5level %logger{36} -%msg%n</Pattern>
            </layout>
        </encoder>
    </appender>

    <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="STDOUT"/>
        <appender-ref ref="grpc-log"/>
    </root>

</configuration>

这是总体代码

这样日志就来了

而且它把每一个trace的日志都是放在一起的

这些上传的日志都是由于trace引起的日志,有traceId才行

启动的时候的日志就不会上传了

3. 告警管理

除了指标的监测之外, SkyWalking 还提供了告警功能. 当系统出现异常时(如接⼝响应慢, 成功率低), ⾃动触发通知, 帮助研发⼈员或者运维⼈员快速定位问题

点击告警

点击进入有告警详情

希望配置告警,比如成功率低于80%的时候告警
还有就是告警希望可以通过邮件等形式告诉我,不然我不可能一直盯着页面看吧

3.1 告警规则

SkyWalking 的告警系统预先定义了⼀部分告警规则, 在 config/alarm-settings.yml ⽂件中, ⽤⼾也可以⾃定义告警规则.

告警规则

默认告警规则

为⽅便起⻅,SkyWalking提供了⼀个默认的alarm-setting.yml, 包括以下规则:

  1. 过去 3 分钟内服务平均响应时间超过 1 秒。
  2. 最近 2 分钟服务成功率低于 80%。
  3. 过去 3 分钟内超过 1 秒的服务响应时间百分位数
  4. 服务实例在过去 2 分钟内的平均响应时间超过 1 秒,并且实例名称与正则表达式匹配。
  5. 终端节点在过去 2 分钟内的平均响应时间超过 1 秒。
  6. 过去 2 分钟内数据库访问平均响应时间超过 1 秒。
  7. 终端节点关系 过去 2 分钟内超过 1 秒的平均响应时间。

service_resp_time_rule是告警的规则名称

规则名称必须是唯一的,而且必须以_rule结尾

expression是告警规则的配置

sum(service_resp_time > 1000) >= 3的意思就是一分钟内(一分钟统计)有三次响应时间大于一秒钟------》告警

period:10表示统计最近十分钟内的

java 复制代码
    silence-period: 5

这个表示静默时长,报警之后5分钟之内,就不报警了

message表示告警的信息

java 复制代码
XXXX_rule: 规则名称, 必须以"_rule" 结尾
• expression:告警表达式, 结果为1时, 触发报警
• period: 告警周期, 评估指标的时间⻓度(以分钟为单位)
• silence-period: 静默期, 在告警触发后, 多⻓时间内不再触发.
	在Time-N (TN)触发告警后,在"TN -> TN +period"时间段内保持静默.
	默认情况下, 它的⼯作⽅式与period相同. 同⼀告警在⼀个周期内只能触发⼀次
• message: 告警消息

所以service_resp_time_rule就是过去 3 分钟内服务平均响应时间超过 1 秒。

database_access_resp_time_rule:过去 2 分钟内数据库访问平均响应时间超过 1 秒。

endpoint_relation_resp_time_rule:终端节点关系 过去 2 分钟内超过 1 秒的平均响应时间。等等

rules:下面的_rule都是SkyWalking默认支持的告警---》都是会生效的

当然你也可以在alarm-settings.yml这个文件中自己配置规则

java 复制代码
#hooks:
#  webhook:
#    default:
#      is-default: true
#      urls:
#        - http://127.0.0.1/notify/
#        - http://127.0.0.1/go-wechat/

这个配置呢

默认告警会显示在SkyWalking的UI界面上

webhook就是主动通知我们告警信息的,而不是让我们自己去ui界面查看

3.2 Webhook

webhook是⼀种允许应⽤程序向外部系统(飞书,微信)实时推送事件或数据的机制, 通常通过HTTP回调实现, 从⽽实现跨系统⾃动化的信息传递

核⼼特征:

• 事件驱动: 当预设条件触发时(如告警触发、数据更新), 主动向⽬标 URL 发送 HTTP 请求(通常为POST)

• 轻量级集成: 接收⽅只需提供⼀个可访问的 HTTP 端点即可接收数据, ⽆需轮询查询

• 灵活扩展: 适⽤于告警通知, 流程触发, 数据同步等场景

3.2.1 SkyWalking 的 WebHook 实现

SkyWalking 提供了WebHook的⽅式, 主要⽤于告警通知

3.2.1.1 告警触发与推送

当监控指标(如响应时间、错误率等)达到告警规则阈值时, SkyWalking会⽣成告警事件, ⾃动把告警信息封装为JSON格式, 通过HTTP的⽅式发送⾄预设的WebHook接收地址.

java 复制代码
#hooks:
#  webhook:
#    default:
#      is-default: true
#      urls:
#        - http://127.0.0.1/notify/
#        - http://127.0.0.1/go-wechat/

http的url就是在这里配置的

告警json格式

我们去查看一下这个文件List<org.apache.skywalking.oap.server.core.alarm.AlarmMessage>

点这个

点击star

跳转到github

然后搜索AlarmMessage

就是这个类,就是告警信息

scopeId、scope: 告警⽬标的监控范围, 参考org.apache.skywalking.oap.server.core.source.DefaultScopeDefine 中定义

DefaultScopeDefine 也可以搜索到

◦ scopeId、scope:就是说你是端点告警还是service告警还是实例告警

Name: 告警⽬标的名称,如服务名、端点名等

就这几个告警

id0: ⽬标实体的主要ID,通常是数据库中的主键

◦ id1: ⽬标实体的次要ID(可选),⽤于更精确的标识

ruleName: 触发告警的规则名称---》alarm-settings.yml中的

◦ alarmMessage: 具体的告警消息内容

◦ startTime: 告警触发的时间戳(毫秒)

◦ 标签: 标签列表,包含与告警相关的附加信息

3.2.1.2 WebHook配置

在 alarm-settings.yml 中定义 WebHook 地址及关联规则

java 复制代码
hooks:
 webhook:
   default:
     is-default: true
     urls:
       - http://127.0.0.1/notify/
       - http://127.0.0.1/go-wechat/

urls 为接收告警的HTTP端点

3.2.1.3 典型应⽤场景

◦ 集成第三⽅系统: 推送告警⾄钉钉, 企业微信、⻜书, 邮箱等协作⼯具.

◦ ⾃动化运维: 触发运维脚本(如⾃动扩容)或联动故障管理系统.

◦ 数据聚合分析: 将告警事件转发⾄⼤数据平台进⾏统计分析

3.2.2 WebHook实践

3.2.2.1 配置WebHook

配置 alarm-settings.yml , 然后重启

我们再来创建一个专门用于告警的应用程序

java 复制代码
hooks:
 webhook:
   default:
     is-default: true
     urls:
       - http://127.0.0.1:8084/alarm/handler
3.2.2.2 接⼝开发
java 复制代码
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-test</artifactId>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <configuration>
                    <includeSystemScope>true</includeSystemScope>
                </configuration>
            </plugin>

        </plugins>
    </build>
java 复制代码
server:
  port: 8084
logging:
  pattern:
    dateformat: HH:mm:ss:SSS
java 复制代码
@RestController
@RequestMapping("/alarm")
public class AlarmController {
    @RequestMapping("/handler")
    public String handler(){

    }
}
java 复制代码
@Setter
@Getter
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 List<Tag> tags;
    private long startTime;
    private transient int period;
    private Set<String> hooks = new HashSet<>();
    private String expression;
    private JsonObject mqeMetricsSnapshot;
}

给我们的告警信息是一个AlarmMessage 的json格式

所以这个类也要创建

java 复制代码
@Setter
@Getter
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 List<Tag> tags;
    private long startTime;
    private transient int period;
    private String expression;
}

我们创建的删除了一些属性

说错了,这个才是给我们的json里面的内容

所以要含有tags,全部属性都要含有

这个Tag怎么处理呢

搜索Tag.java

还是仿照官方的

java 复制代码
@Data
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 List<Tag> tags;
    private long startTime;
    private transient int period;
    private String expression;
    
    @Data
    public class Tag {
        private String key;
        private String value;
    }
}

这样就OK了

java 复制代码
@Slf4j
@RestController
@RequestMapping("/alarm")
public class AlarmController {
    @RequestMapping("/handler")
    public String handler(@RequestBody AlarmMessage alarmMessage){
        log.info("接收到告警,alarmMessage:{}",alarmMessage);
        return "接收到告警"; 
    }
}

然后SkyWalking也要重启

然后jmeter跑起来

我们等待UI界面,直到UI界面出现最新的告警信息---》只要第一个出来了,后面就很快了

这个17:21的是十几分钟以前的

OK,新的告警信息来了

我们看到error了,说json解析异常

他说接收到的json是一个数组

java 复制代码
    @RequestMapping("/handler")
    public String handler(@RequestBody List<AlarmMessage> alarmMessageList){
        log.info("接收到告警,alarmMessageList:{}",alarmMessageList);
        return "接收到告警";
    }

新告警又来了

也是打印出来了

3.2.3 配置126网易邮件告警

SkyWalking的WebHook功能, 会通过HTTP的⽅式, 把告警信息发送⾄预设的WebHook接收地址, 我们可以借此功能在这个接⼝⾥实现发送邮件或者短信等功能, 从⽽达到告警主动通知.

springboot发送邮件

java 复制代码
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-mail</artifactId>
        </dependency>
java 复制代码
spring:
  mail:
    # 指定邮件服务器地址
    host: smtp.qq.com

这个是qq邮箱

java 复制代码
spring:
  mail:
    # 指定邮件服务器地址
    host: smtp.126.com

这个是126邮箱

手机下载网易邮箱,注册网易免费邮箱一个就可以了

java 复制代码
spring:
  mail:
    # 指定邮件服务器地址
    host: smtp.126.com

网易邮箱

然后是申请授权码了

设置--》点击pop3

然后我们开启pop3/SMTP

这样就会出现了

java 复制代码
spring:
  mail:
    # 指定邮件服务器地址
    host: smtp.126.com
    # 登录账户
    username: xxxxx@126.com
    # 授权码
    password: "xxxxxxxx"
    # 端口
    port: 465
    # 默认编码
    default-encoding: UTF-8
    # 使用的协议
    protocol: smtps
    # 其他的属性
    properties:
      "mail.smtp.connectiontimeout": 5000
      "mail.smtp.timeout": 3000
      "mail.smtp.writetimeout": 5000
      "mail.smtp.auth": true
      "mail.smtp.starttls.enable": true
      "mail.smtp.starttls.required": true

只需要改账户名授权码和host就可以了

这里有测试代码,我们直接复制就可以了

java 复制代码
@SpringBootTest
class AlarmServiceApplicationTest {

    @Autowired
    JavaMailSender javaMailSender;

    @Test
    void sendEmail() throws Exception{
        // 创建一个邮件消息
        MimeMessage message = javaMailSender.createMimeMessage();

        // 创建 MimeMessageHelper
        MimeMessageHelper helper = new MimeMessageHelper(message, false);

        // 发件人邮箱和名称
        helper.setFrom("ck_yyds@126.com", "springdoc");
        // 收件人邮箱
        helper.setTo("ck_yyds@126.com");
        // 邮件标题
        helper.setSubject("Hello");
        // 邮件正文,第二个参数表示是否是HTML正文
        helper.setText("Hello <strong> World</strong>!", true);

        // 发送
        javaMailSender.send(message);
    }

}

这样就成功了

我们分装一下发送邮件的方法

java 复制代码
    @Autowired
    private MailProperties mailProperties;

我们注入这个

这个类MailProperties 会读取邮件的配置信息

这些都是默认的配置可以不写

java 复制代码
spring:
  mail:
    # 其他的属性
    properties:
      "personal": "ck告警系统" #发送人姓名
      "subject": "告警通知"#邮件标题

我们设置这个

java 复制代码
@Slf4j
@Configuration
public class Mail {
    @Autowired
    private JavaMailSender javaMailSender;

    @Autowired
    private MailProperties mailProperties;

    public void sendEmail(String to,String content)  {
        try {
            // 创建一个邮件消息
            MimeMessage message = javaMailSender.createMimeMessage();

            // 创建 MimeMessageHelper
            MimeMessageHelper helper = new MimeMessageHelper(message, false);

            String from = mailProperties.getUsername();
            // 发件人邮箱和名称
            helper.setFrom(from, mailProperties.getProperties().getOrDefault("personal",from));//如果没有获取到personal的值,就是用默认值from
            // 收件人邮箱
            helper.setTo(to);
            // 邮件标题
            helper.setSubject(mailProperties.getProperties().getOrDefault("subject","告警通知"));
            // 邮件正文,第二个参数表示是否是HTML正文
            helper.setText(content, true);

            // 发送
            javaMailSender.send(message);
        }catch (Exception e){
            log.error("邮件发送失败,e:{}",e);
        }

    }
}

这样就分装好了

java 复制代码
    @Autowired
    private Mail mail;
java 复制代码
    @RequestMapping("/handler")
    public String handler(@RequestBody List<AlarmMessage> alarmMessageList){
        log.info("接收到告警,alarmMessageList:{}",alarmMessageList);
        mail.sendEmail("ck_yyds@126.com",buildContent(alarmMessageList));
        return "接收到告警";
    }

    private String buildContent(List<AlarmMessage> alarmMessageList) {
        StringBuilder builder = new StringBuilder();
        builder.append("告警详情:");
        for (AlarmMessage message:alarmMessageList){
            builder.append("<br/>scopeId:").append(message.getScopeId())
                    .append("<br/>scope:").append(message.getScope())
                    .append("<br/>Name:").append(message.getName())
                    .append("<br/>Id0:").append(message.getId0())
                    .append("<br/>RuleName:").append(message.getRuleName())
                    .append("<br/>AlarmMessage:").append(message.getAlarmMessage())
                    .append("<br/>StartTime:").append(message.getStartTime())
                    .append("<br/>-------------------------");
        }
        return builder.toString();
    }

然后启动等待告警

3.3 接入飞书

SkyWalking的WebHook功能, 也集成了第三⽅系统, 可以通过简单的配置, 推送告警⾄Slack, 企业微信, 钉钉, ⻜书等.

接入飞书

钉钉和飞书是一样的

微信只支持企业微信

钉钉

  1. 先注册飞书账号
  2. 创建机器⼈
    打开⻜书群组 → 点击右上⻆「设置」→「群机器⼈」→「添加机器⼈」→ 选择「⾃定义机器⼈」
    配置机器⼈名称, 获取⽣成的 Webhook URL(格式为 https://open.feishu.cn/openapis/bot/v2/hook/xxxxxx )

随便找一个群,点击设置

里面有一个群机器人

点击添加机器人

选择自定义机器人

成功之后会有一个webhook的地址--》记录下来

设置这个的话,发送请求就必须要有签名校验了

直接复制这个配置就可以了

但是还要加上第一级:hooks

java 复制代码
hooks:
  feishu:
    default:
      is-default: true
      text-template: |-
        {
          "msg_type": "text",
          "content": {
            "text": "Apache SkyWalking Alarm: \n %s."
          },
          "ats":"feishu_user_id_1,feishu_user_id_2"
        }      
      webhooks:
      - url: https://open.feishu.cn/open-apis/bot/v2/hook/dummy_token
        secret: dummysecret

ats这个是配置具体的userId,就是通知指定的一个人

这里的url改为刚刚添加机器人的webhook地址

secret就改为刚刚的签名

没有设置签名校验的话,secret就不用配置

然后就是重启SkyWalking

初始化慢慢等待吧


新告警来了,飞书也来消息了

如果想使用更加个性化的模版的话,SkyWalking就不行了

可以点击这个链接

在群组中使用机器人

总结

相关推荐
JIngJaneIL11 小时前
基于springboot + vue古城景区管理系统(源码+数据库+文档)
java·开发语言·前端·数据库·vue.js·spring boot·后端
小信啊啊11 小时前
Go语言切片slice
开发语言·后端·golang
北城以北888813 小时前
Spring定时任务与Spring MVC拦截器
spring boot·spring·mvc
Victor35613 小时前
Netty(20)如何实现基于Netty的WebSocket服务器?
后端
缘不易13 小时前
Springboot 整合JustAuth实现gitee授权登录
spring boot·后端·gitee
Kiri霧13 小时前
Range循环和切片
前端·后端·学习·golang
WizLC13 小时前
【Java】各种IO流知识详解
java·开发语言·后端·spring·intellij idea
Mr.朱鹏13 小时前
SQL深度分页问题案例实战
java·数据库·spring boot·sql·spring·spring cloud·kafka
星星不打輰13 小时前
SSM项目--SweetHouse 甜蜜蛋糕屋
java·spring·mybatis·ssm·springmvc
Victor35613 小时前
Netty(19)Netty的性能优化手段有哪些?
后端