k8s 服务发现 以及 gRPC 长连接负载均衡

众所周知 gRPC 是基于 HTTP/2,而 HTTP/2 是基于 TCP 长连接的。

k8s 自带一套基于 DNS 的服务发现机制 —— Service。

但基于 Service 的服务发现对于 gRPC 来说并不是开箱即用的,这里面有很多坑。

错误姿势 ClusterIP Service

gRPC client 直接使用 ClusterIP Service 会导致负载不均衡。因为 HTTP/2 多个请求在一个 TCP 连接上多路复用。

即使这个连接上的 stream 达到 maxConcurrentStreams,也不会创建新的连接,而是阻塞。除非搭配自定义 gRPC 连接池使用。

参见源码:grpc-go/internal/transport/http2_client.go:L107

为什么 HTTP/1.1 不受影响

HTTP/1.1 默认开启 Keepalive,也会保持长连接。 但是 HTTP/1.1 多个请求不会共享一个连接,如果连接池里没有空闲连接则会新建一个,经过 Service 的负载均衡,各个 pod 上的连接是相对均衡的。

正确姿势

长连接负载均衡的原理是与后端每个 pod 都建立一个长连接,LB 算法选择一个写入请求。

gRPC client LB 配合 Headless Service

创建 Headless Service 后,k8s 会生成 DNS 记录,访问 Service 会返回后端多个 pod IP 的 A 记录,这样应用就可以基于 DNS 自定义负载均衡。

在 grpc-client 指定 headless service 地址为 dns:/// 协议,DNS resolver 会通过 DNS 查询后端多个 pod IP,然后通过 client LB 算法来实现负载均衡。这些 grpc-go 这个库都帮你做了。

conn, err := grpc.DialContext(ctx, "dns:///"+headlessSvc,
    grpc.WithInsecure(),
    grpc.WithBalancerName(roundrobin.Name),
    grpc.WithBlock(),
)

完整代码参考:https://github.com/win5do/go-...

Proxy LB 或 ServiceMesh

如果不想在 client 代码中来做 LB,可以使用 Envoy 或 Nginx 反向代理。

Proxy Server 与后端多个 pod 保持长连接,需配置对应模块来识别 HTTP/2 请求,根据 LB 算法选择一个 pod 转发请求。

Istio 做长连接 LB 不要求 Headless Service,因为网格控制器不会直接使用 Service,而是获取 Service 背后 Endpoint(IP)配置到 Envoy 中。

Proxy 如果自身有多个 replicas,则 proxy 与 client 之间也有长连接的问题,这就套娃了。

但 Istio 等服务网格会为每个 pod 注入一个专用的 Envoy 代理作为 sidecar,所以问题不大。

gRPC 使用 Envoy proxy 可参考 Google Cloud 的教程:

https://github.com/GoogleClou...

对比

client 和 proxy 两种方式的对比其实就是 侵入式服务治理 vs 网格化服务治理

侵入式网格化
优点- 性能好,没有多次转发
- 逻辑清晰,开发人员能很快定位问题
- 基础设施下沉,逻辑一致,方便跨语言异构
- 非侵入,应用无感知
- 服务治理生态繁荣
缺点- 多种编程语言需要分别开发 client 库
- 对接监控告警、链路追踪等基础设施工作量大
- 链路长,有性能损耗
- 下层逻辑复杂,不透明,出问题抓瞎
- 还不够成熟

Reference

https://kubernetes.io/blog/20...

https://zhuanlan.zhihu.com/p/...

https://zhuanlan.zhihu.com/p/...


无风的内存空间
编程相关,随手记下
649 声望
58 粉丝
0 条评论
推荐阅读
[ k8s-operator 系列 ] 自动生成 webhook 证书并在过期时自动刷新
下面要介绍的是另一种方式:github.com/open-policy-agent/cert-controller,最初是在 open-policy-agent/gatekeeper 中看到这种用法,感觉非常 Geek。

无风1阅读 3.9k

写给go开发者的gRPC教程-通信模式
本篇为【写给go开发者的gRPC教程系列】第二篇第一篇:protobuf基础第二篇:通信模式上一篇介绍了如何编写 protobuf 的 idl,并使用 idl 生成了 gRPC 的代码,现在来看看如何编写客户端和服务端的代码Simple RPC (...

liangwt2阅读 1k

封面图
写给go开发者的gRPC教程-protobuf基础
序列化协议。gRPC使用protobuf,首先使用protobuf定义服务,然后使用这个文件来生成客户端和服务端的代码。因为pb是跨语言的,因此即使服务端和客户端语言并不一致也是可以互相序列化和反序列化的

liangwt1阅读 991评论 1

封面图
protocol-buffers namespace conflict
在运行grpc服务,加载*.pb.go时可能会报冲突错误,如文件名命名冲突:其实针对文件名冲突的错误处理开发者有移除过"文件冲突检测":[链接]后来发现有问题又加上了"文件冲突检测":[链接]

AVOli阅读 839

写给go开发者的gRPC教程-拦截器
本篇为【写给go开发者的gRPC教程】系列第三篇第一篇:protobuf基础第二篇:通信模式第三篇:拦截器gRPC的拦截器和其他框架的拦截器(也称middleware)作用是一样的。利用拦截器我们可以在不侵入业务逻辑的前提下...

liangwt阅读 659

封面图
《go入门grpc》第五章:protoc生成的.pb.go文件解读
在第三章,以及第四章,我们学习了,如何把proto生产go文件。《go入门grpc》第三章:从 proto 文件自动生成go代码《go入门grpc》第四章:使用Makefile优化protoc命令本章我们学习下protoc --go_out命令 生成的.pb...

海生阅读 477

Grpc使用buf.build 快速编译
本文通过实例来讲解使用buf来快速的编译proto文件,不需要再用protoc命令加各种参数来编译proto文件。事先需要安装buf, 安装方法请参考官网installation我们先建立目录结构auth.proto {代码...} 进入proto文件夹...

这个名字好长阅读 470

649 声望
58 粉丝
宣传栏