Reactive 簡介

1. 概念html

Reactive 很是適合低延遲、高吞吐量的工做負載。vue

Reactive Processing 是一種範式(規範),它使開發人員可以構建非阻塞的、異步的應用程序,這些應用程序可以處理背壓(流控制)react

Reactive Streams 爲無阻塞背壓的異步流處理提供標準。angularjs

Reactor 是基於Reactive Streams規範的第四代響應庫,用於在JVM上構建非阻塞的應用程序。spring

Project Reactor 是一個徹底無阻塞的基礎,其中包括背壓支持。它是Spring生態系統中的響應式堆棧的基礎,而且在諸如Spring WebFlux,Spring Data和Spring Cloud Gateway等項目中都有它的身影。利用Project Reactor能夠高效的響應式系統。剛纔說Reactive Streams是規範,那麼Project Reactor就是實現。編程

2. 響應式編程數組

響應式編程是一種異步編程風格,它關注數據流和變化的傳播。服務器

響應式編程是一種與數據流和變化傳播相關的聲明式編程範式。使用此範例,能夠輕鬆地表示靜態(例如,數組)或動態(例如,事件發射器)數據流,而且還能夠表示關聯執行模型中的推斷出的依賴關係,這有助於更改數據流的自動傳播。 網絡

reactive programming   (響應式編程)多線程

imperative programming(命令式編程)

在命令式編程中,a:=b+c意味着將b+c的結果賦值給a,而且此後b或c的值發生變化不會影響到a的值。而在響應式編程中,a的值會隨着b或c的改變而自動更新,而且不須要從新執行a:=b+c來肯定當前分配給a的值。(PS:是否是很像angularjs、vuejs這種MVVM框架,視圖綁定模型,模型變了,視圖自動就跟着變了)

例如,在 model–view–controller (MVC) 架構中,響應式編程能夠促進基礎模型中的更改,這些更改會自動反映在關聯的視圖中。

響應式編程與面向對象編程中一般使用的觀察者模式具備不少類似之處。

若是從推拉的角度來看的話,響應式編程是「推」,它主動將變化推送給它的訂閱者。Publisher-Subscriber是兩個很是重要的概念。

想象一下,數據流從源出發,通過一個一個節點的處理,最終達到目的地。節點就至關於操做符,處理完了之後就將流發射出去,到下一個節點再執行再發射。

我總以爲這個流程很眼熟,很像 Apache Storm 的處理方式。在一個拓撲結構中,數據流從Spout發出,通過若干bolt的處理,最終聚集到某個地方。

還有一種理解,我以爲也很不錯,說響應式編程是一種經過異步和數據流來構建事務關係的編程模型。事物能夠理解程一次處理過程,一次執行過程。響應式編程就是要構建關係,事務和事務之間的關係。而數據流就像是一個橋樑同樣,數據流從一個事務流向下一個事務。

想象一下,長江流經宜賓、瀘州、重慶、涪陵、萬州、宜昌、荊州、武漢、黃石、鄂州、九江、安慶、銅陵、蕪湖、南京、上海,最終匯入東海。

就像CompleteFuture把Future進行編排同樣。

本質來說,響應式編程上是對數據流或某種變化所做出的反應,可是這個變化何時發生是未知的,因此他是一種基於異步、回調的方式在處理問題

3. NIO

NIO(Non-Blocking I/O)

BIO(Blocking I/O)

在經典的線程模型中,socket.accept()、socket.read()、socket.write()三個主要函數都是同步阻塞的,當一個鏈接在處理I/O的時候,系統是阻塞的,若是使用單線程的話就阻塞在那裏了,但CPU是並無阻塞,若是用多線程的話,就可讓CPU去處理更多的事情。其實這也是全部使用多線程的本質: 當I/O阻塞系統,但CPU空閒的時候,能夠利用多線程使用CPU資源。然而,線程的建立、銷燬、切換成本都是很高的。

事實上,全部的系統I/O都分爲兩個階段:等待就緒和操做。舉例來講,讀函數,分爲等待系統可讀和真正的讀;同理,寫函數分爲等待網卡能夠寫和真正的寫。

