一切都始于我向我们的何通高级软件工程师提出的一个问题: “忘掉通信速度。你真的过行觉得在gRPC中开发通信比REST更好吗 ?” 我不想听到的答案立刻就来了 :“绝对是的。” 在我提出这个问题之前 ,配置我一直在监控我们的解决服务在滚动更新和扩展Pod时出现的奇怪行为。我们的扩展大多数微服务以往都通过REST调用进行通信,没有任何问题
。问题我们已经将一些这些集成迁移到了gRPC,何通主要是免费模板过行因为我们想摆脱REST的开销
。最近
,配置我们观察到了一些问题,解决都指向了同一个方向——我们的扩展gRPC通信 。当然 ,问题我们遵循了在Kubernetes中运行gRPC而不使用服务网格的何通建议实践
,我们在服务器上使用了一个无头服务对象,过行并在gRPC中使用了客户端的配置“轮询”负载平衡与DNS发现等。 Kubernetes内部负载均衡器不是用于负载均衡RPC,而是用于负载均衡TCP连接
。源码下载第四层负载均衡器由于其简单性而很常见,因为它们与协议无关。但是,gRPC破坏了Kubernetes提供的连接级负载均衡
。这是因为gRPC是基于HTTP/2构建的
,而HTTP/2被设计为维护一个长期存在的TCP连接,该连接中的所有请求都可以在任何时间点同时处于活动状态。这减少了连接管理的开销
。建站模板然而 ,在这种情况下,连接级别的负载平衡并不是非常有用,因为一旦建立了连接 ,就不再需要进行负载平衡 。所有的请求都会固定到原始目标Pod ,直到发生新的DNS发现(使用无头服务)。这不会发生,直到至少有一个现有连接断开。 问题示例 : gRPC负载均衡的香港云服务器示例 正如我之前提到的,我们使用“客户端负载均衡”,并使用无头服务对象进行DNS发现。其他选项可能包括使用代理负载均衡或实现另一种发现方法
,该方法将询问Kubernetes API而不是DNS。 除此之外 ,gRPC文档提供了服务器端连接管理提案,我们也尝试过它
。 以下是我为设置以下服务器参数提供的建议
,以及gRPC初始化的模板下载Go代码片段示例: 在现实世界中的行为: gRPC配置更改应用前后的Pod数量 在gRPC配置更改后观察到的新Pod中的网络I/O活动 扩展问题已经解决,但另一个问题变得更加明显。焦点转向了客户端在滚动更新期间出现的gRPC code=UNAVAILABLE 错误。奇怪的是,这只在滚动更新期间观察到,而在单个Pod扩展事件中却没有观察到。 滚动更新期间的gRPC错误数量 部署滚动的过程很简单:创建一个新的副本集,创建一个新的Pod ,当Pod准备就绪时,旧的Pod将从旧的副本集中终止,以此类推
。每个Pod之间的启动时间间隔为15秒。关于gRPC DNS重新发现
,我们知道它仅在旧连接中断或以GOAWAY信号结束时才会启动。因此 ,客户端每15秒开始一次新的重新发现,但获取到了过时的DNS记录。然后
,它们不断进行重新发现,直到成功为止。 除非不是DNS问题... 几乎每个地方都有DNS TTL缓存。基础设施DNS具有其自己的缓存。Java客户端遭受了它们默认的30秒TTL缓存,而Go客户端通常没有实现DNS缓存 。与此相反,Java客户端报告了数百或数千次此问题的发生。当然,我们可以缩短TTL缓存的时间,但为什么要在滚动更新期间只影响gRPC呢 ? 幸运的是
,有一个易于实现的解决方法 。或者更好地说,解决方案:让新Pod启动时设置30秒的延迟。 Kubernetes部署规范允许我们设置新Pod必须处于就绪状态的最短时间 ,然后才会开始终止旧Pod
。在此时间之后
,连接被终止,gRPC客户端收到GOAWAY信号并开始重新发现 。TTL已经过期,因此客户端获取到了新的、最新的记录。 从配置的角度来看,gRPC就像一把瑞士军刀 ,可能不会默认适合您的基础架构或应用程序。查看文档 ,进行调整,进行实验,并充分利用您已经拥有的资源 。我相信可靠和弹性的通信应该是您的最终目标。 我还建议查看以下内容:



