spring cloud性能调优

本文针对公司微服务并发的实际场景以及网上调研的资料,记录影响微服务并发的各类优化配置。java

先说明线上调用的实际例子:
经过zuul网关 调用服务A的接口,服务A的接口里面经过Feign调用服务B的接口。web

问题:
经过JMeter并发测试发现,并发数居然没有达到30次/s,即QPS不到30。这显然不合理。redis

备注:
TPS(吞吐量) 系统在单位时间内处理请求的数量。
QPS(每秒查询率) 每秒的响应请求数spring

第一步:熔断器并发调优
首先想到的是Feign调用并发过大,致使的熔断问题,优化服务A中的熔断配置
hystrix.command.default.fallback.isolation.semaphore.maxConcurrentRequests 若是并发数达到该设置值,请求会被拒绝和抛出异常而且fallback不会被调用。默认10
果真,hystrix在semaphore隔离方案下,最大的并发默认是10。
优化配置:数据库

#线程策略
hystrix.command.default.execution.isolation.strategy=SEMAPHORE
hystrix.command.default.execution.isolation.semaphore.maxConcurrentRequests=500

第二步:Zuul并发调优
经历了将熔断器执行线程并发设置为500后,继续用JMeter进行并发测试,结果QPS到达100后,又出现大量请求失败。
查看日志,发现zuul不少请求链接关闭。json

优化配置:api

#zuul网关配置
zuul.semaphore.max-semaphores=500

再次使用JMeter测试,发现并发500没有在出现问题。tomcat

以上2个就是spring cloud并发调优最核心的2个参数。服务器

下面系统说下spring cloud工程调优的问题
主要从如下几个方面入手:
一、hystrix熔断器并发调优
二、zuul网关的并发参数控制
三、Feign客户端和链接数参数调优
四、Tomcat并发链接数调优
五、timeout超时参数调优
六、JVM参数调优
七、ribbon和hystrix的请求超时,重试以及幂等性配置
网络

下面说明下具体调优参数:

hystrix熔断器调优

表示HystrixCommand.run()的执行时的隔离策略,有如下两种策略

1 THREAD: 在单独的线程上执行,并发请求受线程池中的线程数限制
2 SEMAPHORE: 在调用线程上执行,并发请求量受信号量计数限制
在默认状况下,推荐HystrixCommands 使用 thread 隔离策略,HystrixObservableCommand 使用 semaphore 隔离策略。
只有在高并发(单个实例每秒达到几百个调用)的调用时,才须要修改HystrixCommands 的隔离策略为semaphore 。semaphore 隔离策略一般只用于非网络调用。
说明:高并发时,优先使用semaphore 。

hystrix.threadpool.default.coreSize=10
hystrix.threadpool.default.maximumSize=10
hystrix.threadpool.default.maxQueueSize=-1

#如该值为-1,那么使用的是SynchronousQueue,不然使用的是LinkedBlockingQueue。注意,修改MQ的类型须要重启。例如从-1修改成100,须要重启,由于使用的Queue类型发生了变化
若是想对特定的 HystrixThreadPoolKey 进行配置,则将 default 改成 HystrixThreadPoolKey 便可。

若是隔离策略是SEMAPHORE:

hystrix.command.default.execution.isolation.strategy=SEMAPHORE
hystrix.command.default.execution.isolation.semaphore.maxConcurrentRequests=10# 默认值

若是想对指定的 HystrixCommandKey 进行配置,则将 default 改成 HystrixCommandKey 便可。

zuul参数控制

咱们知道Hystrix有隔离策略:THREAD 以及SEMAPHORE ,默认是 SEMAPHORE 。
查询资料发现是由于zuul默认每一个路由直接用信号量作隔离,而且默认值是100,也就是当一个路由请求的信号量高于100那么就拒绝服务了,返回500。

线程池提供了比信号量更好的隔离机制,而且从实际测试发现高吞吐场景下能够完成更多的请求。可是信号量隔离的开销更小,对于自己就是10ms之内的系统,显然信号量更合适。

当 zuul.ribbonIsolationStrategy=THREAD时,Hystrix的线程隔离策略将会做用于全部路由。
此时,HystrixThreadPoolKey 默认为“RibbonCommand”。这意味着,全部路由的HystrixCommand都会在相同的Hystrix线程池中执行。可以使用如下配置,让每一个路由使用独立的线程池:

zuul:
  threadPool:
    useSeparateThreadPools: true

只有在隔离策略是thread才有效

1. 隔离策略

zuul.ribbon-isolation-strategy=thread

2. 最大信号
当Zuul的隔离策略为SEMAPHORE时:
全局设置默认最大信号量:

zuul.ribbon-isolation-strategy=Semaphore
zuul:
  semaphore:
    max-semaphores: 100 # 默认值

对路由linkflow和oauth单独设置最大信号量

