GoFrame 鏈路跟蹤-GRPC示例

2022-04-01 15:28 更新

在本章節(jié)中,我們將之前介紹?HTTP Client&Server?的示例修改為?GRPC?微服務,并演示如何使用?GoFrame?框架開發(fā)一個簡單的?GRPC?服務端和客戶端,并且為?GRPC?微服務增加鏈路跟蹤特性。

本章節(jié)的示例代碼位于:https://github.com/gogf/gf/tree/master/example/trace/grpc_with_db

目錄結構


Protocol

syntax = "proto3";

package user;

option go_package = "protobuf/user";

import "github.com/gogo/protobuf/gogoproto/gogo.proto";

// User service for tracing demo.
service User {
  rpc Insert(InsertReq) returns (InsertRes) {}
  rpc Query(QueryReq) returns (QueryRes) {}
  rpc Delete(DeleteReq) returns (DeleteRes) {}
}

message InsertReq {
  string Name = 1 [(gogoproto.moretags) = 'v:"required#Please input user name."'];
}

message InsertRes {
  int32 Id = 1;
}

message QueryReq {
  int32 Id = 1 [(gogoproto.moretags) = 'v:"min:1#User id is required for querying."'];
}

message QueryRes {
  int32  Id = 1;
  string Name = 2;
}

message DeleteReq {
  int32 Id = 1 [(gogoproto.moretags) = 'v:"min:1#User id is required for deleting."'];
}

message DeleteRes {}

這里使用到了第三方的 github.com/gogo/protobuf 開源項目,用于注入自定義的Golang struct標簽。這里不詳細介紹,感興趣的小伙伴可以自行了解。未來?Katyusha?微服務框架的官網(wǎng)文檔也會做對這塊詳細介紹,包括?GRPC?工程目錄、開發(fā)規(guī)范、開發(fā)工具、攔截器、注冊發(fā)現(xiàn)、負載均衡等設計話題。

GRPC Server

package main

import (
	"context"
	"fmt"
	"time"

	"github.com/gogf/gf/contrib/trace/jaeger/v2"
	"github.com/gogf/gf/example/trace/grpc_with_db/protobuf/user"
	"github.com/gogf/gf/v2/database/gdb"
	"github.com/gogf/gf/v2/frame/g"
	"github.com/gogf/gf/v2/os/gcache"
	"github.com/gogf/gf/v2/os/gctx"
	"github.com/gogf/katyusha/krpc"
)

type server struct{}

const (
	ServiceName       = "grpc-server-with-db"
	JaegerUdpEndpoint = "localhost:6831"
)

func main() {
	var ctx = gctx.New()
	tp, err := jaeger.Init(ServiceName, JaegerUdpEndpoint)
	if err != nil {
		g.Log().Fatal(ctx, err)
	}
	defer tp.Shutdown(ctx)

	// Set ORM cache adapter with redis.
	g.DB().GetCache().SetAdapter(gcache.NewAdapterRedis(g.Redis()))

	s := krpc.Server.NewGrpcServer()
	user.RegisterUserServer(s.Server, &server{})
	s.Run()
}

// Insert is a route handler for inserting user info into database.
func (s *server) Insert(ctx context.Context, req *user.InsertReq) (res *user.InsertRes, err error) {
	result, err := g.Model("user").Ctx(ctx).Insert(g.Map{
		"name": req.Name,
	})
	if err != nil {
		return nil, err
	}
	id, _ := result.LastInsertId()
	res = &user.InsertRes{
		Id: int32(id),
	}
	return
}

// Query is a route handler for querying user info. It firstly retrieves the info from redis,
// if there's nothing in the redis, it then does db select.
func (s *server) Query(ctx context.Context, req *user.QueryReq) (res *user.QueryRes, err error) {
	err = g.Model("user").Ctx(ctx).Cache(gdb.CacheOption{
		Duration: 5 * time.Second,
		Name:     s.userCacheKey(req.Id),
		Force:    false,
	}).WherePri(req.Id).Scan(&res)
	if err != nil {
		return nil, err
	}
	return
}

// Delete is a route handler for deleting specified user info.
func (s *server) Delete(ctx context.Context, req *user.DeleteReq) (res *user.DeleteRes, err error) {
	err = g.Model("user").Ctx(ctx).Cache(gdb.CacheOption{
		Duration: -1,
		Name:     s.userCacheKey(req.Id),
		Force:    false,
	}).WherePri(req.Id).Scan(&res)
	return
}

func (s *server) userCacheKey(id int32) string {
	return fmt.Sprintf(`userInfo:%d`, id)
}

服務端代碼簡要說明:

  • 首先,服務端需要通過?jaeger.Init?方法初始化?Jaeger?。
  • 可以看到,業(yè)務邏輯和之前HTTP示例項目完全一致,只是接入層修改為了?GRPC?協(xié)議。
  • 我們仍然通過緩存適配器的方式注入?Redis?緩存:
g.DB().GetCache().SetAdapter(gcache.NewAdapterRedis(g.Redis()))
  • 這里也是通過?Cache?方法啟用?ORM?的緩存特性,之前已經做過介紹,這里不再贅述。

GRPC Client

package main

