提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档
文章目录
- 前言
- [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>
大框架
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中
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, 包括以下规则:
- 过去 3 分钟内服务平均响应时间超过 1 秒。
- 最近 2 分钟服务成功率低于 80%。
- 过去 3 分钟内超过 1 秒的服务响应时间百分位数
- 服务实例在过去 2 分钟内的平均响应时间超过 1 秒,并且实例名称与正则表达式匹配。
- 终端节点在过去 2 分钟内的平均响应时间超过 1 秒。
- 过去 2 分钟内数据库访问平均响应时间超过 1 秒。
- 终端节点关系 过去 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就是在这里配置的

我们去查看一下这个文件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接收地址, 我们可以借此功能在这个接⼝⾥实现发送邮件或者短信等功能, 从⽽达到告警主动通知.
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, 企业微信, 钉钉, ⻜书等.
钉钉和飞书是一样的
微信只支持企业微信
- 先注册飞书账号
- 创建机器⼈
打开⻜书群组 → 点击右上⻆「设置」→「群机器⼈」→「添加机器⼈」→ 选择「⾃定义机器⼈」
配置机器⼈名称, 获取⽣成的 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就不行了

可以点击这个链接