gRPC負載均衡-Golang

HelloGod發表於2018-01-23

一. 負載均衡三種解決方案

構建高可用、高效能的通訊服務,通常採用服務註冊與發現、負載均衡和容錯處理等機制實現。根據負載均衡實現所在的位置不同,通常可分為以下三種解決方案:

  • 1、集中式LB(Proxy Model)
  • 2、程式內LB(Balancing-aware Client)
  • 3、獨立 LB 程式(External Load Balancing Service)

出處在這裡,寫的很詳細: 連結地址

二. gRPC的準備

gRPC 預設使用 protocol buffers,這是 Google 開源的一套成熟的結構資料序列化機制(當然也可以使用其他資料格式如 JSON)。其客戶端提供Objective-C、Java介面,伺服器側則有Java、Golang、C++等介面,從而為移動端(iOS/Androi)到伺服器端通訊提供了一種解決方案。連結地址

1.安裝brew,這個自己百度谷歌.

2.終端 :

brew install autoconf automake libtool
複製程式碼

3.安裝golang protobuf

go get -u github.com/golang/protobuf/proto // golang protobuf 庫
go get -u github.com/golang/protobuf/protoc-gen-go //protoc --go_out 工具
複製程式碼

三. 簡單的protobuf

../proto/hello.proto

syntax = "proto3";

package proto;

message SayReq {
    string content = 1;
}

message SayResp {
    string content = 1;
}

service Test{
    rpc Say(SayReq) returns (SayResp) {}
}
複製程式碼

在proto下 , 輸入終端命令 protoc --go_out=plugins=grpc:. hello.proto

生成 hello.pb.go 檔案,通過protoc就能生成不同語言需要的.pb.go檔案


這裡是介紹四種protoc,如果不用到流,可以不看:

1:簡單 RPC

2:伺服器端流式 RPC

3:客戶端流式 RPC

4:雙向流式 RPC

4種方式與以下定義四種服務對應: test.proto檔案: service Test{

rpc LZX1(SayReq) returns (SayResp) {}
rpc LZX2(SayReq) returns (stream SayResp) {}
rpc LZX3(stream SayReq) returns (SayResp) {}
rpc LZX4(stream SayReq) returns (stream SayResp) {}
複製程式碼

}

生成的test.pb.go檔案:

type TestClient interface {

LZX1(ctx context.Context, in *SayReq, opts ...grpc.CallOption) (*SayResp, error)
LZX2(ctx context.Context, in *SayReq, opts ...grpc.CallOption) (Test_LZX2Client, error)
LZX3(ctx context.Context, opts ...grpc.CallOption) (Test_LZX3Client, error)
LZX4(ctx context.Context, opts ...grpc.CallOption) (Test_LZX4Client, error)
複製程式碼

}

所以無流的函式,也就是第一種簡單的RPC,都只是一一對應.只要有流,返回的型別都是 服務結構名_函式名Client;只要客戶端是流式,傳參將不包含其他引數.


四.6種負載均衡演算法

1、輪詢法

2、隨機法

3、源地址雜湊法

4、加權輪詢法

5、加權隨機法

6、最小連線數法

演算法的描述看這裡:連結地址

五. gRPC的例子

例子出處連結:連結地址

架構:

gRPC負載均衡-Golang

以下用的是隨機負載均衡:

客戶端 etcd/client/random/main.go

package main

import (
	etcd "github.com/coreos/etcd/client"
	grpclb "github.com/liyue201/grpc-lb"
	"github.com/liyue201/grpc-lb/examples/proto"
	registry "github.com/liyue201/grpc-lb/registry/etcd"
	"golang.org/x/net/context"
	"google.golang.org/grpc"
	"log"
	"strconv"
	"time"
)

