首頁 > 軟體

Go gRPC進階教學服務超時設定

2022-06-16 10:00:45

前言

gRPC預設的請求的超時時間是很長的,當你沒有設定請求超時時間時,所有在執行的請求都佔用大量資源且可能執行很長的時間,導致服務資源損耗過高,使得後來的請求響應過慢,甚至會引起整個程序崩潰。

為了避免這種情況,我們的服務應該設定超時時間。

前面的入門教學

Go gRPC環境安裝教學範例詳解

Go gRPC教學實現Simple RPC

Go gRPC伺服器端流式RPC教學範例

Go gRPC服務使用者端流式RPC教學

Go gRPC服務雙向流式RPC教學

提到當用戶端發起請求時候,需要傳入上下文context.Context,用於結束超時或取消的請求。

本篇以簡單RPC為例,介紹如何設定gRPC請求的超時時間。

使用者端請求設定超時時間

修改呼叫伺服器端方法

1.把超時時間設定為當前時間+3秒

	clientDeadline := time.Now().Add(time.Duration(3 * time.Second))
	ctx, cancel := context.WithDeadline(ctx, clientDeadline)
	defer cancel()

2.響應錯誤檢測中新增超時檢測

       // 傳入超時時間為3秒的ctx
	res, err := grpcClient.Route(ctx, &req)
	if err != nil {
		//獲取錯誤狀態
		statu, ok := status.FromError(err)
		if ok {
			//判斷是否為呼叫超時
			if statu.Code() == codes.DeadlineExceeded {
				log.Fatalln("Route timeout!")
			}
		}
		log.Fatalf("Call Route err: %v", err)
	}
	// 列印返回值
	log.Println(res.Value)

完整的client.go程式碼

package main
import (
	"context"
	"log"
	"time"
	"google.golang.org/grpc"
	"google.golang.org/grpc/codes"
	"google.golang.org/grpc/status"
	pb "go-grpc-example/6-grpc_deadlines/proto"
)
// Address 連線地址
const Address string = ":8000"
var grpcClient pb.SimpleClient
func main() {
	// 連線伺服器
	conn, err := grpc.Dial(Address, grpc.WithInsecure())
	if err != nil {
		log.Fatalf("net.Connect err: %v", err)
	}
	defer conn.Close()
	ctx := context.Background()
	// 建立gRPC連線
	grpcClient = pb.NewSimpleClient(conn)
	route(ctx, 2)
}
// route 呼叫伺服器端Route方法
func route(ctx context.Context, deadlines time.Duration) {
	//設定3秒超時時間
	clientDeadline := time.Now().Add(time.Duration(deadlines * time.Second))
	ctx, cancel := context.WithDeadline(ctx, clientDeadline)
	defer cancel()
	// 建立傳送結構體
	req := pb.SimpleRequest{
		Data: "grpc",
	}
	// 呼叫我們的服務(Route方法)
	// 傳入超時時間為3秒的ctx
	res, err := grpcClient.Route(ctx, &req)
	if err != nil {
		//獲取錯誤狀態
		statu, ok := status.FromError(err)
		if ok {
			//判斷是否為呼叫超時
			if statu.Code() == codes.DeadlineExceeded {
				log.Fatalln("Route timeout!")
			}
		}
		log.Fatalf("Call Route err: %v", err)
	}
	// 列印返回值
	log.Println(res.Value)
}

伺服器端判斷請求是否超時

當請求超時後,伺服器端應該停止正在進行的操作,避免資源浪費。

// Route 實現Route方法
func (s *SimpleService) Route(ctx context.Context, req *pb.SimpleRequest) (*pb.SimpleResponse, error) {
	data := make(chan *pb.SimpleResponse, 1)
	go handle(ctx, req, data)
	select {
	case res := <-data:
		return res, nil
	case <-ctx.Done():
		return nil, status.Errorf(codes.Canceled, "Client cancelled, abandoning.")
	}
}
func handle(ctx context.Context, req *pb.SimpleRequest, data chan<- *pb.SimpleResponse) {
	select {
	case <-ctx.Done():
		log.Println(ctx.Err())
		runtime.Goexit() //超時後退出該Go協程
	case <-time.After(4 * time.Second): // 模擬耗時操作
		res := pb.SimpleResponse{
			Code:  200,
			Value: "hello " + req.Data,
		}
		// //修改資料庫前進行超時判斷
		// if ctx.Err() == context.Canceled{
		// 	...
		// 	//如果已經超時,則退出
		// }
		data <- &res
	}
}

一般地,在寫庫前進行超時檢測,發現超時就停止工作。

完整server.go程式碼

package main
import (
	"context"
	"log"
	"net"
	"runtime"
	"time"
	"google.golang.org/grpc"
	"google.golang.org/grpc/codes"
	"google.golang.org/grpc/status"
	pb "go-grpc-example/6-grpc_deadlines/proto"
)
// SimpleService 定義我們的服務
type SimpleService struct{}
const (
	// Address 監聽地址
	Address string = ":8000"
	// Network 網路通訊協定
	Network string = "tcp"
)
func main() {
	// 監聽本地埠
	listener, err := net.Listen(Network, Address)
	if err != nil {
		log.Fatalf("net.Listen err: %v", err)
	}
	log.Println(Address + " net.Listing...")
	// 新建gRPC伺服器範例
	grpcServer := grpc.NewServer()
	// 在gRPC伺服器註冊我們的服務
	pb.RegisterSimpleServer(grpcServer, &SimpleService{})
	//用伺服器 Serve() 方法以及我們的埠資訊區實現阻塞等待,直到程序被殺死或者 Stop() 被呼叫
	err = grpcServer.Serve(listener)
	if err != nil {
		log.Fatalf("grpcServer.Serve err: %v", err)
	}
}
// Route 實現Route方法
func (s *SimpleService) Route(ctx context.Context, req *pb.SimpleRequest) (*pb.SimpleResponse, error) {
	data := make(chan *pb.SimpleResponse, 1)
	go handle(ctx, req, data)
	select {
	case res := <-data:
		return res, nil
	case <-ctx.Done():
		return nil, status.Errorf(codes.Canceled, "Client cancelled, abandoning.")
	}
}
func handle(ctx context.Context, req *pb.SimpleRequest, data chan<- *pb.SimpleResponse) {
	select {
	case <-ctx.Done():
		log.Println(ctx.Err())
		runtime.Goexit() //超時後退出該Go協程
	case <-time.After(4 * time.Second): // 模擬耗時操作
		res := pb.SimpleResponse{
			Code:  200,
			Value: "hello " + req.Data,
		}
		// //修改資料庫前進行超時判斷
		// if ctx.Err() == context.Canceled{
		// 	...
		// 	//如果已經超時,則退出
		// }
		data <- &res
	}
}

執行結果

伺服器端:

:8000 net.Listing...
goroutine still running

使用者端:

Route timeout! 

總結

超時時間的長短需要根據自身服務而定,例如返回一個hello grpc,可能只需要幾十毫秒,然而處理大量資料的同步操作則可能要很長時間。需要考慮多方面因素來決定這個超時時間,例如系統間端到端的延時,哪些RPC是序列的,哪些是可以並行的等等。

教學原始碼地址:https://github.com/Bingjian-Zhu/go-grpc-example

參考:https://grpc.io/blog/deadlines/

以上就是Go gRPC進階服務超時設定的詳細內容,更多關於Go gRPC超時設定的資料請關注it145.com其它相關文章!


IT145.com E-mail:sddin#qq.com