Spring Cloud Eureka是Spring Cloud Netflix微服务套件中的一部分,基于Netflix Eureka做了二次封装,主要负责完成微服务架构中的服务治理功能。出于好奇想知道Eureka可以注册的最大服务实例数是多少,于是有了下面的测试。
Eureka服务注册与发现相关源码介绍
eureka整体架构图(图片来源网络)
可以看到eueka按逻辑上可以划分为3个模块:
eureka-server、eureka-client-service-provider、eureka-client-service-consumer。
- eureka-server:服务端,提供服务注册和发现。
- eureka-client-service-provider:客户端,服务提供者,通过http rest告知服务端注册,更新,取消服务。
- eureka-client-service-consumer:客户端,服务消费者,通过http rest从服务端获取需要服务的地址列表,然后配合一些负载均衡策略(ribbon)来调用服务端服务。
首先, 对于服务注册中心、服务提供者、服务消费者这三个主要元素来说,后者(Eureka客户端)在整个运行机制中是大部分通信行为的主动发起者,而注册中心主要是处理请求的接收者。所以以下会我们会分别基于Eureka客户端、服务端入手看看他们是如何完成这些行为的。
client端:
主要做了如下几件事:
- 服务注册(Registry)——初始化时执行一次,向服务端注册自己服务实例节点信息包括ip、端口、实例名等,基于POST请求。
1 | public EurekaHttpResponse Void register(InstanceInfo info) { |
- 服务续约(renew)——每隔30s向服务端PUT一次,保证当前服务节点状态信息实时更新,不被服务端失效剔除。
1 | public EurekaHttpResponse InstanceInfo sendHeartBeat (String appName, String id, InstanceInfo info, InstanceStatus overriddenStatus) { |
- 更新已经注册服务列表(fetchRegistry)——每隔30s从服务端GET一次增量版本信息,然后和本地比较并合并,保证本地能获取到其他节点最新注册信息。
1 |
|
- 服务下线(cancel)——在服务shutdown的时候,需要及时通知服务端把自己剔除,以避免客户端调用已经下线的服务。
1 | public EurekaHttpResponse Void cancel (String appName, String id) { |
Server端:
我们知道eureka client是通过Jersey Client基于Http协议与eureka server交互来注册服务、续约服务、取消服务、服务查询等。同时,Server端还会维护一份服务实例清单,并每隔90s对未续约的实例进行失效剔除。所以,eureka server肯定要提供上述http的服务端的Jersey Server实现,由于此次测试针对客户端模拟,服务端对应接口就先不在这描述了。
测试过程
- 测试工具:
| 工具选项 | 描述 | 配置/能力 |
|---|---|---|
| 测试机器 | CentOS release 5.4 (Final) 3台 | 8c16g,open files:65535,max user processes:65535 |
| Eureka服务端 | 单机部署,boot版本(Dalston.SR5) | -XX:MaxHeapSize=4g(默认) |
| Wireshark | Windows平台抓包工具 | 抓取HTTP、TCP等报文内容、协议相关信息 |
| UAV监控 | 公司自研监控平台 | 实时监控采集应用性能指标 |
测试方案:
1、先启动多组Eureka客户端并用Wireshark抓取其真实请求,然后结合Eureka源码分析其调用逻辑关系(基于TCP短链接交互)。
2、根据源码,将客户端http调用方式进行池化,即每笔实例注册流程:(注册、获取实例、续约)等调用请求统一从连接池获取连接,获取实例过程为每次获取delta(增量)。
3、每笔流程完成后sleep0.5s,保证所有节点续约、获取实例(间隔30s)的频率对服务端负载均匀。
4、串行模拟整个流程,每完成500笔,整体观察5分钟记录服务端cpu、内存、线程、连接数等信息。直到服务端或客户端出现大量异常(超时、失效剔除等可能异常)则认为到达eureka注册瓶颈。
5、更改服务端servlet容器配置,尝试进行优化(最大连接数、线程数等)从新开始流程测试直到最优。
模拟测试流程图
- 数据记录
可以看出到实例注册数到7000多时候,连接数不稳定飙到10000左右,同时此时客户端开始大量报错超时,服务端开始拒绝连接:
此时连接数截图详细,可以清楚看到conns达到10000阀值后接着下降:
jstack-F 后显示大量tomcat workThreadPoolExecutor线程block在Socket上:
此时结果和预期猜测一样。MaxConnection=10000,且大于AcceptCount=100时,Tomcat会触发拒绝连接。
而我们使用的spring boot版本使用内嵌Tomcat版本8.5 接着改了服务端tomcat配置改成如下配置:
1 |
|
}
再次从新开始一轮测试,数据记录如下:
| 实例数 | 7100 | 8000 |
|---|---|---|
| cpu | 749% | 790% |
| mem | 17.7 | 18% |
| conn | 12007 | 13690 |
| Threads | 1982 | 1997 |
可以看出,在修改了tomcat对应配置,将最大连接数调至20000,线程数调至5000后,Eureka可注册的实例数突破了7000,连接数也突破了10000,实例数注册到8000后才开始报错,看出此时cpu已经接近满负载,操作系统本身调度已经压到极限,于是结束了本次测试。
注意:有一种误解,就是我们常说一台机器有65536个端口,那么承载的连接数就是65536个,这个说法是极其错误的,这就混淆了源端口和访问目标端口。系统是通过一个四元组来标识一个TCP连接,(src_ip,src_port,dst_ip,dst_port)即源IP、源端口、目标IP、目标端口。比如我们有一台服务192.168.0.1,开启端口80.那么所有的客户端都会连接到这台服务的80端口上面,我们做压测的时候,利用压测客户端,这个客户端的连接数是受到端口数的限制,但是服务器上面的连接数可以达到成千上万个,一般可以达到百万(4C8G配置),至于上限是多少,需要看其自身优化的程度(可通过操作系统的文件句柄数量限制、TCP参数进行调优),但是参数值并不是设置的越大越好,有的需要考虑服务器的硬件配置,参数对服务器上其它服务的影响等。
结论: Eureka Server服务实例注册量的负载值和操作系统、应用容器本身对应的配置相关,调整操作系统可打开最大文件句柄、进程数,调整应用容器相关最大连接数、线程数、NIO服务器模型引入等等手段都可提高我们应用服务整体吞吐量。
参考资料:
https://www.jianshu.com/p/5ffb71b4c13d
https://tomcat.apache.org/tomcat-8.0-doc/config/http.html
http://yeming.me/2016/12/01/eureka1/