須要說明的是等待就緒的阻塞是不使用CPU的,是在「空等」;而真正的讀寫操做的阻塞是使用CPU的,真正在」幹活」。

以socket.read()爲例子:

傳統的BIO裏面socket.read(),若是TCP RecvBuffer裏沒有數據,函數會一直阻塞,直到收到數據,返回讀到的數據。

對於NIO,若是TCP RecvBuffer有數據,就把數據從網卡讀到內存,而且返回給用戶;反之則直接返回0,永遠不會阻塞。 

在BIO模型中,沒有辦法知道到底能不能寫、能不能讀,只能」傻等」。而在NIO模型中,若是一個鏈接不能讀寫(socket.read()返回0或者socket.write()返回0),咱們能夠把這件事記下來,記錄的方式一般是在Selector上註冊標記位,而後切換到其它就緒的鏈接(channel)繼續進行讀寫。

NIO的主要事件有幾個:讀就緒、寫就緒、有新鏈接到來。那麼,首先須要註冊當這幾個事件到來的時候所對應的處理器,而後在合適的時機告訴事件選擇器:我對這個事件感興趣,最後用一個死循環選擇就緒的事件。select是阻塞的,因此你能夠放心大膽地在一個while(true)裏面調用這個函數而不用擔憂CPU空轉。

總結起來就是:註冊全部感興趣的事件處理器,單線程輪詢選擇就緒事件,執行事件處理器。

咱們大概能夠總結出NIO是怎麼解決掉線程的瓶頸並處理海量鏈接的:

NIO由原來的阻塞讀寫(佔用線程)變成了單線程輪詢事件,找到能夠進行讀寫的網絡描述符進行讀寫。除了事件的輪詢是阻塞的(沒有可乾的事情必需要阻塞),剩餘的I/O操做都是純CPU操做,沒有必要開啓多線程。

NIO由原來的阻塞讀寫(佔用線程)變成了單線程輪詢事件,找到能夠進行讀寫的網絡描述符進行讀寫。除了事件的輪詢是阻塞的(沒有可乾的事情必需要阻塞),剩餘的I/O操做都是純CPU操做,沒有必要開啓多線程。而且因爲線程的節約,鏈接數大的時候由於線程切換帶來的問題也隨之解決,進而爲處理海量鏈接提供了可能。單線程處理I/O的效率確實很是高,沒有線程切換,只是拼命的讀、寫、選擇事件。但如今的服務器,通常都是多核處理器,若是可以利用多核心進行I/O,無疑對效率會有更大的提升。

 

Buffer(緩衝區)

在NIO中,全部數據都是用緩衝區處理的。在讀取數據時,它是直接讀到緩衝區中的;在寫入數據時,它也是寫入到緩衝區中的。 

Channel(通道)

通道是一個對象,經過它能夠讀取和寫入數據,固然了全部數據都經過Buffer對象來處理。咱們永遠不會將字節直接寫入通道中,相反是將數據寫入包含一個或者多個字節的緩衝區。一樣不會直接從通道中讀取字節,而是將數據從通道讀入緩衝區,再從緩衝區獲取這個字節。

Selector(選擇器)

Selector類是NIO的核心類,Selector(選擇器)選擇器提供了選擇已經就緒的任務的能力。Selector會不斷的輪詢註冊在上面的全部channel,若是某個channel爲讀寫等事件作好準備,那麼就處於就緒狀態,經過Selector能夠不斷輪詢發現出就緒的channel,進行後續的IO操做。一個Selector可以同時輪詢多個channel。這樣,一個單獨的線程就能夠管理多個channel,從而管理多個網絡鏈接。這樣就不用爲每個鏈接都建立一個線程,同時也避免了多線程之間上下文切換致使的開銷。

 

 

參考:

https://spring.io/reactive

https://www.jianshu.com/p/d47835316016

http://www.javashuo.com/article/p-tnozoqxe-cg.html

https://tech.meituan.com/2016/11/04/nio.html 

相關文章
相關標籤/搜索