func main() {
	etcdConfg := etcd.Config{
		Endpoints: []string{"http://120.24.44.201:2379"},
	}
	r := registry.NewResolver("/grpc-lb", "test", etcdConfg) // 載入registry
	b := grpclb.NewBalancer(r, grpclb.NewRandomSelector())   //載入grpclbs
	c, err := grpc.Dial("", grpc.WithInsecure(), grpc.WithBalancer(b))
	if err != nil {
		log.Printf("grpc dial: %s", err)
		return
	}
	defer c.Close()

	client := proto.NewTestClient(c)
	var num int
	for i := 0; i < 1000; i++ {
		resp, err := client.Say(context.Background(), &proto.SayReq{Content: "random"})
		if err != nil {
			log.Println(err)
			time.Sleep(time.Second)
			continue
		}
		time.Sleep(time.Second)
		num++
		log.Printf(resp.Content + ",  clientOfnum: " + strconv.Itoa(num))
	}
}
複製程式碼

服務端 etcd/server/main.go

package main

import (
	"flag"
	"fmt"
	etcd "github.com/coreos/etcd/client"
	"github.com/liyue201/grpc-lb/examples/proto"
	registry "github.com/liyue201/grpc-lb/registry/etcd"
	"golang.org/x/net/context"
	"google.golang.org/grpc"
	"log"
	"net"
	"sync"
	"time"
)

var nodeID = flag.String("node", "node1", "node ID")
var port = flag.Int("port", 8080, "listening port")

type RpcServer struct {
	addr string
	s    *grpc.Server
}

func NewRpcServer(addr string) *RpcServer {
	s := grpc.NewServer()
	rs := &RpcServer{
		addr: addr,
		s:    s,
	}
	return rs
}

func (s *RpcServer) Run() {
	listener, err := net.Listen("tcp", s.addr)
	if err != nil {
		log.Printf("failed to listen: %v", err)
		return
	}
	log.Printf("rpc listening on:%s", s.addr)

	proto.RegisterTestServer(s.s, s)
	s.s.Serve(listener)
}

func (s *RpcServer) Stop() {
	s.s.GracefulStop()
}

var num int

func (s *RpcServer) Say(ctx context.Context, req *proto.SayReq) (*proto.SayResp, error) {
	num++
	text := "Hello " + req.Content + ", I am " + *nodeID + ", serverOfnum: " + strconv.Itoa(num)
	log.Println(text)

	return &proto.SayResp{Content: text}, nil
}

func StartService() {
	etcdConfg := etcd.Config{
		Endpoints: []string{"http://120.24.44.201:2379"},
	}

	registry, err := registry.NewRegistry(
		registry.Option{
			EtcdConfig:  etcdConfg,
			RegistryDir: "/grpc-lb",
			ServiceName: "test",
			NodeID:      *nodeID,
			NData: registry.NodeData{
				Addr: fmt.Sprintf("127.0.0.1:%d", *port),
				//Metadata: map[string]string{"weight": "1"},
			},
			Ttl: 10 * time.Second,
		})
	if err != nil {
		log.Panic(err)
		return
	}
	server := NewRpcServer(fmt.Sprintf("0.0.0.0:%d", *port))
	wg := sync.WaitGroup{}

	wg.Add(1)
	go func() {
		server.Run()
		wg.Done()
	}()

	wg.Add(1)
	go func() {
		registry.Register()
		wg.Done()
	}()

	//stop the server after one minute
	//go func() {
	//	time.Sleep(time.Minute)
	//	server.Stop()
	//	registry.Deregister()
	//}()

	wg.Wait()
}

func main() {
	flag.Parse()
	StartService()
}
複製程式碼

服務端的程式碼如下, 使用以下命令執行3個服務程式,再啟動客戶端。

go run main.go -node node1 -port 28544

go run main.go -node node2 -port 18562

go run main.go -node node3 -port 27772

六.最終效果

(客戶端不停的隨機訪問三個服務端)

客戶端:

gRPC負載均衡-Golang


服務端node1:

gRPC負載均衡-Golang


服務端node2:

gRPC負載均衡-Golang


服務端node3:

gRPC負載均衡-Golang


斷開服務端node1,node3,client照樣能跑,並只連線到node2服務端:

客戶端:

gRPC負載均衡-Golang

伺服器node2:

gRPC負載均衡-Golang


再次啟動node1,node3服務端,client自動連線上,並繼續隨機訪問3臺服務端:

客戶端:

gRPC負載均衡-Golang

服務端node1:

gRPC負載均衡-Golang

服務端node3:

gRPC負載均衡-Golang


最後,關閉所有服務端,客戶端處於阻塞狀態.

相關文章