轉至:https://blog.csdn.net/w1014074794/article/details/88571880
本文針對公司微服務併發的實際場景以及網上調研的資料,記錄影響微服務併發的各種優化配置。
先說明線上調用的實際例子:
通過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傳遞的耗時。
你的系統架構中,只要涉及到了重試,那麼必須上接口的冪等性保障機制。
否則的話,試想一下,你要是對一個接口重試了好幾次,結果人家重複插入了多條數據,該怎麼辦呢?
其實冪等性保證本身並不複雜,根據業務來,常見的方案:
可以在數據庫裏建一個唯一索引,插入數據的時候如果唯一索引衝突了就不會插入重複數據
或者是通過redis裏放一個唯一id值,然後每次要插入數據,都通過redis判斷一下,那個值如果已經存在了,那麼就不要插入重複數據了。
類似這樣的方案還有一些。總之,要保證一個接口被多次調用的時候,不能插入重複的數據。
更多資料:
https://blog.csdn.net/qq_30353203/article/details/76690272
https://www.cnblogs.com/xingzc/p/5778010.html
https://www.cnblogs.com/killerqi/p/10907344.html