天天看點

雲原生實踐之 RSocket 從入門到落地:Servlet vs RSocket

技術實踐的作用在于:除了用于建構業務,也是為了驗證某項技術或架構是否值得大規模推廣。

本期開始,我們推出《RSocket 從入門到落地》系列文章,通過執行個體和對比來介紹RSocket。主要圍繞RSocket如何實作Polyglot RPC、Service Registry、 Service Discovery、 IoT聯結等次元,為讀者們揭開RSocket的面紗,希望對大家在Java API規範的技術選型過程中有所借鑒。

第一篇文章我們将通過Servlet和RSocket的對比,快速了解RSocket的一些基本知識。要說明的是其實RSocket與Servlet并不是同類的産品,但是大家對Servlet都很熟悉,功能對比相對友善一些。

閱讀本系列文章,需要大家對Java有了解,其中可能會涉及到Kotlin,有少部分C++和Python(不做要求),如果了解Spring Boot則最好。

什麼是 Servlet ?

維基百科上的解釋是"Servlet,全稱Java Servlet,是用Java編寫的伺服器端程式。 其主要功能在于互動式地浏覽和修改資料,生成動态Web内容”。

對于Java程式員來說,解釋這個概念直接上代碼,這樣才能友善了解,如下:

public abstract class HttpServlet extends Servlet {
    protected abstract void doGet(HttpServletRequest req,HttpServletResponse resp)
        throws ServletException, IOException;

    protected abstract void doPost(HttpServletRequest req,HttpServletResponse resp)
       throws ServletException, IOException;
}
           

是以,Servlet就是提供HTTP Request,處理後,最終調用HTTP Response完成輸出。沒錯,就是這個,大家可别小瞧這個class,幾乎所有符合Servlet規範的web架構的第一個Java類都是從這裡開始的,包括Struts、Spring MVC和阿裡巴巴内部用到的WebX。很多開發者根據這個class寫了Web Framework,來解決不同的問題。

什麼是 RSocket

rsocket.io給出的解釋是"RSocket是一個二進制的協定,以異步消息的方式提供4種對等的互動模型,以位元組流的方式運作在TCP, WebSockets, Aeron等傳輸層之上”。

通過這個定義,大家可以有一個基本了解:二進制協定、異步消息、七層協定和運作在TCP、WebSocket以及Aeron之上。同樣的,我們通過代碼來解釋這個概念,如下:

public interface RSocket extends Availability, Closeable {

  Mono<Payload> requestResponse(Payload payload);

  Mono<Void> fireAndForget(Payload payload);

  Flux<Payload> requestStream(Payload payload);

  Flux<Payload> requestChannel(Publisher<Payload> payloads);

  Mono<Void> metadataPush(Payload payload);

  default double availability() {
  return isDisposed() ? 0.0 : 1.0;
}
           

展開闡述一下:

四個模型:

requestResponse、fireAndForget、requestStream和requestChannel,它們和doGet、doPost沒有差別。

參數:

Payload,前面說到基于消息通訊,那就是拿到消息傳回消息,Got!等一下,為何不叫Message?請原諒我們的英文水準,暫時可以了解為同義詞吧。對于一個消息來說,由兩部分組成,原資訊(metadata)和資料(data)。原資訊是指路由資訊等,例如要調用那個服務,你的資料的mime type是什麼,資料則是指調用的參數值和傳回的結果。

metadataPush:

這個是什麼?推送元資訊的,可以告訴對方的一些元資訊,至于是什麼,可以自己定義。我了解為:如果是一個叢集,我可以将叢集的資訊給你,然後讓你和各個work node連接配接;我要下線啦,大家做好準備等等。

availability:

為何要這個? 這個可以了解問健康度檢查,如果為0,則表示不可用,這在load balance的情況下非常實用。Servlet缺少這個,是以我們要自行加入Health URL等,如/ok.jsp :) 那為何不是布爾值,true或者false?僅是個人了解:double值可以作為權重,如1.0表示處理能力非常好,0.8一般,這個就看你如何處理了。

Mono和Flux:

這是Reactive程式設計要求,通過異步的方式來提升系統的處理能力。RSocket定義中有一個異步關鍵字,Mono和Flux就是來處理異步的。

Servlet 和 RSocket的差別

其實兩者的共同點非常明顯:Servlet是一套Java的API規範,基于HTTP協定之上;RSocket也是一套API規範(支援多種語言),基于自定義的二進制協定之上。 可以不用關心協定的細節,直接實作接口寫代碼就可以,然後功能就會Ready。 這裡我們還是想列舉一下它們兩者之間的重大差別:

協定層:

Servlet是基于HTTP協定的,RSocket則是自定義協定。 标準化方面,HTTP尚不用說。 但是RSocket的自定義二進制協定性能非常好,解析友善。如果覺得HTTP非常簡單,那是1.1,2.0版本開始是有點複雜的。這裡我們可以了解為:RSocket定位高性能通訊,比HTTP高非常多(号稱10倍)。這裡要注意的是:RSocket并不是天然的極緻高性能,要實作極緻高性能需要根據自己業務場景優化才行。

指令和通訊模式:

