k8s gRPC负载均衡问题

在 K8S 下部署服务,缺省情况下会被分配一个地址(也就是 ClusterIP),客户端的请求会发送给它,然后再通过负载均衡转发给后端某个 pod:

在这里插入图片描述

grpc http2.0链接复用问题

HTTP/1.1 不是实现了基于 KeepAlive 的连接复用么?为什么 HTTP/1.1 的复用没问题,而 HTTP/2 的复用就有问题?

答案是 HTTP/1.1 的 复用是串行的,当请求到达的时候,如果没有空闲连接那么就新创建一个连接,如果有空闲连接那么就可以复用,同一个时间点,连接里最多只能承载一个请求,结果是 HTTP/1.1 可以连接多个 pod;

而 HTTP/2 的复用是并行的,当请求到达的时候,如果没有连接那么就创建连接,如果有连接,那么不管其是否空闲都可以复用,同一个时间点,连接里可以承载多个请求,结果是 HTTP/2 仅仅连接了一个 pod。

如果默认使用直链方式也就是交给k8s自己去处理负载均衡,在http1.x短连接是没有问题的,grpc是使用http2实现的,它是长链接多路复用,后续发送的请求也都会打到相同pod,造成负载不均衡

问题复现

package main

import (
	"context"
	"fmt"
	"log"
	"sync"
	"time"

	pb "codeup.aliyun.com/rpc/proto/test"
	"google.golang.org/grpc"
)

var (
	client   pb.TestSrvServiceClient
	GrpcAddr = "svc cluster_ip:8351"
)

func init() {
	conn, err := grpc.Dial(GrpcAddr, grpc.WithInsecure(), grpc.WithBlock())
	if err != nil {
		log.Fatalf("did not connect: %v", err)
	}
	client = pb.NewTestSrvServiceClient(conn)
}

func getAliyunServiceClient() pb.TestSrvServiceClient {
	return client
}

func main() {
	c := getAliyunServiceClient()
	wg := &sync.WaitGroup{}
	for i := 1; i < 200; i++ {
		wg.Add(1)
		go func(c pb.TestSrvServiceClient, i int, wg *sync.WaitGroup) {
			ctx, cancel := context.WithTimeout(context.Background(), time.Second)
			defer cancel()
			res, err := c.GetGroupResult(ctx, &pb.GetGroupResultRequest{
				ExperimentId: int64(i),
			})
			if err != nil {
				log.Println("err", err)
			} else {
				fmt.Println("res", res)
			}
			time.Sleep(time.Millisecond * 100)
			wg.Done()
		}(c, i, wg)
	}
	wg.Wait()
}

查看日志,发现3个pod的服务大部分的请求都打在了一个pod上面
在这里插入图片描述

解决方案:

Proxy负载均衡

在 Proxy 中实现负载均衡:采用 Envoy 做代理,和每台后端服务器保持长连接,当客户端请求到达时,代理服务器依照规则转发请求给后端服务器,从而实现负载均衡。

Client负载均衡

在 Client 中实现负载均衡:把服务部署成 headless service,这样服务就有了一个域名,然后客户端通过域名访问 gRPC 服务,DNS resolver 会通过 DNS 查询后端多个服务器地址,然后通过算法来实现负载均衡。

如服务发现consul等,则不存在此问题

K8S headless service服务详解

为什么需要无头服务?
客户端想要和指定的的Pod直接通信
并不是随机选择
开发人员希望自己控制负载均衡的策略,不使用Service提供的默认的负载均衡的功能,或者应用程序希望知道属于同组服务的其它实例。

https://www.cnblogs.com/wuchangblog/p/14032057.html

Logo

开源、云原生的融合云平台

更多推荐