[譯]Introduction to Concurrency in Spring Boot
- 2019 年 10 月 3 日
- 筆記
當我們使用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-boot/