通過Nginx實現gRPC服務的負載均衡 | gRPC雙向資料流的互動控制系列(3)
前情提要
本系列的第一篇文章 通過一個例子介紹了go語言實現gRPC雙向資料流的互動控制,第二篇文章介紹瞭如何通過Websocket與gRPC互動。通過這兩篇文章,我們可以一窺gRPC雙向資料流的開發方式,但是在生產環境當中一臺伺服器(一個服務端程式)是不夠的,我們往往會面臨各種複雜情況:訪問量上來了一臺伺服器不夠怎麼辦?伺服器掛了怎麼辦?有實戰經驗的讀者肯定知道答案:上負載均衡(Load Balancing)啊!
gRPC服務如何做負載均衡?
gRPC官方部落格上有一篇文章《gRPC Load Balancing》(https://grpc.io/blog/loadbalancing),詳細介紹了幾種方案,並分析了幾種方案各自的優劣。並附了一張解決方案表:
在gRPC的Github上還有一篇文章叫《Load Balancing in gRPC》(https://github.com/grpc/grpc/blob/master/doc/load-balancing.md),如果英文看著費勁可以看一篇中文的《gRPC服務發現&負載均衡》(https://segmentfault.com/a/1190000008672912)。
測試Nginx對gRPC服務的支援
因為上面幾篇文章介紹的很詳細了,所以本文不再展開討論。我們可以注意到上表中被紅框圈起來的部分寫著“Nginx coming soon”,現在這個Nginx的解決方案已經來了——2018年3月17日,Nginx官方宣佈nginx 1.13.10支援gRPC (https://www.nginx.com/blog/nginx-1-13-10-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;
}
}
第三步:把go語言實現gRPC雙向資料流的互動控制 一文中的client.go 中的服務端地址改為nginx服務的地址(比如:127.0.0.1:80)
第四步: (1)執行server.go (2)執行nginx服務 (3)執行client.go
如果沒什麼意外,gRPC客戶端發出的訊息可以通過nginx後被gRPC服務端收到。
我們可以通過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 main
import (
"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 服務埠 string
func 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資料流服務,至此大功告成?!
總結
gRPC服務端的負載均衡有很多種方案,也各有優劣,但是用Nginx似乎是最簡單的一種。總之,我們還得根據具體的業務場景來選擇具體的實現方案。
gRPC雙向資料流系列
(之一): gRPC雙向資料流的互動控制
(之二): 通過Websocket與gRPC互動
相關文章
- 透過Nginx實現gRPC服務的負載均衡 | gRPC雙向資料流的互動NginxRPC負載
- 通過Websocket與gRPC互動 | gRPC雙向資料流的互動控制系列(2)WebRPC
- gRPC雙向資料流的互動控制(go語言實現)| gRPC雙向資料流的互動控制系列(1)RPCGo
- GRPC 負載均衡實現RPC負載
- gRPC的負載均衡RPC負載
- Nginx服務系列——負載均衡Nginx負載
- grpc雙向流RPC
- Kubernetes 中的 gRPC 負載均衡RPC負載
- golang grpc 負載均衡GolangRPC負載
- grpc套路(四)php通過grpc呼叫golang的grpc介面服務RPCPHPGolang
- gRPC負載均衡(自定義負載均衡策略)RPC負載
- gRPC負載均衡(客戶端負載均衡)RPC負載客戶端
- 基於 gRPC 的服務註冊與發現和負載均衡的原理與實戰RPC負載
- nginx實現兩臺服務負載均衡Nginx負載
- 在 kubernetes 環境中實現 gRPC 負載均衡RPC負載
- 通過Nginx、Consul、Upsync實現動態負載均衡和服務平滑釋出Nginx負載
- Docker Swarm :gRPC 基於 DNS 的負載均衡DockerSwarmRPCDNS負載
- 【Nginx】Windows平臺下配置Nginx服務實現負載均衡NginxWindows負載
- 基於gRPC的註冊發現與負載均衡的原理和實戰RPC負載
- nginx實現負載均衡Nginx負載
- 基於Docker + Consul + Nginx + Consul-template的服務負載均衡實現DockerNginx負載
- SAP 應用服務負載均衡的實現負載
- Nginx實現簡單的負載均衡Nginx負載
- Nginx 動靜分離與負載均衡的實現Nginx負載
- SpringCloud微服務系列- 服務間通訊之負載均衡SpringGCCloud微服務負載
- Linux下玩轉nginx系列(五)---nginx實現負載均衡LinuxNginx負載
- Nginx入門到實戰(3)負載均衡和快取服務Nginx負載快取
- 【Nginx】如何使用Nginx實現MySQL資料庫的負載均衡?看完我懂了!!NginxMySql資料庫負載
- gRPC 的增刪改查系列之啟動服務RPC
- Nginx實現請求的負載均衡 + keepalived實現Nginx的高可用Nginx負載
- Ambassador 0.52 新特性:會話親和性、負載均衡控制、gRPC-Web會話負載RPCWeb
- nginx配置web服務|反向代理|負載均衡NginxWeb負載
- nginx+tomcat實現負載均衡NginxTomcat負載
- Consul-template+nginx實現自動負載均衡Nginx負載
- 使用 grpcurl 通過命令列訪問 gRPC 服務RPC命令列
- Nginx 高階篇(三)負載均衡的實現Nginx負載
- 通過nginx進行udp報文負載均衡NginxUDP負載
- 通過原生js實現資料的雙向繫結JS