gRPC 是一个高性能、开源和通用的 RPC 框架,面向移动和 HTTP/2 设计,也是目前流行的微服务架构中比较突出的跨语言 RPC 框架。

一直以来,我们的微服务都是基于 gRPC 来开发,使用的语言有 .NETJAVANode.js,整体还比较稳定,当然整个过程中踩过的坑也不少,今天主要介绍 gRPC 服务使用 Docker Swarm 部署遇到的问题。

问题描述

服务端空闲(没有接受到任何请求)一段时间后(不到 20 分钟),客户端 第一次 向服务端发请求会失败,重新请求则成功,具体错误日志如下,提示 gRPC 服务端将连接重置:

1234567
Grpc.Core.RpcException: Status(StatusCode=Unavailable, Detail="Connection reset by peer")   at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)   at Grpc.Core.Internal.AsyncCall`2.UnaryCall(TRequest msg)   at Grpc.Core.DefaultCallInvoker.BlockingUnaryCall[TRequest,TResponse](Method`2 method, String host, CallOptions options, TRequest request)   at Grpc.Core.Interceptors.InterceptingCallInvoker.<BlockingUnaryCall>b__3_0[TRequest,TResponse](TRequest req, ClientInterceptorContext`2 ctx)   at Grpc.Core.ClientBase.ClientBaseConfiguration.ClientBaseConfigurationInterceptor.BlockingUnaryCall[TRequest,TResponse](TRequest request, ClientInterceptorContext`2 context, BlockingUnaryCallContinuation`2 continuation)

解决方案

方案1:重试机制

最初通过查看官方文档对 StatusCode=Unavailable 的解释,发现当前遇到的问题确实可以使用重试机制来处理,所以在客户端对 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
[root@node1]~# 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
[root@node1]~# 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 服务 :

123
net.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 代码例子(其他语言类似):

12345678910
var server = new Server(new List<ChannelOption>{  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 swarm

  • IPVS

  • IPVS connection timeout issue

  • Keepalive User Guide for gRPC Core

  • Grpc_arg_keys

聊聊 Docker Swarm 部署 gRPC 服务的坑相关推荐

  1. 使用Docker Swarm部署MinIO ​​​​​​​

    使用Docker Swarm部署MinIO Docker Engine在Swarm模式下提供集群管理和编排功能. MinIO服务器可以在Swarm的分布式模式下轻松部署,创建一个多租户,高可用性和可扩 ...

  2. 使用 Docker Stack 部署多服务集群

    使用 Docker Stack 部署多服务集群 前言 单机模式下,我们可以使用 Docker Compose 来编排多个服务,而在 上一篇文章 中介绍的 Docker Swarm 只能实现对单个服务的 ...

  3. 使用Docker Swarm来运行服务

    本文讲的是使用Docker Swarm来运行服务[编者的话]本文介绍了Docker 1.12中的Docker Swarm Mode,包括如何安装Docker Engine.如何建立Swarm集群.如何 ...

  4. Docker快速部署PostgreSQL服务

    Docker快速部署PostgreSQL服务 快速开始 请新建一个目录postgresql,进入目录postgresql,将以下文件保存为docker-compose.yml,然后执行docker-c ...

  5. k8s多集群搭建istio共享控制平面(多网络)及部署grpc服务分流实践

    个人博客原文:http://www.lampnick.com/php/913 本文目标 部署一个多集群的共享的istio服务网格 部署一套基于grpc的服务 对grpc服务进行流量管理 架构图如下 前 ...

  6. Docker下部署ftp服务

    Docker下部署ftp服务 文章目录 Docker下部署ftp服务 前言 一. 查找vsftpd镜像 二.pull vsftpd最新版镜像到本地 二.配置vsftpd 1.创建vsftpd容器 2. ...

  7. docker swarm 集群服务编排部署指南(docker stack)

    Docker Swarm 集群管理 概述 Docker Swarm 是 Docker 的集群管理工具.它将 Docker 主机池转变为单个虚拟 Docker 主机,使得容器可以组成跨主机的子网网络.D ...

  8. docker swarm部署的docker服务无法访问外部的postgresql

    在使用portainer(docker swarm)方式部署thingsboard集群服务时发现tb-node节点无法访问外部的postgresql服务,通过docker logs container ...

  9. 代码级操作指南 | 如何在Docker Swarm中运行服务

    最新版本Docker Engine v1.12中,包含了多项与Docker Swarm紧密相关的功能变更.在今天的文章中,数人云将和大家探讨如何利用Docker的Swarm Mode进行服务部署. 在 ...

最新文章

  1. 编译-C语言库FFTW支持iOS平台的静态库
  2. 干货来袭-整套完整安全的API接口解决方案
  3. C语言训练题-有重复的数据
  4. php7与apache整合,apache集成php7.3.5的详细步骤
  5. android 画面,Android 界面组成
  6. 【zookeeper】Zk创建空节点 不带数据的
  7. 摘抄一篇:图的存储结构
  8. Google 杀死 URL 的第一步
  9. SonarLint黄线警告python:S125
  10. uploadify组件文件上传那些事
  11. appium安装教程
  12. 手机麦克风结构原理图_麦克风工作原理是什么
  13. CAD绘制填充并设置填充透明度
  14. 三维点云语义分割基础知识
  15. 祝贺父亲节快乐的python代码_2019祝爸爸父亲节快乐的最新父亲节祝福说说句子大全...
  16. Tourists【广义圆方树+树链剖分+方点的特别优化】
  17. 清华大学计算机相关夏令营,夏令营报名
  18. Resources Root目录和Sources Root目录的区别
  19. 翻倍增长!C-V2X商业化“提速”,新一代模组加速“助跑”
  20. Hololens2开发笔记-重刷系统(正常发布版本和内部预览版本)

热门文章

  1. linux系统服务设置命令--chkconfig命令参数及用法详解
  2. plex 乱码_Plex Media Center现在支持播客
  3. r中汇率市场_如何在Word 2013表中汇总行和列
  4. 如何使用oracle查询,oracle 表查询
  5. 【前端基础进阶】JS-Object 功能详解
  6. 【ACR2015】依那西普按需维持治疗策略有效抑制RA骨破坏进展
  7. Python通过amqp消息队列协议中的Qpid实现数据通信
  8. [Linux程序设计][调试][ElectricFence]
  9. IKVM 编程武林之.NET派的北冥神功
  10. WSUS专题之二:部署与规划1