這是一個建立於 的文章,其中的資訊可能已經有所發展或是發生改變。
grpc-lb採用用戶端進程內負載平衡方式,支援隨機、輪詢、一致性雜湊三種負載平衡策略,並支援服務端權重。可採用etcd或consul作為註冊中心。
項目地址:
https://github.com/liyue201/grpc-lb
基本架構,服務提供者起來後向註冊中心註冊自己的資訊,ip、連接埠、權重等,並保持心跳。用戶端監聽註冊中心,擷取伺服器列表,一旦伺服器發生變化,用戶端馬上更新本地的伺服器列表。用戶端每個請求都通過負載平衡策略選擇一個合適的伺服器去訪問。
隨機負載平衡用戶端例子:
package mainimport ( 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")func main() { etcdConfg := etcd.Config{ Endpoints: []string{"http://120.24.44.201:4001"}, } r := registry.NewResolver("/grpc-lb", "test", etcdConfg) b := grpclb.NewBalancer(r, grpclb.NewRandomSelector()) 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) resp, err := client.Say(context.Background(), &proto.SayReq{Content: "random"}) if err != nil { log.Println(err) return } log.Printf(resp.Content)}
輪詢負載平衡,只需把NewRandomSelector改成NewRoundRobinSelector即可。
package mainimport ( 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")func main() { etcdConfg := etcd.Config{ Endpoints: []string{"http://120.24.44.201:4001"}, } r := registry.NewResolver("/grpc-lb", "test", etcdConfg) b := grpclb.NewBalancer(r, grpclb.NewRoundRobinSelector()) 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) resp, err := client.Say(context.Background(), &proto.SayReq{Content: "round robin"}) if err != nil { log.Println(err) return } log.Printf(resp.Content)}
一致性雜湊負載平衡,需要給每個請求傳一個雜湊的參數,這個根據應用情境而定,就是下面這個例子中的hashData。
package mainimport ( "fmt" 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" "time")func main() { etcdConfg := etcd.Config{ Endpoints: []string{"http://120.24.44.201:4001"}, } r := registry.NewResolver("/grpc-lb", "test", etcdConfg) b := grpclb.NewBalancer(r, grpclb.NewKetamaSelector(grpclb.DefaultKetamaKey)) c, err := grpc.Dial("", grpc.WithInsecure(), grpc.WithBalancer(b), grpc.WithTimeout(time.Second)) if err != nil { log.Printf("grpc dial: %s", err) return } client := proto.NewTestClient(c) for i := 0; i < 10; i++ { ctx := context.Background() hashData := fmt.Sprintf("aaaa %d", i) resp, err := client.Say(context.WithValue(ctx, grpclb.DefaultKetamaKey, hashData), &proto.SayReq{Content: "ketama"}) if err != nil { log.Println(err) time.Sleep(time.Second) continue } log.Printf(resp.Content) time.Sleep(time.Second) }}
服務端的代碼如下, 使用以下命令運行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
package mainimport ( "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()}func (s *RpcServer) Say(ctx context.Context, req *proto.SayReq) (*proto.SayResp, error) { text := "Hello " + req.Content + ", I am " + *nodeID log.Println(text) return &proto.SayResp{Content: text}, nil}func StartService() { etcdConfg := etcd.Config{ Endpoints: []string{"http://120.24.44.201:4001"}, } 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"}, //這裡配置權重,不配置預設是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()}//go run main.go -node node1 -port 28544//go run main.go -node node2 -port 18562//go run main.go -node node3 -port 27772func main() { flag.Parse() StartService()}
390 次點擊