亚洲免费av电影一区二区三区,日韩爱爱视频,51精品视频一区二区三区,91视频爱爱,日韩欧美在线播放视频,中文字幕少妇AV,亚洲电影中文字幕,久久久久亚洲av成人网址,久久综合视频网站,国产在线不卡免费播放

        ?

        基于響應(yīng)式的RPC系統(tǒng)設(shè)計

        2021-09-16 01:51:24胡喜明
        計算機工程與設(shè)計 2021年9期
        關(guān)鍵詞:序列化調(diào)用權(quán)值

        胡喜明,胡 淼

        (杭州電子科技大學(xué) 通信工程學(xué)院,浙江 杭州 310018)

        0 引 言

        當(dāng)前互聯(lián)網(wǎng)開發(fā)中,網(wǎng)站業(yè)務(wù)流量的增加以及業(yè)務(wù)復(fù)雜度的提升使得傳統(tǒng)單體架構(gòu)不足以滿足當(dāng)前的需求,大部分互聯(lián)網(wǎng)開發(fā)者采用分布式架構(gòu)來構(gòu)建當(dāng)前復(fù)雜的業(yè)務(wù)[1]。該架構(gòu)中采用業(yè)務(wù)拆分的理念,根據(jù)功能拆分為多個服務(wù),通過RPC系統(tǒng)實現(xiàn)業(yè)務(wù)間通信,該系統(tǒng)肩負(fù)著服務(wù)間協(xié)調(diào)以及數(shù)據(jù)交換的責(zé)任[2]。

        響應(yīng)式編程作為一種新生的編程范式,提供了基于事件驅(qū)動的形式對異步化數(shù)據(jù)進行處理。該范式下允許開發(fā)人員去實現(xiàn)擁有時間驅(qū)動、異步化、可擴展的響應(yīng)式系統(tǒng)。該范式下認(rèn)為同步阻塞的開發(fā)形式是對資源的一種浪費,而原生異步化雖然能解決單一的事務(wù)異步問題,但是對多個事務(wù)組合性回調(diào)問題無法處理,因此引入響應(yīng)式編程范式來解決該問題[3]。當(dāng)前幾乎所有的開發(fā)語言和框架都在向著響應(yīng)式編程跟進,Java在1.8后也引入了響應(yīng)式編程的開發(fā)模式,然而當(dāng)前主流的RPC系統(tǒng)均采用Netty作為底層通訊框架,該框架無法支持響應(yīng)式方法的遠(yuǎn)程調(diào)用,使得響應(yīng)式編程在分布式項目中實現(xiàn)難度加大[4]。

        為解決該問題,本文提出了一種基于響應(yīng)式的RPC系統(tǒng),系統(tǒng)中采用基于響應(yīng)式編程的Reactor-netty組件作為跨進程通信手段,實現(xiàn)服務(wù)間非阻塞調(diào)用。項目中還設(shè)計了一種動態(tài)負(fù)載均衡策略,實現(xiàn)根據(jù)服務(wù)調(diào)用情況的動態(tài)化選擇。該系統(tǒng)采用優(yōu)化后的SPI機制實現(xiàn)良好的功能擴展。最后,通過與Netty作為底層通信框架的系統(tǒng)對比,該系統(tǒng)具有明顯的性能優(yōu)勢。

        1 相關(guān)技術(shù)介紹

        響應(yīng)式編程是一種關(guān)注傳輸中的數(shù)據(jù)流以及數(shù)據(jù)變化傳遞的異步編程模式,這也意味著可以應(yīng)用編程語言進行靜態(tài)和動態(tài)數(shù)據(jù)流的表示。Reactor是響應(yīng)式編程的第四代函數(shù)庫,同時也是一種為了實現(xiàn)響應(yīng)式形式的具體編程規(guī)范,其主要為了解決jvm中異步方法的缺點,具有協(xié)調(diào)多個異步任務(wù)、較好的可讀性、豐富的數(shù)據(jù)流運算符、懶加載訂閱模式、背壓等優(yōu)點。該框架主要用于在JVM平臺上基于響應(yīng)式流規(guī)范構(gòu)建非阻塞異步應(yīng)用。java語言在1.8版本后引入了Reactor框架,其中可組合形式的反應(yīng)類型Flux和Mono是其核心對象。Flux對象表示0到N項的反應(yīng)序列,而一個Mono對象表示單值或空(0或1)的結(jié)果[3]。

        Reactor庫函數(shù)的下屬分支Reactor-netty作為響應(yīng)式編程家族的一員支持上述兩種反應(yīng)類型的封裝,其底層基于Netty框架,并對netty進行響應(yīng)式編程封裝,將其轉(zhuǎn)換為異步事件驅(qū)動的網(wǎng)絡(luò)應(yīng)用程序框架。Reactor-netty內(nèi)部仍然保留了Netty的主從多線程模型,擁有Netty框架的全部優(yōu)勢[5]。同時Reactor-netty內(nèi)部直接繼承了Java函數(shù)式的API接口,支持HTTP、TCP等協(xié)議的調(diào)用。Reactor-netty 通過Reactor Streams中的背壓理論進行數(shù)據(jù)流量控制,發(fā)布者和訂閱者可以進行數(shù)據(jù)流量協(xié)商,保證服務(wù)高質(zhì)量傳輸。其中背壓分為4種策略:

        (1)OnBackpressureBuffer策略:對下游的請求數(shù)據(jù)采用緩存的形式,保證系統(tǒng)不會壓力過大。

        (2)OnBackpressureDrop策略:元素就緒時,根據(jù)下游是否有未滿足的request來發(fā)出當(dāng)前數(shù)據(jù)。

        (3)OnBackpressureLatest策略:一直發(fā)送當(dāng)前最新的數(shù)據(jù)。

        (4)OnBackpressureError策略:當(dāng)前數(shù)據(jù)已經(jīng)滿了,再次添加請求直接報錯[3]。

        圖1為OnBackpressureError策略的背壓原理,當(dāng)訂閱者的消費能力遠(yuǎn)小于發(fā)布者,訂閱者可以通知發(fā)布者進行服務(wù)的取消和終止功能,保證傳輸數(shù)據(jù)流量合理。

        圖1 OnBackpressureError策略背壓原理

        2 響應(yīng)式RPC系統(tǒng)概述

        RPC是分布式系統(tǒng)的核心通信組件,肩負(fù)著實現(xiàn)高效服務(wù)間通信的職責(zé)[6]。如圖2所示,一個完整的RPC流程包括3類角色:服務(wù)提供者、服務(wù)消費者以及服務(wù)注冊中心[7-9]。分布式系統(tǒng)的服務(wù)調(diào)用流程如下:

        圖2 RPC架構(gòu)

        (1)服務(wù)提供方實現(xiàn)相應(yīng)的發(fā)布接口,并將服務(wù)信息注冊到注冊中心。

        (2)注冊中心接收到注冊信息后,對信息進行內(nèi)部存儲并開啟服務(wù)監(jiān)控功能,保證服務(wù)下線后及時通知服務(wù)消費方。

        (3)服務(wù)消費方向注冊中心發(fā)送服務(wù)訂閱請求,通過注冊中心所提供的信息在本地實現(xiàn)負(fù)載均衡選取指定地址。

        (4)獲取到指定地址后,服務(wù)消費者向該地址發(fā)送服務(wù)調(diào)用請求,服務(wù)提供方對接收到的請求進行解析并向消費方返回請求結(jié)果[10]。

        圖3為響應(yīng)式RPC功能層次,根據(jù)RPC各個角色的功能將架構(gòu)分為:數(shù)據(jù)傳輸層、服務(wù)治理層、服務(wù)調(diào)用層以及Ext層4部分。

        圖3 響應(yīng)式RPC功能層次

        數(shù)據(jù)傳輸層主要用于對服務(wù)提供者以及服務(wù)消費者提供數(shù)據(jù)通信,需要保證雙方在數(shù)據(jù)傳輸過程中高效、正確。為保證該需求,RPC系統(tǒng)在服務(wù)雙方需要定義統(tǒng)一的數(shù)據(jù)管理標(biāo)準(zhǔn)。其中主要功能包括:通訊協(xié)議、通訊框架、編碼以及對象序列化方式。

        服務(wù)治理層主要用于對服務(wù)進行管控,在進行服務(wù)遠(yuǎn)程調(diào)用時,可以方便獲取到服務(wù)注冊信息。項目中采用注冊中心的形式實現(xiàn)服務(wù)治理,其主要功能包括:服務(wù)持久化注冊表、服務(wù)發(fā)現(xiàn)、服務(wù)注冊、服務(wù)下線、服務(wù)監(jiān)控。

        服務(wù)調(diào)用層是對服務(wù)消費者所發(fā)起的請求進行調(diào)用,與本地服務(wù)調(diào)用不同,遠(yuǎn)程調(diào)用需要根據(jù)服務(wù)的注冊信息進行具體地址的選擇。同時RPC框架需要對接收到的服務(wù)信息進行解析。因此該層主要提供的功能為:代理調(diào)用以及服務(wù)負(fù)載均衡管理。

        Ext層是對當(dāng)前服務(wù)的擴展層,從圖中可以看到Ext層是貫穿于其它層之間,對上述3層中的組件進行擴展,根據(jù)企業(yè)需求實現(xiàn)RPC系統(tǒng)定制化。

        3 響應(yīng)式RPC系統(tǒng)架構(gòu)設(shè)計

        3.1 數(shù)據(jù)傳輸層設(shè)計

        數(shù)據(jù)傳輸層是RPC系統(tǒng)的核心基礎(chǔ)功能,一個RPC傳輸系統(tǒng)的性能好壞取決于當(dāng)前傳輸?shù)姆绞揭约胺€(wěn)定性。該層的設(shè)計主要圍繞如何提高性能以及減少無效字段傳輸?shù)姆矫婵紤]。此時需要對傳輸協(xié)議、通信框架、數(shù)據(jù)編碼以及序列化方式進行優(yōu)化選取。

        3.1.1 數(shù)據(jù)傳輸協(xié)議

        對于一個完善的RPC框架,數(shù)據(jù)的傳輸不僅要考慮粘包拆包的問題,而且還需要考慮到一些擴展性的需求,因此需要自定義一套私有化協(xié)議,在協(xié)議中需要保證基本業(yè)務(wù)的實現(xiàn),同時要考慮到對數(shù)據(jù)的擴展性支持,保證開發(fā)人員可以基于當(dāng)前的協(xié)議進行業(yè)務(wù)上的擴展。

        圖4為協(xié)議整體設(shè)計,其中字段解釋如下:

        圖4 私有化協(xié)議

        (1)Protocal:該字段是標(biāo)識當(dāng)前傳輸?shù)膮f(xié)議,可以通過該字段方便地轉(zhuǎn)換所傳輸?shù)膮f(xié)議。

        (2)Type:該字段是標(biāo)識當(dāng)前協(xié)議可以傳輸?shù)念愋?,分為request、response、oneway這3種。其中request為請求是發(fā)送的報文類型,response為接收到請求并處理結(jié)束進行返回時的報文類型,而oneway為客戶端單方面請求無需服務(wù)端響應(yīng)的報文。

        (3)Command:該字段標(biāo)識當(dāng)前請求所對應(yīng)的命令,分為request、heart、response。其中heart為心跳檢測請求,定時檢測服務(wù)是否存活。

        (4)Id:該id字段是對當(dāng)前請求的唯一標(biāo)識,每個請求都會初始化一個requestId保證請求的唯一性。

        (5)ClassNameLen:該字段標(biāo)識當(dāng)前請求class名稱的長度,可提前解析出要調(diào)用的class。

        (6)Timeout:該字段標(biāo)識當(dāng)前請求的超時時間。

        (7)BodyLen:該字段標(biāo)識在序列化后傳入的信息長度。

        (8)Body:該字段標(biāo)識在序列化后的二進制信息。其中主要包括請求的方法名、方法參數(shù)類型、方法具體參數(shù)。

        上述請求協(xié)議字段中,ClassNameLen字標(biāo)識了當(dāng)前存入的Class的長度,在高性能的網(wǎng)絡(luò)框架中系統(tǒng)都是采用Reactor多線程模式,即一個IO線程去掛載多個連接,如果將數(shù)據(jù)處理都放在當(dāng)前這個線程將會影響到其它掛載的數(shù)據(jù),因此在當(dāng)前的服務(wù)框架中BodyLen以及Body字段的解析可以不放在當(dāng)前的IO線程中,而是在后續(xù)開啟的業(yè)務(wù)線程中進行,保證IO線程的流暢。

        3.1.2 Reactor-netty通訊框架設(shè)計

        Reactor-netty是在原生Netty框架上的響應(yīng)式封裝,其底層仍然沿用Netty的handler機制。handler是針對不同事件的處理模式,根據(jù)傳入的事件進行處理并通過Pipeline實現(xiàn)handler之間的連接,當(dāng)前handler在處理完事件后傳遞給下一個handler。handler分為input以及output兩種,其中input為接收時所需要處理的事件,output為向外傳輸時所需要處理的事件。Reactor-netty在隱藏了大部分netty內(nèi)部細(xì)節(jié)的同時增加響應(yīng)式背壓,只向外暴露簡潔的API方法。項目中主要在TCP協(xié)議基礎(chǔ)上進行私有化設(shè)計,Reactor-netty對外提供了易于使用的TcpServer模塊以及TcpClient模塊。

        項目中TcpServer模塊的核心代碼如下所示,首先需要進行服務(wù)開啟以及端口配置,其內(nèi)置了create以及port方法,在創(chuàng)建后需要綁定netty底層的bootStrap進行事件的輪訓(xùn)監(jiān)聽。在設(shè)置了啟動端口后需要對netty的channel管道進行配置,通過ChannelOption類進行參數(shù)選取,之后需要對生命周期中的連接初始化進行回調(diào)綁定,其中doOnConnection方法是在連接了遠(yuǎn)程服務(wù)器的時候會被回調(diào),其內(nèi)部需要對讀寫超時、序列化、粘包解析、心跳、連接處理進行配置,該方法是框架提供的生命周期事件管理中的一種,其它的還包括doOnBind、doOnBound、doOnUnbound、doOnLifecycle。上述方法都是在產(chǎn)生了對應(yīng)事件時自動調(diào)用。最后需要為Reactor-netty注入事件處理器,通過handle方法對NettyInbound和NettyOutbound進行初始化,實現(xiàn)業(yè)務(wù)模塊。

        TcpServer

        //創(chuàng)建服務(wù)

        .create()

        //綁定事務(wù)組

        .doOnBind(bootstrap-> bootstrap.option(Chan-nelOption.SO_BACKLOG, serverProperties.getBacklog()))

        //綁定端口

        .port(serverProperties.getPort())

        .runOn(loopResources)

        //綁定channel配置值

        .option(ChannelOption.SO_KEEPALIVE, ser-verProperties.isKeepAlive())

        .option(ChannelOption.SO_REUSEADDR, true)

        //其余的option值設(shè)定,省略

        ……

        //初始化設(shè)置

        .doOnConnection(this::initConnection)

        //一系列handler綁定

        .handle(this::handler);

        //異步回調(diào)組合

        Mono.just(pkg)

        .map(RpcDataPackage::getData)

        .handle(data, synchronousSink)-> {}

        .publish(rpcHandlerFunction::handle)

        .onErrorMap()

        .handle(resp,synchronousSink)-> {}

        .onErrorResume()

        .publish(nettyOutbound::send)

        .doOnError(e-> log.error("conn catch error.", e));

        項目中的客戶端采用TcpClient封裝,其內(nèi)部配置流程與TcpServer類似,差別在于handle處理事件不同,其主要是作為請求封裝以及相應(yīng)的接收,因此在handler中需要對動態(tài)代理進行封裝,將數(shù)據(jù)封裝為傳輸所需要的代碼,并通過doOnSubscribe方法實現(xiàn)事件的注冊監(jiān)聽,對請求響應(yīng)的數(shù)據(jù)包裝,最后將包裝后的消息響應(yīng)給客戶端。

        數(shù)據(jù)整體傳輸過程中,由于有了Reactor-netty的封裝可以在請求的參數(shù)以及返回值中使用Mono以及Flux類型的對象,實現(xiàn)響應(yīng)式支持,如上代碼所示,在異步回調(diào)組合中,通過流式編程對方法進行組合式編寫,Mono的組合形式是對Future異步回調(diào)機制的優(yōu)化,實現(xiàn)基于事件的異步通知。

        3.1.3 編碼以及序列化功能

        由3.1.1節(jié)的數(shù)據(jù)傳輸層協(xié)議設(shè)計可知,在序列化之前,需要對前置自定義協(xié)議字段進行編碼操作。首先需要創(chuàng)建一個ByteBuf字節(jié)緩沖區(qū),用于存儲整體編碼后的數(shù)據(jù)。從當(dāng)前傳入的對象中解析出Protocal協(xié)議字段插入到緩沖區(qū)中并獲取對應(yīng)的協(xié)議對象,然后將協(xié)議中的Type、Command、Id、ClassName、Timeout按照順序進行解析存儲在ByteBuf中。此時對數(shù)據(jù)的編碼階段已經(jīng)結(jié)束,后續(xù)請求中的詳細(xì)數(shù)據(jù)需要對其進行序列化處理。在Reactor-netty中通過實現(xiàn)MessageToMessageDecoder接口中的decode方法,在方法內(nèi)部對數(shù)據(jù)進行預(yù)處理以及調(diào)用反序列化方法。通過實現(xiàn)MessageToByteEncoder接口中的encode方法對傳入的對象進行獲取以及序列化方法調(diào)用,將二進制數(shù)據(jù)存入Reactor-Netty內(nèi)置的Buffer緩沖區(qū)中。

        在實現(xiàn)數(shù)據(jù)編碼的過程中離不開序列化的支持,序列化是將java對象向著二進制對象轉(zhuǎn)化,反序列化是序列化的逆過程。在當(dāng)前RPC框架中,數(shù)據(jù)均以二進制進行傳輸,因此詳細(xì)請求信息需要進行序列化操作。

        系統(tǒng)中采用接口+實現(xiàn)類形式進行序列化機制的擴展。

        @SPI

        public interface SerializerInterface {

        //序列化

        byte[]dose(M seri);

        //反序列化

        Object des(byte[]by, Class cls);

        }

        在序列化的選取中,考慮當(dāng)前主流的4種序列化方式j(luò)ava、hession、Kryo、ProtoBuf。原生java序列化性能方面存在不足,Hession框架在速度上相對java原生有所提高但是數(shù)據(jù)序列化后長度不理想、ProtoBuf雖然性能方面很出眾但是需要編寫特定的IDL文件,在代碼接入方面不理想。

        本項目最終選取Kryo作為當(dāng)前框架的序列化方式,Kryo性能好,兼容性強,可提前對數(shù)據(jù)長度進行設(shè)置,提高資源利用率。

        (1)Kryo序列化

        在類KryoSerializer中,通過對SerializerInterface接口的serialize方法重寫,實現(xiàn)數(shù)據(jù)的序列化功能。核心代碼如下:

        ByteArrayOutputStream oss=null;

        Output out1=null;

        private final ThreadLocal kryoLocal = new ThreadLocal(){

        @Override

        protected Kryo initialValue(){

        Kryo kryo = new Kryo();

        //循環(huán)引用

        kryo.setReferences(true);

        kryo.setRegistrationRequired(false);

        return kryo;

        }

        };

        //序列化操作

        public byte[]dose(M seri){

        //生成對應(yīng)Stream流

        oss = new ByteArrayOutputStream();

        //生成Out輸出流

        out1 = new Output(oss);

        try {

        kryoLocal.get().writeObject(output,seri);

        out1.flush();

        byte[]result = oss.toByteArray();

        return result;

        } catch(Exception e){

        //處理報錯信息

        ……

        }

        }

        Kryo是線程不安全的,在序列化過程中首先需要對Kryo進行線程私有化管理,通過ThreadLocal實現(xiàn)線程隔離,保證每個線程拿到自己的Kryo實例。初始化后創(chuàng)建ByteArrayOutputStream字節(jié)流緩沖區(qū)以及OutPut輸出流,Kryo通過writeObject方法將數(shù)據(jù)寫入到OutPut輸出流中的字節(jié)流緩沖區(qū)中,最后從字符流緩沖區(qū)獲取當(dāng)前的二進制字節(jié)序列結(jié)束整個序列化流程,并關(guān)閉釋放所有資源。

        (2)Kryo反序列化

        在類KryoSerializer中的deserialize方法實現(xiàn)了數(shù)據(jù)的反序列化功能,通過該方法實現(xiàn)了二進制到類對象的轉(zhuǎn)化。核心代碼如下:

        ByteArrayInputStream in1=null;

        Input in1=null;

        //反序列化實現(xiàn)

        public Object des(byte[]by, Class cls)

        {

        //初始化輸入流緩沖區(qū)

        is1= new ByteArrayInputStream(bytes);

        //初始化輸入流

        in1 = new Input(is);

        try {

        Object result = kryoLocal.get().readObject(input, clazz);

        return result;

        } catch(Exception e){

        //處理報錯信息

        ……

        }

        }

        反序列化方法中傳入了二進制字節(jié)序列,首先創(chuàng)建ByteArrayInputStream字節(jié)輸入流緩沖區(qū)以及Input輸入流對象,并對傳入的二進制字節(jié)序列進行裝載。通過Kryo的readObject方法實現(xiàn)二進制字節(jié)流到對象的轉(zhuǎn)換,然后釋放所有資源。

        以上是基于Kryo實現(xiàn)的編解碼以及序列化過程,該框架可以有效解決原生java序列化所帶來的性能問題,提高了RPC服務(wù)的相應(yīng)速度以及效率。

        3.2 服務(wù)治理層設(shè)計

        服務(wù)治理在RPC中主要以注冊中心的形式存在,用于對服務(wù)的發(fā)布、查詢、監(jiān)控以及下線處理。通過注冊中心將服務(wù)提供方以及服務(wù)消費方兩者連接在一起,服務(wù)提供方注冊服務(wù)信息到注冊中心,服務(wù)消費方從注冊中心獲取信息,實現(xiàn)兩者通信。同時注冊中心還兼具了服務(wù)的心跳管理功能,保證了服務(wù)與注冊中心之間保持連接。

        本系統(tǒng)中采用Zookeeper作為注冊中心,其內(nèi)部采用樹形結(jié)構(gòu)的目錄,與傳統(tǒng)的文件系統(tǒng)不同,Zookeeper的節(jié)點可以設(shè)置為臨時以及持久化兩種,同時在其內(nèi)部擁有Watcher機制,Zookeeper允許開發(fā)者對某一些節(jié)點添加Watcher監(jiān)控,根據(jù)節(jié)點的變化來進行操作觸發(fā),并且在Watcher機制對時間僅做一次響應(yīng),做到了輕量級的數(shù)據(jù)通知[11]。系統(tǒng)在整合中主要分為服務(wù)注冊、服務(wù)發(fā)現(xiàn)、服務(wù)移除。

        (1)服務(wù)注冊

        在服務(wù)注冊階段,Zookeeper獲取到當(dāng)前注冊的信息,檢測當(dāng)前父節(jié)點是否存在,不存在則創(chuàng)建父節(jié)點。在父節(jié)點下將當(dāng)前注冊的服務(wù)作為子節(jié)點。將當(dāng)前的注冊信息緩存在本地內(nèi)存中并對節(jié)點注冊Watcher監(jiān)聽,如果服務(wù)服務(wù)下線,直接將當(dāng)前內(nèi)存中的數(shù)據(jù)清除,保證本地內(nèi)存和Zookeeper的一致性。

        (2)服務(wù)發(fā)現(xiàn)

        在服務(wù)注冊階段已經(jīng)將注冊好的服務(wù)緩存在本地內(nèi)存中,調(diào)用服務(wù)發(fā)現(xiàn)請求時可以直接從本地拉取所需要的服務(wù),并響應(yīng)給服務(wù)消費方。

        (3)服務(wù)移除

        根據(jù)傳入的信息進行地址拼接,系統(tǒng)對拼接后的地址進行檢測,查詢傳入的URL地址信息是否在Zookeeper中,若信息存在則進行節(jié)點刪除操作。服務(wù)已經(jīng)注冊了Watcher監(jiān)聽事件,對服務(wù)進行刪除后會觸發(fā)本地內(nèi)存移除操作,保證服務(wù)信息在本地與遠(yuǎn)程的一致性。

        3.3 服務(wù)調(diào)用層設(shè)計

        服務(wù)調(diào)用層主要是建立在數(shù)據(jù)傳輸層之上,在數(shù)據(jù)傳輸之前以及之后需要對數(shù)據(jù)的調(diào)用形式進行處理。其中分為Provider提供方以及Consumer消費方,Consumer對請求數(shù)據(jù)進行封裝,借助服務(wù)治理層獲取地址進行路由選擇,然后通過數(shù)據(jù)傳輸層將當(dāng)前請求傳送給Provider。Provider通過數(shù)據(jù)傳輸層對傳輸?shù)恼埱筮M行解析,采用動態(tài)代理形式對請求方法進行調(diào)用,將調(diào)用結(jié)果封裝后傳輸給Consumer完成整體調(diào)用。整體流程如圖5所示。

        圖5 調(diào)用流程

        圖5流程中可以看到,在調(diào)用階段需要對服務(wù)進行代理調(diào)用以及服務(wù)地址的負(fù)載均衡選擇,接下來將對這兩部分進行分析。

        3.3.1 服務(wù)對象代理

        系統(tǒng)在對請求進行反序列化后可以獲取到當(dāng)前的類信息,服務(wù)提供方無法直接對服務(wù)接口進行調(diào)用,需要采取服務(wù)代理的模式,根據(jù)獲取到的方法名稱,參數(shù)類型、參數(shù)實體調(diào)用真實方法。系統(tǒng)中基于java動態(tài)代理技術(shù),該技術(shù)是java內(nèi)置的方法,調(diào)用方法的內(nèi)部需要傳入最終方法的名稱以及參數(shù)類型,以此來保證唯一方法的獲取。在獲取到當(dāng)前的Method方法類后,調(diào)用其invoke方法并傳入真實參數(shù)得到返回結(jié)果。具體調(diào)用核心代碼如下:

        try {

        //參數(shù)賦值service的類信息、className、方法類型、方法參數(shù)

        Class serviceClass

        String methodName

        Class[]parameterTypes

        Object[]parameters

        //jdk動態(tài)代理

        Method method = serviceClass.getMethod(methodName, parameterTypes);

        method.setAccessible(true);

        //得到返回結(jié)果

        Object result = method.invoke(serviceBean, parameters);

        RpcResponse.setResult(result);

        } catch(Throwable t){

        //錯誤處理

        RpcResponse.setErrorMsg(t);

        }

        //返回結(jié)果

        return RpcResponse;

        3.3.2 服務(wù)負(fù)載均衡

        負(fù)載均衡的目的是將當(dāng)前的服務(wù)請求均衡地分布給所屬的網(wǎng)絡(luò)系統(tǒng)[12]。單節(jié)點項目中由于大批量的請求全部都在一臺機器上處理會導(dǎo)致性能上的瓶頸,因此系統(tǒng)必然需要集群化配置。而在集群環(huán)境下如何選擇每次請求的地址是需要根據(jù)負(fù)載均衡的配置策略決定的。當(dāng)前主流的選取策略有輪詢、隨機權(quán)重選擇、最少連接數(shù)選擇等[13]。上述負(fù)載均衡策略均為靜態(tài)化選擇,是通過管理員手動配置參數(shù)來實現(xiàn)服務(wù)器的負(fù)載均衡,靜態(tài)化的優(yōu)勢在于可以方便的配置,并且算法實現(xiàn)相對容易,在小型系統(tǒng)中開銷比較小,可以滿足小系統(tǒng)的開發(fā)。然而靜態(tài)化的節(jié)點選取是無法考慮到后續(xù)在實際運行過程中系統(tǒng)真實的負(fù)載情況[14]。為解決此問題,項目中提出了動態(tài)負(fù)載均衡的思想,該思想的核心是根據(jù)服務(wù)器在處理數(shù)據(jù)時的請求成功以及超時情況進行地址選取。如果當(dāng)前服務(wù)處理時間在閾值范圍內(nèi),可以認(rèn)為當(dāng)前服務(wù)器正常運行。如果出現(xiàn)異?;蛘叱瑫r,說明當(dāng)前服務(wù)器可能負(fù)載壓力過大,需要做出調(diào)整。該方法基于最大權(quán)值策略,每次選取當(dāng)前權(quán)值最大的服務(wù)器進行路由。

        該方法的執(zhí)行流程如圖6所示。

        圖6 負(fù)載均衡流程

        首先對每個服務(wù)的權(quán)值設(shè)置初始值為100,開啟一個循環(huán)的線程,等待客戶端請求。當(dāng)接收到RPC的請求后根據(jù)權(quán)值計算出被選擇的概率,計算規(guī)則為當(dāng)前服務(wù)每次請求成功一次,其對應(yīng)的權(quán)值加一,而如果當(dāng)前服務(wù)出現(xiàn)了異常或者超時,權(quán)值減少5。一般出現(xiàn)異?;蛘叱瑫r的情況相對較少,除非是服務(wù)器直接宕機無法接收請求。權(quán)值需要設(shè)置上下界限,以100作為權(quán)值上界,0作為權(quán)值下界,如果當(dāng)前權(quán)值降為0,等待1 min,給服務(wù)器緩沖的時間,如果1 min后仍然出現(xiàn)調(diào)用超時,問題直接進行服務(wù)器的下線操作,如果成功則將權(quán)值回復(fù)到60。修改的權(quán)值信息都會自動更新到注冊中心,可以從注冊中心獲取到當(dāng)前權(quán)值信息。

        4 SPI模式優(yōu)化

        Java SPI是一種允許開發(fā)人員在接入端實現(xiàn)外部擴展的模式,該模式對定義好的接口進行擴展,開發(fā)者可以采用代碼無侵入的形式對當(dāng)前框架增添功能,實現(xiàn)了整體服務(wù)的插拔式配置。

        使用Java SPI模式需要遵循如下流程:

        (1)服務(wù)提供者需要定義所需要擴展的接口,并且在當(dāng)前項目的resources資源目錄中新建META-INF/services文件夾,該文件夾主要作為記錄擴展點文件的地址,在其內(nèi)部創(chuàng)建一個以接口完整路徑命名的文件,內(nèi)容是當(dāng)前接口的實現(xiàn)類路徑。

        (2)主程序內(nèi)部通過ServiceLoader類進行動態(tài)裝載,其內(nèi)部掃描上一步文件中配置的所有類名,一次性全部加載到JVM中。

        (3)將加載到的類文件信息全部進行初始化操作并生成對應(yīng)的類。

        (4)根據(jù)傳入的參數(shù)進行輪詢比較,找到匹配的類進行獲取。

        按照上述配置,Java開發(fā)者可以對服務(wù)進行擴展。然而Java SPI仍然存在如下的缺點:

        (1)Java內(nèi)置的SPI機制對所有的擴展類進行了實例化操作,在開發(fā)中可能只是需要對部分類進行實例化操作,且無法實現(xiàn)單例模式。

        (2)無法對擴展點進行排序以及分組。

        (3)擴展點文件只被限制在META-INF/services目錄中無法額外擴展。

        基于原生Java SPI的問題,對其進行了優(yōu)化處理,使得當(dāng)前系統(tǒng)中的SPI機制支持對自定義擴展來設(shè)置單例/多例、支持實現(xiàn)類排序功能、支持只創(chuàng)建所需要的類,解決原生全量加載問題、支持根據(jù)特征屬性值區(qū)分不同類別、支持基于注解支持自動掃描實現(xiàn)類。

        如圖7所示為優(yōu)化后的SPI類關(guān)系,在啟動加載階段,RPC內(nèi)部會根據(jù)配置通過ExtensionLoader擴展類加載器進行數(shù)據(jù)加載,讀取服務(wù)配置的目錄,根據(jù)目錄信息查詢擴展接口對應(yīng)配置文件,在該文件內(nèi)部循環(huán)加載這個文件下的描述文件,并且按照行進行讀取,其中相同接口只會被加載進內(nèi)存一次,通過對注解的驗證以及解析生成對應(yīng)的Class文件,最后在使用擴展類時才會對其進行加載實例化操作。

        圖7 優(yōu)化后SPI類關(guān)系

        圖7中可以看到優(yōu)化后的SPI加入了兩個注解,分為@SPI以及@Extension,其中@SPI標(biāo)志著當(dāng)前擴展的接口,而@Extension標(biāo)志當(dāng)前擴展的實現(xiàn)類。系統(tǒng)通過對注解解析實現(xiàn)類加載功能,實現(xiàn)注解化配置。

        public @interface SPI{

        //擴展類是否使用單例,默認(rèn)使用

        boolean singleton()default true;

        }

        public @interface Extension {

        //擴展點名字

        String value();

        //優(yōu)先級排序,大的優(yōu)先級高

        int order()default 0;

        //擴展類是分類名稱

        boolean category()default false;

        }

        在上述注解類代碼中可以看到,其內(nèi)部可配置多個屬性,通過SPI機制的優(yōu)化功能就是通過對屬性值的解析實現(xiàn)的?;趦?yōu)化后的SPI擴展機制,開發(fā)人員可實現(xiàn)自定義擴展功能,并且無需對RPC底層代碼進行修改,實現(xiàn)第三方自由化擴展。

        5 實驗測試

        5.1 系統(tǒng)功能測試

        系統(tǒng)功能測試主要是檢測RPC的基本調(diào)用功能以及響應(yīng)式編程方法是否可以正常調(diào)用。

        在本地電腦搭建RPC環(huán)境,應(yīng)用虛擬機單獨開啟Zookeeper服務(wù),項目啟動后查看Zookeeper注冊中心可以看到當(dāng)前服務(wù)已經(jīng)注冊成功,圖8為當(dāng)前服務(wù)的樹形結(jié)構(gòu)展示,這里在服務(wù)端開啟集群形式,因此在Zookeeper的服務(wù)信息中,記錄了兩個地址端口號不同的地址。

        圖8 Zookeeper樹形結(jié)構(gòu)

        為驗證系統(tǒng)中響應(yīng)式編程的支持,客戶端編寫測試代碼發(fā)起遠(yuǎn)程服務(wù)調(diào)用,如下代碼所示有兩個方法,分為響應(yīng)式方法以及普通方法的調(diào)用,響應(yīng)式模式的編程方法中需要接收Mono對象作為返回值,而Mono對象在傳統(tǒng)RPC框架中并不支持,項目中通過編寫入?yún)⒁约胺祷刂刀紴镸ONO對象的方法,進行測定,經(jīng)測定系統(tǒng)可以正確的調(diào)用兩個方法并獲取返回值。該系統(tǒng)在功能方面可正常運行,符合預(yù)期要求。

        //引入響應(yīng)式編程特有類MONO

        import reactor.core.publisher.Mono;

        @Service("testService")

        public interface TestService {

        //響應(yīng)式方法

        Mono testMethod(Mono RequestMono);

        //普通方法

        Request testMethod2(Request request);

        }

        5.2 系統(tǒng)性能測試

        這一部分主要針對基于Reactor-netty的RPC系統(tǒng)和基于Netty的RPC系統(tǒng)的響應(yīng)性能進行測試。通過maven插件對接入了RPC項目的客戶端代碼以及服務(wù)端代碼進行打包,將打包好的JAR文件分別上傳到服務(wù)器中。測試中選取5臺服務(wù)器,其中兩臺服務(wù)器分別部署基于Netty的RPC服務(wù)提供方和服務(wù)消費方,再選取兩臺作為基于Rea-ctor-netty的RPC系統(tǒng)的服務(wù)方以及消費方,最后一臺單獨部署Zookeeper注冊中心。5臺機器配置見表1。

        表1 服務(wù)器配置

        實驗中采用并發(fā)測試工具Jmeter對兩個注冊中心系統(tǒng)進行性能壓力測試。Jmeter模擬高并發(fā)下服務(wù)調(diào)用,其內(nèi)部通過多線程機制設(shè)置并發(fā)訪問數(shù)量,系統(tǒng)中以1k大小的對象進行數(shù)據(jù)傳遞,并在控制臺打印傳輸?shù)淖址?,根?jù)并發(fā)數(shù)的不同對比兩個rpc數(shù)據(jù)響應(yīng)速度。

        表2為客戶端并發(fā)模擬服務(wù)調(diào)用請求的響應(yīng)時間。由表中可知,在并發(fā)數(shù)量較小的情況下兩者的服務(wù)響應(yīng)時間相近。隨著并發(fā)訪問量的提升,基于Netty的RPC響應(yīng)時間遠(yuǎn)大于基于Reactor-netty的RPC系統(tǒng),可以看出在服務(wù)調(diào)用方面響應(yīng)式Reactor-netty的RPC系統(tǒng)有明顯的性能優(yōu)勢。

        表2 并發(fā)性能測試結(jié)果

        6 結(jié)束語

        本文設(shè)計了一款基于響應(yīng)式的RPC系統(tǒng),解決了當(dāng)前java語言編寫的RPC系統(tǒng)無法支持響應(yīng)式流編程的問題。該系統(tǒng)采用Reactor-netty框架在Nett的基礎(chǔ)上實現(xiàn)性能優(yōu)化;通過響應(yīng)式編程提升代碼可讀性,通過Mono類實現(xiàn)基于事件的異步回調(diào)組合形式;系統(tǒng)集成Kryo序列化方法,提升整體數(shù)據(jù)傳輸性能;系統(tǒng)應(yīng)用Zookeeper實現(xiàn)注冊中心的功能,為服務(wù)的治理提供保障;系統(tǒng)在傳輸?shù)刂愤x擇方面采用動態(tài)負(fù)載均衡,提升系統(tǒng)在集群環(huán)境下的處理能力;通過優(yōu)化后的SPI機制實現(xiàn)服務(wù)擴展;最后,通過和Netty作為通訊框架的RPC系統(tǒng)對比可知,該系統(tǒng)在請求速度方面的性能更加優(yōu)越。

        猜你喜歡
        序列化調(diào)用權(quán)值
        一種融合時間權(quán)值和用戶行為序列的電影推薦模型
        CONTENTS
        如何建構(gòu)序列化閱讀教學(xué)
        甘肅教育(2020年14期)2020-09-11 07:58:36
        核電項目物項調(diào)用管理的應(yīng)用研究
        LabWindows/CVI下基于ActiveX技術(shù)的Excel調(diào)用
        基于權(quán)值動量的RBM加速學(xué)習(xí)算法研究
        基于系統(tǒng)調(diào)用的惡意軟件檢測技術(shù)研究
        Java 反序列化漏洞研究
        作文訓(xùn)練微格化、序列化初探
        語文知識(2015年12期)2015-02-28 22:02:15
        利用RFC技術(shù)實現(xiàn)SAP系統(tǒng)接口通信
        99精品国产综合久久麻豆| 亚洲av日韩av综合| 久久精品波多野结衣中文字幕| 亚洲性无码av在线| 国产女人精品一区二区三区 | 国产自拍在线观看视频| 国产精品无码久久综合网| 亚洲欧洲精品无码av| 色偷偷av亚洲男人的天堂| 久久精品国产亚洲综合色| 久久人妻av无码中文专区| 国产成人精品久久二区二区91 | 亚洲一区二区三区99区| 日韩一区二区中文字幕视频| 国产让女高潮的av毛片| 国产成人精品午夜二三区波多野 | 中文字幕有码高清| av影片手机在线观看免费网址| 成人免费无遮挡在线播放| 亚洲а∨天堂久久精品2021| 免费男人下部进女人下部视频| 2017天天爽夜夜爽精品视频| 丰满人妻无套内射视频| 亚洲一区二区三区免费网站| 18精品久久久无码午夜福利 | 国产激情在线观看视频网址| 精品精品国产高清a毛片 | 可以免费在线看黄的网站| 国产一级av理论手机在线| av一区二区三区在线| 最近中文字幕免费完整版| 亚洲一区日韩无码| 狠狠躁夜夜躁人人爽天天不卡| 亚洲国产一区二区中文字幕| 欧美精品色婷婷五月综合| 亚洲av无码一区二区三区四区| 日韩AV无码一区二区三区不卡毛片| 偷拍一区二区三区在线观看 | 天天躁夜夜躁狠狠躁婷婷| 亚洲欧美国产国产综合一区| 亚洲综合免费|