GRPC: 如何實現分布式日志跟蹤?


簡介: 本文將介紹如何在 gRPC 分布式場景中,實現 API 的日志跟蹤。

介紹

本文將介紹如何在 gRPC 分布式場景中,實現 API 的日志追蹤。

什么是 API 日志追蹤?

一個 API 請求會跨多個微服務,我們希望通過一個唯一的 ID 檢索到整個鏈路的日志。

我們將會使用 rk-boot 來啟動 gRPC 服務。

請訪問如下地址獲取完整教程:

安裝

go get github.com/rookie-ninja/rk-boot

快速開始

rk-boot 默認集成了 grpc-gateway,並且會默認啟動。

我們會創建 /api/v1/greeter API 進行驗證,同時開啟 logging, meta 和 tracing 攔截器以達到目的。

1. 創建 api/v1/greeter.proto

syntax = "proto3";

package api.v1;

option go_package = "api/v1/greeter";

service Greeter {
  rpc Greeter (GreeterRequest) returns (GreeterResponse) {}
}

message GreeterRequest {
  string name = 1;
}

message GreeterResponse {
  string message = 1;
}

2. 創建 api/v1/gw_mapping.yaml

type: google.api.Service
config_version: 3

# Please refer google.api.Http in https://github.com/googleapis/googleapis/blob/master/google/api/http.proto file for details.
http:
  rules:
    - selector: api.v1.Greeter.Greeter
      get: /api/v1/greeter

3. 創建 buf.yaml

version: v1beta1
name: github.com/rk-dev/rk-demo
build:
  roots:
    - api

4. 創建 buf.gen.yaml

version: v1beta1
plugins:
  # protoc-gen-go needs to be installed, generate go files based on proto files
  - name: go
    out: api/gen
    opt:
     - paths=source_relative
  # protoc-gen-go-grpc needs to be installed, generate grpc go files based on proto files
  - name: go-grpc
    out: api/gen
    opt:
      - paths=source_relative
      - require_unimplemented_servers=false
  # protoc-gen-grpc-gateway needs to be installed, generate grpc-gateway go files based on proto files
  - name: grpc-gateway
    out: api/gen
    opt:
      - paths=source_relative
      - grpc_api_configuration=api/v1/gw_mapping.yaml
  # protoc-gen-openapiv2 needs to be installed, generate swagger config files based on proto files
  - name: openapiv2
    out: api/gen
    opt:
      - grpc_api_configuration=api/v1/gw_mapping.yaml

5. 編譯 proto file

$ buf generate
如下的文件會被創建。
$ tree api/gen 
api/gen
└── v1
    ├── greeter.pb.go
    ├── greeter.pb.gw.go
    ├── greeter.swagger.json
    └── greeter_grpc.pb.go
 
1 directory, 4 files

6. 創建 bootA.yaml & serverA.go

Server-A 監聽 1949 端口,並且發送請求給 Server-B。

我們通過 rkgrpcctx.InjectSpanToNewContext() 方法把 Tracing 信息注入到 Context 中,發送給 Server-B。

---
grpc:
  - name: greeter                   # Name of grpc entry
    port: 1949                      # Port of grpc entry
    enabled: true                   # Enable grpc entry
    interceptors:
      loggingZap:
        enabled: true
      meta:
        enabled: true
      tracingTelemetry:
        enabled: true
package main

import (
    "context"
    "demo/api/gen/v1"
    "fmt"
    "github.com/rookie-ninja/rk-boot"
    "github.com/rookie-ninja/rk-grpc/interceptor/context"
    "google.golang.org/grpc"
)

// Application entrance.
func main() {
    // Create a new boot instance.
    boot := rkboot.NewBoot(rkboot.WithBootConfigPath("bootA.yaml"))

    // Get grpc entry with name
    grpcEntry := boot.GetGrpcEntry("greeter")
    grpcEntry.AddRegFuncGrpc(registerGreeter)
    grpcEntry.AddRegFuncGw(greeter.RegisterGreeterHandlerFromEndpoint)

    // Bootstrap
    boot.Bootstrap(context.Background())

    // Wait for shutdown sig
    boot.WaitForShutdownSig(context.Background())
}

func registerGreeter(server *grpc.Server) {
    greeter.RegisterGreeterServer(server, &GreeterServer{})
}

type GreeterServer struct{}

func (server *GreeterServer) Greeter(ctx context.Context, request *greeter.GreeterRequest) (*greeter.GreeterResponse, error) {
    // Call serverB at 2008 with grpc client
    opts := []grpc.DialOption{
        grpc.WithBlock(),
        grpc.WithInsecure(),
    }
    conn, _ := grpc.Dial("localhost:2008", opts...)
    defer conn.Close()
    client := greeter.NewGreeterClient(conn)

    // Inject current trace information into context
    newCtx := rkgrpcctx.InjectSpanToNewContext(ctx)
    client.Greeter(newCtx, &greeter.GreeterRequest{Name: "A"})

    return &greeter.GreeterResponse{
        Message: fmt.Sprintf("Hello %s!", request.Name),
    }, nil
}

7. 創建 bootB.yaml & serverB.go

Server-B 監聽 2008 端口。

---
grpc:
  - name: greeter                   # Name of grpc entry
    port: 2008                      # Port of grpc entry
    enabled: true                   # Enable grpc entry
    interceptors:
      loggingZap:
        enabled: true
      meta:
        enabled: true
      tracingTelemetry:
        enabled: true
