目录
1. grpc基于客户端的负载均衡
客户端首先取得服务端的节点 endpoint 列表,基于一定的负载均衡策略选择到特定的 endpoint,直连发起请求.
grpc负载均衡策略:pick_first选第一个(默认);round_robin轮询
常用的负载均衡策略:随机、轮询、加权随机、加权轮询、源地址哈希法、最小连接数法
2. 服务端
过程:
- etcd lease创建一个租约
- etcd endpoint manager创建一个节点管理器
- endpoint manager注册一个节点,写入etcd kv,并绑定租约id
- 持续续租keepalive
写一下——
server的main.go入口:
func main() {
// 初始化server并注册service
srv := grpc.NewServer()
api.RegisterHelloServiceServer(srv, &HelloService{})
printDesc(api.HelloService_ServiceDesc)
// 创建端口监听器
port := ":9210"
addr := "127.0.0.1" + port
listener, err := net.Listen("tcp", port)
if err != nil {
panic(err)
}
// 将服务注册到etcd
if err := etcd.RegisterServer2Etcd(context.Background(), addr); err != nil {
panic(err)
}
// 启动服务
fmt.Println("Start at", port)
if err := srv.Serve(listener); err != nil {
panic(err)
}
}
etcd.go:
const (
serviceName = "mytest/helloService"
etcdUrl = "http://127.0.0.1:2380"
etcdUser = "root"
etcdPwd = "123456"
)
// 服务注册
func RegisterServer2Etcd(ctx context.Context, serverAddr string) error {
// 初始化etcd client
cli, err := NewEtcdClient()
if err != nil {
slog.Error("new etcd client err", "err", err)
return err
}
// 创建服务端节点管理模块
etcdMgr, err := endpoints.NewManager(cli, serviceName)
if err != nil {
slog.Error("new etcd manager err", "err", err)
return err
}
// 创建一个10s的租约
lease := clientv3.NewLease(cli)
resp, err := lease.Grant(ctx, 10)
if err != nil {
slog.Error("create etcd lease err", "err", err)
return err
}
leaseId := resp.ID
// 将节点注册到etcd中,并携带租约id
err = etcdMgr.AddEndpoint(
ctx,
NamingKey(serverAddr),
endpoints.Endpoint{
Addr: serverAddr,
Metadata: map[string]interface{}{"k": "v"},
},
clientv3.WithLease(leaseId),
)
if err != nil {
slog.Error("add endpoint to etcd err", "err", err)
return err
}
// 每隔5s续约一次
go func() {
for {
select {
case <-time.After(time.Second * 5):
// 续约
_, err := lease.KeepAliveOnce(ctx, leaseId)
if err != nil {
slog.Warn("etcd lease keepalive err", "err", err)
}
case <-ctx.Done():
return
}
}
}()
return nil
}
func NewEtcdClient() (*clientv3.Client, error) {
return clientv3.New(clientv3.Config{
Endpoints: strings.Split(etcdUrl, ";"),
Username: etcdUser,
Password: etcdPwd,
})
}
func Target() string {
return "etcd:///" + serviceName
}
func NamingKey(addr string) string {
return fmt.Sprintf("%+v/%+v", serviceName, addr)
}
3. 客户端
过程:
- etcd resolver创建一个服务发现构造器Builder
- grpc Dial连接时,将resolverBuilder注入conn
- 创建grpc client,并调用接口,每次调用时会做节点选择
写一下——
func main() {
conn, err := NewGrpcConn()
if err != nil {
panic(err)
}
defer conn.Close()
cli := api.NewHelloServiceClient(conn)
_, err = cli.SimpleHello(context.Background(), &api.Person{
Name: "test",
})
if err != nil {
slog.Error("call 1 failed.", "err", err)
}
}
func NewGrpcConn() (*grpc.ClientConn, error) {
// 初始化etcd client
cli, err := etcd.NewEtcdClient()
if err != nil {
slog.Error("new etcd client err", "err", err)
return nil, err
}
// 创建服务发现构造器 resolverBuilder
resolverBuilder, err := resolver.NewBuilder(cli)
if err != nil {
slog.Error("new etcd resolver builder err", "err", err)
return nil, err
}
// 创建grpc连接代理
conn, err := grpc.Dial(
// 连接的target, 值如 etcd:///{serviceName}
etcd.Target(),
// tls
grpc.WithTransportCredentials(insecure.NewCredentials()),
// 服务发现
grpc.WithResolvers(resolverBuilder),
// 负载均衡算法,round_robin, 默认为pick_first
grpc.WithDefaultServiceConfig(`{"loadBalancingPolicy":"round_robin"}`),
)
return conn, err
}
几个角色:
- Resolver解析器:将服务名解析为具体的实例地址,可以基于DNS或静态配置,也可以自定义如etcd/consul作为服务注册与发现中心
- Balancer负载均衡器:维护实例列表,并监听实例状态;管理负载均衡器
- Picker选择器:是Balancer基于某一负载均衡策略创建的,用于选择具体的一个服务实例