GoFrame HTTP示例-數(shù)據(jù)操作

2022-04-01 15:05 更新

HTTP+DB+Redis+Logging

我們?cè)賮?lái)看一個(gè)相對(duì)完整一點(diǎn)的例子,包含幾個(gè)常用核心組件的鏈路跟蹤示例,示例代碼地址:https://github.com/gogf/gf/tree/master/example/trace/http_with_db

客戶(hù)端

package main

import (
	"github.com/gogf/gf/contrib/trace/jaeger/v2"
	"github.com/gogf/gf/v2/database/gdb"
	"github.com/gogf/gf/v2/frame/g"
	"github.com/gogf/gf/v2/net/ghttp"
	"github.com/gogf/gf/v2/net/gtrace"
	"github.com/gogf/gf/v2/os/gctx"
)

const (
	ServiceName       = "http-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 (
		err    error
		client = g.Client()
	)
	// Add user info.
	var insertRes = struct {
		ghttp.DefaultHandlerResponse
		Data struct{ Id int64 } `json:"data"`
	}{}
	err = client.PostVar(ctx, "http://127.0.0.1:8199/user/insert", g.Map{
		"name": "john",
	}).Scan(&insertRes)
	if err != nil {
		panic(err)
	}
	g.Log().Info(ctx, "insert result:", insertRes)
	if insertRes.Data.Id == 0 {
		g.Log().Error(ctx, "retrieve empty id string")
		return
	}

	// Query user info.
	var queryRes = struct {
		ghttp.DefaultHandlerResponse
		Data struct{ User gdb.Record } `json:"data"`
	}{}
	err = client.GetVar(ctx, "http://127.0.0.1:8199/user/query", g.Map{
		"id": insertRes.Data.Id,
	}).Scan(&queryRes)
	if err != nil {
		panic(err)
	}
	g.Log().Info(ctx, "query result:", queryRes)

	// Delete user info.
	var deleteRes = struct {
		ghttp.DefaultHandlerResponse
	}{}
	err = client.PostVar(ctx, "http://127.0.0.1:8199/user/delete", g.Map{
		"id": insertRes.Data.Id,
	}).Scan(&deleteRes)
	if err != nil {
		panic(err)
	}
	g.Log().Info(ctx, "delete result:", deleteRes)
}

客戶(hù)端代碼簡(jiǎn)要說(shuō)明:

首先,客戶(hù)端也是需要通過(guò)?jaeger.Init?方法初始化?Jaeger?。

在本示例中,我們通過(guò)HTTP客戶(hù)端向服務(wù)端發(fā)起了3次請(qǐng)求:

  1. ?/user/insert? 用于新增一個(gè)用戶(hù)信息,成功后返回用戶(hù)的ID。
  2. ?/user/query? 用于查詢(xún)用戶(hù),使用前一個(gè)接口返回的用戶(hù)ID。
  3. ?/user/delete? 用于刪除用戶(hù),使用之前接口返回的用戶(hù)ID。

服務(wù)端

package main

import (
	"context"
	"fmt"
	"time"

	"github.com/gogf/gf/contrib/trace/jaeger/v2"
	"github.com/gogf/gf/v2/database/gdb"
	"github.com/gogf/gf/v2/frame/g"
	"github.com/gogf/gf/v2/net/ghttp"
	"github.com/gogf/gf/v2/os/gcache"
	"github.com/gogf/gf/v2/os/gctx"
)

type cTrace struct{}

const (
	ServiceName       = "http-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()))

	// Start HTTP server.
	s := g.Server()
	s.Use(ghttp.MiddlewareHandlerResponse)
	s.Group("/", func(group *ghttp.RouterGroup) {
		group.ALL("/user", new(cTrace))
	})
	s.SetPort(8199)
	s.Run()
}

type InsertReq struct {
	Name string `v:"required#Please input user name."`
}
type InsertRes struct {
	Id int64
}

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

type QueryReq struct {
	Id int `v:"min:1#User id is required for querying"`
}
type QueryRes struct {
	User gdb.Record
}

// 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 (c *cTrace) Query(ctx context.Context, req *QueryReq) (res *QueryRes, err error) {
	one, err := g.Model("user").Ctx(ctx).Cache(gdb.CacheOption{
		Duration: 5 * time.Second,
		Name:     c.userCacheKey(req.Id),
		Force:    false,
	}).WherePri(req.Id).One()
	if err != nil {
		return nil, err
	}
	res = &QueryRes{
		User: one,
	}
	return
}

type DeleteReq struct {
	Id int `v:"min:1#User id is required for deleting."`
}
type DeleteRes struct{}

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

func (c *cTrace) userCacheKey(id int) string {
	return fmt.Sprintf(`userInfo:%d`, id)
}