package main

import (
    "context"
    "demo/api/gen/v1"
    "fmt"
    "github.com/rookie-ninja/rk-boot"
    "google.golang.org/grpc"
)

// Application entrance.
func main() {
    // Create a new boot instance.
    boot := rkboot.NewBoot(rkboot.WithBootConfigPath("bootB.yaml"))

    // Get grpc entry with name
    grpcEntry := boot.GetGrpcEntry("greeter")
    grpcEntry.AddRegFuncGrpc(registerGreeterB)
    grpcEntry.AddRegFuncGw(greeter.RegisterGreeterHandlerFromEndpoint)

    // Bootstrap
    boot.Bootstrap(context.Background())

    // Wait for shutdown sig
    boot.WaitForShutdownSig(context.Background())
}

func registerGreeterB(server *grpc.Server) {
    greeter.RegisterGreeterServer(server, &GreeterServerB{})
}

type GreeterServerB struct{}

func (server *GreeterServerB) Greeter(ctx context.Context, request *greeter.GreeterRequest) (*greeter.GreeterResponse, error) {
    return &greeter.GreeterResponse{
        Message: fmt.Sprintf("Hello %s!", request.Name),
    }, nil
}

8. 文件夾結構

├── api
│   ├── gen
│   │   └── v1
│   │     ├── greeter.pb.go
│   │     ├── greeter.pb.gw.go
│   │     ├── greeter.swagger.json
│   │     └── greeter_grpc.pb.go
│   └── v1
│       ├── greeter.proto
│       └── gw_mapping.yaml
├── bootA.yaml
├── bootB.yaml
├── buf.gen.yaml
├── buf.yaml
├── go.mod
├── go.sum
├── serverA.go
└── serverB.go

9. 啟動 ServerA & ServerB

$ go run serverA.go
$ go run serverB.go

10. 往 ServerA 發送請求

¥ curl "localhost:1949/api/v1/greeter?name=rk-dev"

11. 驗證日志

兩個服務的日志中,會有同樣的 traceId,不同的 requestId。

我們可以通過 grep traceId 來追蹤 RPC。

  • ServerA
------------------------------------------------------------------------
endTime=2021-10-20T00:02:21.739688+08:00
...
ids={"eventId":"0d145356-998a-4999-ab62-6f1b805274a0","requestId":"0d145356-998a-4999-ab62-6f1b805274a0","traceId":"c36a45eb076066df39fa407174012369"}
...
operation=/api.v1.Greeter/Greeter
resCode=OK
eventStatus=Ended
EOE
  • ServerB
------------------------------------------------------------------------
endTime=2021-10-20T00:02:21.739125+08:00
...
ids={"eventId":"8858a6eb-e953-42ad-bdc3-c466bbbd798e","requestId":"8858a6eb-e953-42ad-bdc3-c466bbbd798e","traceId":"c36a45eb076066df39fa407174012369"}
...
operation=/api.v1.Greeter/Greeter
resCode=OK
eventStatus=Ended
EOE

概念

當我們沒有使用例如 jaeger 調用鏈服務的時候,我們希望通過日志來追蹤分布式系統里的 RPC 請求。

rk-boot 的攔截器會通過 openTelemetry 庫來向日志寫入 traceId 來追蹤 RPC。

當啟動了日志攔截器,原數據攔截器,調用鏈攔截器的時候,攔截器會往日志里寫入如下三種 ID。

EventId

當啟動了日志攔截器,EventId 會自動生成。

---
grpc:
  - name: greeter                   # Name of grpc entry
    port: 1949                      # Port of grpc entry
    enabled: true                   # Enable grpc entry
    interceptors:
      loggingZap:
        enabled: true
------------------------------------------------------------------------
...
ids={"eventId":"cd617f0c-2d93-45e1-bef0-95c89972530d"}
...

RequestId

當啟動了日志攔截器和原數據攔截器,RequestId 和 EventId 會自動生成,並且這兩個 ID 會一致。

---
grpc:
  - name: greeter                   # Name of grpc entry
    port: 1949                      # Port of grpc entry
    enabled: true                   # Enable grpc entry
    interceptors:
      loggingZap:
        enabled: true
      meta:
        enabled: true
------------------------------------------------------------------------
...
ids={"eventId":"8226ba9b-424e-4e19-ba63-d37ca69028b3","requestId":"8226ba9b-424e-4e19-ba63-d37ca69028b3"}
...
即使用戶覆蓋了 RequestId,EventId 也會保持一致。
rkgrpcctx.AddHeaderToClient(ctx, rkgrpcctx.RequestIdKey, "overridden-request-id")
------------------------------------------------------------------------
...
ids={"eventId":"overridden-request-id","requestId":"overridden-request-id"}
...

TraceId

當啟動了調用鏈攔截器,traceId 會自動生成。

---
grpc:
  - name: greeter                   # Name of grpc entry
    port: 1949                      # Port of grpc entry
    enabled: true                   # Enable grpc entry
    interceptors:
      loggingZap:
        enabled: true
      meta:
        enabled: true
      tracingTelemetry:
        enabled: true
------------------------------------------------------------------------
...
ids={"eventId":"dd19cf9a-c7be-486c-b29d-7af777a78ebe","requestId":"dd19cf9a-c7be-486c-b29d-7af777a78ebe","traceId":"316a7b475ff500a76bfcd6147036951c"}
...

原文鏈接
本文為阿里雲原創內容,未經允許不得轉載。 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM