前言
對於一個大型的幾十個、幾百個微服務構成的微服務架構系統,通常會有以下問題,比如
- 如何串聯整個呼叫鏈路,快速定位問題
- 如何捋清各個微服務的依賴關係
- 如何進行各個微服務介面的效能分析
- 如何跟蹤整個業務流程的呼叫處理順序
一、什麼是 SkyWalking
skywalking 是一個國產開源框架,2015年開源,2017年加入 Apache 孵化器。skywalking 是分散式系統的應用程式效能監視工具,轉為微服務、雲原生架構和基於容器(Docker、k8s、Mesos)架構而設計的。它是一款優秀的 APM(Application Performance Management)工具,包括了分散式追蹤、效能指標分析、應用和服務依賴分析等
1.1、鏈路追蹤框架對比
- Zipkin 是 Twitter 開源的呼叫鏈分析工具,目前基於springcloud sleuth 得到了廣泛的使用,特點是輕量,使用部署簡單。
- Pinpoint 是韓國人開源的基於位元組碼注入的呼叫鏈分析,以及應用監控分析工具。特點是支援多種外掛, UI 功能強大,接入端無程式碼侵入。
- SkyWalking 是本土開源的基於位元組碼注入的呼叫鏈分析,以及應用監控分析工具。特點是支援多種外掛, UI 功能較強,接入端無程式碼侵入。目前已加入 Apache 孵化器。
- CAT 是大眾點評開源的基於編碼和配置的呼叫鏈分析,應用監控分析,日誌採集,監控報警等一系列的監控平臺工具。
專案 | Cat | Zipkin | Skywalking |
---|---|---|---|
呼叫鏈視覺化 | 有 | 有 | 有 |
聚合報告 | 非常豐富 | 少 | 較豐富 |
服務依賴圖 | 簡單 | 簡單 | 好 |
埋點方式 | 侵入式 | 侵入式 | 非侵入,位元組碼增強 |
VM監控指標 | 好 | 無 | 有 |
支援語言 | java/.net | 豐富 | java/.net/Nodejs/php/go |
儲存機制 | mysql(報表)、本地檔案/HDFS(呼叫鏈) | 記憶體、es、mysql等 | H2、es等 |
社群支援 | 主要在國內 | 國外主流 | Apache 支援 |
使用案例 | 美團、攜程、陸金所 | 京東、阿里定之後不開源 | 華為、小米、噹噹、微眾銀行 |
APM | 是 | 否 | 是 |
開發基礎 | eBay cal | Google Dapper | Google Dapper |
是否支援 webflux | 否 | 是 | 是 |
Github stars(2022.8) | 17.1k | 15.6k | 20k |
在三種鏈路監控元件中, skywalking的探針對吞吐量的影響最小。
1.2、Skywalking 主要特性
1、多種監控手段,可以透過語言探針和 service mesh 獲得監控的資料。
2、支援多種語言自動探針(agent),包括Java,.NET Core 和 Node.JS。
3、輕量高效,無需大資料平臺和大量的伺服器資源。
4、模組化,UI、儲存、叢集管理都有多種機制可選。
5、支援告警。
6、優秀的視覺化解決方案。
二、SkyWalking 環境搭建部署
- skywalking agent和業務系統繫結在一起,負責收集各種監控資料
- Skywalking oapservice 是負責處理監控資料的,比如接受 skywalking agent 的監控資料,並儲存在資料庫中;接受 skywalking webapp 的前端請求,從資料庫查詢資料,並返回資料給前端。Skywalking oapservice 通常以叢集的形式存在。
- skywalking webapp,前端介面,用於展示資料。
- 用於儲存監控資料的資料庫,比如mysql、elasticsearch 等。
2.1、下載 SkyWalking
OAP Server 下載
Java-agent 接入
OAP 目錄結構
- webapp:UI 前端(web監控頁面)的 jar 包和配置檔案
- oap-libs:後臺應用的 jar 包,以及它依賴的 jar 包,裡面有一個 server-starter.jar 啟動程式
- config:啟動後臺應用程式的配置檔案,是使用的各種配置
- bin:各種指令碼,一般使用指令碼 startup.sh 來啟動 web 頁面和對應的後臺應用:
oapService.*
:預設使用後臺程式的啟動指令碼;(使用的是預設模式啟動,還支援其他模式)oapServiceInit.*
:使用 init 模式啟動;在此模式下,OAP 伺服器啟動以執行初始化工作,用於叢集環境。為了防止多節點同時啟動導致衝突,單節點執行oapServiceInit.sh進行初始化,其他節點執行oapServiceNoInit.sh 等待初始化完成後再啟動。oapServiceNoInit.*
:使用 no init 模式啟動;在此模式下,OAP 伺服器不進行初始化webappService.*
:UI 前端的啟動指令碼startup.*
:同時啟動oapService.*
、webappService.*
指令碼;
Java Agents 目錄結構
- activations:工具包
- bootstrap-plugins:啟動外掛,預設載入
- config:配置檔案
- optional-plugins:可選擴充外掛,啟動不載入,如需載入將其移入 plugins 目錄下
- optional-reporter-plugins:可選統計類外掛,啟動不載入
- plugins:服務類外掛
- skywalking-agent.jar:客戶端主程式,需要被服務啟動時引用
2.2、搭建 SkyWalking OAP 服務
為了方式埠衝突,將前端頁面地址進行修改
vim webapp/webapp.yml
server:
port: 8868
SkyWalking UI 介面是透過請求 SkyWalking OAP 服務來獲得的
啟動指令碼bin/startup.sh
$ bash startup.sh
SkyWalking OAP started successfully!
SkyWalking Web Application started successfully!
日誌資訊儲存在 logs 目錄
logs/
├── oap.log
├── skywalking-oap-server.log
└── webapp-console.log
啟動成功之後會啟動兩個服務,一個是 skywalking-oap-server,一個是 skywalking-web-ui
skywalking-oap-server 服務啟動後會暴露 11800 和 12800 兩個埠,分別為手機監控資料的埠 11800 和接受前端請求的埠 12800,修改埠可以修改config/application.yml
2.3、SkyWalking 中三個概念
- 服務(Service):表示對請求提供相同行為的一系列或一組工作負載,在使用 Agent 時,可以定義服務的名字;
- 服務例項(Service Instance):上述的一組工作負載中的每一個工作負載稱為一 個例項,一個服務例項實際就是作業系統上的一個真實程式;
- 端點(Endpoint):對於特定服務所接收的請求路徑,如 HTTP 的 URI 路徑和 gRPC 服務的類名+方法簽名;
三、SkyWalking 接入微服務
3.1、linux 透過 jar 包方式接入
準備一個 springboot 程式,打包成可執行 jar 包,寫一個 shell 指令碼,在啟動專案的 shell 指令碼上,透過 -javaagent 引數進行配置 skywalking agent 來跟蹤微服務;
startup.sh 指令碼
#! /bin/bash
# SkyWalking Agent 配置
# Agent 名字,一般使用`spring.application.name`
export SW_AGENT_NAME=springboot-skywalking-demo
# 配置 Collector 地址
export SW_AGENT_COLLECTOR_BACKEND_SERVICERS=127.0.0.1:11800
export JAVA_AGENT=-javaagent:skywalking-agent.java 地址
# jar 啟動
java $JAVA_AGENT -jar spring-boot-skywalking-demo.jar
等同於
java -javaagent:skywalking-agent.jar 地址 -DSW_AGENT_COLLECTOR_BACKEND_SERVICERS=127.0.0.1:11800 -DSW_AGENT_NAME=springboot-skywalking-demo -jar spring-boot-skywalking-demo.jar
引數名對應 agent/config/agent.config 配置檔案中的屬性
屬性對應原始碼:org.apache.skywalking.apm.agent.core.conf.Config.java
# The service name in UI
agent.service_name=${SW_AGENT_NAME:Your_ApplicationName}
# Backend service addresses
collector.backend_sercvice=${SW_AGENT_COLLECTOR_BACKEND_SERVICES:127.0.0.1:11800}
3.2、Windows 環境 - IDEA 中使用 SkyWalking
在執行程式中配置 jvm 引數
# 指定 agent.jar 所在位置
-javaagent:/Users/hudu/Environment/skywalking/skywalking-agent/skywalking-agent.jar
# 指定服務名字
-DSW_AGENT_NAME=服務名稱
# 指定 skywalking 的 collector 服務的 IP 及埠
-DSW_AGENT_COLLECTOR_BACKEND_SERVICES=127.0.0.1:11800
-javaagent 必須繫結本機物理路徑的 skywalking-agent.jar
具體效果參考下面的接入多個微服務
3.2、Skywalking 跨多個微服務跟蹤
Skywalking 跨多個微服務跟蹤,只需要每個微服務啟動時新增 javaagent 引數即可。
-javaagent:/Users/hudu/Environment/skywalking/skywalking-agent/skywalking-agent.jar
-DSW_AGENT_NAME=服務名稱
-DSW_AGENT_COLLECTOR_BACKEND_SERVICES=127.0.0.1:11800
測試:
啟動微服務,接入 seata 的 order 和 stock 服務
請求一個介面進行測試http://localhost:8088/order/add
,可以看到 skywalking 控制檯中已經有服務記錄,呼叫鏈路圖如下所示。
四、Skywalking 持久化跟蹤資料
預設使用的是 H2 記憶體資料庫
config/application.yml
storage:
selector: ${SW_STORAGE:mysql}
mysql:
properties:
jdbcUrl: ${SW_JDBC_URL:"jdbc:mysql://localhost:3306/swtest?rewriteBatchedStatements=true"}
dataSource.user: ${SW_DATA_SOURCE_USER:root}
dataSource.password: ${SW_DATA_SOURCE_PASSWORD:root@1234}
建立 swtest 資料庫
啟動 skywalking 服務的時候會自動建立需要的表
oap-lib
目錄中新增 mysql-connector-java.jar 包
五、自定義 SkyWalking 鏈路追蹤
如果我們希望對專案中的業務方法實現鏈路追蹤,方便我們排查問題,可以使用如下的程式碼
5.1、引入依賴
<!--SkyWalking 工具類-->
<dependency>
<groupId>org.apache.skywalking</groupId>
<artifactId>apm-toolkit-trace</artifactId>
<version>8.11.0</version>
</dependency>
5.2、@Trace 將方法加入追蹤鏈路
如果一個業務方法想再 ui 介面的跟蹤鏈路上顯示出來,只需要再業務方法上加上@Trace
註解即可
5.3、加入 @Tags 或 @Tag
我們還可以為追蹤鏈路增加其他額外的資訊,比如記錄引數和返回資訊。實現方式:在方法上增加@Tag或者@Tags。
@Tag 註解中,key=方法名,value=returnedObj 返回值,arg[0] 引數
@GetMapping("/get/{id}")
@Trace
@Tags({@Tag(key="result",value = "returnedObj"),
@Tag(key="param",value = "arg[0]")})
public Order get(@PathVariable Integer id) {
return orderService.get(id);
}
可以看到方法引數和返回值都追蹤到了。
六、效能分析
skywalking 的效能分析,在根據服務名稱、端點名稱,以及相應的規則建立了任務列表後,在呼叫了此任務列表的端點後。skywalking 會自動記錄,剖析當前埠,生成剖析結果,具體流程如圖:
然後請求服務至少三次,由於設定了取樣次數
七、整合日誌框架
這裡以 logback 為例
7.1、引入依賴
<!--配置-->
<dependency>
<groupId>org.apache.skywalking</groupId>
<artifactId>apm-toolkit-logback-1.x</artifactId>
<version>8.11.0</version>
</dependency>
logback-spring.xml 配置
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<include resource="org/springframework/boot/logging/logback/defaults.xml"/>
<!-- 控制檯輸出 -->
<appender name="console" 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>${CONSOLE_LOG_PATTERN}</Pattern>
</layout>
</encoder>
</appender>
<root level="INFO">
<appender-ref ref="console" />
</root>
</configuration>
新增 tid
透過 日誌的 tid 去 skywalking 中查詢介面
透過 gRpc 方式將日誌反饋到 skywalking 中去
logback-spring.xml 中 新增
<!--日誌配置-->
<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="console" />
<appender-ref ref="grpc-log" />
</root>
此時可以看到 skywalking 中增加了日誌資訊。
注意事項
如果 Skywalking 與服務不在同一伺服器內,需要進行配置的修改,否則日誌無法上報。
在skywalking-agent/config/agent.config
配置檔案中新增如下配置
plugin.toolkit.log.grpc.reporter.server_host=$[SW_GRPC_LOG_SERVER_HOST:192.168.33.62]
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]
以上配置是預設配置資訊,agent 與 oap 在本地的可以不配
配置名 | 解釋 | 預設值 |
---|---|---|
plugin.toolkit.log.transmit_formatted | 是否以格式化或未格式化的格式傳輸記錄的資料 | true |
plugin.toolkit.grpc.reporter.server_host | 指定要向其報告日誌資料的grpc伺服器的主機 | 127.0.0.1 |
plugin.toolkit.grpc.reporter.server_port | 指定要向其報告日誌資料的grpc伺服器的埠 | 11800 |
plugin.toolkit.log.grpc.reporter.max_message_size | 指定grcp客戶端要報告的日誌資料的最大大小 | 10485760 |
plugin.toolkit.log.grpc.reporter.upstream_timeout | 客戶端向上遊傳送資料時將超時多長時間,單位秒 | 30 |
八、SkyWalking 告警功能
SkyWalking 告警功能是在 6.x 版本新增的,其核心由一組規則驅動,這些規則定義在config/alarm-settings.yml
檔案中。告警規則的定義分為兩部分:
- 告警規則:它們定義了應該如何處罰度量告警,應該考慮什麼條件
- Webhook(網路鉤子):定義警告觸發時,哪些服務終端需要被告知
8.1、告警規則
SkyWalking 的發行版都會預設提供config/alarm-settings.yml
檔案,裡面預先定義了一些常用的告警規則。如下:
- 過去3分鐘內服務平均響應時間超過1秒。
- 過去2分鐘服務成功率低於80%。
- 過去3分鐘內服務響應時間超過1s的百分比。
- 服務例項在過去2分鐘內平均響應時間超過1s,並且例項名稱與正規表示式匹配。
- 過去2分鐘內端點平均響應時間超過1秒。
- 過去2分鐘內資料庫訪問平均響應時間超過1秒。
- 過去2分鐘內端點關係平均響應時間超過1秒。
這些預定義的告警規則,開啟config/alarm-setting.yml
檔案即可看到。
告警規則配置項說明:
Rule name
:規則名稱,也是在告警資訊中顯示的唯一名稱。 必須以 rule 結尾,字首可自定義Metrics name
:度量名稱,取值為 oal 指令碼中的度量名,目前只支援 long、double 和 int 型別。 詳見Official OAL scriptInclude names
:該規則作用於哪些實體名稱,比如服務名,終端名(可選,預設為全部)Exclude names
:該規則作不用於哪些實體名稱,比如服務名,終端名(可選,預設為空)Threshold
:閾值OP
:運算子,目前支援 >、<、=Period
:多久告警規則需要被核實一下。 這是一個時間視窗, 與後端部署環境時間相匹配Count
:在一個 Period 視窗中,如果 values 超過 Threshold 值(按op),達到Count值, 需要傳送警報Silence period
:在時間N中觸發報警後,在TN -> TN + period這個階段不告警。預設情況下, 它和Period一樣,這意味著相同的告警(在同一個Metrics name擁有相同的id)在同一個Period內只會觸發一次message
:告警訊息
例如
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.
表示三分鐘到十分鐘之內,如果請求次數大於 5 次,並且有介面請求超過 1 秒,就進行報警
8.2、Webhook(網路鉤子)
Webhook可以簡單理解為是一種Web層面的回撥機制,通常由一些事件觸發,與程式碼中的事件回撥類似,只不過是Web層面的。由於是Web層面的,所以當事件發生時,回撥的不再是程式碼中的方法或函式,而是服務介面。例如,在告警這個場景,告警就是一個事件。當該事件發生時,SkyWalking 就會主動去呼叫一個配好的介面,該介面就是所謂的Webhook。
SkyWalking 的告警訊息會透過 HTTP 請求進行傳送,請求方法為 POST,Content-Type 為application/json,其 JSON 資料實基於List<org.apache.skywalking.oap.server.core.alarm.AlarmMessage> 進行序列化的。JSON 資料示例
[{
"scopeId": 1,
"scope": "SERVICE",
"name": "serviceA",
"id0": "12",
"id1": "",
"ruleName": "service_resp_time_rule",
"alarmMessage": "alarmMessage xxxx",
"startTime": 1560524171000,
"tags": [{
"key": "level",
"value": "WARNING"
}]
}, {
"scopeId": 1,
"scope": "SERVICE",
"name": "serviceB",
"id0": "23",
"id1": "",
"ruleName": "service_resp_time_rule",
"alarmMessage": "alarmMessage yyy",
"startTime": 1560524171000,
"tags": [{
"key": "level",
"value": "CRITICAL"
}]
}]
- scopeId, scope. 所有範圍都在 org.apache.skywalking.oap.server.core.source.DefaultScopeDefine 中定義
- name. 目標範圍實體名稱。請按照實體名稱定義
- id0. 範圍實體的 ID 與名稱匹配。使用關係範圍時,它是源實體 ID
- id1. 使用關係範圍時,它將是目標實體 ID。否則,它是空的
- ruleName. 您在 中配置的規則名稱alarm-settings.yml
- alarmMessage. 報警訊息
- startTime. 告警時間以毫秒為單位,介於當前時間和 UTC 時間 1970 年 1 月 1 日午夜之間
config/alarm-settings.yml
配置中配置鉤子
webhooks:
- http://127.0.0.1:8088/alarm/notify
定義 AlarmMessage
import lombok.Data;
//import org.apache.skywalking.oap.server.core.analysis.manual.searchtag.Tag;
import java.util.List;
@Data
public class SwAlarmMessage {
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;
}
}
控制層程式碼
@RestController
@RequiredArgsConstructor
@RequestMapping("/alarm")
public class SwAlarmController {
Logger log = LoggerFactory.getLogger(this.getClass());
/**
* 接收Skywalking服務的告警通知併傳送至郵箱
* 必須是post請求
* @param alarmList
*/
@PostMapping("/notify")
public void receive(@RequestBody List<SwAlarmMessage> alarmList){
// SimpleMailMessage mailMessage = new SimpleMailMessage();
// // 傳送者郵箱
// mailMessage.setFrom("from");
// // 接收者郵箱
// mailMessage.setTo("to");
// // 主題
// mailMessage.setSubject("主題");
// // 郵件內容
// String content = getContent(alarmList);
// mailMessage.setText("content");
// sender.send(mailMessage);
String content = getContent(alarmList);
log.info("告警郵箱已傳送..."+content);
}
private String getContent(List<SwAlarmMessage> alarmList){
StringBuilder sb = new StringBuilder();
alarmList.forEach(message -> sb.append("scopeId: ").append(message.getScopeId())
.append("\nscope: ").append(message.getScope())
.append("\n目標 Scope 的實體類名稱: ").append(message.getName())
.append("\nScope 實體類的 ID: ").append(message.getId0())
.append("\nid1: ").append(message.getId1())
.append("\n告警規則名稱: ").append(message.getRuleName())
.append("\n告警訊息內容: ").append(message.getAlarmMessage())
.append("\n告警時間: ").append(message.getStartTime())
.append("\n標籤: ").append(message.getTags())
.append("\n\n-----------------\n\n "));
return sb.toString();
}
}
api 閘道器配置
spring:
gateway:
# 路由規則
routes:
- id: alarm_route
uri: lb://sw-alarm-server
predicates:
- Path=/alarm/**
請求介面,由於介面響應時間慢,skywalking 傳送的告警。
八、Skywalking 高可用
Sykwalking 叢集是集那個skywalking oap 作為一個服務註冊到 nacos 上,只要 skywalking oap 服務沒有全部當機,保證有一個 skywalking oap 在執行,就能進行跟蹤
搭建 skywalking oap 叢集需要:
- 至少一個 Nacos(也可以是 nacos 叢集)
- 至少一個 ElasticSearch/mysql(也可以是 es/mysql 叢集)
- 至少 2 個 skywalking oap 服務
- 至少 1 個 UI(也可以是叢集,多個,用 Nginx 代理統一入口)
8.1、修改 config/application.yml 檔案
使用 Nacos 作為註冊中心,預設為單機模式
8.2、配置 UI 服務 webapp/webapp.yml 檔案中的 oap-service,填寫多個地址
8.3、啟動服務測試
啟動 Skywalking 服務,指定SpringBoot 應用的 jvm 引數
-DSW_AGENT_COLLECTOR_BACKEND_SERVICES=192.168.33.61:11800,192.168.33.62:11800
本作品採用《CC 協議》,轉載必須註明作者和本文連結