分布式链路追踪Skywalking
简介
skywalkings是2015年开源的一款国产框架,2017年的时候加入了Apache孵化器。skywalking是分布式应用程序的性能监控工具,具有多种监控手段,作为APM工具,它具有分布式追踪、性能指标分析、应用和服务依赖分析等功能。可以通过语言探针来获取监控数据。专门是为了微服务(spring cloud)、云原生架构与容器架构(docker/k8s)而设计的。
Skywalking整体架构一共分四块:
Skywalking-Agent(项目引入,Skywalking-Agent 收集应用程序链路信息,交给Skywalking-OAP处理器)
Skywalking-OAP-Server 把 Skywalking-Agent 发过来的 Tracing 数据交给 Analysis Core 分析,最后将结果存储到外部存储器。
数据存储(H2/mysql/ElasticSearch)
Skywalking UI 负责给用户查看链路等信息
安装
1. 安装存储器es
【1】创建好配置和数据目录,并配置所有端口都可以访问 [root@node1 wulei]# mkdir -p /usr/local/wulei/elasticsearch/config [root@node1 wulei]# mkdir -p /usr/local/wulei/elasticsearch/data [root@node1 wulei]# echo "http.host: 0.0.0.0" > /usr/local/wulei/elasticsearch/config/elasticsearch.yml [root@node1 wulei]# chmod -R 777 /usr/local/wulei/elasticsearch/ 【2】下载并启动 9200是我们rest请求端口,9300是集群交互端口 docker run --name wl-es -p 9200:9200 -p 9300:9300 --privileged=true -e "discovery.type=single-node" -e ES_JAVA_OPTS="-Xms512m -Xms512m" -v /usr/local/wulei/elasticsearch/config/elasticsearch.yml:/usr/share/elasticsearch/config/elasticsearch.yml -v /usr/local/wulei/elasticsearch/data:/usr/share/elasticsearch/data -v /usr/local/wulei/elasticsearch/plugins:/usr/share/elasticsearch/plugins -d elasticsearch:7.16.1
查看es首页 http://192.168.200.100:9200/
2. 安装oap
docker run --name wl-oap --restart always -d \ -e TZ=Asia/Shanghai -p 12800:12800 -p 11800:11800 \ --link wl-es -e SW_STORAGE=elasticsearch7 \ -e SW_STORAGE_ES_CLUSTER_NODES=wl-es:9200 \ apache/skywalking-oap-server:8.5.0-es7
参数:
--link <name or id>:alias ,添加到另一个容器的链接,可以添加别名或者不加
–link后面的参数和elasticsearch容器名⼀致;
SW_STORAGE=elasticsearch7 是固定的,使用es7;
SW_STORAGE_ES_CLUSTER_NODES:wl_es7也可改为es服务器部署的Ip地址,比如ip:9200
3. 安装ui界面
docker run -d --name wl-skywalking-ui \ --restart=always \ -e TZ=Asia/Shanghai \ -p 8080:8080 \ --link wl-oap \ -e SW_OAP_ADDRESS=wl-oap:12800 \ apache/skywalking-ui:8.5.0
SkyWalking UI界面访问地址 ip:8080
这时候看es索引,会发现有很多 SkyWalking 的索引了
http://192.168.200.100:9200/_cat/indices?v=true&pretty。 sw开头的都是。
仪表盘
1. Skywalking ui控制栏
仪表盘:查看被监控服务的运行状态
拓扑图:以拓扑图的方式展现服务的关系
追踪:以接口的列表方式展现
性能剖析:对端点进行采样分析
日志:可查看服务日志
告警:触发告警的告警列表,包括了服务的失败率,超时等待
2. 展示栏(Global全局维度)
Global、Service、Instance、Endpoint不同展示面板
Services load:服务每分钟请求数
Slow Services:慢响应服务,单位ms
Un-Health services(Apdex): Apdex性能指标,1为满分
Slow Endpoint:慢响应端点,单位ms
Global Response Latency:百分比响应延时,不同百分比的延时时间,单位ms
Global Heatmap:服务响应时间热力分布图,根据时间段内不同响应时间的数量显示颜色深度;
底部栏:展示数据的时间区间,点击可以调整
3. 展示栏(Service服务维度)
Service Apdex(数字):当前服务的评分
Service Apdex(折线图):不同时间的Apdex评分
Service Avg Response Times:平均响应延时,单位ms
Service Response Time Percentile:百分比响应延时
Successful Rate(数字):请求成功率
Successful Rate(折线图):不同时间的请求成功率
Servce Load(数字):每分钟请求数
Servce Load(折线图):不同时间的每分钟请求数
Servce Instances Load:每个服务实例的每分钟请求数
4. 展示栏(Instance服务维度,不过对于监控CPU、内存等,Promethus 是个更好的选择)
Service instance load:当前实例的每分钟请求数
Service Instance Successful Rate:当前实例的请求成功率
Service Instance Latency:当前实例的响应延时
JVM CPU:jvm占用CPU的百分比
JVM Memory:JVM内存占用大小,单位m
JVM GC Time:JVM垃圾回收时间,包含YGC和OGC
JVM GC Count:JVM垃圾回收次数,包含YGC和OGC
JVM Thread Count:JVM线程数
5. 展示栏(Endpoint维度,即接口维度的意思)
Endpoint Load in Current Service:每个端点的每分钟请求数
Slow Endpoints in Current Service:每个端点的最慢请求时间,单位ms
Successful Rate in Current Service:每个端点的请求成功率
Endpoint Load:当前端点每个时间段的请求数据
Endpoint Avg Response Time:当前端点每个时间段的请求行响应时间
Endpoint Response Time Percentile:当前端点每个时间段的响应时间占比
Endpoint Successful Rate:当前端点每个时间段的请求成功率
6. 接口追踪
左侧:接口列表,请求为红色表示异常,蓝色表示正常
右侧:追踪列表,api的各个连接点按照端点的先后顺序和时间排序
Agent
1. 下载 https://skywalking.apache.org/downloads/。(如下图,解压后整个文件夹都需要用到,不能只复制 skywalking-agent.jar ,它会依赖到其他包)
2. 指定运行参数
-javaagent:C:\Users\Administrator\Desktop\apache-skywalking-apm-bin\agent\skywalking-agent.jar
-Dskywalking.agent.service_name=Shop_Service
-Dskywalking.collector.backend_service=192.168.200.100:11800
3. 此时启动项目后先访问接口,再刷新下 skywalking 就能看到我们的面板信息了。
查看效果
我们通过探针的形式收集数据,能看到服务的整体监控信息,响应统计、接口耗时、gc信息、执行的sql语句。但它只能看到服务与服务之间的链路信息(上游节点1 —> 本节点 —> 下游节点1。想要整个链路都能看到,那就整个链路都用探针监听就好了),看不到服务内部具体的信息(服务内部方法a调用方法b),看内部信息我们需要接入链路追踪的方式。
链路追踪
<!-- skywalking链路追踪 --> <dependency> <groupId>org.apache.skywalking</groupId> <artifactId>apm-toolkit-trace</artifactId> <version>8.5.0</version> </dependency> /** * @Tag(key = "方法名-input", value = "arg[0]"),arg[0] 表示第一个参数 * @Tag(key = "方法名-output", value = "returnedObj"),returnedObj 是固定写法 */ @Trace @Tags( { @Tag(key = "test1-input", value = "arg[0]"), @Tag(key = "test1-output", value = "returnedObj") } ) @Override public List<Area> test1(Long parentId) { List<Area> areas = listByPId(parentId); test2(); return areas; }
方法加上 @Trace 注解后,就会把该方法(仅仅是该方法)加入链路监控了,再叠加一个 @Tags 后则能看到该方法的出参入参。如果某方法仅仅只加@Tags、由于不在链路上,所以是看不到参数的。
性能分析
新建任务:同一个服务在监听的时间段内,只能监听一个接口,该服务无法创建多个监听任务。
进行分析
日志上报
1. 指定 skywalking oap 地址。修改 apache-skywalking-apm-bin\agent\config\agent.config 文件,指定 日志数据的grpc 的ip、prot、日志文件最大大小、上传日志超时时间。
plugin.toolkit.log.grpc.reporter.server_host=${SW_GRPC_LOG_SERVER_HOST:192.168.200.100} 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}
2. 添加日志依赖
<!--链路追踪,上报日志不需要用到这个jar--> <!-- <dependency>--> <!-- <groupId>org.apache.skywalking</groupId>--> <!-- <artifactId>apm-toolkit-trace</artifactId>--> <!-- <version>8.5.0</version>--> <!-- </dependency>--> <!--日志上报 skywalking8.4.0之后才支持--> <dependency> <groupId>org.apache.skywalking</groupId> <artifactId>apm-toolkit-logback-1.x</artifactId> <version>8.5.0</version> </dependency>
3. 指定日志输出格式
<!--控制台日志--> <appender name="console" class="ch.qos.logback.core.ConsoleAppender"> <encoder> <charset>UTF-8</charset> <pattern>%date [%thread] [%file:%line] [%level %logger{0}] - %msg%n</pattern> </encoder> </appender> <!-- 随意写一个 appender ,甚至可以不在下面的 <root>中引用他 。 不写一个 TraceIdPatternLogbackLayout 的日志,启动时会报错找不到[tid],tid是在 TraceIdPatternLogbackLayout 类中定义好的。 如果我们在上面的console中使用[tid]也会报错说找不到,所以通常我们直接用该appender做console就好了 --> <appender name="wulei-test" class="ch.qos.logback.core.ConsoleAppender"> <encoder class="ch.qos.logback.core.encoder.LayoutWrappingEncoder"> <charset>UTF-8</charset> <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> <!-- 上报到skywalking --> <appender name="skywalking" 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} [%tid] [%thread] %-5level %logger{36} -%msg%n</Pattern> </layout> </encoder> </appender> <root level="${LOG_LEVEL}"> <appender-ref ref="console" /> <appender-ref ref="skywalking" /> </root>
最终的日志配置
<!--控制台日志--> <appender name="console" class="ch.qos.logback.core.ConsoleAppender"> <encoder class="ch.qos.logback.core.encoder.LayoutWrappingEncoder"> <charset>UTF-8</charset> <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> <!-- 上报到skywalking --> <appender name="skywalking" 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} [%tid] [%thread] %-5level %logger{36} -%msg%n</Pattern> </layout> </encoder> </appender> <root level="${LOG_LEVEL}"> <appender-ref ref="console" /> <appender-ref ref="skywalking" /> </root>
告警规则
1. 在 apache-skywalking-apm-bin\config\alarm-settings.yml 中内置了一些默认告警规则。
# SkyWalking 的发行版都会默认提供config/alarm-settings.yml文件,里面预先定义了一些常用的告警规则。如下: # 1 最近3分钟内服务平均响应时间超过1秒 # 2 最近2分钟内服务成功率低于80% # 3 最近3分钟的服务响应时间百分比超过1秒 # 4 最近2分钟内服务实例的平均响应时间超过1秒 # 5 最近2分钟内数据库访问的平均响应时间超过1秒 # 6 最近2分钟内端点平均响应时间超过1秒 # 7 过去2分钟内端点关系的平均响应时间超过1秒 # # 然后我们配置好webhook地址就好了 # Sample alarm rules. rules: # 最近3分钟内服务平均响应时间超过1秒. 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. # 最近2分钟内服务成功率低于80% 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 # 最近3分钟的服务响应时间百分比超过1秒 service_resp_time_percentile_rule: 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 # 最近2分钟内服务实例的平均响应时间超过1秒 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 # 最近2分钟内数据库访问的平均响应时间超过1秒 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 # 最近2分钟内端点平均响应时间超过1秒 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 # 过去2分钟内端点关系的平均响应时间超过1秒 # 默认没有打开,并提示:由于端点的数量远远多于服务和实例,端点相关度量告警将比服务和实例告警消耗更多内存 # # endpoint_avg_rule: # metrics-name: endpoint_avg # op: ">" # threshold: 1000 # period: 10 # count: 2 # silence-period: 5 # message: Response time of endpoint {name} is more than 1000ms in 2 minutes of last 10 minutes webhooks: - http://192.168.200.100:8882/webhook/ # - http://127.0.0.1/go-wechat/
配置讲解:
Rule name:规则名称,也是在告警信息中显示的唯一名称。必须以_rule结尾,前缀可自定义
Metrics name:度量名称,取值为oal脚本中的度量名,目前只支持long、double和int类型。
OP: 操作符,目前支持 >、<、= Period:多久告警规则需要被核实一下。这是一个时间窗口,与后端部署环境时间相匹配
Threshold:阈值
period:评估度量标准的时间长度,也就是告警检查周期,分钟为单位
Count:在一个 Period 窗口中,如果values超过Threshold值(按op),达到Count值,需要发送警报
Silence period:在时间N中触发报警后,在TN -> TN + period这个阶段不告警。 默认情况下,它和Period一样,这意味着 相同的告警(在同一个Metrics name拥有相同的Id)在同一个Period内只会触发一次
message:告警消息
2. 编写webhook
import lombok.Data; import lombok.extern.slf4j.Slf4j; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RestController; import java.util.List; @Slf4j @RestController public class CallbackController { /** * 接收skywalking服务的告警通知并发送至邮箱 * 必须是post */ @PostMapping("/webhook") public void webhook(@RequestBody List<AlarmMessage> alarmMessageList){ String content = getContent(alarmMessageList); log.info("告警邮件已发送..."+content); } private String getContent(List<AlarmMessage> alarmList) { StringBuilder sb = new StringBuilder(); for (AlarmMessage dto : alarmList) { sb.append("scopeId: ").append(dto.getScopeId()) .append("\nscope: ").append(dto.getScope()) .append("\n目标 Scope 的实体名称: ").append(dto.getName()) .append("\nScope 实体的 ID: ").append(dto.getId0()) .append("\nid1: ").append(dto.getId1()) .append("\n告警规则名称: ").append(dto.getRuleName()) .append("\n告警消息内容: ").append(dto.getAlarmMessage()) .append("\n告警时间: ").append(dto.getStartTime()) .append("\n标签: ").append(dto.getTags()) .append("\n\n---------------\n\n"); } return sb.toString(); } @Data public static 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 transient boolean onlyAsCondition; @Data public static class Tag{ private String key; private String value; } } }
最终效果:告警面板会有告警记录,并且会把告警消息推送到webhook中。(我这边没有推送成功,如果你知道是什么原因,麻烦在评论区说一下,谢谢啦。我这边不是端口问题,webhook服务用postman是可以正常请求的)