服務(wù)端代碼簡(jiǎn)要說(shuō)明:

  • 首先,客戶(hù)端也是需要通過(guò)?jaeger.Init?方法初始化?Jaeger?。
  • 在本示例中,我們使用到了數(shù)據(jù)庫(kù)和數(shù)據(jù)庫(kù)緩存功能,以便于同時(shí)演示?ORM?和?Redis?的鏈路跟蹤記錄。
  • 我們?cè)诔绦騿?dòng)時(shí)通過(guò)以下方法設(shè)置當(dāng)前數(shù)據(jù)庫(kù)緩存管理的適配器為?redis?。
g.DB().GetCache().SetAdapter(gcache.NewAdapterRedis(g.Redis()))
  • 在?ORM?的操作中,需要通過(guò)?Ctx?方法將上下文變量傳遞到組件中,?orm?組件會(huì)自動(dòng)識(shí)別當(dāng)前上下文中是否包含?Tracing?鏈路信息,如果包含則自動(dòng)啟用鏈路跟蹤特性。
  • 在?ORM?的操作中,這里使用?Cache?方法緩存查詢(xún)結(jié)果到?redis?中,并在刪除操作中也使用?Cache?方法清除?redis?中的緩存結(jié)果。

效果查看

啟動(dòng)服務(wù)端:


啟動(dòng)客戶(hù)端:


在?Jaeger?上查看鏈路信息:


可以看到,這次請(qǐng)求總共產(chǎn)生了14個(gè)?span?,其中客戶(hù)端有4個(gè)?span?,服務(wù)端有10個(gè)?span?,每一個(gè)?span?代表一個(gè)鏈路節(jié)點(diǎn)。不過(guò),我們注意到,這里產(chǎn)生了3個(gè)?errors?。我們點(diǎn)擊詳情查看什么原因呢。


我們看到好像所有的?redis?操作都報(bào)錯(cuò)了,隨便點(diǎn)擊一個(gè)?redis?的相關(guān)?span?,查看一下詳情呢:


原來(lái)是?redis?連接不上報(bào)錯(cuò)了,這樣的話(huà)所有的?orm?緩存功能都失效了,但是可以看到并沒(méi)有影響接口邏輯,只是所有的查詢(xún)都走了數(shù)據(jù)庫(kù)。這個(gè)報(bào)錯(cuò)是因?yàn)槲冶镜赝舜蜷_(kāi)?redis server?,我趕緊啟動(dòng)一下本地的?redis server?,再看看效果:


再把上面的客戶(hù)端運(yùn)行一下,查看?jaeger?:



現(xiàn)在就沒(méi)有報(bào)錯(cuò)了。

?HTTP Client&Server?、?Logging?組件在之前已經(jīng)介紹過(guò),因此這里我們主要關(guān)注?orm?和?redis?組件的鏈路跟蹤信息。

ORM鏈路信息

Attributes/Tags

我們隨便點(diǎn)開(kāi)一個(gè)?ORM?鏈路?Span?,看看?Attributes/Tags?信息:


可以看到這里的?span.kind?是?internal?,也就是之前介紹過(guò)的方法內(nèi)部?span?類(lèi)型。這里很多?Tags?在之前已經(jīng)介紹過(guò),因此這里主要介紹關(guān)于數(shù)據(jù)庫(kù)相關(guān)的?Tags?:

Attribute/Tag
說(shuō)明
db.type 數(shù)據(jù)庫(kù)連接類(lèi)型。如mysqlmssqlpgsql等等。
db.link 數(shù)據(jù)庫(kù)連接信息。其中密碼字段被自動(dòng)隱藏。
db.group 在配置文件中的數(shù)據(jù)庫(kù)分組名稱(chēng)。

Events/Process


Event/Log
說(shuō)明
db.execution.sql 執(zhí)行的具體SQL語(yǔ)句。由于ORM底層是預(yù)處理,該語(yǔ)句為方便查看自動(dòng)拼接而成,僅供參考。
db.execution.type 執(zhí)行的SQL語(yǔ)句類(lèi)型。常見(jiàn)為DB.ExecContextDB.QueryContext,分別代表寫(xiě)操作和讀操作。
db.execution.cost

當(dāng)前SQL語(yǔ)句執(zhí)行耗時(shí),單位為ms毫秒。

Redis鏈路信息

Attributes/Tags


Attribute/Tag
說(shuō)明
redis.host Redis連接地址。
redis.port Redis連接端口。
redis.db Redis操作db。

Events/Process


Event/Log
說(shuō)明
redis.execution.command Redis執(zhí)行指令。
redis.execution.arguments Redis執(zhí)行指令參數(shù)。
redis.execution.cost

Redis執(zhí)行指令執(zhí)行耗時(shí),單位為ms毫秒。


以上內(nèi)容是否對(duì)您有幫助:
在線(xiàn)筆記
App下載
App下載

掃描二維碼

下載編程獅App

公眾號(hào)
微信公眾號(hào)

編程獅公眾號(hào)