本文針對公司微服務并發的實際場景以及網上調研的資料,記錄影響微服務并發的各種優化配置。
先說明線上調用的實際例子:
通過zuul網關 調用服務A的接口,服務A的接口裡面通過Feign調用服務B的接口。
問題:
通過JMeter并發測試發現,并發數竟然沒有達到30次/s,即QPS不到30。這顯然不合理。
備注:
TPS(吞吐量) 系統在機關時間内處理請求的數量。
QPS(每秒查詢率) 每秒的響應請求數
第一步:熔斷器并發調優
首先想到的是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很多請求連接配接關閉。
優化配置:
# zuul網關配置
zuul.semaphore.max-semaphores=500
再次使用JMeter測試,發現并發500沒有在出現問題。
以上2個就是spring cloud并發調優最核心的2個參數。
下面系統說下spring cloud工程調優的問題
主要從以下幾個方面入手:
1、hystrix熔斷器并發調優
2、zuul網關的并發參數控制
3、Feign用戶端和連接配接數參數調優
4、Tomcat并發連接配接數調優
5、timeout逾時參數調優
6、JVM參數調優
7、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才有效
- 隔離政策
zuul.ribbon-isolation-strategy=thread
-
最大信号
當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# 預設值
1
2
3
代碼詳見:
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傳遞的耗時。