情景引入
很早之前埂息,Java就火起來了皮璧,是因?yàn)樗朴陂_發(fā)和處理網(wǎng)絡(luò)方面的應(yīng)用舟扎。
Java有一個(gè)愛好,就是喜歡制定規(guī)范標(biāo)準(zhǔn)悴务,但自己又不善于去實(shí)現(xiàn)睹限。
反倒是一些服務(wù)提供商使用它的規(guī)范標(biāo)準(zhǔn)來制造應(yīng)用服務(wù)器而賺的盆滿缽滿。
企業(yè)用戶因要使用這些應(yīng)用服務(wù)器而向提供商支付高額費(fèi)用讯檐,而且也不是特別好用邦泄。
一個(gè)青年才俊為了打破這種局面而奔走呼號(hào)、奮發(fā)圖強(qiáng)裂垦。
自我介紹
顯然顺囊,這個(gè)青年才俊就是后來的Spring。
因企業(yè)應(yīng)用大都和web相關(guān)蕉拢,而Java的web標(biāo)準(zhǔn)中較核心的一部分其實(shí)就是JavaEE里的Servlet特碳。
Spring和Servlet“相親相愛”一番后,我就來到了這個(gè)世界晕换。我的全名叫Spring MVC午乓,這里的Spring既是我的姓也是我的“爸爸”,那Servlet就是我的“媽媽”了闸准,大家叫我MVC就行了益愈。
那個(gè)年代社會(huì)很落后,條件也不好夷家,好歹我們要求也不高蒸其,求個(gè)溫飽就行了。
所以我的媽媽Servlet和她的閨蜜Filter天生就是同步阻塞的库快,包括她們同事HttpServletRequest的getParameter摸袁,getPart等這些方法也都是阻塞的。
雖然我的爸爸Spring給了我23條染色體來進(jìn)行改良义屏,但不要忘了我還從Servlet媽媽那里繼承了23條靠汁,所以我也是同步阻塞的。不過我的“長(zhǎng)相”已經(jīng)好看很多了闽铐,因?yàn)镾pring爸爸知道蝶怔,在以后的日子里,除了拼實(shí)力之外兄墅,顏值也是非常重要的踢星。
因?yàn)槲覌寢孲ervlet是一個(gè)規(guī)范,我爸爸Spring是一個(gè)框架察迟,所以我跟他們一樣斩狱,都是無法自己獨(dú)立運(yùn)行的。
所以在我們要運(yùn)行的時(shí)候扎瓶,必須要尋找一個(gè)特殊的“家”所踊,通常稱它為Servlet容器,比如tomcat就算非常知名的一個(gè)概荷。
Servlet容器熟知我極有可能阻塞當(dāng)前執(zhí)行線程秕岛,所以專門量身打造。它給我準(zhǔn)備了一個(gè)非常大的線程池误证,里面有好多線程继薛。每過來一個(gè)請(qǐng)求,它就扔給我一個(gè)線程愈捅,說自己玩去吧遏考,隨便“折騰”。
好在那時(shí)美國(guó)那個(gè)叫喬布斯的家伙被自己的公司趕出去在外面“流浪”蓝谨,Servlet容器為我量身打造的這種方法完全能夠勝任日常灌具,關(guān)鍵還非常的簡(jiǎn)單。
這種小富即安的日子就這樣往前過著譬巫。
兄弟出生
生命不息咖楣,變化不止。隨著喬布斯推出iphone芦昔,智能機(jī)瞬間大火诱贿,全民進(jìn)入移動(dòng)互聯(lián)網(wǎng)時(shí)代。激增的網(wǎng)民數(shù)量咕缎,給現(xiàn)有軟件架構(gòu)帶來極大的挑戰(zhàn)珠十。
一般來說,社會(huì)越發(fā)達(dá)凭豪,分工越精細(xì)宵睦,對(duì)單一工種的要求就越高。
軟件也是如此墅诡,在傳統(tǒng)“大塊頭”軟件表現(xiàn)的越來越格格不入的時(shí)候壳嚎,微服務(wù)就如一絲春風(fēng)吹了進(jìn)來。
按它的指導(dǎo)原則末早,將大軟件按某種方式拆分為一個(gè)個(gè)小工程烟馅。小工程規(guī)模小,便于管理然磷,而且機(jī)動(dòng)性也好郑趁,功能聚合性更好。它承受的并發(fā)應(yīng)該更高姿搜。
有人覺得與微服務(wù)比起來寡润,過去使用的web服務(wù)器如tomcat略顯笨重捆憎,不夠輕量級(jí)。也有人說tomcat內(nèi)部一個(gè)請(qǐng)求一個(gè)線程這種阻塞執(zhí)行方式消耗太多線程梭纹,不太容易支撐超高并發(fā)躲惰。
無論怎么說,簡(jiǎn)而言之一句話变抽,一個(gè)全新的時(shí)代已經(jīng)到來础拨。
此時(shí)我們需要一個(gè)更加輕量級(jí)web應(yīng)用,它使用更少的硬件資源和線程绍载,反而更容易處理高并發(fā)诡宗。那么它一定是異步非阻塞的。
這樣的使命自然落到了響應(yīng)式編程的范疇上了击儡。所以我的爸爸Spring審時(shí)度勢(shì)塔沃,在5.0之后就趕緊把我推出來了。
沒錯(cuò)阳谍,我就是Spring WebFlux芳悲,這里的Spring既是我的姓也是我爸爸。大家可以叫我WebFlux边坤。初來乍到名扛,好多人都對(duì)我不熟悉,請(qǐng)容許我介紹一番茧痒。
首先這個(gè)響應(yīng)式究竟是什么意思呢肮韧?響應(yīng)式這個(gè)術(shù)語,指的是一個(gè)編程模型旺订,它是圍繞著對(duì)變化的反映來構(gòu)建的弄企。
如網(wǎng)絡(luò)組件用來響應(yīng)I/O事件,UI控制器用來響應(yīng)鼠標(biāo)事件等等区拳。按照這種意識(shí)的話拘领,非阻塞就是響應(yīng)式的,對(duì)操作完成或數(shù)據(jù)可用通知事件的響應(yīng)方式樱调。
另外一個(gè)關(guān)于響應(yīng)式的機(jī)制是非阻塞后壓约素。在命令式代碼中,同步阻塞調(diào)用帶有自然的后壓迫使調(diào)用者等待笆凌。
在異步代碼中圣猎,它變得非常重要,用來控制事件的速率乞而,以至于不讓一個(gè)快速的事件源壓垮它的響應(yīng)者送悔。就是響應(yīng)者能夠控制事件源發(fā)射事件的快慢。
因?yàn)轫憫?yīng)式編程是非阻塞的,所以我也是非阻塞的欠啤,因此我通常運(yùn)行在非阻塞web服務(wù)器上荚藻,如Netty,Undertow等洁段。
因?yàn)槲也粫?huì)阻塞線程的執(zhí)行应狱,所以使用一個(gè)小的固定數(shù)量的線程池(event loop workers)來處理請(qǐng)求。典型地眉撵,線程數(shù)與CPU的核數(shù)相同侦香。
這里還要感謝我的姥爺Java 8落塑,他老人家引入了lambda表達(dá)式造就了函數(shù)式編程API纽疟。這對(duì)于非阻塞應(yīng)用和連續(xù)式API來說是一個(gè)非常棒的東西,允許以聲明的方式把異步邏輯組合起來憾赁。
我感覺我的爸爸Spring已經(jīng)超越了一個(gè)框架污朽,成為一個(gè)平臺(tái)了。所以他自己并沒有親自去實(shí)現(xiàn)響應(yīng)式處理龙考,而是為我選擇Reactor作為響應(yīng)式庫(kù)蟆肆。
Reactor提供Flux和Mono類型,擁有豐富的操作符晦款,支持非阻塞后壓炎功,使用函數(shù)式API來組合異步邏輯。并且Reactor強(qiáng)烈聚焦于Java服務(wù)器端缓溅。它在開發(fā)時(shí)就已經(jīng)與爸爸Spring親密協(xié)作了蛇损。
爸爸說,我也支持其它的庫(kù)如RxJava坛怪,但看樣子似乎讓我更愛Reactor一些淤齐。
這就是我,WebFlux袜匿,一個(gè)集天時(shí)地利于一身的幸運(yùn)兒更啄。但你是不是已經(jīng)暈暈的啦,沒關(guān)系居灯,慢慢來祭务。
包羅萬象
我想,大家都看出了我爸爸Spring的野心怪嫌,他不僅要成為一個(gè)平臺(tái)待牵,還要建起自己的生態(tài)系統(tǒng),豎起壁壘喇勋。
所以他的核心事業(yè)就是進(jìn)行抽象缨该,組合和裝配,進(jìn)而包羅萬象川背。說的掉渣一些贰拿,就是哪個(gè)技術(shù)好蛤袒,就給它整合進(jìn)來。
為了抹平底層不同web服務(wù)器的差異膨更,我爸爸抽象了一個(gè)最低級(jí)別的契約接口妙真,HttpHandler,用于響應(yīng)式HTTP請(qǐng)求的處理荚守。
Mono handle(ServerHttpRequest request, ServerHttpResponse response);
它是一個(gè)通用的接口珍德,要橫跨不同的運(yùn)行時(shí)。它是有意設(shè)計(jì)成最小化的矗漾,只有一個(gè)方法锈候,主要唯一目的就是在不同的HTTP服務(wù)器API上面成為一個(gè)最小化的抽象。
如果想用Netty服務(wù)器的話敞贡,就基于Netty實(shí)現(xiàn)一下泵琳,同理也可以基于Undertow實(shí)現(xiàn)一下,等等誊役,只要以后有了新的服務(wù)器获列,都可以加進(jìn)來的。
顯而易見蛔垢,HttpHandler的目標(biāo)是抽象出來對(duì)不同HTTP服務(wù)器的使用击孩,說白了就是為了和底層服務(wù)器對(duì)接。但由于太偏底層鹏漆,不利用上層代碼使用巩梢。
為此,我的爸爸又抽象出一個(gè)稍微高一點(diǎn)級(jí)別的契約接口甫男,WebHandler且改,用于Web請(qǐng)求處理。很明顯板驳,WebHandler的目標(biāo)是提供web應(yīng)用中廣泛使用的通用特性又跛,如Session、表單數(shù)據(jù)和附件等等若治,也是為了更容易和上層代碼對(duì)接慨蓝。
很自然的,WebHandler是構(gòu)建于HttpHandler之上的端幼,換句話說WebHander的處理會(huì)通過一個(gè)適配器HttpWebHandlerAdapter最終代理給HttpHandler來執(zhí)行礼烈。
WebHandler接口也只有一個(gè)方法:
Mono handle(ServerWebExchange exchange);
參數(shù)類型是ServerWebExchange,可以這樣理解婆跑,你發(fā)一個(gè)請(qǐng)求此熬,給你一個(gè)響應(yīng),相當(dāng)于用請(qǐng)求交換了一個(gè)響應(yīng),而且是在服務(wù)器端交換的犀忱。
其實(shí)募谎,整個(gè)web請(qǐng)求的處理過程是一個(gè)鏈?zhǔn)降模詈蟛攀且粋€(gè)WebHandler阴汇,它前面可以插入多個(gè)錯(cuò)誤處理器数冬,WebExceptionHandler,多個(gè)過濾器搀庶,WebFilter拐纱。
這是錯(cuò)誤處理器接口:
Mono handle(ServerWebExchange exchange, java.lang.Throwable ex);
這是過濾器接口:
Mono filter(ServerWebExchange exchange, WebFilterChain chain);
可見,我的爸爸Spring的抽象能力非常強(qiáng)哥倔,對(duì)下抽象一個(gè)接口秸架,抹平了不同服務(wù)器的差異。對(duì)上抽象一個(gè)接口未斑,可以用于支撐不同的編程模型咕宿。
都有哪些編程模型呢币绩,請(qǐng)繼續(xù)往下看吧蜡秽。
皮囊之下
上面我在介紹自己的時(shí)候使用了美顏,所以諸位很難看清我的“真面目”缆镣,下面就來進(jìn)行一下自我剖析芽突,看看真實(shí)的我。
我包含一個(gè)輕量級(jí)函數(shù)式編程模型董瞻,函數(shù)被用來參與處理請(qǐng)求寞蚌,它是相對(duì)于基于注解編程模型的另一種選擇,這種編程模型叫做函數(shù)式端點(diǎn)钠糊,functional endpoints挟秤,是構(gòu)建于上面提到的WebHandler之上的。
我是使用HandlerFunction來處理一個(gè)HTTP請(qǐng)求的抄伍,這是一個(gè)函數(shù)式接口艘刚,也稱處理函數(shù):
@FunctionalInterface
public interface HandlerFunction {
reactor.core.publisher.Mono handle(ServerRequest request);
}
帶有一個(gè)ServerRequest參數(shù),返回一個(gè)Mono<ServerResponse>截珍,其中request和response對(duì)象都是不可變的攀甚,HandlerFunction就等價(jià)于Controller中的@RequestMapping標(biāo)記的方法。
實(shí)際當(dāng)中岗喉,請(qǐng)求很多秋度,處理函數(shù)也很多,如何知道一個(gè)請(qǐng)求過來后钱床,該由哪個(gè)處理函數(shù)去處理呢荚斯?
這自然要用到我的另一個(gè)函數(shù)式接口RouterFunction來搞定,稱為路由函數(shù):
@FunctionalInterface
public interface RouterFunction {
reactor.core.publisher.Mono> route(ServerRequest request);
}
帶有一個(gè)ServerRequest參數(shù),返回一個(gè)Mono<HandlerFunction>事期。就是它把一個(gè)請(qǐng)求路由到一個(gè)HandlerFunction的拐格,當(dāng)路由函數(shù)匹配時(shí),就返回一個(gè)處理函數(shù)刑赶,否則返回一個(gè)空的Mono捏浊。RouterFunction等價(jià)于@RequestMapping注解,但主要不同的是路由函數(shù)提供的不僅是數(shù)據(jù)撞叨,還有行為金踪。
下面通過一些示例,來更加直觀的幫助大家認(rèn)識(shí)這兩個(gè)函數(shù)式接口牵敷。
因處理函數(shù)是函數(shù)式接口胡岔,所以可以直接用一個(gè)lambda表達(dá)式來處理請(qǐng)求,如下:
HandlerFunction handler = request -> Response.ok().body("Hello World");
這就表示當(dāng)任何一個(gè)請(qǐng)求過來時(shí)枷餐,都返回Hello World作為響應(yīng)靶瘸。
在實(shí)際應(yīng)用中,處理邏輯一般都很復(fù)雜毛肋,肯定不是一個(gè)lambda表達(dá)式能搞定的怨咪,此時(shí)希望把處理方法專門寫到一個(gè)類里,就叫處理器類润匙,和MVC里的Controller差不多一回事诗眨。
下面就是一個(gè)Person的處理器類:
public class PersonHandler {
public Mono listPeople(ServerRequest request) {
// ...
}
public Mono createPerson(ServerRequest request) {
// ...
}
public Mono getPerson(ServerRequest request) {
// ...
}
}
此時(shí)就可以通過處理函數(shù),引用這些處理器方法了孕讳,如下:
PersonHandler handler = new PersonHandler();
HandlerFunction list = handler::listPeople;
HandlerFunction create = handler::createPerson;
HandlerFunction get = handler::getPerson;
要想使請(qǐng)求能夠正確被路由匠楚,首先要定義好路由函數(shù),如下:
RouterFunction route = RouterFunctions.route()
.GET("/person/{id}", get)
.GET("/person", list)
.POST("/person", create)
.build();
它表示當(dāng)以GET方法請(qǐng)求/person/{id}時(shí)厂财,最終會(huì)由getPerson方法處理芋簿。當(dāng)以GET方法請(qǐng)求/person時(shí),最后會(huì)由listPeople方法處理璃饱。同理与斤,以POST方法請(qǐng)求/person時(shí),會(huì)由createPerson方法處理帜平。
可見幽告,一個(gè)路由函數(shù)可以包含多個(gè)路由規(guī)則,實(shí)際當(dāng)中裆甩,可以定義多個(gè)路由函數(shù)冗锁,這些路由函數(shù)可以組合在一起。
路由函數(shù)是按順序計(jì)算的嗤栓,如果第一個(gè)路由不匹配冻河,計(jì)算第二個(gè)箍邮,等等。因此叨叙,把更加具體的路由放到通用路由前面是非常有意義的锭弊。注意這和基于注解的不同。
怎么樣擂错,關(guān)掉濾鏡的我是不是更加真實(shí)了味滞。我相信你也看明白了,至少要記住钮呀,這是基于函數(shù)式的一種編程模型剑鞍,叫做函數(shù)式端點(diǎn)。
雨露均沾
像我這樣的幸運(yùn)兒爽醋,你們一定以為Spring爸爸對(duì)我非常溺愛吧蚁署,告訴你,確實(shí)是這樣的蚂四。不過考慮到大家伙一路走來對(duì)Spring的不離不棄光戈,爸爸也設(shè)身處地為你們著想。
為此,我除了支持函數(shù)式端點(diǎn)這種編程模型之外,還支持一種編程模型叫基于注解的控制器,annotated controllers,沒錯(cuò)氢烘,就是MVC里的那個(gè)。
話說的再白一些愚屁,就是大家已經(jīng)非常熟悉的Spring MVC那套東西死宣,我百分之百的完全支持,妥妥的惋啃,放心使用哼鬓。
但是,并不是所有的控制器方法參數(shù)都支持響應(yīng)式類型边灭,只有一些支持异希,如WebSession,java.security.Principal绒瘦,@RequestBody称簿,HttpEntity<B>,@RequestPart等惰帽。
下面看一個(gè)示例:
@PostMapping("/")
public String handle(@RequestBody Mono> parts) {
// ...
}
@PostMapping("/")
public String handle(@RequestBody Flux parts) {
// ...
}
@PostMapping("/accounts")
public void handle(@RequestBody Mono account) {
// ...
}
不過對(duì)于控制器方法的所有返回值憨降,都是支持響應(yīng)式類型的。
各有千秋
Spring MVC和Spring WebFlux可以一起使用该酗,從設(shè)計(jì)上講授药,它們互為繼續(xù)士嚎、互為一致。
它們的關(guān)系悔叽,請(qǐng)看下圖莱衩,既有共同的部分,也有互相獨(dú)立的部分娇澎。
Spring MVC的特點(diǎn)就是笨蚁,它是命令式編程,代碼非常容易寫趟庄,也好理解和調(diào)試赚窃。但是它是同步的,會(huì)有人覺得它性能不好岔激。
但是我要說的是勒极,響應(yīng)式和非阻塞通常來講也不會(huì)使應(yīng)用運(yùn)行的更快。相反虑鼎,非阻塞方式要求做更多的事情辱匿,而且還會(huì)稍微增加一些必要的處理時(shí)間。也就是說炫彩,還可能稍稍變慢一點(diǎn)匾七,what,那為啥還要用它呢江兢?
響應(yīng)式和非阻塞的關(guān)鍵好處是昨忆,在使用很少固定數(shù)目的線程和較少的內(nèi)存情況下的擴(kuò)展能力。
這使應(yīng)用在負(fù)載下更有適應(yīng)能力杉允,因?yàn)樗鼈円砸粋€(gè)更加具有可預(yù)見性的方式在擴(kuò)展邑贴。
為了能夠觀察到這些好處,你需要有一些延遲才行叔磷,比如一個(gè)既不可靠且速度又慢的網(wǎng)絡(luò)I/O拢驾,這才是響應(yīng)式開始展示它強(qiáng)勁的地方,帶來的差異(驚喜)可能是巨大的哦改基。
其實(shí)技術(shù)無好壞繁疤,各有各的適用場(chǎng)景罷了。
本文轉(zhuǎn)載自微信公眾號(hào):編程新說