routes:
    linkflow:
      path: /api1/**
      serviceId: lf
      stripPrefix: false
      semaphore:
        maxSemaphores: 2000
    oauth:
      path: /api2/**
      serviceId: lf
      stripPrefix: false
      semaphore:
        maxSemaphores: 1000

3.zuul并发链接参数
针对url的路由配置

zuul:
  host:
    max-total-connections: 200 # 默认值
    max-per-route-connections: 20 # 默认值

针对serviceId的路由配置

serviceId:
  ribbon:
    MaxTotalConnections: 0   # 默认值
    MaxConnectionsPerHost: 0 # 默认值

Feign参数调优

在默认状况下 spring cloud feign在进行各个子服务之间的调用时,http组件使用的是jdk的HttpURLConnection,没有使用线程池。本文先从源码分析feign的http组件对象生成的过程,而后经过为feign配置http线程池优化调用效率。
有种可选的线程池:HttpClient和OKHttp
我的比较推荐OKHttp,请求封装的很是简单易用,性能也很ok。

当使用HttpClient时,可以下设置:

feign.httpclient.enabled=true
feign.httpclient.max-connections=200# 默认值
feign.httpclient.max-connections-per-route=50# 默认值

代码详见:

org.springframework.cloud.netflix.feign.FeignAutoConfiguration.

HttpClientFeignConfiguration#connectionManager

org.springframework.cloud.netflix.feign.ribbon.HttpClientFeignLoadBalancedConfiguration.

HttpClientFeignConfiguration#connectionManager

当使用OKHttp时,可以下设置:

feign.okhttp.enabled=true
feign.okhttp.max-connections=200# 默认值
feign.okhttp.max-connections-per-route=50# 默认值

代码详见:

org.springframework.cloud.netflix.feign.FeignAutoConfiguration.

OkHttpFeignConfiguration#httpClientConnectionPool 。

org.springframework.cloud.netflix.feign.ribbon.OkHttpFeignLoadBalancedConfiguration.

OkHttpFeignConfiguration#httpClientConnectionPool

tomcat调优

若是使用的是内嵌的tomcat保持默认就好

server.tomcat.max-connections=0 # Maximum number of connections that the server accepts and processes at any given time.
server.tomcat.max-http-header-size=0 # Maximum size, in bytes, of the HTTP message header.
server.tomcat.max-http-post-size=0 # Maximum size, in bytes, of the HTTP post content.
server.tomcat.max-threads=0 # Maximum number of worker threads.
server.tomcat.min-spare-threads=0 # Minimum number of worker threads.

因为默认的最大链接数,最大线程数都是0,没有限制,因此在spring boot中启动内嵌的tomcat,通常保持默认的配置就能够了。

JVM参数调优

关于Jvm调优Oracle官网有一份指导说明:
Oracle官网对Jvm调优的说明
有兴趣你们能够去看看。

执行启动设置Jvm参数的操做。

java   -Xms1024m -Xmx1024m -XX:MetaspaceSize=128m -XX:MaxMetaspaceSize=128m  -Xmn256m -Xss256k -XX:SurvivorRatio=8 -XX:+UseConcMarkSweepGC    -jar   user-1.0.0.jar

关于这些设置的JVM参数是什么意思,请参考第二步中的oracle官方给出的调优文档。

我在这边简单说一下:
-XX:MetaspaceSize=128m (元空间默认大小)
-XX:MaxMetaspaceSize=128m (元空间最大大小)
-Xms1024m (堆最大大小)
-Xmx1024m (堆默认大小)
-Xmn256m (新生代大小)
-Xss256k (棧最大深度大小)
-XX:SurvivorRatio=8 (新生代分区比例 8:2)
-XX:+UseConcMarkSweepGC (指定使用的垃圾收集器,这里使用CMS收集器)
-XX:+PrintGCDetails (打印详细的GC日志)

请求的超时,重试,以及幂等性

#配置首台服务器重试1次
ribbon.MaxAutoRetries=1
##配置其余服务器重试1次
ribbon.MaxAutoRetriesNextServer=1
##获取链接的超时时间
ribbon.ConnectTimeout=1000
###请求处理时间
ribbon.ReadTimeout=1000
##每一个操做都开启重试机制
ribbon.OkToRetryOnAllOperations=true

#开启Feign请求压缩
feign.compression.request.enabled=true
feign.compression.request.mime-types=text/xml,application/xml,application/json
feign.compression.request.min-request-size=2048
feign.compression.response.enabled=true
#配置断路器超时时间,默认是1000(1秒)
feign.hystrix.enabled=true
#feign use okhttp
feign.httpclient.enabled=false
feign.okhttp.enabled=true
#是否开启超时熔断, 若是为false, 则熔断机制只在服务不可用时开启
hystrix.command.default.execution.timeout.enabled=true
hystrix.command.default.execution.isolation.thread.timeoutInMilliseconds=10000

请求在1s内响应,超过1秒先同一个服务器上重试1次,若是仍是超时或失败,向其余服务上请求重试1次。
那么整个ribbon请求过程的超时时间为:
ribbonTimeout = (ribbonReadTimeout + ribbonConnectTimeout) * (maxAutoRetries + 1) * (maxAutoRetriesNextServer + 1);
ribbonTimeout = (1000 + 1000) * (1 + 1) * (1 + 1) = 8000
因为Hystrix timeout必定要大于ribbonTimeout 超时,因此
hystrix.command.default.execution.isolation.thread.timeoutInMilliseconds>8000

超时设置是为了防止某些耗时操做积压在线程池中,致使后续请求没法进行,压爆服务器。
重试是为了防止网络抖动等缘由出现偶然性异常的自动补偿机制,不过这时必定要保证全部接口的幂等性。
Feign请求压缩是为了减小网络IO传递的耗时

你的系统架构中,只要涉及到了重试,那么必须上接口的幂等性保障机制。
不然的话,试想一下,你要是对一个接口重试了好几回,结果人家重复插入了多条数据,该怎么办呢?
其实幂等性保证自己并不复杂,根据业务来,常见的方案
能够在数据库里建一个惟一索引,插入数据的时候若是惟一索引冲突了就不会插入重复数据
或者是经过redis里放一个惟一id值,而后每次要插入数据,都经过redis判断一下,那个值若是已经存在了,那么就不要插入重复数据了。
相似这样的方案还有一些。总之,要保证一个接口被屡次调用的时候,不能插入重复的数据。

Spring Cloud各组件调优参数