I want to fly higher
          programming Explorer
          posts - 114,comments - 263,trackbacks - 0

          參考

          簡(jiǎn)介

          RPC,即 Remote Procedure Call(遠(yuǎn)程過程調(diào)用),說得通俗一點(diǎn)就是:調(diào)用遠(yuǎn)程計(jì)算機(jī)上的服務(wù),就像調(diào)用本地服務(wù)一樣。
          RPC 可基于 HTTP 或 TCP 協(xié)議,Web Service 就是基于 HTTP 協(xié)議的 RPC,
          它具有良好的跨平臺(tái)性,但其性能卻不如基于 TCP 協(xié)議的 RPC。會(huì)兩方面會(huì)直接影響 RPC 的性能,一是傳輸方式,二是序列化。
          眾所周知,TCP 是傳輸層協(xié)議,HTTP 是應(yīng)用層協(xié)議,而傳輸層較應(yīng)用層更加底層,
          在數(shù)據(jù)傳輸方面,越底層越快,因此,在一般情況下,TCP 一定比 HTTP 快。
          就序列化而言,Java 提供了默認(rèn)的序列化方式,但在高并發(fā)的情況下,
          這種方式將會(huì)帶來一些性能上的瓶頸,于是市面上出現(xiàn)了一系列優(yōu)秀的序列化框架,比如:Protobuf、Kryo、Hessian、Jackson 等,
          它們可以取代 Java 默認(rèn)的序列化,
          從而提供更高效的性能。
          為了支持高并發(fā),傳統(tǒng)的阻塞式 IO 顯然不太合適,因此我們需要異步的 IO,即 NIO。
          Java 提供了 NIO 的解決方案,Java 7 也提供了更優(yōu)秀的 NIO.2 支持,用 Java 實(shí)現(xiàn) NIO 并不是遙不可及的事情,只是需要我們熟悉 NIO 的技術(shù)細(xì)節(jié)。
          我們需要將服務(wù)部署在分布式環(huán)境下的不同節(jié)點(diǎn)上,通過服務(wù)注冊(cè)的方式,
          讓客戶端來自動(dòng)發(fā)現(xiàn)當(dāng)前可用的服務(wù),并調(diào)用這些服務(wù)。
          這需要一種服務(wù)注冊(cè)表(Service Registry)的組件,讓它來注冊(cè)分布式環(huán)境下所有的服務(wù)地址(包括:主機(jī)名與端口號(hào))。
          

          應(yīng)用、服務(wù)、服務(wù)注冊(cè)表之間的關(guān)系見下圖:
          


          每臺(tái) Server 上可發(fā)布多個(gè) Service,這些 Service 共用一個(gè) host 與 port,
          在分布式環(huán)境下會(huì)提供 Server 共同對(duì)外提供 Service。此外,為防止 Service Registry 出現(xiàn)單點(diǎn)故障,因此需要將其搭建為集群環(huán)境。
          本文將為您揭曉開發(fā)輕量級(jí)分布式 RPC 框架的具體過程,
          該框架基于 TCP 協(xié)議,提供了 NIO 特性,提供高效的序列化方式,同時(shí)也具備服務(wù)注冊(cè)與發(fā)現(xiàn)的能力。
          根據(jù)以上技術(shù)需求,我們可使用如下技術(shù)選型:
          Spring:它是最強(qiáng)大的依賴注入框架,也是業(yè)界的權(quán)威標(biāo)準(zhǔn)。
          Netty:它使 NIO 編程更加容易,屏蔽了 Java 底層的 NIO 細(xì)節(jié)。
          Protostuff:它基于 Protobuf 序列化框架,面向 POJO,無需編寫 .proto 文件。
          ZooKeeper:提供服務(wù)注冊(cè)與發(fā)現(xiàn)功能,開發(fā)分布式系統(tǒng)的必備選擇,同時(shí)它也具備天生的集群能力。
          

          源代碼目錄結(jié)構(gòu)

          • rpc-client
            • 實(shí)現(xiàn)了rpc的服務(wù)動(dòng)態(tài)代理(RpcProxy)以及基于Netty封裝的一個(gè)客戶端網(wǎng)絡(luò)層(RpcClient)
          • rpc-common
            • 封裝了RpcRequest和RpcResponse,即rpc請(qǐng)求和響應(yīng)的數(shù)據(jù)結(jié)構(gòu)
            • 基于Netty提供了編解碼器
            • 提供了序列化反序列化等工具
          • rpc-registry
            • 提供了服務(wù)發(fā)現(xiàn)和注冊(cè)接口
          • rpc-registry-zookeeper
            • 基于zookeeper的服務(wù)發(fā)現(xiàn)和注冊(cè)接口
          • rpc-server
            • rpc服務(wù)器(RpcServer)的實(shí)現(xiàn),用來監(jiān)聽rpc請(qǐng)求以及向Zookeeper注冊(cè)服務(wù)地址
            • rpc服務(wù)本地調(diào)用
          • rpc-sample-api
            • rpc測(cè)試公共api服務(wù)接口
          • rpc-sample-client
            • rpc測(cè)試客戶端
          • rpc-sample-server
            • rpc測(cè)試服務(wù)啟動(dòng)程序和服務(wù)實(shí)現(xiàn)

          啟動(dòng)順序

          • 配置Zookeeper
            • 解壓zookeeper-3.4.9
            • 進(jìn)入conf目錄,重命名zoo_sample.cfg為zoo.cfg(或者復(fù)制一份重命名)并修改一些配置選項(xiàng)如dataDir.另外可以看到默認(rèn)的clientPort是2181
            • 將bin目錄加入環(huán)境變量PATH,這樣則可直接使用zkServer命令直接啟動(dòng)
          • 啟動(dòng)rpc-sample-server工程的下RpcBootstrap
          • 啟動(dòng)rpc-sample-client工程下的測(cè)試程序HelloClient等

          關(guān)鍵實(shí)現(xiàn)和核心模塊分析

          • RpcBootstrap
            • 加載spring.xml實(shí)例化RpcServer
              • 兩個(gè)參數(shù)分別是rpc服務(wù)地址(127.0.0.1:8000)和基于ZooKeeper的服務(wù)注冊(cè)接口實(shí)現(xiàn)(使用ZkClient連接Zookeeper的2181端口)
            • 加載過程中,會(huì)首先調(diào)用setApplicationContext方法
              • 掃描com.xxx.rpc.sample.server下帶有@RpcService注解的類,本例是HelloServiceImpl和HelloServiceImpl2,即有兩個(gè)rpc服務(wù)類,其中HelloServiceImpl2加了一個(gè)版本號(hào)用來區(qū)分第一個(gè)服務(wù)類,掃描后放入handlerMap,即服務(wù)名和服務(wù)對(duì)象之間的映射map
            • 加載后,調(diào)用afterPropertiesSet方法
              • 啟動(dòng)Netty服務(wù)端,監(jiān)聽8000端口;channelpipeline增加編解碼器(RpcDecoder、RpcEncoder)和邏輯處理類(RpcServerHandler)
                • RpcEncoder,編碼器,消息格式為4個(gè)字節(jié)的消息長(zhǎng)度和消息體;直接使用Protostuff進(jìn)行序列化,編碼對(duì)象為RpcResponse
                • RpcDecoder,解碼器;已解決粘包問題;使用Objenesis和Protostuff繼續(xù)反序列化
                • RpcServerHandler,收到RpcRequest后直接從handlerMap找到對(duì)應(yīng)的服務(wù)類反射進(jìn)行方法調(diào)用(使用了CGLib);最后向客戶端寫入rpc響應(yīng),完畢則主動(dòng)關(guān)閉連接(所以從這里看是短連接)
                  • ctx.writeAndFlush(response).addListener(ChannelFutureListener.CLOSE)
                    • 這行代碼相當(dāng)于在rpc響應(yīng)發(fā)送的這個(gè)操作完成之后關(guān)閉連接
                  • 注意Netty強(qiáng)烈建議直接通過添加監(jiān)聽器的方式獲取I/O操作結(jié)果;當(dāng)I/O操作完成之后,I/O線程會(huì)回調(diào)ChannelFuture中GenericFutureListener#operationComplete方法
              • 綁定端口成功后,向Zookeeper注冊(cè)上面的兩個(gè)rpc服務(wù)

          ChannelFutureListener CLOSE = new ChannelFutureListener() {
                          @Override
                          public void operationComplete(ChannelFuture future) {
                              future.channel().close();
                          }
          };
          

          • RpcProxy
            • 初始化亦通過加載spring.xml,指定了基于zookeeper的服務(wù)發(fā)現(xiàn)類ZooKeeperServiceDiscovery
            • create方法,主要使用了jdk的動(dòng)態(tài)代理;當(dāng)代理方法執(zhí)行的時(shí)候,首先根據(jù)請(qǐng)求的服務(wù)名利用Zookeeper的服務(wù)發(fā)現(xiàn)接口獲取服務(wù)的address;然后封裝rpc請(qǐng)求調(diào)用Netty客戶端連接服務(wù)地址并發(fā)送
              • 關(guān)于RpcClient,同Netty服務(wù)端,需要設(shè)置channelpipeline的編解碼器和邏輯處理handler
              • Channel channel = future.channel();
                channel.writeAndFlush(request).sync();
                channel.closeFuture().sync();
                return response;
              • 注意上部分代碼,發(fā)送rpc請(qǐng)求后等待發(fā)送完畢;發(fā)送完畢后等待連接關(guān)閉,此時(shí)線程阻塞直到服務(wù)端發(fā)送完回復(fù)消息并主動(dòng)關(guān)閉連接,線程繼續(xù);所以這個(gè)例子并沒有會(huì)有request對(duì)不上reponse的問題,因?yàn)槊看蝦pc調(diào)用都是短連接且當(dāng)前執(zhí)行線程掛起;另外服務(wù)端收到request的時(shí)候,也會(huì)用requestId作為response的requestId

          可改進(jìn)地方

          • 本人覺得spring相對(duì)較厚重,所以將spring去掉,對(duì)象實(shí)例化和依賴注入用比較簡(jiǎn)單的方式去處理;不過比較麻煩的是對(duì)于掃描@RpcService注解這部分需要手動(dòng)處理
          • 目前該示例提供的兩個(gè)服務(wù)均是在同一個(gè)端口8000下的服務(wù);如何測(cè)試不同的兩個(gè)服務(wù)在不同的端口?按照該例子的設(shè)計(jì),一個(gè)RpcServer即一個(gè)rpc發(fā)布服務(wù)器,該監(jiān)聽的端口下可以注冊(cè)不同很多服務(wù)(當(dāng)然一個(gè)Netty server本身可以bind多個(gè)端口,這個(gè)暫時(shí)不考慮實(shí)現(xiàn));如果需要增加不同的服務(wù),則需要單獨(dú)啟動(dòng)RpcServer并向Zookeeper注冊(cè)

          其他待調(diào)研rpc框架

          posted on 2017-02-13 21:41 landon 閱讀(2675) 評(píng)論(0)  編輯  收藏 所屬分類: ServerFrameworkNetWork
          主站蜘蛛池模板: 怀化市| 芦溪县| 陆河县| 旬阳县| 达孜县| 岳阳县| 故城县| 美姑县| 隆德县| 新巴尔虎右旗| 曲麻莱县| 晋州市| 铜山县| 宝清县| 威海市| 邛崃市| 墨竹工卡县| 荃湾区| 白河县| 阳信县| 建宁县| 河东区| 清涧县| 东方市| 东丰县| 克拉玛依市| 锡林郭勒盟| 板桥市| 平顶山市| 闻喜县| 利川市| 右玉县| 宝丰县| 阿勒泰市| 黎川县| 江北区| 朝阳区| 运城市| 海伦市| 建宁县| 云霄县|