Spring Boot 2.0的新特性解讀
Spring Boot 2.0在2018年3月份發布了2.0的版本,在這個版本當中,加入了非常多的新特性,既集成了Spring Framework5.0,也集成了響應式的Spring,針對Metrics以及configuration進行了一些改進和升級。
本文中,Pivotal雲計算資深架構師劉凡將針對下列內容為您詳盡的介紹這些新特性。旨在幫助大家更快地在項目當中運用到Spring Boot2.0。我們也在文中提供了視頻和PPT供您閱覽。
響應式編程和響應式Spring
Actuator/Actuator Endpoint
Metrics/Micrometer
Configuration Properties Binding
首先,我們先來看一組數據:
2017年,Spring Boot1.X下載量增長300%;
通過Initializer創建的項目增長了256%;
在Github上Java項目的forks,Spring Boot1.X排名第1;
Java項目的Stars排名第四;
超過四百名的社區貢獻者貢獻到Spring Boot這個項目。
Spring Boot對Framework以及Infrastructure的升級
講響應式編程之前,先看一下Spring Boot對Framework以及Infrastructure的升級。Spring Boot 2.0支持JAVA8+,如果想用JAVA7或者JAVA6的版本的話,可能只能用Spring Boot1.X。Spring Boot支持Tomcat8.5,支持Hibernate5.2,支持Thymeleaf3。
一、響應式編程和響應式Spring
Spring Boot最重要的一個Feature,就是響應式編程。去年年底,Pivotal發布了Spring5.0版本,在5.0裡面有一個很重要的Feature——響應式編程。在Spring Boot的發布當中,也集成了Spring5.0的版本。響應式編程主要是講效率的問題,如何通過並行計算來提高整個應用程序的效率。
響應式編程的由來
最初在單核CPU時代,各個晶元廠商都在增加晶元的製造工藝,增加更高的組頻。中央處理器到了一定階段後,廠商達到了製造的瓶頸。於是就有一個很好的創意和發明,他們創造了多核CPU:一個處理器當中放入了多個核心,以達到更好的並行計算的性能。
阿姆達定律
在固定負載下,通過增強系統並行計算能力所能帶來的性能加速S為:
所以在並行計算的領域,有一個非常重要的理論支撐——阿姆達爾定律。阿姆達爾定律主要內容指:在固定的負載下,通過增強系統並行計算的能力,所能達到的性能加速的加速S是這樣的一個公式。這個公式里,P是並行計算所佔比例;s為並行計算的節點數;S為並行計算處理效果加速比。當CPU的核數或者節點數趨於無窮的時候,加速比可以達到1/(1-P)。
通俗地講,系統的性能取決於最慢的那個可以做並行計算的能力。換而言之,只能做串型的,最慢的工作任務,會決定了整個系統能夠調優,包括改善它的性能達到最好的加速比的瓶頸。
在上面的曲線圖裡可以看到,橫軸是CPU的核數,縱軸是加速比。舉一個例子,假如說整個工作負載當中有95%是可以通過並行計算來進行加速的。那麼它在CPU的核數趨於無窮的時候,它最大的加速比是達到20倍。
阻塞+線程池(Servlet)
最初的時候使用Servlet做應用伺服器,它其實是一種阻塞+線程池的模式。簡單舉一個生活當中的例子來說明一下阻塞和非阻塞的區別。大家可能在生活當中有這樣的經歷:就是在周末的時候要燒水、要洗衣服。這兩件事情有兩種做法,一種就是先燒水,等水燒開了再去洗衣服。這種方式實際上就是阻塞的,每一個任務都需要前一個任務的完成才能進行自己的任務。那麼什麼是非阻塞呢?非阻塞的情況是:假設有一種水壺是響水壺,當水燒開了之後會發出聲音提醒我。洗衣機也有一種功能,它有一個提示音,如果洗衣服洗好了,就會發出提示音提示我。這時候我們就可以這樣做:把水放在燃氣爐上開始燒,同時也把衣服放到洗衣機里開始洗,這時候就可以不管了,坐在客廳里看電視。當我的水燒開了和洗衣機提示我了,我再去處理後面的事情。這種方式在編程模式來講就是非阻塞的。
Servlet模型就是這樣的,每一個request來了之後,在後台的Server都需要有一個線程去處理這個request。這樣帶來一個很大的問題:當有很多request的時候,並發量很大,很有可能container裡面的線程池就爆滿,從而導致應用伺服器崩潰。
非阻塞+事件循環(Netty)
所以如果使用非阻塞+事件循環,比較典型的應用伺服器是Netty,這實際上是非阻塞的模式,在中間有一個工作的線程池,這個工作線程池是用事件驅動的方式來處理客戶端發過來的request。對於應用伺服器來講,它是非同步的IO。在後台,當工作完成了這個任務之後,返回到中間的工作線程,然後把結果再返回到request當中去。這樣的話,極大的提高了並行計算的性能。
響應式編程的模型
舉個例子,如果想要完成捕捉滑鼠點擊大於等於2的事件,沒有用響應式編程,會發發現需要寫很多程序邏輯來實現這樣的一個大於等於2的點擊事件。如果我們用流的方式來處理的話,首先在時間序列上有很多個點擊事件,第一個操作取在250毫秒之間點擊的流,完成第一個操作之後會生成一個新的流,在這個流裡邊可以看見這個點擊操作按照250毫秒節流閥生成了一個新的流 。
然後再針對這個新形成的流,進行一次映射的操作。取到這個點擊流的長度後,又生成一個新的流。 最後我對這個流做一個過濾的操作,過濾操作就是我點擊次數大於等於2的,最終作為結果。
整個響應式編程都是基於流的操作。所以在響應式編程裡面有兩個非常重要的對象,一個是Subscriber作為觀察者,另一個是Publisher作為被觀察者。它們兩個是怎麼交互的呢?首先是觀察者需要去Subscribe被觀察者的動作,一旦被觀察者處理完了需要處理的工作任務之後,通過onNext把返回的結果返回到觀察者。這個地方onNext會帶上一個星, 代表它可以從0到N,從0到數據的整個傳輸完成。
在傳輸完成之後,會觸發一個事件叫onCompleted,即成功地把數據傳輸給被觀察者之後,如果說在傳輸數據當中有發生錯誤,就會觸發另外一個事件叫onError。在流當中如果一旦有onError的話,後續的操作都會中斷。所以,對於響應式編程,一共是兩個對象、四個事件。
響應式的Spring
響應式Spring作為Spring 5.0一個非常重要的Feature,去年發布出來之後,大家也做了很多研究。這裡簡單介紹一下響應式編程的Reactive Stream的關係。
Reactive Stream是2013年由Pivotal和Netflix、Lightbend三家公司提出來的響應式的Stream的標準。 看過Reactive的源碼的人知道,Reactive Stream只是定義了Publish和Subscriber的一個介面。
對於這個標準,不同的廠商有一個實現。比如說JDK9用flow去實現Publisher。而RxJAVA有observe和observable去實現訂閱和被訂閱者的關係。
今天主要介紹Pivotal的Reactive。這也是Spring5通過這個library來實現的響應式編程。除了這三個響應式庫之外,還有像市面上Akka等等,都是對Reactive Stream這個流實現的類庫。
接下來主要介紹一下Reactor當中響應式編程的模型。在Reactor里有兩個非常重要的對象,一個是Flux,一個是Mono。這兩個對象實際上都是前面講的Publisher的對象。
Flux
0…N個元素的集合
Flux代表的是0到N的元素的集合。如圖,在時間序列裡面一共放有六個元素,通過Flux發射出來,在最後第六元素有一個小豎線,代表這個流發射已經成功完成了。中間每一個流都可以對它做一些操作,在這個Operator裡面做的一些操作,生成一個新的流,可以發現在第三個傳輸完了之後,有一個叉。也就是說發生了一些異常,這個異常導致後面的這些數據就不能再生成到新的流裡邊了。
Flux這個模型非常適合於後台有資料庫的操作,會產生一些集合類,可以通過Flux這種模型去分裝list集合類。
Flux示例代碼
在Flux當中可以先去創建一個stream1的流,在這個流裡面加入三個整型的數據。然後對這個流做映射操作,把每一個數據都乘以2。最後再對這個流進行一個過濾的操作,就是只剩下大於2的元素。這個流其實最終輸出是4和6。
第二個創建一個字元串的流,在裡邊放了ABC三個字元串。接下來對stream1和stream2做一個zip操作,zip操作實際上就生成了一個元組的流。通過doOnNext,把原組裡面的數據一一列印出來,最終通過subscribe結束這個流。因為我們之前已經把1這個數據過濾掉了,最後的輸出應該是4a和6b。
除了zip的操作之外,還可以做兩個流的合併。通過merge的操作,最終可以得到的是4、6、a、b、c這樣的流。
接下來對比一下,如果使用傳統的編程模式,不使用響應式編程的模式,大概是什麼樣呢?
不使用響應式
假設需要做一個後端的http的操作,我們需要調用後台的一個rest的介面,通過這種阻塞式的,假如需要調用一千次http的介面,在後續的工作當中需要等待一千次調用結束了,才能夠做接下來的工作。
如果轉變成用響應式編程的模式來做,就會變成這樣:
使用響應式
range操作實際上就是做一個循環。通過flatMap去調用後端Reactive的方法,在後端也通過Reactive的方式去實現調用。把這個調用的結果發送到handlebody裡面去,這次調用一共只有一次調用,也不用等每一次調用完成就可以得到最後的結果。
大家會問,一千次的話,是不是後台真的是在Reactor裡面是同時去執行的?這裡面有一個默認值,因為Reactor是基於事件驅動的,也是在後台實現的時候是用了binding queue這種方式,所以它其實是有一個隊列在後面的。默認值實際上是有一個queue size 256。如果想去調整這個數值的話,可以多加一個新的參數。
Mono
0…1個元素
在Reactor裡面第二個對象是Mono。Mono表明的是0到1個元素。Mono的對象適用於什麼樣的場景呢?比如說需要通過一個ID去取到一個數據對象,獲取一個user的ID,通過ID取掉user本身。這個時候就比較適合用Mono這個對象。Mono也同樣是我們放入一個元素,正常的話是complete,出錯的話就是一個叉,onError的事件。
Mono示例代碼
Mono的示例代碼跟Flux非常像,需要注意的地方是,在just後面只能放入一個元素,假如放入多個元素的話,這個地方編譯就報錯了。
Spring MVC 還是 WebFlux?
在Spring Boot1.0的時候,很多開發人員多在用Spring MVC。Spring Boot2.0以後,可能會有一個問題:我還可不可以用MVC?是不是要把原有的代碼都用WebFlux去改寫它?用這種響應式編程的模型去改寫它?
Spring MVC 還是 WebFlux?
其實在Spring Boot2.0裡面,不需要改寫原有的Spring MVC,因為像@Controller等等這些註解,在Spring WebFlux,同樣可以支持這些註解。所以不用擔心要把原有的這些Controller或者這些註解都統統改寫成WebFlux。
在WebFlux里新增了一些內容,比如說Functional endpoint。 WebFlux的應用伺服器默認使用的是Netty。當然如果大家有想用TomCat等其他的響應式的伺服器的話,也是可以通過配置把它設置到用TomCat或者其他的伺服器。
WebFlux用這種非阻塞的模式,給我們帶來的不光是性能的提升,更多的是可以通過這種方式得到更好的擴展性。因為它是基於事件驅動,事件驅動把不同的系統之間可以更好的解耦,後期進行擴展會更加容易,同時還可以繼續使用原來熟悉的這些註解。
Spring WebFlux示例代碼
上圖是一段Spring WebFlux的示例代碼。可以看到,在這個示例代碼當中使用了剛才提到的這兩個對象,一個是Flux,一個是Mono。對於Mono來講,是通過ID的方式,因為ID一般都是資料庫的主鍵,所以它應該是要麼返回0個元素,要麼返回1個元素,也就是說,要麼找到了這個元素,要麼沒有找到,所以它返回的是一個Mono的對象。
有一個比較特殊的,如果調用資料庫的save的方法,其實save沒有任何的返回池,可以用Mono void的方式去替換這種空的返回值。這是在應用伺服器端的。
如果是對於數據的響應式的API的話,現在Reactive data的這種API,目前能支持的資料庫是四個,包括MongoDB、Redis等等。有一些關係型資料庫目前還不能支持Reactive的方式。
Spring WebFlux.fn,fn指的是function。在WebFlux裡面可以支持函數式的介面。函數式的介面是一個定義的路由,在這個路由實現的時候,這段代碼可以完全等價於RequestMapping。前期寫了很多的怎麼去getMapping、postMapping等等,可以通過這麼幾行代碼就可以完全等價的實現,它的代碼量是非常少的。而且如果熟悉這種方式的話,它的可讀性也是非常強的。
Router裡面有兩個參數,一個參數是RequestPredicate,另外一個是HandleFunction。HandleFunction需要提供一個Server的Request,作為它的輸入。它的輸出是一個Mono的ServerResponse對象。執行完了之後,會返回到客戶端。
定義了多個訪問的路徑,可以通過它來查詢整個list資料庫,也可以去推送、創建一個新的person。
WebClient
上文提到的WebFlux實際上是在伺服器端的,在客戶端Spring也提供了響應式的客戶端,就是WebClient。相對比RestTemplate,在Spring MVC可能會用RestTemplate來作為http去調用Server端的服務。
相比RestTemplate, WebClient有以下的不同:
非阻塞、響應式,使用更少的硬體資源提供更高的並發量
基於Java 8 lambdas提供了functional API
同時支持同步和非同步的場景
支持流操作
首先它是非阻塞的、響應式的,而且使用了比較少的硬體資源,來提供更高的並發量。它是基於JAVA8的Lambda的表達式,提供了Functional API。所以當使用Functional API去完成一大段代碼的時候,還是很精鍊的,看到的代碼也會比較優雅。同時它可以支持同步和非同步的場景,支持流的這些操作。
WebClient的示例代碼
這是一段WebClient的示例代碼,通過WebClient最終可以通過ID的方式去取到一個Mono的person,通過一個/Quotes的方法,去得到一個Flux這樣一個對象。
Spring Boot 2.0 on Pivotal Cloud Foundry
在開發運維的時候,很需要一個運維平台,所以PCF(Pivotal Cloud Foundry)作為一個PaaS平台,很好地支持了Spring Boot 2.0運行在這個平台上面。
這個平台讓你可以很容易去部署雲原生的Spring Boot應用。並且是一鍵式的,只要敲一個命令,不用進行任何的配置,就可以把應用推到這個平台上去。推上去了之後,可以去重啟應用,可以去停止,也可以在這個平台上看到這個應用的一些啟動或者一些事件,還有使用的一些服務、路由以及日誌。
響應式編程會給我們編碼帶來哪些好處呢?
代碼可讀性高
生產者消費者之間控制流的最佳實踐,有效避免內存溢出
較少的線程,使IO任務可以運行在非同步/非阻塞的模式
交互性強、實時性高
良好的擴展性,利用動作/事件觸發notification給下游系統
響應式編程會主要是用在哪些場景裡面呢?
適合高並發消息處理的場景
有延遲的遠程調用
大量慢客戶端連接
往客戶端推送消息
實時資料庫查詢
UI事件實時處理
大數據
實時數據分析
HTTP/2
比如說有高並發的,客戶端是慢客戶端,有比較大延遲的遠程調用。如果有實時的資料庫查詢,還有大數據的場景,都可能比較適合用響應式編程去完成大家的應用程序的開發。
二、Actuator和Actuator Endpoint
Actuator是Spring Boot提供給開發人員去做監控、運維,以及可以通過Actuator Endpoint去跟Spring Boot應用做一些交互的組件。在Actuator裡面,Spring Boot2.0也支持了三種模式,一個是Spring MVC,一個是Service WebFlux,一個是Jersey。
用Spring Boot2.0 Actuator也很簡單,只要Maven裡面加入這樣一個依賴,Gradle的話用這樣的dependencies。
在Spring Boot2.0Actuator,Endpoint做了一些更新和升級,有一些是新增的,像普羅米修斯這樣的Endpoint以及Scheduledtasks、Sessions,這三個是新增的Endpoint。也有一些進行了一些更名。
Health Endpoints
在Actuator經常用到的幾個端點,一個是Health為這個端點,去判斷應用程序的健康狀況。這個端點如果有定製化的需要,可以去實現Health Indicator這樣的介面,去返回一個Health對象,在這兒做具體的邏輯、去判斷這個服務或者Component是不是健康的。
如果說要用響應式的方式來返回,可以去繼承ReactiveHealthIndicator這個介面,它返回的就是一個Mono的Health的對象。
在Spring Boot2.0,HealthIndicator,我們提供的主要是支持Spring MVC的。對於Actuator主要有兩個,一個是Mongo,一個是Redis。
三、Metrics/Micrometer
Spring Boot2.0用了一個新的組件Micrometer替換了原來的Metrics來進行實現。Micrometer是一個外置的監控的Metrics組件,主要是給Metrics提供一個統一的介面,來為其監控系統作為一個門面。這個非常類似SLF4J,SLF4J是給日誌提供的統一介面。而Micrometer是給Metrics(應用的度量指標)提供了一個統一的介面。通過Micrometer可以外接很多監控系統,比如說現在支持的Prometheus、Netflix Atlas、Datadog、InfluxDB。
支持的監控系統
Micrometer也支持多維度的應用度量指標,從上圖我們可以看到在Micrometer里可以支持Dimensional的監控系統,這是多維度的,通過定義你的一些標籤、名稱,去定義一些新的維度。假設可以對CPU或者內存使用率取最大值,通過一個表達式來生成一個新的Metrics,在監控系統裡面可以自定義這些維度。不同的監控系統也有自己的取數據的方式,可以有poll的方式,可以有push的方式。大家有興趣的話,或者在項目當中有需要的話,可以去集成這些外置的監控系統。
Prometheus的集成
對於Prometheus的集成也非常簡單,只要在文件裡面加入一個dependency,這個dependency加進去了之後,在Actuator-Prometheus,就可以看到Prometheus需要的Spring Boot應用的數據了。
四、Configuration Properties Binding
最後我們介紹一下對於Configuration Properties Binding的改進。
但是這裡面也有一定的規則,首先兩個元素之間需要用點去分割,只能用字母或者是數字。單詞可以用橫線或者下劃線去分開。如果需要取一些系統的環境變數,其實在系統環境變數是不能用點的,可以用下劃線的方式去替換這個點。如果系統環境變數裡面想放一些數組,也可以通過下劃線加上它的下標去放數組。這樣的話可以保證Configuration Binding的行為和集合類的行為保持一致。
對於Security,現在默認的是會打開我們的Security,當然也可以通過你的配置去進行Security的配置。
本文作者:劉凡,Pivotal大中華區雲計算資深架構師,長期從事軟體研發和技術創新工作,曾先後就職於石化盈科、Adobe中國研發中心、IBM等大型國內外IT企業,從事軟體產品研發,系統架構設計,研發管理等工作。在十多年的軟體行業從業經歷中,積累了豐富的分散式系統架構設計、自動化平台運維和系統穩定性調優等相關經驗。近期主要專註於微服務雲原生應用的開發和設計,支持多個知名客戶企業進行數字化轉型,對敏捷開發和傳統巨石應用拆分以及往雲上遷移擁有豐富的實戰經驗。
↓↓↓
※GemFire與Greenplum的最佳集成實踐之實施經驗談
※Pivotal數據科學了解一下
TAG:Pivotal |