一. 負載均衡三種解決方案
構建高可用、高效能的通訊服務,通常採用服務註冊與發現、負載均衡和容錯處理等機制實現。根據負載均衡實現所在的位置不同,通常可分為以下三種解決方案:
- 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的例子
例子出處連結:連結地址
架構:
以下用的是隨機負載均衡:
客戶端 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
六.最終效果
(客戶端不停的隨機訪問三個服務端)
客戶端:
服務端node1:
服務端node2:
服務端node3:
斷開服務端node1,node3,client照樣能跑,並只連線到node2服務端:
客戶端:
伺服器node2:
再次啟動node1,node3服務端,client自動連線上,並繼續隨機訪問3臺服務端:
客戶端:
服務端node1:
服務端node3:
最後,關閉所有服務端,客戶端處於阻塞狀態.