Nginx gRPC Streaming 负载均衡的排坑和思考
前言:
我们知道nginx在1.13版本之后就可以支持grpc的负载均衡了。官方给出的使用也很简单,类似proxy_pass的语法。但在使用的过程中遇到短连接的问题。
该文章后续仍在不断的更新修改中, 请移步到原文地址 http://xiaorui.cc/?p=5970
大量的timewait短连接:
我们知道grpc是基于http2的,http2的设计就是长连接的设计,在连接上可以跑多个stream来规避http 1.1 中的队头阻塞(Head of line blocking),简单说http1协议导致单个连接不能多路复用。
以前在cdn公司的基础架构组干过,所以对nginx算是熟悉了。我知道在使用nginx proxy_pass upstream的时候,需要配置keepalive,不然nginx做负载均衡转发一律会按照短连接处理。 没想到grpc upstream也要配置keepalive。
没有在nginx upstream keepalive连接池配置的时候,timewait的连接会到4w左右。需要注意的是 keepalive是单个worker的连接池,毕竟nginx是多进程的,在nginx的架构模型下是不能连接共享的。
nginx grpc timewait// xiaorui.cc server { listen 6666 http2; server_name localhost; #charset koi8-r; access_log /var/log/nginx/host.access.log main; location / { grpc_pass grpc://grpcservers; } } upstream grpcservers { server 10.161.11.181:8090; server 10.161.11.180:8090; server 10.161.11.179:8090; server 10.161.11.178:8090; server 10.161.11.177:8090; keepalive 2000; }
有朋友问,使用nginx做grpc的负载均衡是否存在异常? 我使用client -> nginx -> 5个grpc server实例来测试,负载均衡没问题。
思考:
grpc unaryRpc可以负载均衡,这个好理解,一个请求一个返回,最后单个连接client的请求会均衡到nginx upstream下游的主机上。经过我的测试,server/client streaming和bidi streaming模式,nginx grpc也完美支持。
只要client跟nginx后面的服务建立了streaming的请求,那么就一直占用这个连接了,同一时间nginx到服务下游的连接不能复用。就是说如果有10000个client streaming长请求,那么nginx就要创建10000个连接对接下游服务。而unary rpc请求是可以复用连接池的。
nginx grpc streaminggrpc的streaming请求可以理解为有状态的请求,nginx是怎么区分的请求?看了下nginx 1.13关于grpc的代码,没怎么看明白 。通过tcpdump抓包来分析grpc streaming bidi/unary,没有发现标明grpc请求类型的标识。
我先前开发过一个grpc网关,是通过protobuf描述符、动态注册、各种反射来实现的。通过fullMethod和protobuf描述符里的descriptor可以分析该请求是否有streaming。有兴趣的可以看看我封装的库。 https://github.com/rfyiamcool/grpcall
总结:
grpc的负载均衡除了借助外部的gateway之外,还可以使用内置的grpc balancer接口实现。
我司的k8s ingress网关采用envoy做负载均衡,个人觉得envoy的强大值得拥有。这里不是说envoy比nginx强大,我可以负责的说envoy性能比nginx差不少。envoy的优势在微服务体系下有更灵活的配置和适配,比如对接istio,自定义xds接口,rest api -> grpc转换。