.NET6接入Skywalking鏈路追蹤完整流程
一、Skywalking介紹
Skywalking是一款分佈式鏈路追蹤組件,什麼是鏈路追蹤?
隨着微服務架構的流行,服務按照不同的維度進行拆分,一次請求往往需要涉及到多個服務。互聯網應用構建在不同的軟件模塊集上,這些軟件模塊,有可能是由不同的團隊開發、可能使用不同的編程語言來實現、有可能布在了幾千台服務器,橫跨多個不同的數據中心。
然後微服務面臨了這些問題:
- 某個核心服務掛了,導致大量報錯,如何快速確定哪裡出了問題?
- 用戶請求響應延遲高,怎麼確定是哪些服務導致的?
- 應用程序有性能瓶頸,怎樣確定瓶頸在哪裡?
- 如何准實時的了解應用部署環境(CPU、內存、進程、線程、網絡、帶寬)情況,以便快速擴容/縮容、流量控制、業務遷移
- 如何統計各個調用的性能指標,比如:吞吐量(TPS)、響應時間及錯誤記錄等
分佈式鏈路跟蹤系統就是為了解決這些問題應運而生。
分佈式鏈路追蹤組件
- 阿里巴巴鷹眼(EagleEye)
- 美團CAT
- 京東Hydra
- Twitter Zipkin (Java經常用到) 【.NET Java】
- Apache SkyWalking (APM) 【go,python,.NET, Java】
- Pinpoint(APM)
.NET用的最多的兩款是SkyWalking、Zipkin。這裡介紹Skywalking使用。
Skywalking有哪些功能?
- 多種監控手段。可以通過語言探針和 service mesh 獲得監控是數據。
- 多個語言自動探針。包括 Java,.NET Core 和 Node.JS。
- 輕量高效。無需大數據平台,和大量的服務器資源。
- 模塊化。UI、存儲、集群管理都有多種機制可選。
- 支持告警。
- 優秀的可視化解決方案。
**Skywalking整體架構 **
整個架構,分成上、下、左、右四部分:
- 探針基於不同的來源可能是不一樣的, 但作用都是收集數據, 將數據格式化為 SkyWalking 適用的格式.
- 平台後端是一個支持集群模式運行的後台, 用於數據聚合, 數據分析以及驅動數據流從探針到用戶界面的流程. 平台後端還提供了各種可插拔的能力, 如不同來源數據(如來自 Zipkin)格式化, 不同存儲系統以及集群管理. 你甚至還可以使用觀測分析語言來進行自定義聚合分析.
- 存儲是開放式的. 你可以選擇一個既有的存儲系統, 如 ElasticSearch, H2 或 MySQL 集群(Sharding-Sphere 管理), 也可以選擇自己實現一個存儲系統. 當然, 我們非常歡迎你貢獻新的存儲系統實現.
- 用戶界面對於 SkyWalking 的最終用戶來說非常炫酷且強大. 同樣它也是可定製以匹配你已存在的後端的
Tracing、Logging和Metrics
在微服務領域,很早以來就形成了Tracing、Logging和Metrics相輔相成,合力支撐多維度、多形態的監控體系,三類監控各有側重:
Tracing:它在單次請求的範圍內,處理信息。 任何的數據、元數據信息都被綁定到系統中的單個事務上。例如:一次調用遠程服務的RPC執行過程;一次實際的SQL查詢語句;一次HTTP請求的業務性ID;
Logging:日誌,不知道大家有沒有想過它的定義或者邊界。Logging即是記錄處理的離散事件,比如我們應用的調試信息或者錯誤信息等發送到ES;審計跟蹤時間信息通過Kafka處理送到BigTable等數據倉儲等等,大多數情況下記錄的數據很分散,並且相互獨立,也許是錯誤信息,也許僅僅只是記錄當前的事件狀態,或者是警告信息等等。
Metrics:當我們想知道我們服務的請求QPS是多少,或者當天的用戶登錄次數等等,這時我們可能需要將一部分事件進行聚合或計數,也就是我們說的Metrics。可聚合性即是Metrics的特徵,它們是一段時間內某個度量(計數器或者直方圖)的原子或者是元數據。例如接收的HTTP數量可以被建模為計數器,每次的HTTP請求即是我們的度量元數據,可以進行簡單的加法聚合,當持續了一段時間我們又可以建模為直方圖。
二、Skywalking搭建
這裡用Docker搭建
數據存儲用ES,搭建ES
docker run -d -p 9200:9200 -p 9300:9300 --name es -e "discovery.type=single-node" -e ES_JAVA_OPTS="-Xms128m -Xmx256m" elasticsearch:7.16.3
搭建skywalking-oap服務,這裡用的是8.9.1版本,oap服務需要關聯ES
docker run --name skywalking-oap \
--restart always \
-p 11800:11800 -p 12800:12800 -d \
-e TZ=Asia/Shanghai \
-e SW_ES_USER= \
-e SW_ES_PASSWORD= \
-e SW_STORAGE=elasticsearch \
-e SW_STORAGE_ES_CLUSTER_NODES=192.168.101.10:9200 \
-v /etc/localtime:/etc/localtime:ro \
apache/skywalking-oap-server:8.9.1
搭建skywalking-ui界面,需要關聯oap服務
docker run -d \
--name skywalking-ui \
--restart always \
-p 8080:8080 \
--link skywalking-oap:skywalking-oap \
-e TZ=Asia/Shanghai \
-e SW_OAP_ADDRESS=//skywalking-oap:12800 \
-v /etc/localtime:/etc/localtime:ro \
apache/skywalking-ui:8.9.1
搭建完成,打開ip:8080查看skywalking界面
三、.NET6接入Skywalking
1、單個服務接入
新建一個.NET6站點,安裝Nuget包
SkyAPM.Agent.AspNetCore
Properties下launchSettings.json增加
“ASPNETCORE_HOSTINGSTARTUPASSEMBLIES”: “SkyAPM.Agent.AspNetCore”, //必須配置
“SKYWALKING__SERVICENAME”: “Service1” // 必須配置,在skywalking做標識,服務名稱
{
"iisSettings": {
"windowsAuthentication": false,
"anonymousAuthentication": true,
"iisExpress": {
"applicationUrl": "//localhost:6413",
"sslPort": 44308
}
},
"profiles": {
"NET6AndSkyWalking": {
"commandName": "Project",
"dotnetRunMessages": true,
"launchBrowser": true,
"applicationUrl": "//localhost:5025",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development",
"ASPNETCORE_HOSTINGSTARTUPASSEMBLIES": "SkyAPM.Agent.AspNetCore", //必須配置
"SKYWALKING__SERVICENAME": "Service1" // 必須配置,在skywalking做標識,服務名稱
}
},
"IIS Express": {
"commandName": "IISExpress",
"launchBrowser": true,
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
}
}
}
如果不在launchSettings.json加,也可以在Program.cs加
Environment.SetEnvironmentVariable("ASPNETCORE_HOSTINGSTARTUPASSEMBLIES", "SkyAPM.Agent.AspNetCore");
Environment.SetEnvironmentVariable("SKYWALKING__SERVICENAME", "Service1");
生成skywalking.json文件
1、安裝CLI(SkyAPM.DotNet.CLI),cmd命令:
dotnet tool install -g SkyAPM.DotNet.CLI
2、自動生成skyapm.json文件,cmd命令:
1.dotnet skyapm config [service name] [server]:11800
2.eg: dotnet skyapm config service1 192.168.101.10:11800
執行了命令會生成skywalking.json文件,把skywalking.json放到項目根目錄,設置較新複製到輸出目錄,然後修改ServiceName設置為項目名,和上面的SKYWALKING__SERVICENAME一致。
skyapm.json文件
{
"SkyWalking": {
"ServiceName": "service1",
"Namespace": "",
"HeaderVersions": [
"sw8"
],
"Sampling": {
"SamplePer3Secs": -1,
"Percentage": -1.0
},
"Logging": {
"Level": "Information",
"FilePath": "logs\\skyapm-{Date}.log"
},
"Transport": {
"Interval": 3000,
"ProtocolVersion": "v8",
"QueueSize": 30000,
"BatchSize": 3000,
"gRPC": {
"Servers": "192.168.101.10:11800",
"Timeout": 10000,
"ConnectTimeout": 10000,
"ReportTimeout": 600000,
"Authentication": ""
}
}
}
}
SamplePer3Secs:每三秒採樣的Trace數量 ,默認為負數,代表在保證不超過內存Buffer區的前提下,採集所有的Trace
Percentage:採樣率,默認負數,採集全部,其它數值格式:0.5,0.8…
這時候運行項目已經有基本的鏈路追蹤功能了!
試一下運行,項目,然後看鏈路追蹤界面(注意一下時間搜索的時間範圍向後選一些),這個可能會延遲幾秒鐘才顯示出來。
儀錶盤
追蹤
自定義鏈路日誌
自定義鏈路日誌可以在重要的地方加上,這樣就能知道程序跑到這個地方時的關鍵信息了。
public class HomeController : Controller
{
private readonly IEntrySegmentContextAccessor _segContext;
public HomeController(IEntrySegmentContextAccessor segContext)
{
_segContext = segContext;
}
public IActionResult Index()
{
return View();
}
/// <summary>
/// 自定鏈路日誌
/// </summary>
/// <returns></returns>
public string SkywalkingLog()
{
//獲取全局traceId
var traceId = _segContext.Context.TraceId;
_segContext.Context.Span.AddLog(LogEvent.Message("自定義日誌1"));
Thread.Sleep(1000);
_segContext.Context.Span.AddLog(LogEvent.Message("自定義日誌2"));
return traceId;
}
}
調用/Home/SkywalkingLog後Skywalking界面效果,看到了程序添加的日誌
2、多服務追蹤
鏈路追蹤在多服務的時候才能體現它的精髓,一個鏈路能跟蹤到請求涉及的所有服務。
這裡新增一個.NET6的web項目,前面的步驟和上面的Service1一樣,只是把服務名改為Service2。
然後在Service2增加一個接口 /UserInfo/GetUserInfo
public class UserInfoController : Controller
{
private readonly IEntrySegmentContextAccessor _segContext;
public UserInfoController(IEntrySegmentContextAccessor segContext)
{
_segContext = segContext;
}
[HttpGet]
public string GetUserInfo(string userId)
{
string result = $"userId:{userId},userName:張三";
_segContext.Context.Span.AddLog(LogEvent.Message(result));
return result;
}
}
然後在Service1增加一個接口/Home/GetUser調用Service2
後在Service2增加一個接口 /UserInfo/GetUserInfo
public class UserInfoController : Controller
{
private readonly IEntrySegmentContextAccessor _segContext;
public UserInfoController(IEntrySegmentContextAccessor segContext)
{
_segContext = segContext;
}
[HttpGet]
public string GetUserInfo(string userId)
{
string result = $"userId:{userId},userName:張三";
_segContext.Context.Span.AddLog(LogEvent.Message(result));
return result;
}
}
然後在Service1增加一個接口/Home/GetUser調用Service2
public async Task<string> GetUser()
{
var client = new HttpClient();
//調用Service2
var response=await client.GetAsync("//localhost:5199/UserInfo/GetUserInfo");
var result = await response.Content.ReadAsStringAsync();
return result;
}
然後調用Service1的接口/Home/GetUser
然後看鏈路追蹤,會顯示出對應的Service對應的耗時,點進去還能看到當前服務的詳情和打的日誌。
多服務的時候還能看到服務之間對應的調用關係
四、微服務網關接入Skywalking
新建一個.NET6的Web項目,引用Nuget包
SkyAPM.Agent.AspNetCore
Ocelot
把Web項目改為Ocelot網關
首先在根目錄新增ocelot.json文件
{
"Routes": [
{
//轉發到下游服務地址--url變量
"DownstreamPathTemplate": "/{url}",
//下游http協議
"DownstreamScheme": "http",
//負載方式,
"LoadBalancerOptions": {
"Type": "RoundRobin" // 輪詢
},
"DownstreamHostAndPorts": [
{
"Host": "127.0.0.1",
"Port": 5025 //服務端口
} //可以多個,自行負載均衡
],
//上游地址
"UpstreamPathTemplate": "/T1/{url}", //網關地址--url變量 //衝突的還可以加權重Priority
"UpstreamHttpMethod": [ "GET", "POST", "DELETE", "PUT" ]
},
{
//轉發到下游服務地址--url變量
"DownstreamPathTemplate": "/{url}",
//下游http協議
"DownstreamScheme": "http",
//負載方式,
"LoadBalancerOptions": {
"Type": "RoundRobin" // 輪詢
},
"DownstreamHostAndPorts": [
{
"Host": "127.0.0.1",
"Port": 5199 //服務端口
} //可以多個,自行負載均衡
],
//上游地址
"UpstreamPathTemplate": "/T2/{url}", //網關地址--url變量 //衝突的還可以加權重Priority
"UpstreamHttpMethod": [ "GET", "POST", "DELETE", "PUT" ]
}
]
}
然後把Program.cs修改為Ocelot網關
using Ocelot.DependencyInjection;
using Ocelot.Middleware;
using SkyApm.Utilities.DependencyInjection;
var builder = WebApplication.CreateBuilder(args);
builder.Configuration.AddJsonFile("ocelot.json", optional: false, reloadOnChange: true);//ocelot
builder.Services.AddSkyApmExtensions(); // 添加Skywalking相關配置
builder.Services.AddOcelot(); //ocelot
var app = builder.Build();
app.UseOcelot().Wait(); //ocelot
app.Run();
修改Properties下的launchSettings.json文件
{
"iisSettings": {
"windowsAuthentication": false,
"anonymousAuthentication": true,
"iisExpress": {
"applicationUrl": "//localhost:5127",
"sslPort": 44306
}
},
"profiles": {
"Ocelot.Web": {
"commandName": "Project",
"dotnetRunMessages": true,
"launchBrowser": true,
"applicationUrl": "//localhost:7019;//localhost:5019",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development",
"ASPNETCORE_HOSTINGSTARTUPASSEMBLIES": "SkyAPM.Agent.AspNetCore", //skywalking必須配置
"SKYWALKING__SERVICENAME": "Ocelot.Web" // 必須配置,在skywalking做標識
}
},
"IIS Express": {
"commandName": "IISExpress",
"launchBrowser": true,
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
}
}
}
把skyapm.json複製過來修改一下
{
"SkyWalking": {
"ServiceName": "Ocelot.Web",
"Namespace": "",
"HeaderVersions": [
"sw8"
],
"Sampling": {
"SamplePer3Secs": -1,
"Percentage": -1.0
},
"Logging": {
"Level": "Information",
"FilePath": "logs\\skyapm-{Date}.log"
},
"Transport": {
"Interval": 3000,
"ProtocolVersion": "v8",
"QueueSize": 30000,
"BatchSize": 3000,
"gRPC": {
"Servers": "192.168.101.10:11800",
"Timeout": 10000,
"ConnectTimeout": 10000,
"ReportTimeout": 600000,
"Authentication": ""
}
}
}
}
到這裡就完成了,啟動網關項目,Service1項目,Service2項目,訪問/T1/Home/GetUser
訪問成功,看Skywalking追蹤界面,三個站點都追蹤到了
五、配置Skywalking告警
進入容器
docker exec -it 7c21 /bin/bash
如果報文件夾不存在就用下面的,因為版本不一樣可能會不一樣
docker exec -it 7c21 /bin/sh
進入config目錄
配置文件規則解讀
通過cat alarm-settings.yml
可以查閱文件內容,如下:
# Sample alarm rules.
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
# Active endpoint related metrics alarm will cost more memory than service and service instance metrics alarm.
# Because the number of endpoint is much more than service and instance.
#
# 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:
# - //127.0.0.1/notify/
# - //127.0.0.1/go-wechat/
規則常用指標解讀:
rule name: 規則名稱,必須唯一,必須以 _rule結尾;
metrics name: oal(Observability Analysis Language)腳本中的度量名;名稱在SkyWalking後端服務中已經定義,進入容器skywalking-oap之後,進入如下目錄就可以找到。
include names: 本規則告警生效的實體名稱,如服務名,終端名;
exclude-names:將此規則作用於不匹配的實體名稱上,如服務名,終端名;
threshold: 閾值,可以是一個數組,即可以配置多個值;
op: 操作符, 可以設定 >, <, =;
period: 多久檢查一次當前的指標數據是否符合告警規則;以分鐘為單位
count: 超過閾值條件,達到count次數,觸發告警;
silence period:在同一個周期,指定的silence period時間內,忽略相同的告警消息;
更多告警規則詳情,請參照這個地址:
//github.com/apache/skywalking/blob/master/docs/en/setup/backend/backend-alarm.md
修改告警規則
rules:
service_sal_rule:
# 指定指標名稱
metrics-name: service_sal
# 小於
op: "<"
# 指定閾值
threshold: 8000
# 每10分鐘檢測告警該規則
period: 10
# 觸發2次規則就告警
count: 2
# 設置三分鐘內容相同告警,不重複告警
silence-period: 3
# 配置告警信息
message: Successful rate of service {name} is lower than 80% in 2 minutes of last 10 minutes
概要:服務成功率在過去2分鐘內低於80%
告警API編寫
這個本質還是SkyWalking根據規則進行檢查,如果符合規則條件,就通過WebHook、gRPCHook、WeChat Hook、Dingtalk Hook、Feishu Hook等方式進行消息通知;接收到告警數據信息之後,可以自行處理消息。這裡為了方便,就採用WebHook的方式進行演示,即觸發告警條件之後,SkyWalking會調用配置的WebHook 接口,並傳遞對應的告警信
定義數據模型
public class AlarmMsg
{
public int scopeId { get; set; }
public string? scope { get; set; }
public string? name { get; set; }
public string? id0 { get; set; }
public string? id1 { get; set; }
public string? ruleName { get; set; }
public string? alarmMessage { get; set; }
}
定義WebHook調用API,這裡在Service1下的HomeController里加接口接收告警信息
/// <summary>
/// 故意報錯測試告警
/// </summary>
/// <returns></returns>
/// <exception cref="Exception"></exception>
public string Error()
{
//故意報錯
throw new Exception($"出錯啦:{DateTime.Now}");
}
/// <summary>
/// 告警
/// </summary>
/// <param name="msgs"></param>
[HttpPost]
public void AlarmMsg([FromBody]List<AlarmMsg>List<AlarmMsg> msgs)
{
string msg = $"{DateTime.Now},觸發告警:";
msg += msgs.FirstOrDefault()?.alarmMessage;
Console.WriteLine(msg);
//todo 發郵件或發短訊
}
配置webkook
webhooks:
- //192.168.101.9:5025/Home/AlarmMsg
重啟 Skywalking-oap服務
請求幾次/Home/Error產生錯誤請求
等待告警webhook調用
到這裡,告警據完成了。
六、Skywalking無入侵原理解密
為什麼要在launchSettings.json文件裏面加SkyAPM.Agent.AspNetCore呢,為什麼加了就可以了呢?
其實用的是.NET Core框架裏面的擴展,它是怎做到的呢,舉個例子
在Service1做測試,建一個CustomHostingStartup.cs
namespace NET6AndSkyWalking.Models
{
/// <summary>
/// 必須實現IHostingStartup 接口
/// 必須標記HostingStartup特性
/// 發生在HostBuild時候,IOC容器初始化之前,無侵入式擴展
/// </summary>
public class CustomHostingStartup : IHostingStartup
{
public void Configure(IWebHostBuilder builder)
{
Console.WriteLine("自定義擴展執行...");
//拿到IWebHostBuilder,一切都可做
}
}
}
啟動Service1,可以看到新增的擴展打印了
如果這個擴展是在其他類庫呢?
新建一個Common類庫,把剛才的類移到Common類庫,然後Service添加對Common的引用。
這時候就要修改launchsettings.json文件,加入Common的程序集了
啟動Service1,成功執行
同樣的道理,通過查看源碼可以看到,SkyAPM.Agent.AspNetCore組件裏面也有這樣的一個類,把Skywalking的代碼無侵入擴展進來了。
演示源碼://github.com/weixiaolong325/NET6AndSkyWalking