简介
代码
proto
syntax = "proto3";
import "google/protobuf/empty.proto";
option go_package = ".;proto";
service Test{
rpc Test(google.protobuf.Empty) returns(TestResponse);
}
message TestResponse{
string msg = 1;
}
输入代码生成对应的go代码
protoc -I . proto.proto --go_out=plugins=grpc:.
server
package service
import (
"context"
"github.com/prometheus/common/log"
"google.golang.org/protobuf/types/known/emptypb"
"load_balance/server/proto"
)
type Service struct{}
func (s Service) Test(ctx context.Context, empty *emptypb.Empty) (*proto.TestResponse, error) {
log.Info("收到一个请求")
return &proto.TestResponse{Msg: "test"}, nil
}
package main
import (
"fmt"
"github.com/hashicorp/consul/api"
"github.com/satori/go.uuid"
"google.golang.org/grpc"
"google.golang.org/grpc/health"
"google.golang.org/grpc/health/grpc_health_v1"
"load_balance/server/proto"
"load_balance/server/service"
"log"
"net"
"strings"
)
func main() {
server := grpc.NewServer()
proto.RegisterTestServer(server, &service.Service{})
port := GenFreePort()
listen, err := net.Listen("tcp", fmt.Sprintf(":%d", port))
if err != nil {
log.Fatalf("监听端口:%d失败: %s", port, err.Error())
}
config := api.DefaultConfig()
config.Address = "192.168.193.128:8500"
consulClient, err := api.NewClient(config)
if err != nil {
log.Fatalf("连接consul失败: %s", err.Error())
}
// grpc注册服务的健康检查
grpc_health_v1.RegisterHealthServer(server, health.NewServer())
// 每个服务的ID必须不同;这里使用uuid;
// Name相同ID不同consul会认为是两个实例;
// Name和ID都相同consul会认为是一个实例会出现覆盖
registration := &api.AgentServiceRegistration{
Address: "192.168.1.103",
Port: port,
ID: fmt.Sprintf("%s", strings.ReplaceAll(uuid.NewV4().String(), "-", "")),
Name: "test-server",
Tags: []string{"manual"},
Check: &api.AgentServiceCheck{
Interval: "5s", // 指定运行此检查的频率
Timeout: "5s", // 超时时间
GRPC: fmt.Sprintf("%s:%d", "192.168.1.103", port), // 健康检查HTTP请求
DeregisterCriticalServiceAfter: "30s", // 触发注销的时间
},
}
err = consulClient.Agent().ServiceRegister(registration)
if err != nil {
log.Fatalf("注册服务失败: %s", err.Error())
}
fmt.Printf("服务启动成功;PORT:%d\n", port)
err = server.Serve(listen)
}
// GenFreePort 获取一个空闲的端口;端口避免写死,因为要启动多个实例,测试负载均衡
func GenFreePort() int {
addr, err := net.ResolveTCPAddr("tcp", "localhost:0")
if err != nil {
panic(err)
}
listen, err := net.ListenTCP("tcp", addr)
if err != nil {
panic(err)
}
defer listen.Close()
return listen.Addr().(*net.TCPAddr).Port
}
client
package main
import (
"context"
"fmt"
"google.golang.org/grpc/credentials/insecure"
"google.golang.org/protobuf/types/known/emptypb"
"load_balance/server/proto"
"log"
// 这里一定要import;很重要
_ "github.com/mbobakov/grpc-consul-resolver"
"google.golang.org/grpc"
)
func main() {
conn, err := grpc.Dial(
// consul://192.168.193.128:8500 consul地址
// test-serve 拉取的服务名
// wait=14s 等待时间
// tag=manual 筛选条件
// 底层就是利用grpc-consul-resolver将参数解析成HTTP请求获取对应的服务
"consul://192.168.193.128:8500/test-server?wait=14s&tag=manual",
grpc.WithTransportCredentials(insecure.NewCredentials()),
grpc.WithDefaultServiceConfig(`{"loadBalancingPolicy": "round_robin"}`),
)
if err != nil {
log.Fatal(err)
}
defer conn.Close()
client := proto.NewTestClient(conn)
for i := 0; i < 10; i++ {
resp, err := client.Test(context.Background(), &emptypb.Empty{})
if err != nil {
panic(err)
}
fmt.Println(resp.Msg)
}
}
_ "github.com/mbobakov/grpc-consul-resolver"
这段代码一定要import进去;只有import进去grpc-consul-resolver中consul包下的init函数才会执行,init是为了把自己的解析器注册进去;如果不import进去就获取不到对应的服务;
grpc.WithDefaultServiceConfig(`{"loadBalancingPolicy": "round_robin"}`)
这段代码添加的是负载均衡的策略,这里使用到的是轮询(service_config.proto);目前也只支持轮询.
测试
- 启动两个server
-
观察consul
-
启动client测试负载均衡
从测试结果中,12次调用两个服务各6次,测试负载均衡完毕.