透過Nginx實現gRPC服務的負載均衡 | gRPC雙向資料流的互動
第一步:下載nginx最新的stable版(本文發稿時是1.14.0,如果會用docker的也可以下載其alpine版本)。
第二步:配置nginx的config檔案如下
server { # nginx的監聽埠按你的實際情況設定 listen 80 http2; access_log /var/log/nginx/access.log main; location / { # 把下面的 grpc://127.0.0.1:3000換成你自己的grpc伺服器地址 grpc_pass grpc://127.0.0.1:3000; } }
第三步:把 一文中的client.go 中的服務端地址改為nginx服務的地址(比如:127.0.0.1:80)
第四步:
(1)執行server.go
(2)執行nginx服務
(3)執行client.go
如果沒什麼意外,gRPC客戶端發出的訊息可以透過nginx後被gRPC服務端收到。
nginx日誌
我們可以透過nginx日誌觀察到相應的資訊。
一個小坑
上述連線雖然已經實現,但是如果我們的客戶端有連續一分鐘沒有輸入資訊,會出現接收資訊出錯的情況。
連線被nginx斷開
這種情形在沒有使用nginx的時候不會出現,由於以前使用nginx給websocket做反向代理時也出現過類似情況,故而推斷是nginx對超過一段時間的連線進行了斷開。
新增心跳
解決上述問題可以採取的一個方法是增加心跳(如果您發現了什麼別的好辦法可以解決這個問題,比如在nginx裡配置一些引數,請留言告訴我)
client.go
新增一段隔40秒傳送心跳的程式碼
package main import ( "bufio" "context" "flag" "io" "log" "os" "time" "google.golang.org/grpc" proto "chat" // 根據proto檔案自動生成的程式碼) var 伺服器地址 string func init() { flag.StringVar(&伺服器地址, "server", "127.0.0.1:80", "伺服器地址") } func main() { // 建立連線 conn, err := grpc.Dial(伺服器地址, grpc.WithInsecure()) if err != nil { log.Printf("連線失敗: [%v]n", err) return } defer conn.Close() client := proto.NewChatClient(conn) // 宣告 context ctx := context.Background() // 建立雙向資料流 stream, err := client.BidStream(ctx) if err != nil { log.Printf("建立資料流失敗: [%v]n", err) return } // 啟動一個 goroutine 接收命令列輸入的指令 go func() { log.Println("請輸入訊息...") 輸入 := bufio.NewReader(os.Stdin) for { // 獲取 命令列輸入的字串, 以回車 n 作為結束標誌 命令列輸入的字串, _ := 輸入.ReadString('n') // 向服務端傳送 指令 if err := stream.Send(&proto.Request{Input: 命令列輸入的字串}); err != nil { return } } }() // 新新增的部分: 啟動一個 goroutine 每隔40秒傳送心跳包 go func() { for { // 每隔 40 秒傳送一次 time.Sleep(40 * time.Second) log.Println("傳送心跳包") // 心跳字元用"n" if err := stream.Send(&proto.Request{Input: "n"}); err != nil { return } } }() for { // 接收從 服務端返回的資料流 響應, err := stream.Recv() if err == io.EOF { log.Println(" 收到服務端的結束訊號") break } if err != nil { // TODO: 處理接收錯誤 log.Println("接收資料出錯:", err) break } log.Printf("[客戶端收到]: %s", 響應.Output) } }
server.go
新增一段檢測心跳的程式碼
package mainimport ( "flag" "io" "log" "net" "strconv" "google.golang.org/grpc" proto "chat" // 根據proto檔案自動生成的程式碼)// Streamer 服務端type Streamer struct{}// BidStream 實現了 ChatServer 介面中定義的 BidStream 方法func (s *Streamer) BidStream(stream proto.Chat_BidStreamServer) error { ctx := stream.Context() for { select { case <-ctx.Done(): log.Println("收到客戶端透過context發出的終止訊號") return ctx.Err() default: // 接收從客戶端發來的訊息 輸入, err := stream.Recv() if err == io.EOF { log.Println("客戶端傳送的資料流結束") return nil } if err != nil { log.Println("接收資料出錯:", err) return err } // 如果接收正常,則根據接收到的 字串 執行相應的指令 switch 輸入.Input { case "結束對話n", "結束對話": log.Println("收到'結束對話'指令") if err := stream.Send(&proto.Response{Output: "收到結束指令"}); err != nil { return err } // 收到結束指令時,透過 return nil 終止雙向資料流 return nil case "返回資料流n", "返回資料流": log.Println("收到'返回資料流'指令") // 收到 收到'返回資料流'指令, 連續返回 10 條資料 for i := 0; i < 10; i++ { if err := stream.Send(&proto.Response{Output: "資料流 #" + strconv.Itoa(i)}); err != nil { return err } } // 攔截心跳字元"n" case "n": log.Println("收到心跳包") // 只接收心跳不回發資料也可以 default: // 預設情況下, 返回 '服務端返回: ' + 輸入資訊 log.Printf("[收到訊息]: %s", 輸入.Input) if err := stream.Send(&proto.Response{Output: "服務端返回: " + 輸入.Input}); err != nil { return err } } } } } var 服務埠 stringfunc init() { flag.StringVar(&服務埠, "port", "3000", "服務埠") } func main() { log.Println("啟動服務端...") server := grpc.NewServer() // 註冊 ChatServer proto.RegisterChatServer(server, &Streamer{}) address, err := net.Listen("tcp", ":"+服務埠) if err != nil { panic(err) } if err := server.Serve(address); err != nil { panic(err) } }
新增完成後再度測試,連線不會再被nginx打斷。
Nginx實現服務端負載均衡的配置檔案
心跳的坑趟過去之後,剩下的其實就簡單了,我們修改nginx的配置檔案:
upstream backend { # 把下面的服務端地址和埠改成你自己的 server 127.0.0.1:3000; server 127.0.0.1:3001; } server { listen 80 http2; access_log /var/log/nginx/access.log main; location / { grpc_pass grpc://backend; } }
按如下順序啟動
(1)執行多個 server.go ,按照nginx配置檔案輸入埠引數(如 server.go -port 3001)
(2)執行nginx服務
(3)執行多個client.go, (也可以執行websocket的那個程式,記得把心跳程式碼加上,多開幾個瀏覽器視窗)
我們可以觀察到開啟的多個server都在進行gRPC資料流服務,至此大功告成!
作者:阿狸不歌
連結:
來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/1727/viewspace-2819642/,如需轉載,請註明出處,否則將追究法律責任。
相關文章
- 通過Nginx實現gRPC服務的負載均衡 | gRPC雙向資料流的互動控制系列(3)NginxRPC負載
- gRPC雙向資料流的互動控制(go語言實現)| gRPC雙向資料流的互動控制系列(1)RPCGo
- 通過Websocket與gRPC互動 | gRPC雙向資料流的互動控制系列(2)WebRPC
- GRPC 負載均衡實現RPC負載
- gRPC的負載均衡RPC負載
- grpc雙向流RPC
- Kubernetes 中的 gRPC 負載均衡RPC負載
- golang grpc 負載均衡GolangRPC負載
- gRPC負載均衡(自定義負載均衡策略)RPC負載
- gRPC負載均衡(客戶端負載均衡)RPC負載客戶端
- 基於 gRPC 的服務註冊與發現和負載均衡的原理與實戰RPC負載
- nginx實現兩臺服務負載均衡Nginx負載
- 在 kubernetes 環境中實現 gRPC 負載均衡RPC負載
- Docker Swarm :gRPC 基於 DNS 的負載均衡DockerSwarmRPCDNS負載
- 【Nginx】Windows平臺下配置Nginx服務實現負載均衡NginxWindows負載
- Nginx服務系列——負載均衡Nginx負載
- 基於gRPC的註冊發現與負載均衡的原理和實戰RPC負載
- grpc套路(四)php通過grpc呼叫golang的grpc介面服務RPCPHPGolang
- nginx實現負載均衡Nginx負載
- 基於Docker + Consul + Nginx + Consul-template的服務負載均衡實現DockerNginx負載
- SAP 應用服務負載均衡的實現負載
- Nginx實現簡單的負載均衡Nginx負載
- Nginx 動靜分離與負載均衡的實現Nginx負載
- 通過Nginx、Consul、Upsync實現動態負載均衡和服務平滑釋出Nginx負載
- 【Nginx】如何使用Nginx實現MySQL資料庫的負載均衡?看完我懂了!!NginxMySql資料庫負載
- Nginx實現請求的負載均衡 + keepalived實現Nginx的高可用Nginx負載
- nginx配置web服務|反向代理|負載均衡NginxWeb負載
- nginx+tomcat實現負載均衡NginxTomcat負載
- Consul-template+nginx實現自動負載均衡Nginx負載
- Nginx 高階篇(三)負載均衡的實現Nginx負載
- 透過自定義feignclient 的LoadBalancerFeignClient實現靈活的負載均衡策略client負載
- java版gRPC實戰之三:服務端流JavaRPC服務端
- 為什麼對gRPC做負載均衡會很棘手?RPC負載
- C# 實現 gRPC 服務和呼叫C#RPC
- Nginx如何實現四層負載均衡?Nginx負載
- Keepalived實現Nginx負載均衡高可用Nginx負載
- Docker Compose+nginx實現負載均衡DockerNginx負載
- LVS和Nginx實現負載均衡功能的比較Nginx負載