HTTP的指令不隻是get和post,其他還有head、put、delete和options等。Servlet2.0添加了流式的支援,但是這些指令都是為浏覽器設計的,并非為服務通訊設計的,而且它們都是request/response模式,是以也叫做 request command。其他例如流式推送、fireAndForget和雙向通訊,Servlet2.0都不支援。基本上,我們可以将HTTP定位為request/response這一種通訊模式。這個說法也許有争議,因為HTTP也有polling和websocket等,但是這些設計都是為了hack和高效通訊的改造,而不是内置的通訊模式。

message:

HTTP1.1是基于文本的通訊,2.0是基于message的。 message的好處是什麼呢?基于message的好處是異步化。message都必須有一個ID,這個消息發送出去後,就不用等立即傳回,可以繼續發其他message,收到message後,再根據傳回的message ID和之前的發出去的message ID進行比對。如果不是message,内容發出去後,就要等着傳回的結果進行比對,然後才能發下一個message,這也是為何很多人抱怨www是World Wide Wait。

Reactive程式設計模型:

RSocket要求基于Reactive程式設計模型,對Java來說,主要是Reactor和RxJava,由于Spring在RSocket上貢獻頗多,外加RSocket Java SDK還要基于Netty-Reactor,是以預設的接口就是Reactor API。異步化對程式設計确實比較有挑戰,如callback、Future和Promise等,對比傳統不是那麼友好,是以Reactive在傳統和異步化上推出了Reactive程式設計模型,算是兼顧,這個看大家如何了解,如果對Functional Programming也能接受的話,那Reactive就沒有問題。

對等通訊:

我們傳統的了解是Client -> Server模式,例如寫一個Servlet運作在服務端的,然後再用JS寫一個Servlet運作在浏覽器端,這樣服務端可以反向調用浏覽器,例如訂單狀态變更時,需要将詳情區域重新整理一下。但是RSocket沒有這個概念,大家的地位是對等的,都可以在server端,我調用你的服務,你也可以調用我的服務。後續我們會有詳細的Demo來介紹這個使用場景,如無監聽端口對外提供服務,從網際網路反向通路内部服務。RSocket Broker就是基于這種對等通訊來實作的。

Singleton & Prototype scope:

這裡我們套用Spring的Singleton scope和Prototype scope來看Servlet和RSocket的不同。 Singleton scope表示JVM唯一,而Prototype scope是每次調用都需要建立。類比而言,Servlet的class基本都是singleton的,但是RSocket确未必,主要原因是前面說到的對等通訊,如果要給連接配接的另一方發送請求,就需要hold住連接配接的另一方(peer RSocket),是以這個handler就不能singleton的,如果隻是單方通訊,不用在乎setup payload,那麼RSocket的handler為Singleton也沒有關系。

當然還有一項細小差别,這些就不做介紹了。鑒于個人能力,可能我了解的不夠徹底,漏掉了重大的差別,大家了解和使用後,歡迎回報一下。我們再通過圖例來對比下兩者的不同:

雲原生實踐之 RSocket 從入門到落地:Servlet vs RSocket

RSocket Demo

這裡我們将RSocket的Demo介紹一下。由于沒有client -> server這種通訊模型,是以我們用requester和responder來說明,但是角色也是互換的,requester可以為responder,在實際的編碼過程中,其實就将requester預設調整為responder。

Responder代碼:

RSocketFactory.receive()
            .acceptor(new SocketAcceptor() {
                @Override
                public Mono<RSocket> accept(ConnectionSetupPayload setup, RSocket sendingSocket) {
                    return Mono.just(new RSocketHandlerImpl());
                }
            })
            .transport(TcpServerTransport.create("0.0.0.0", 42252))
            .start()
            .subscribe();
           

Responder主要是RSocketFactory.receive(),接收外部來的連接配接。接下來你隻需要一個RSocket的接口實作給acceptor就可以了。 這裡說明一下SocketAcceptor接口。對于Responder來說,它需要驗證requester能否可以連接配接到自己,這個非常有用,如初始鑒權等,一旦鑒權通過,連接配接建立好後,後續就不需要驗證了。這裡的ConnectionSetupPayload是requester發給responder的建立連接配接的資料。這個是Servlet中沒有的,後續我們還會提供更多的實踐,第一篇文章裡僅驗證是否可以連接配接。

Requester代碼:

RSocketFactory.connect()
                    .acceptor(new Function<RSocket, RSocket>() {
                        @Override
                        public RSocket apply(RSocket peerRsocket) {
                            return Mono.just(new RSocketHandlerImpl()) ;
                        }
                    })
                    .transport(TcpClientTransport.create("localhost", 42252))
                    .start()
                    .block();
           

RSocketFactory.connect() 是表示要連接配接到目标的responder上,然後也有RSocket實作給acceptor表示接收從對方過來的調用請求。 最好的block()表示采用同步方式等待responder傳回,這個是需要的,如目标服務當機或者不存在等,應用可以快速自我發現。 但是在load balance的情況下,我們未采用block這種方式,而是使用Mono方式,這樣可以實作自動重連,新位址推送等。

實際上,如果使用Spring Boot,可能就需要1-2個Bean,SocketAcceptor和RSocket Bean,其他都是通過注入方式完成,不需要寫很多重複代碼。 目前rsocket-spring-boot-starter已經開發快完成了,是以不用擔心代碼的複雜性。

本文作者:雷卷,社群ID linux-china,Java程式員,阿裡巴巴資深技術專家。