在实习期间,公司流量平台的服务是基于gRPC微服务框架的,但是在使用K8s进行配置时,却发现出现了负载均衡失效的问题,那么是什么原因导致的,又该如何解决呢?一起和野生菌探究吧~
1. gRPC
gRPC是由google开发的,是一款语言中立、平台中立、开源的RPC(Remote Procedure Call,远程过程调用)框架。
在gRPC里客户端应用可以像调用本地对象一样直接调用另一台不同的机器上服务端应用的方法,使得您能够更容易地创建分布式应用和服务。与许多 RPC框架类似,gRPC也是基于以下理念:定义一个服务,指定其能够被远程调用的方法(包含参数和返回类型)。在服务端实现这个接口,并运行一个 gRPC 服务器来处理客户端调用。
特性
基于HTTP/2
HTTP/2 提供了连接多路复用、双向流、服务器推送、请求优先级、首部压缩等机制。可以节省带宽、降低TCP链接次数、节省CPU,帮助移动设备延长电池寿命等。gRPC 的协议设计上使用了HTTP2 现有的语义,请求和响应的数据使用HTTP Body 发送,其他的控制信息则用Header 表示。
IDL使用ProtoBuf
gRPC使用ProtoBuf来定义服务,ProtoBuf是由Google开发的一种数据序列化协议(类似于XML、JSON、hessian)。ProtoBuf能够将数据进行序列化,并广泛应用在数据存储、通信协议等方面。压缩和传输效率高,语法简单,表达力强。
多语言支持
gRPC支持多种语言,并能够基于语言自动生成客户端和服务端功能库。目前已提供了C版本grpc、Java版本grpc-java 和 Go版本grpc-go,其它语言的版本正在积极开发中,其中,grpc支持C、C++、Node.js、Python、Ruby、Objective-C、PHP和C#等语言,grpc-java已经支持Android开发。
2. HTTP2.0
HTTP/2,也就是超文本传输协议第2版,不论是1还是2,HTTP的基本语义是不变的,比如方法语义(GET/PUST/PUT/DELETE),状态码(200/404/500等),Range Request,Cacheing,Authentication、URL路径, 不同的主要是下面几点:
多路复用
在 HTTP/1.1 协议中 「浏览器客户端在同一时间,针对同一域名下的请求有一定数量限制。超过限制数目的请求会被阻塞」。 HTTP/2 的多路复用(Multiplexing) 则允许同时通过单一的 HTTP/2 连接发起多重的请求-响应消息。
因此 HTTP/2 可以很容易的去实现多流并行而不用依赖建立多个 TCP 连接,HTTP/2 把 HTTP 协议通信的基本单位缩小为一个一个的帧,这些帧对应着逻辑流中的消息。并行地在同一个 TCP 连接上双向交换消息。
二进制帧
HTTP/2 传输的数据是二进制的。相比 HTTP/1.1 的纯文本数据,二进制数据一个显而易见的好处是:更小的传输体积。这就意味着更低的负载。二进制的帧也更易于解析而且不易出错,纯文本帧在解析的时候还要考虑处理空格、大小写、空行和换行等问题,而二进制帧就不存在这个问题。
头部压缩
HTTP是无状态协议。简而言之,这意味着每个请求必须要携带服务器需要的所有细节,而不是让服务器保存住之前请求的元数据。因为http2没有改变这个范式,所以它也需要这样(携带所有细节),因此 HTTP 请求的头部需要包含用于标识身份的数据比如 cookies,而这些数据的量也在随着时间增长。每一个请求的头部都包含这些大量的重复数据,无疑是一种很大的负担。对请求头部进行压缩,将会大大减轻这种负担,尤其对移动端来说,性能提高非常明显。
HTTP/2 使用的压缩方式是 HPACK。
HTTP2.0在客户端和服务器端使用“首部表”来跟踪和存储之前发送的键-值对,对于相同的数据,不再通过每次请求和响应发送;通信期间几乎不会改变的通用键-值对(用户代理、可接受的媒体类型,等等)只需发送一次。
事实上,如果请求中不包含首部(例如对同一资源的轮询请求),那么首部开销就是零字节。此时所有首部都自动使用之前请求发送的首部。
如果首部发生变化了,那么只需要发送变化了数据在Headers帧里面,新增或修改的首部帧会被追加到“首部表”。首部表在 HTTP2.0的连接存续期内始终存在,由客户端和服务器共同渐进地更新。
添加请求优先级
为了方便流的传输顺序,每个流都有权重和依赖。每个流的权重值在1~256之间,每个流可以详细给出对其他流的依赖。权重和依赖的结合可以使客户端构建出优先级二叉树的形式,来表达出更想依次得到哪些响应,然后服务端可以按权重分配硬件资源。
服务器推送
服务端可以为每个客户端请求发送多个响应,也就是说,除了原始的响应,服务端还可以给客户端发送额外的资源。服务器推送的资源可以由客户端缓存,推送的资源可以在不同的页面上重复使用,推送的资源可以与其他资源一起复用,推送的资源可以由服务器决定优先级,推送的资源也可以被客户端拒绝。
3. 长连接与短连接
HTTP的长连接和短连接本质上是TCP长连接和短连接。HTTP属于应用层协议,在传输层使用TCP协议,在网络层使用IP协议。 IP协议主要解决网络路由和寻址问题,TCP协议主要解决如何在IP层之上可靠地传递数据包,使得网络上接收端收到发送端所发出的所有包,并且顺序与发送顺序一致。TCP协议是可靠的、面向连接的。
短连接
HTTP1.0默认是短连接:也就是说每次与服务器交互,都需要新开一个连接。 连接->传输数据->关闭连接 比如HTTP是无状态的的短链接,浏览器和服务器每进行一次HTTP操作,就建立一次连接,但任务结束就中断连接。 因为连接后接收了数据就断开了,所以每次数据接受处理不会有联系。 这也是HTTP协议无状态的原因之一。
长连接
连接->传输数据->保持连接 -> 传输数据-> …->直到一方关闭连接,多是客户端关闭连接。 长连接指建立SOCKET连接后不管是否使用都保持连接,但安全性较差。
在HTTP1.1中默认就使用持久化连接来解决:建立一次连接,多次请求均由这个连接完成。
HTTP2所有性能增强的核心在于新的二进制分帧层(不再以文本格式来传输了),它定义了如何封装http消息并在客户端与服务器之间传输。HTTP2连接上传输的每个帧都关联到一个“流”。流是一个独立的,双向的帧序列可以通过一个HTTP2的连接在服务端与客户端之间不断的交换数据。
什么时候用长连接,短连接?
1、长连接多用于操作频繁,点对点的通讯,而且连接数不能太多情况。每个TCP连接都需要三步握手, 这需要时间,如果每个操作都是先连接,再操作的话那么处理速度会降低很多,所以每个操作完后都 不断开,次处理时直接发送数据包就OK了,不用建立TCP连接。例如:数据库的连接用长连接, 如果 用短连接频繁的通信会造成socket错误,而且频繁的socket 创建也是对资源的浪费。
2、像WEB网站的http服务一般都用短链接,因为长连接对于服务端来说会耗费一定的资源,而像WEB网 站这么频繁的成千上万甚至上亿客户端的连接用短连接会更省一些资源,如果用长连接,而且同时有成 千上万的用户,如果每个用户都占用一个连接的话,那可想而知吧。所以并发量大,但每个用户无需频繁操作情况下需用短连接好。
4. k8s中gRPC负载均衡失效
Kubernetes 的默认负载平衡通常不能与 gRPC 一起使用,在不使用 LoadBalance service 的情况下,因为 HTTP/2 链接复用特性,导致客户端的所有请求都发往同一个 Pod,导致负载不均衡。
原因可见gRPC Load Balancing on Kubernetes without Tears 首先,让我们了解为什么我们需要为 gRPC 做一些特别的事情。
gRPC 是应用程序开发人员越来越普遍的选择。与 JSON-over-HTTP 等替代协议相比,gRPC 可以提供一些显着的好处,包括显着降低(反)序列化成本、自动类型检查、形式化 API 和更少的 TCP 管理开销。
但是,gRPC 也打破了标准的连接级负载平衡,包括 Kubernetes 提供的负载平衡。这是因为 gRPC 是建立在 HTTP/2 之上的,而 HTTP/2 旨在拥有一个TCP长连接,所有请求都通过该连接进行多路复用——这意味着多个请求可以在任何时间点在同一个连接上处于活动状态。通常,这很好,因为它减少了连接管理的开销。但是,这也意味着(如您所想)连接级别的平衡不是很有用。一旦建立连接,就无需再进行平衡了。所有请求都将固定到单个目标 pod,如下所示:
那么为什么对于HTTP/1.1没有影响呢?
HTTP/1.1 也有长连接的概念,之所以在 HTTP/1.1中没有出现这个问题,是因为 HTTP/1.1 有几个特性自然会导致 TCP 连接循环。正因为如此,连接级别的平衡“足够好”,对于大多数 HTTP/1.1 应用程序,我们不需要做更多的事情。
要了解原因,让我们更深入地了解 HTTP/1.1。与 HTTP/2 相比,HTTP/1.1 不能多路复用请求。每个 TCP 连接一次只能激活一个 HTTP 请求。客户端发出请求,例如GET /foo,然后等待服务器响应。当请求-响应周期发生时,不能在该连接上发出其他请求。
通常,我们希望大量请求并行发生。因此,为了有并发的 HTTP/1.1 请求,我们需要建立多个 HTTP/1.1 连接,并在所有这些连接上发出我们的请求。此外,长期 HTTP/1.1 连接通常会在一段时间后过期,并被客户端(或服务器)断开。这两个因素结合在一起意味着 HTTP/1.1 请求通常会在多个 TCP 连接之间循环,因此连接级别的平衡是有效的。
所以我们怎样实现gRPC的负载均衡(load balance)呢?
现在回到 gRPC。由于我们无法在连接层面进行均衡,所以为了做 gRPC 负载均衡,我们需要从连接均衡转向request均衡。换句话说,我们需要为每个目标打开一个 HTTP/2 连接,并在这些连接之间平衡request,如下所示: 在网络方面,这意味着我们需要在 L5/L7 而不是 L3/L4 做出决策,即我们需要了解通过 TCP 连接发送的协议。
我们如何做到这一点?有几个选择。首先,我们的应用程序代码可以手动维护自己的目标负载平衡池,我们可以配置我们的 gRPC 客户端以使用这个负载平衡池。这种方法为我们提供了最大的控制权,但它在 Kubernetes 等环境中可能非常复杂,在 Kubernetes 重新调度 Pod 时,池会随着时间而变化。我们的应用程序必须观察 Kubernetes API 并与 Pod 保持同步。
或者,使用如下两种方法:
代理负载平衡
在代理负载均衡中,客户端将rpc发送给LB (load Balancer)代理。LB将RPC调用分发到一个可用的后端服务器,该后端服务器实现为调用提供服务的实际逻辑。LB跟踪每个后端的负载,并实现公平分配负载的算法。客户端本身并不知道后台服务器。客户端是不可信的。这种体系结构通常用于面向用户的服务,其中来自开放互联网的客户端可以连接到服务器。
客户端负载均衡
在客户端负载平衡中,客户端知道许多后端服务器,并为每个RPC选择一个后端服务器。如果客户端希望实现基于服务器负载报告的负载均衡算法。对于简单的部署,客户机可以在可用的服务器之间轮询请求。
我们考虑使用 gRPC client LB 配合 Headless Service
使用gRPC client LB 配合 Headless Service在 Kubernetes 上实现 gRPC 负载平衡
···· 未完待续····