Spring Cloud Eureka是Spring Cloud Netflix微服务套件之一,基于Netflix Eureka做了二次封装,主要负责完成微服务架构中的服务治理功能。我们试图在公司推动统一的的服务注册框架,必须非常情况Eureka性能的优劣。
Eureka按逻辑上可以划分为3个模块:
对于服务注册中心、服务提供者、服务消费者这三个主要元素来说,后者(Eureka客户端)在整个运行机制中是大部分通信行为的主动发起者,而注册中心主要是处理请求的接收者。所以以下会我们会分别基于Eureka客户端、服务端入手看看他们是如何完成这些行为的。
主要步骤如下:
public EurekaHttpResponse<Void> register(InstanceInfo info) {
String urlPath = "apps/" + info.getAppName();
ClientResponse response = null;
try {
Builder resourceBuilder = jerseyClient.resource(serviceUrl).path(urlPath).getRequestBuilder();
addExtraHeaders(resourceBuilder);
response = resourceBuilder
.header("Accept-Encoding", "gzip")
.type(MediaType.APPLICATION_JSON_TYPE)
.accept(MediaType.APPLICATION_JSON)
.post(ClientResponse.class, info);
return anEurekaHttpResponse(response.getStatus()).headers(headersOf(response)).build();
} finally {
if (logger.isDebugEnabled()) {
logger.debug("Jersey HTTP POST {}/{} with instance {}; statusCode={}", serviceUrl, urlPath, info.getId(),
response == null ? "N/A" : response.getStatus());
}
if (response != null) {
response.close();
}
}
}
public EurekaHttpResponse<InstanceInfo> sendHeartBeat(String appName, String id, InstanceInfo info, InstanceStatus overriddenStatus) {
String urlPath = "apps/" + appName + '/' + id;
ClientResponse response = null;
try {
WebResource webResource = jerseyClient.resource(serviceUrl)
.path(urlPath)
.queryParam("status", info.getStatus().toString())
.queryParam("lastDirtyTimestamp", info.getLastDirtyTimestamp().toString());
if (overriddenStatus != null) {
webResource = webResource.queryParam("overriddenstatus", overriddenStatus.name());
}
Builder requestBuilder = webResource.getRequestBuilder();
addExtraHeaders(requestBuilder);
response = requestBuilder.put(ClientResponse.class);
EurekaHttpResponseBuilder<InstanceInfo> eurekaResponseBuilder = anEurekaHttpResponse(response.getStatus(), InstanceInfo.class).headers(headersOf(response));
if (response.hasEntity()) {
eurekaResponseBuilder.entity(response.getEntity(InstanceInfo.class));
}
return eurekaResponseBuilder.build();
} finally {
if (logger.isDebugEnabled()) {
logger.debug("Jersey HTTP PUT {}/{}; statusCode={}", serviceUrl, urlPath, response == null ? "N/A" : response.getStatus());
}
if (response != null) {
response.close();
}
}
}
private EurekaHttpResponse<InstanceInfo> getInstanceInternal(String urlPath) {
ClientResponse response = null;
try {
Builder requestBuilder = jerseyClient.resource(serviceUrl).path(urlPath).getRequestBuilder();
addExtraHeaders(requestBuilder);
response = requestBuilder.accept(MediaType.APPLICATION_JSON_TYPE).get(ClientResponse.class);
InstanceInfo infoFromPeer = null;
if (response.getStatus() == Status.OK.getStatusCode() && response.hasEntity()) {
infoFromPeer = response.getEntity(InstanceInfo.class);
}
return anEurekaHttpResponse(response.getStatus(), InstanceInfo.class)
.headers(headersOf(response))
.entity(infoFromPeer)
.build();
} finally {
if (logger.isDebugEnabled()) {
logger.debug("Jersey HTTP GET {}/{}; statusCode={}", serviceUrl, urlPath, response == null ? "N/A" : response.getStatus());
}
if (response != null) {
response.close();
}
}
}
public EurekaHttpResponse<Void> cancel(String appName, String id) {
String urlPath = "apps/" + appName + '/' + id;
ClientResponse response = null;
try {
Builder resourceBuilder = jerseyClient.resource(serviceUrl).path(urlPath).getRequestBuilder();
addExtraHeaders(resourceBuilder);
response = resourceBuilder.delete(ClientResponse.class);
return anEurekaHttpResponse(response.getStatus()).headers(headersOf(response)).build();
} finally {
if (logger.isDebugEnabled()) {
logger.debug("Jersey HTTP DELETE {}/{}; statusCode={}", serviceUrl, urlPath, response == null ? "N/A" : response.getStatus());
}
if (response != null) {
response.close();
}
}
}
我们知道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监控 | 公司自研监控平台 | 实时监控采集应用性能指标 |
可以看出到实例注册数到==7000多==时候,连接数不稳定飙到10000左右,同时此时客户端开始大量报错超时,服务端开始拒绝连接:
此时连接数截图详细,可以清楚看到conns达到10000阀值后接着下降:
jstack-F 后显示大量tomcat workThreadPoolExecutor线程block在Socket上:
此时结果和预期猜测一样。MaxConnection=10000,且大于AcceptCount=100时,Tomcat会触发拒绝连接。
而我们使用的spring boot版本使用内嵌Tomcat版本8.5
接着改了服务端tomcat配置改成如下配置:
@Override
public void customize(Connector connector) {
Http11NioProtocol protocol = (Http11NioProtocol) connector.getProtocolHandler();
// 设置最大连接数
protocol.setMaxConnections(20000);// 10000
// 设置最大线程数
protocol.setMaxThreads(5000);// 200
// protocol.setConnectionTimeout(1); // 20s
protocol.setAcceptCount(1000); // 100
// protocol.setKeepAliveTimeout(1);
System.out.println("protocol.get:" + protocol.getMaxConnections());
}
实例数 | 7100 | 8000 |
---|---|---|
cpu | 749% | 790% |
mem | 17.7 | 18% |
conn | 12007 | 13690 |
Threads | 1982 | 1997 |
可以看出,在修改了tomcat对应配置,将最大连接数调至20000,线程数调至5000后,Eureka可注册的实例数突破了7000,连接数也突破了10000,实例数注册到8000后才开始报错,看出此时cpu已经接近满负载,操作系统本身调度已经压到极限,于是结束了本次测试。
结论:
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/
作者:宜信-技术研发中心-开发工程师-王猛
xujin
关注
发布 43 |
评论 15 |