国外设计师灵感网站,网站seo优化实例,购物网站促销方案,公司官网网站搭建gRPC 是一个高性能、开源和通用的 RPC 框架#xff0c;面向移动和 HTTP/2 设计#xff0c;也是目前流行的微服务架构中比较突出的跨语言 RPC 框架。一直以来#xff0c;我们的微服务都是基于 gRPC 来开发#xff0c;使用的语言有 .NET、JAVA、Node.js#xff0c;整体还比较… gRPC 是一个高性能、开源和通用的 RPC 框架面向移动和 HTTP/2 设计也是目前流行的微服务架构中比较突出的跨语言 RPC 框架。一直以来我们的微服务都是基于 gRPC 来开发使用的语言有 .NET、JAVA、Node.js整体还比较稳定当然整个过程中踩过的坑也不少今天主要介绍 gRPC 服务使用 Docker Swarm 部署遇到的问题。问题描述服务端空闲没有接受到任何请求一段时间后不到 20 分钟客户端 第一次 向服务端发请求会失败重新请求则成功具体错误日志如下提示 gRPC 服务端将连接重置1234567Grpc.Core.RpcException: Status(StatusCodeUnavailable, DetailConnection reset by peer) at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw() at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task) at Grpc.Core.Internal.AsyncCall2.UnaryCall(TRequest msg) at Grpc.Core.DefaultCallInvoker.BlockingUnaryCall[TRequest,TResponse](Method2 method, String host, CallOptions options, TRequest request) at Grpc.Core.Interceptors.InterceptingCallInvoker.BlockingUnaryCallb__3_0[TRequest,TResponse](TRequest req, ClientInterceptorContext2 ctx) at Grpc.Core.ClientBase.ClientBaseConfiguration.ClientBaseConfigurationInterceptor.BlockingUnaryCall[TRequest,TResponse](TRequest request, ClientInterceptorContext2 context, BlockingUnaryCallContinuation2 continuation)解决方案方案1重试机制最初通过查看官方文档对 StatusCodeUnavailable 的解释发现当前遇到的问题确实可以使用重试机制来处理所以在客户端对 gRPC 服务的调用全部添加了重试策略。虽然当时确实解决了问题但也一直怀疑我们在使用方式上肯定有问题毕竟 gRPC 在很多开源项目中都被验证过理论上肯定不是这么处理问题的所以并不推荐这么玩。方案2调整 TCP keepalive在进行日志分析时发现生产环境并没有此类型错误日志所以问题基本和代码本身没什么关系猜测是环境上的原因而本地开发环境和生产环境的最大区别是开发环境的服务通过 Docker Swarm 进行部署线上环境则是使用 k8s 。所以尝试从 Docker Swarm 上进行问题定位最终找到相关资料 gRPC streaming keepAlive doesn’t work with docker swarm (虽然 issue 聊的是 grpc-go 但其实和语言无关) 和 IPVS connection timeout issue 问题和我们遇到的基本一致。经过多次测试验证确定出问题的原因是当通过 Docker Swarm 部署 基于 overlay 网络 gRPC 服务基于 TCP客户端调用服务端会经过 IPVS 处理IPVS 简单来说就是传输级的负载均衡器可以将基于 TCP 和 UDP 的服务请求转发到真实服务。gRPC 服务启动时IPVS 中会将此 TCP 连接记录到连接跟踪表但为了保持连接跟踪表干净900s默认的 timeout不支持调整内空闲的连接会被清理 IPVS 更多介绍12[rootnode1]~# ipvsadm -l --timeoutTimeout (tcp tcpfin udp): 900 120 300所以当客户端发请求时如果 IPVS 的连接跟踪表中不存在对应连接则会返回 Connection reset by peer 重置后第二次请求就正常了。所以解决方式就是使 IPVS 的连接跟踪表一直有该服务的连接状态在 Linux 的内核参数中有 TCP 的 keepalive 默认设置时间是 7200s我们只需要将其改成小于 900s这样不到 900s 就发起探测使连接状态一直保持。因为如果使用默认的 7200s 探测一次IPVS 的连接跟踪表中此服务可能在 900s 的时候就已经被清理所以在 901s~7200s 这个区间内有客户端请求进来就会出错。1234[rootnode1]~# sysctl -a | grep keepalivenet.ipv4.tcp_keepalive_time 7200 # 表示当 keepalive 启用的时候TCP 发送 keepalive 消息的频度缺省是2小时net.ipv4.tcp_keepalive_probes 9 # 如果对方不予应答探测包的发送次数net.ipv4.tcp_keepalive_intvl 75 # keepalive 探测包的发送间隔修改可通过编辑 /etc/sysctl.conf 文件调整后需 重启 gRPC 服务 123net.ipv4.tcp_keepalive_time 800 #800s 没必要太小其他两个参数也可相应做些调整net.ipv4.tcp_keepalive_probes 3net.ipv4.tcp_keepalive_intvl 15如果不希望修改内核参数也可以在 gRPC 服务代码中通过修改 grpc.keepalive_time_ms参考Keepalive User Guide for gRPC Core 和 Grpc_arg_keys服务端默认 grpc.keepalive_time_ms 也是 7200s和内核参数一样以下是 .NET 代码例子其他语言类似12345678910var server new Server(new ListChannelOption{ new ChannelOption(grpc.keepalive_time_ms, 800000), // 发送 keepalive 探测消息的频度 new ChannelOption(grpc.keepalive_timeout_ms, 5000), // keepalive 探测应答超时时间 new ChannelOption(grpc.keepalive_permit_without_calls, 1) // 是否允许在没有任何调用时发送 keepalive}){ Services { ServiceA }, Ports { new ServerPort(host, port, ServerCredentials.Insecure) },};再回头看看为什么生产环境的 k8s 没有这个问题首先 kube-proxy 是支持 IPTABLES 和 IPVS 两种模式的但目前我们使用的是 IPTABLES当然还有很多区别不过涉及更多运维层面的介绍我就不吹逼了毕竟不在掌握范围内 。参考链接gRPC streaming keepAlive doesn’t work with docker swarmIPVSIPVS connection timeout issueKeepalive User Guide for gRPC CoreGrpc_arg_keys