[譯]Introduction to Concurrency in Spring Boot
當我們使用springboot
構建服務的時候需要處理併發。一種錯誤的觀念認為由於使用了Servlets
,它對於每個請求都分配一個執行緒來處理,所以就沒有必要考慮併發。在這篇文章中,我將提供一些建議,用於處理springboot
中的多執行緒問題以及如何避免一些可能導致的情況。
spring boot 併發基礎
當我們考慮springboot
應用的併發的時候需要考慮的關鍵點有一下幾個:
- 最大執行緒數-也就是應用於處理請求的最大執行緒數
- 共享外部資源-外部共享資源的呼叫,比如資料庫
- 非同步方法呼叫-當某些呼叫等待返回的時候會釋放執行緒到執行緒池中
- 共享內部資源-內部資源的呼叫,比如說快取和共享的應用狀態
接下來我們會一個一個地解釋它們,看看他們是從哪些方面影響我們使用springboot
編寫應用程式的。
springboot應用中的最大執行緒數
第一個需要引起注意的就是你正在使用的是有限的執行緒數量。
如果你使用的是Tomcat
作為嵌入式伺服器(預設),那麼可以使用server.tomcat.max-threads
屬性來控制允許的執行緒數。它預設被置為0,這意味著預設使用的是tomcat
本身的預設值200
。
知道這一點很重要,因為你可能需要增加這個數字以更有效地處理服務提供的資源。在處理外部資源時也會出現問題......
關於共享外部資源的問題
呼叫資料庫和其他REST介面會導致可觀的時間開銷。
剛剛對於執行緒數的限制意味著你真的希望避免執行太久的、太慢的、同步的請求。如果你正在等待一些很慢的處理完成而持有執行緒,那麼你很可能沒有充分利用伺服器。
如果存在很多長時間執行的執行緒在等待返回,你可能最終會遇到這樣一種情況:真正快速、簡單的請求等待很長時間,“永遠等待”直到被終止。
那麼怎麼優化這種情況呢?
非同步方法呼叫來拯救
一個通常很有用的方法是在一次請求中請求多個數據。理想情況下,如果你需要呼叫三個服務:A、B和C;你不會希望用以下方式:
- 呼叫A
- 等待A的返回
- 呼叫B
- 等待B的返回
- 呼叫C
- 等待C的返回
- 把A、B、C的返回組合起來然後結束處理
如果每個服務需要花費3秒鐘返回,那麼整個處理過程需要9秒。更好的處理方式應該是這樣:
- 呼叫A
- 呼叫B
- 呼叫C
- 等待A、B、C三個服務的返回
- 把A、B、C的返回組合起來然後結束處理
這種方式中,請求三個服務的過程中不需要等待某個服務完成。如果假設A、B、C三個服務沒有互相依賴的話,等待返回只需要3秒。
非同步和響應式微服務的概念本身就十分有趣。我推薦閱讀以下文章:
The reactive section of this blog, especially Getting Reactive with Spring
- The reactive manifesto
- Spring Boot 2 and WebFlux
- Project Reactor by Pivotal
- Eclipse Vert.X – reactive microservices
ReactiveX (RxJava)
這些話題都非常引人入勝,但是現在我們繼續關注Spring Boot......
在springboot中使用非同步呼叫
你是怎麼在springboot
中開啟非同步方法呼叫的呢?首先可以在標註有@SpringBootApplication
註解的應用類上使用@EnableAsync
註解。
應用該註解之後,就可以在返回CompletableFuture<>
的服務中使用@Async
註解。因為使用了@EnableAsync
之後,@Async
標註的方法將會執行在後臺的執行緒池中。
如果利用好了非同步執行,在效能上將可以避免很多不必要的坑,讓你的服務更快、更好的響應。
對於這方面在springboot中的詳細實現我非常推薦這篇文章the example from the official Spring website
共享內部資源
雖然前面的部分討論的是我們通常無法控制的外部資源,但是系統的內部資源卻是我們能夠完全控制的。
知道這一點後,避免共享資源所導致的問題的最佳建議就是——不要共享他們。
Spring的Services
層和Controller
層預設是單例的,這一點需要尤其關注和小心。一旦在你的服務中涉及到可變狀態那麼你就需要像處理其他標準應用一樣處理它。
其他潛在的共享資源有快取、自定義的伺服器範圍元件(通常是監控和安全元件)。
如果你不可避免的需要共享一些狀態,我提供如下建議:
- 使用不可變物件。如果你的物件是不可變的這樣可以避免很多併發相關的錯誤。如果需要改變某些狀態,直接建立一個新的物件就是了。
- 瞭解你的集合類。並不是所有的集合類都是執行緒安全的,一個通常的陷阱就是把HashMap當成執行緒安全的來使用(如果需要併發訪問,那麼應該使用ConcurrentHashMap或者HashTable獲取其他執行緒安全的解決方案)
- 不要假設第三方庫是執行緒安全的。大多數程式碼都不是,並且必須控制對共享狀態的訪問。
- 如果你打算依賴共享--可以適當的學習一下併發相關的知識。我非常強烈的推薦《Java Concurrency in Practice》。雖然是2006年寫的,但是在現在任然很實用。
總結
spring中的併發和多執行緒是非常重要的話題。在這篇文章中,我想強調一些當你編寫springboot應用需要注意的關鍵領域。如果你想成功地構建高質量、高需求的服務,你需要圍繞這個話題做出仔細地考量和權衡。我希望這篇文章能夠成為一個很好的入門。
PS:本文翻譯自https://www.e4developer.com/2018/03/30/introduction-to-concurrency-in-spring-bo