Go 微服務框架 go-micro 使用客戶端 RPC 調用服務端方法返回 408 怎麼解決?

大家好,我是 frank。

01 介紹

本文我們使用 go-micro 構建微服務的服務端和客戶端,並使用 gin 集成客戶端構建 HTTP Api,在代碼中模擬客戶端 RPC 調用服務端方法返回 408 的問題,以及怎麼解決?

客戶端輸出日誌:

{"id":"go.micro.client","code":408,"detail":"context deadline exceeded","status":"Request Timeout"}

02 使用 go-micro 構建服務端和客戶端

關於 proto 和 consul 的相關內容,不是本文的重點,將不再贅述,感興趣的讀者朋友們可以查閱公衆號的歷史文章。

創建服務端服務的代碼:

func main() {
 registry := consul.NewRegistry(func(options *registry.Options) {
  options.Addrs = []string{"127.0.0.1:8500"}
  options.Timeout = 5 * time.Second
 })
 // create a new service
 service := grpc.NewService(
  micro.Name("go.micro.srv.user"),
  micro.Registry(registry),
 )
 // handler
 user.RegisterUserHandler(service.Server(), new(user_handler.User))
 // initialise flags
 service.Init()
 // start the service
 service.Run()
}

服務端方法:

type User struct{}

func (u *User) Login(ctx context.Context, req *user.LoginRequest, rsp *user.LoginResponse) error {
 time.Sleep(10 * time.Second) // 模擬超時響應
 rsp.Username = "Welcome " + req.Email
 return nil
}

客戶端代碼:

func main() {
 r := NewRouter()
 server := &http.Server{
  Addr:           ":8080",
  Handler:        r,
  ReadTimeout:    time.Second * 20,
  WriteTimeout:   time.Second * 20,
  MaxHeaderBytes: 1 << 20,
 }
 if err := server.ListenAndServe(); err != nil {
  log.Fatal(err)
 }
}

// 省略 User.Login 相關代碼

func NewRouter() *gin.Engine {
 r := gin.New()
 userHandler := new(User)
 r.GET("/login", userHandler.Login)
 return r
}

func NewClient() user.UserService {
 registry := consul.NewRegistry(func(options *registry.Options) {
  options.Addrs = []string{"127.0.0.1:8500"}
  options.Timeout = 5 * time.Second
 })
 client := grpc.NewClient(
  client.DialTimeout(15*time.Second),
  client.RequestTimeout(15*time.Second),
  client.Registry(registry),
 )
 userClient := user.NewUserService("go.micro.srv.user", client)
 return userClient
}

分別啓動服務端和客戶端, 然後使用 curl 請求 Api:

➜  /Users/frank curl http://127.0.0.1:8080/login
{"data":"Welcome gopher@88.com"}%

閱讀上面的運行結果,可以發現我們構建的服務端和客戶端運行正常。

03 模擬返回 408 的問題

我們在服務端的方法中使用 time.Sleep(10 * time.Second) 模擬延長響應時長,我們修改客戶端代碼,將客戶端超時時間也設置爲 10s

修改客戶端代碼:

client.DialTimeout(10*time.Second),
client.RequestTimeout(10*time.Second),

閱讀上面這段代碼,我們將客戶端超時時間改爲 10s,然後重啓客戶端應用,使用 curl 請求 Api:

/Users/frank curl http://127.0.0.1:8080/login

運行 curl,沒有返回響應結果,我們查看客戶端的日誌發現:

{"id":"go.micro.client","code":408,"detail":"context deadline exceeded","status":"Request Timeout"}

原因是服務端方法中,我們在代碼中使用 time.Sleep(10 * time.Second) 模擬響應需要 10s,而在客戶端中,我們定義的客戶端超時時間由原來的 15s 改爲 10s,所以導致返回 408 的問題。

需要注意的是,go-micro 中 client 的默認超時時間是 5s

04 解決方法

我們在瞭解完問題出現的原因之後,聰明的讀者朋友們可能已經有了解決問題的方法。

解決該問題,有兩種解決方法,第一種是修改 client 的超時時間,延長超時時間至足夠接收到響應結果的時長,但是需要注意的是,http server 的讀寫時間也要滿足可以接收到響應結果的時長,本文我們設置爲 20s,如下所示:

server := &http.Server{
  Addr:           ":8080",
  Handler:        r,
  ReadTimeout:    time.Second * 20,
  WriteTimeout:   time.Second * 20,
  MaxHeaderBytes: 1 << 20,
 }

而且還需要注意其上下游服務之間的超時時間,避免出現雪崩等問題。

第二種是優化服務端方法的響應時間,將其響應時間縮短至客戶端的超時時間以內。具體如何優化,要根據實際情況決定,比如是否因爲數據庫讀寫耗時太長,代碼的時間複雜度太高等。

05 總結

本文我們介紹怎麼解決客戶端 RPC 調用服務端的方法,返回錯誤碼 408 的問題,我們使用 go-micro 和 gin 構建了示例代碼,並通過修改示例代碼,分析出現返回錯誤碼 408 問題的原因。

讀者朋友們在遇到該問題時,建議優先採用第二種方法解決,如果使用第一種方法,需要特別注意避免分佈式系統的雪崩問題。

參考資料:

  1. https://github.com/go-micro/go-micro

  2. https://gin-gonic.com/docs/

本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源https://mp.weixin.qq.com/s/DyqAKm8gsB-8eVzWO2LGFQ