import (
	"github.com/gogf/gf/contrib/trace/jaeger/v2"
	"github.com/gogf/gf/example/trace/grpc_with_db/protobuf/user"
	"github.com/gogf/gf/v2/frame/g"
	"github.com/gogf/gf/v2/net/gtrace"
	"github.com/gogf/gf/v2/os/gctx"
)

const (
	ServiceName       = "grpc-client-with-db"
	JaegerUdpEndpoint = "localhost:6831"
)

func main() {
	var ctx = gctx.New()
	tp, err := jaeger.Init(ServiceName, JaegerUdpEndpoint)
	if err != nil {
		g.Log().Fatal(ctx, err)
	}
	defer tp.Shutdown(ctx)

	StartRequests()
}

func StartRequests() {
	ctx, span := gtrace.NewSpan(gctx.New(), "StartRequests")
	defer span.End()

	var client, err = user.NewClient()
	if err != nil {
		g.Log().Fatalf(ctx, `%+v`, err)
	}

	// Baggage.
	ctx = gtrace.SetBaggageValue(ctx, "uid", 100)

	// Insert.
	insertRes, err := client.User().Insert(ctx, &user.InsertReq{
		Name: "john",
	})
	if err != nil {
		g.Log().Fatalf(ctx, `%+v`, err)
	}
	g.Log().Info(ctx, "insert id:", insertRes.Id)

	// Query.
	queryRes, err := client.User().Query(ctx, &user.QueryReq{
		Id: insertRes.Id,
	})
	if err != nil {
		g.Log().Errorf(ctx, `%+v`, err)
		return
	}
	g.Log().Info(ctx, "query result:", queryRes)

	// Delete.
	_, err = client.User().Delete(ctx, &user.DeleteReq{
		Id: insertRes.Id,
	})
	if err != nil {
		g.Log().Errorf(ctx, `%+v`, err)
		return
	}
	g.Log().Info(ctx, "delete id:", insertRes.Id)

	// Delete with error.
	_, err = client.User().Delete(ctx, &user.DeleteReq{
		Id: -1,
	})
	if err != nil {
		g.Log().Errorf(ctx, `%+v`, err)
		return
	}
	g.Log().Info(ctx, "delete id:", -1)
}

客戶端代碼簡要說明:

  1. 首先,客戶端也是需要通過?jaeger.Init?方法初始化?Jaeger?。
  2. 客戶端非常簡單,內部初始化以及默認攔截器的設置已經由?Katyusha?框架封裝好了,開發(fā)者只需要關心業(yè)務邏輯實現(xiàn)即可,

效果查看

啟動服務端:


啟動客戶端:


這里客戶端的執(zhí)行最后報了一個錯誤,那是我們故意為之,目的是演示?GRPC?報錯時的鏈路信息展示。我們打開?jaeger?查看一下鏈路跟蹤信息:


可以看到本次請求涉及到兩個服務:?tracing-grpc-client?和?tracing-grpc-server?,即客戶端和服務端。整個請求鏈路涉及到17個?span?,客戶端5個?span?,服務端12個?span?,并且產生了2個錯誤。我們點擊查看詳情:


我們點擊查看一下最后接口調用錯誤的?span?情況:


看起來像個參數(shù)校驗錯誤,點擊查看?Events/Logs?中的請求參數(shù):


查看?Process?中的?Log?信息可以看到,是由于傳遞的參數(shù)為-1,不滿足校驗規(guī)則,因此在數(shù)據(jù)校驗的時候報錯返回了。

GRPC Client

由于?orm?、?redis?、?logging?組件在之前的章節(jié)中已經介紹過鏈路信息,因此我們這里主要介紹?GRPC Client&Server?的鏈路信息。

Attributes


Attribute/Tag
說明
net.peer.ip 請求的目標IP。
net.peer.port 請求的目標端口。
rpc.grpc.status_code GRPC的內部狀態(tài)碼,0表示成功,非0表示失敗。
rpc.service RPC的服務名稱,注意這里是RPC而不是GRPC,因為這里是通用定義,客戶端支持多種RPC通信協(xié)議,GRPC只是其中一種。
rpc.method RPC的方法名稱。
rpc.system RPC協(xié)議類型,如:grpcthrift等。

Events/Logs


Event/Log
說明
grpc.metadata.outgoing GRPC客戶端請求提交的Metadata信息,可能會比較大。
grpc.request.baggage GRPC客戶端請求提交的Baggage信息,用于服務間鏈路信息傳遞。
grpc.request.message

GRPC客戶端請求提交的Message數(shù)據(jù),可能會比較大,最大只記錄512KB,如果超過該大小則忽略。僅對Unary請求類型有效。

grpc.response.message GRPC客戶端請求接收返回的的Message信息,可能會比較大。僅對Unary請求類型有效。

GRPC Server

Attributes


?GRPC Server?端的?Attributes?含義同?GRPC Client?,在同一請求中,打印的數(shù)據(jù)基本一致。

Events


?GRPC Server?端的?Events?與?GRPC Client?不同的是,在同一請求中,服務端接收到的?metadata?為?grpc.metadata.incoming?,其他同?GRPC Client?。


以上內容是否對您有幫助:
在線筆記
App下載
App下載

掃描二維碼

下載編程獅App

公眾號
微信公眾號

編程獅公眾號