需求理解
本次專案,是將go2sky作為agent,在用戶的代碼中匯入,并借助go2sky收集golang runtime metrics,并將metrics上報到skywalking-OAP,skywalking-OAP提供對應的UI進行展示,最終呈現給用戶的應該類似下面的界面:
設計方案
總體流程
收集golang runtime metrcis的設計分為go2Sky和skywalking OAP兩個模塊:- go2Sky完成對golang runtime metrcis的收集并通過gRPC上報到skywalking OAP
-
skywalking OAP接收來自go2sky的資料,并對資料進行處理并持久化
客戶端方案
客戶端的目的是收集golang runtime metrcis并且格式化,再通過gRPC發送到服務端 設計客戶端方案時,主要考慮以下幾點:- 確定收集各型別golang runtime metrcis的收集工具
- 確定資料收集和資料上報的協作方式
指標收集
經過調研,確定使用golang runtime包中的工具類和shirou/gopsutil完成golang runtime metrcis的收集,具體如下表| 指標名 | 說明 | 收集方式 | 計算方式 |
| heapAlloc | 堆記憶體, 堆中已經分配給物件的位元組數,GC記憶體回收后HeapAlloc取值相應減小, | runtime.MemStats.HeapAlloc | 直接使用 |
| stackInUse | 堆疊記憶體 | runtime.MemStats.StackInuse | 直接使用 |
| gcNum | 垃圾回收-gc次數 | runtime.MemStats.NumGC | 計算每次收集周期內發生的gc次數 |
| gcPauseTime | 垃圾回收-gc時長 | runtime.MemStats.PauseNs |
需要計算:
PauseNs是一個回圈佇列,記錄最近垃圾回收系統中斷的時間
|
| goroutineNum | 協程數量,當前存在的協程數量, | runtime.NumGoroutine() | 直接使用 |
| threadNum | 執行緒數量 | runtime.ThreadCreateProfile(nil) | 直接使用 |
| cpuUsedRate | CPU使用率 | cpu.Percent | 直接使用 |
| memUsedRate | 物理記憶體使用率 | mem.UsedPercent | 直接使用 |
資料上報
設計思路:- 考慮到skywalking已經集成了gRPC并且go2sky上報trace和JavaAgent上報metrics都是通過gRPC,因此golang runtime metrics也采取gRPC的方式進行上報,
- 考慮到一次請求只上報一個metric物件,因此采取非stream模式的gRPC連接,
- 考慮到資料收集和資料上報程序的解耦,使用“生產者-消費者”模式:啟動時開啟兩個goroutine,一個負責收集資料并向chan中發送,一個負責從chan中取出資料,并通過gRPC上報,chan設定1000的buffer
- OAP存盤指標需要通過service和serviceInstance確定指標的來源,參考trace收集方案,service由用戶輸入、serviceInstance根據UUID和ip地址生成
- 資料協議
syntax = "proto3";
package skywalking.v3;
option java_package = "org.apache.skywalking.apm.network.language.agent.v3";
option go_package = "github.com/easonyipj/skywalking-goapi/collect/language/agent/v3";
import "common/Common.proto";
// Define the Golang metrics report service.
service GolangMetricReportService {
rpc collect (GolangMetricCollection) returns (Commands) {
}
}
message GolangMetricCollection {
repeated GolangMetric metrics = 1;
string service = 2;
string serviceInstance = 3;
}
message GolangMetric {
int64 time = 1;
int64 heapAlloc = 2;
int64 stackInUse = 3;
int64 gcNum = 4;
float gcPauseTime = 5;
int64 goroutineNum = 6;
int64 threadNum = 7;
float cpuUsedRate = 8;
float memUsedRate = 9;
}
服務端方案
服務端主要完成以下的作業:- 啟動時通過定義的OAL腳本動態生成Golang Metrics相關的類和對應的Dispatcher
- 啟動時自動檢查Golang Metrics相關的表是否已創建,如果未創建則自動創建
- 啟動時注冊gRPC handler 接收客戶端的資料
- 將資料進行處理后持久化
- 接收前端的資料查詢請求
服務端初始化
服務端初始化時,主要完成上面的作業1-3,在充分了解了JVM Mertics的收集流程后,完成服務端的初始化需要新增下面的內容:- golang mertices相關的Source子類;
- golang mertices相關的OAL腳本
- 新增注冊gRPC handler
- 新增golang metrics的處理類GolangSourceDispatcher
詳細方案如下:
- skywalking OAP中的每個metric都對應一個org.apache.skywalking.oap.server.core.source.Source的子類和一個OAL腳本中的一行,因此需要先定義好Source類和OAL腳本;
-
- 在oap-server/server-core/src/main/java/org/apache/skywalking/oap/server/core/source中新增golang mertices相關的Source子類,用于保存相關的指標和生成對應的資料庫表,以CPU指標為例,需新建下面的類,關鍵點:
- 繼承Source
- @ScopeDeclaration中的id和scope()方法回傳的id保持一致
@ScopeDeclaration(id = SERVICE_INSTANCE_GOLANG_CPU, name = "ServiceInstanceGolangCPU", catalog = SERVICE_INSTANCE_CATALOG_NAME) @ScopeDefaultColumn.VirtualColumnDefinition(fieldName = "entityId", columnName = "entity_id", isID = true, type = String.class) public class ServiceInstanceGolangCPU extends Source { @Override public int scope() { return DefaultScopeDefine.SERVICE_INSTANCE_JVM_CPU; } @Override public String getEntityId() { return String.valueOf(id); } @Getter @Setter private String id; @Getter @Setter @ScopeDefaultColumn.DefinedByField(columnName = "name", requireDynamicActive = true) private String name; @Getter @Setter @ScopeDefaultColumn.DefinedByField(columnName = "service_name", requireDynamicActive = true) private String serviceName; @Getter @Setter @ScopeDefaultColumn.DefinedByField(columnName = "service_id") private String serviceId; @Getter @Setter private double usePercent; }
- 新增OAL腳本
- 在oap-server/server-starter/src/main/resources/oal中新增處理golang runtime mertics的的OAL檔案
instance_golang_cpu = from(ServiceInstanceJVMCPU.usePercent).doubleAvg(); - 在oap-server/oal-grammar/src/main/antlr4/org/apache/skywalking/oal/rt/grammar/OALLexer.g4和OALParser.g4中新增相關的關鍵字
- 在oap-server/server-core/src/main/java/org/apache/skywalking/oap/server/core/source中新增golang mertices相關的Source子類,用于保存相關的指標和生成對應的資料庫表,以CPU指標為例,需新建下面的類,關鍵點:
- 服務端初始化時,應該注冊好gRPC handler,并且使用OAL腳本動態生成處理golang runtime mertics的類:
- 在apm-network模塊的proto中新增上文文GolangMetric.proto,并執行編譯命令./mvnw compile -Dmaven.test.skip=true
- 在oap-server/server-receiver-plugin新增處理golang mertices的plugin模塊skywalking-golang-receiver-plugin,目錄結構如下:
├── skywalking-golang-receiver-plugin │ ├── pom.xml │ └── src │ ├── main │ │ ├── java │ │ │ └── org │ │ │ └── apache │ │ │ └── skywalking │ │ │ └── oap │ │ │ └── server │ │ │ └── receiver │ │ │ └── golang │ │ │ ├── module │ │ │ │ └── GolangModule.java │ │ │ └── provider │ │ │ ├── GolangModuleProvider.java │ │ │ ├── GolangOALDefine.java │ │ │ └── handler │ │ │ └── GolangMetricReportServiceHandler.java │ │ └── resources │ │ └── services │ │ ├── org.apache.skywalking.oap.server.library.module.ModuleDefine │ │ └── org.apache.skywalking.oap.server.library.module.ModuleProvider │ └── test │ └── java- 新增接收gRPC請求的handler GolangMetricReportServiceHandler,負責決議資料并初步封裝,然后呼叫golang metrics的處理類GolangSourceDispatcher的sendMetric方法
- 新增相應的OALDefine - GolangOALDefine.java,需要在構造方法中傳入對應OAL腳本的名稱和source類所在的位置,以生成相關的表,示例如下
public class GolangOALDefine extends OALDefine { public static final GolangOALDefine INSTANCE = new GolangOALDefine(); private GolangOALDefine() { super( "oal/golang.oal", "org.apache.skywalking.oap.server.core.source" ); } } - 新增相應的GolangModule和GolangModuleProvider,并在resources/META-INF/services目錄下的組態檔指定實作類的全路徑名(同時在oap-server/server-starter/pom.xml中補充新增的skywalking-golang-receiver-plugin依賴,便于進行跨包SPI;在application.yml中新增),便于啟動時通過SPI機制加載這些類,GolangModuleProvider中完成對gRPC handelr-GolangMetricReportServiceHandler的注冊和OAL腳本的決議(動態生成類并且自動生成相關資料庫表)
public class GolangModuleProvider extends ModuleProvider { @Override public String name() { return "default"; } @Override public Class<? extends ModuleDefine> module() { return GolangModule.class; } @Override public ModuleConfig createConfigBeanIfAbsent() { return null; } @Override public void prepare() throws ServiceNotProvidedException, ModuleStartException { } @Override public void start() throws ServiceNotProvidedException, ModuleStartException { getManager().find(CoreModule.NAME) .provider() .getService(OALEngineLoaderService.class) .load(GolangOALDefine.INSTANCE); GRPCHandlerRegister grpcHandlerRegister = getManager().find(SharingServerModule.NAME) .provider() .getService(GRPCHandlerRegister.class); GolangMetricReportServiceHandler golangMetricReportServiceHandler = new GolangMetricReportServiceHandler(getManager()); grpcHandlerRegister.addHandler(golangMetricReportServiceHandler); } @Override public void notifyAfterCompleted() throws ServiceNotProvidedException, ModuleStartException { } @Override public String[] requiredModules() { return new String[] { CoreModule.NAME, SharingServerModule.NAME }; } }
-
資料接收和處理
服務端初始化時,注冊了gRPC handler,遵循現有的metrics處理流程,gRPC handler接收資料后,將會對servceName和InstanceName格式化,然后呼叫GolangSourceDispatcher的sendMetric方法:- 在oap-server/analyzer/agent-analyzer/src/main/java/org/apache/skywalking/oap/server/analyzer/provider中新增GolangSourceDispatcher,用于接受gRPC handler的資訊,使用source包中的物體類封裝這些資訊,并呼叫sourceReceiver.receive方法
├── agent-analyzer │ ├── pom.xml │ ├── src │ │ ├── main │ │ │ ├── java │ │ │ │ └── org │ │ │ │ └── apache │ │ │ │ └── skywalking │ │ │ │ └── oap │ │ │ │ └── server │ │ │ │ └── analyzer │ │ │ │ ├── module │ │ │ │ │ └── AnalyzerModule.java │ │ │ │ └── provider │ │ │ │ ├── AnalyzerModuleConfig.java │ │ │ │ ├── AnalyzerModuleProvider.java │ │ │ │ ├── golang │ │ │ │ │ └── GolangSourceDispatcher.java │ │ │ │ ├── jvm │ │ │ │ │ └── JVMSourceDispatcher.java
資料持久化
通過對JVM Metrics采集流程的調研發現,通過呼叫OALEngineLoaderService根據OAL腳本動態生成類后,會呼叫MetricsStreamProcessor的create方法會為每個指標創建作業任務和作業流,其中就包含了三種型別的MetricsPersistentWorker,分別每分鐘、小時和天進行一次持久化;因此資料持久化部分不需要額外新增,復用現有流程并測驗即可,資料查詢
根據之前的調研,資料查詢是通過/graphql介面,采用的是Armeria框架和GraphQL,在初步看了這兩個技術的介紹后,總結出前端資料查詢的大概流程:- Server端啟動時,當CoreModule及其依賴初始化后,會呼叫UITemplateInitializer(getManager()).initAll()完成UI模版的加載,UI模版的位置是:oap-server/server-starter/src/main/resources/ui-initialized-templates
- 同時也會使用SPI機制加載GraphQLQueryProvider,并呼叫期prepare和start方法完成查詢模塊的初始化
- 前端發送的GraphQL被決議到指定的服務,比如MetricsQuery的readMetricsValues方法,該方法會最終呼叫對應的DAO層代碼(根據不同資料庫),以MySQL為例,最終呼叫H2MetricsQueryDAO的readMetricsValue,進行SQL陳述句的拼接和執行,
- 前端發送的請求中具體的引數是根據UI組態檔中的metrics決議的,JVM的Metircs定義在general-instance.json中
- 在oap-server/server-starter/src/main/resources/ui-initialized-templates的general-instance.json檔案中新增相關配置,以CPU使用率指標為例:
{ "name": "Golang", "children": [ { "x": 18, "y": 0, "w": 6, "h": 13, "i": "4", // i不能和其他面板(比如JVM)的i重復 "type": "Widget", "widget": { "title": "CPU" }, "graph": { "type": "Line", "step": false, "smooth": false, "showSymbol": false, "showXAxis": true, "showYAxis": true }, "metrics": [ "instance_golang_cpu" ], "metricTypes": [ "readMetricsValues" ], "moved": false } ] } - 前端需重新加載儀表盤,更新相關UI

3. 效果如下

前端展示
根據不同的指標,采取不同的展示樣式,目前暫定都以折線圖形式展示| 指標名 | 樣式 |
| heapAlloc | 折線圖 |
| stackAlloc | 折線圖 |
| gcNum | 折線圖 |
| gcPauseTime | 折線圖 |
| goroutineNum | 折線圖 |
| threadNum | 折線圖 |
| cpuUsedRate | 折線圖 |
| memUsedRate | 折線圖 |
用戶接入
用戶使用go2sky時自動上報資料Github鏈接
https://github.com/easonyipj/skywalking-goapi:go2sky的gRPC api https://github.com/easonyipj/go2sky:主要收集指標的代碼 https://github.com/easonyipj/skywalking-data-collect-protocol:collect protocol https://github.com/easonyipj/skywalking :oap server轉載請註明出處,本文鏈接:https://www.uj5u.com/qita/502522.html
標籤:其他
