亚洲香蕉成人av网站在线观看_欧美精品成人91久久久久久久_久久久久久久久久久亚洲_热久久视久久精品18亚洲精品_国产精自产拍久久久久久_亚洲色图国产精品_91精品国产网站_中文字幕欧美日韩精品_国产精品久久久久久亚洲调教_国产精品久久一区_性夜试看影院91社区_97在线观看视频国产_68精品久久久久久欧美_欧美精品在线观看_国产精品一区二区久久精品_欧美老女人bb

首頁 > 學院 > 開發設計 > 正文

你應該知道的RPC原理

2019-11-11 07:48:14
字體:
來源:轉載
供稿:網友

-- 學習rpc,找到一篇rpc的入門,感覺是講的非常好,唯一遺憾就是沒服務治理這一塊內容,轉載學習下!

在學校期間大家都寫過不少程序,比如寫個hello world服務類,然后本地調用下,如下所示。這些程序的特點是服務消費方和服務提供方是本地調用關系。

  而一旦踏入公司尤其是大型互聯網公司就會發現,公司的系統都由成千上萬大大小小的服務組成,各服務部署在不同的機器上,由不同的團隊負責。這時就會遇到兩個問題:1)要搭建一個新服務,免不了需要依賴他人的服務,而現在他人的服務都在遠端,怎么調用?2)其它團隊要使用我們的新服務,我們的服務該怎么發布以便他人調用?下文將對這兩個問題展開探討。

1 public interface HelloWorldService {2     String sayHello(String msg);3 }復制代碼
1 public class HelloWorldServiceImpl implements HelloWorldService {2     @Override3     public String sayHello(String msg) {4         String result = "hello world " + msg;5         System.out.PRintln(result);6         return result;7     }8 }復制代碼
1 public class Test {2     public static void main(String[] args) {3         HelloWorldService helloWorldService = new HelloWorldServiceImpl();4         helloWorldService.sayHello("test");5     }6 }

1 如何調用他人的遠程服務?

  由于各服務部署在不同機器,服務間的調用免不了網絡通信過程,服務消費方每調用一個服務都要寫一坨網絡通信相關的代碼,不僅復雜而且極易出錯。

  如果有一種方式能讓我們像調用本地服務一樣調用遠程服務,而讓調用者對網絡通信這些細節透明,那么將大大提高生產力,比如服務消費方在執行helloWorldService.sayHello("test")時,實質上調用的是遠端的服務。這種方式其實就是RPC(Remote Procedure Call Protocol),在各大互聯網公司中被廣泛使用,如阿里巴巴的hsf、dubbo(開源)、Facebook的thrift(開源)、Google grpc(開源)、Twitter的finagle(開源)等。

  要讓網絡通信細節對使用者透明,我們需要對通信細節進行封裝,我們先看下一個RPC調用的流程涉及到哪些通信細節:

1)服務消費方(client)調用以本地調用方式調用服務;

2)client stub接收到調用后負責將方法、參數等組裝成能夠進行網絡傳輸的消息體;

3)client stub找到服務地址,并將消息發送到服務端;

4)server stub收到消息后進行解碼;

5)server stub根據解碼結果調用本地的服務;

6)本地服務執行并將結果返回給server stub;

7)server stub將返回結果打包成消息并發送至消費方;

8)client stub接收到消息,并進行解碼;

9)服務消費方得到最終結果。

  RPC的目標就是要2~8這些步驟都封裝起來,讓用戶對這些細節透明。

1.1 怎么做到透明化遠程服務調用?

  怎么封裝通信細節才能讓用戶像以本地調用方式調用遠程服務呢?對java來說就是使用代理!java代理有兩種方式:1) jdk 動態代理;2)字節碼生成。盡管字節碼生成方式實現的代理更為強大和高效,但代碼維護不易,大部分公司實現RPC框架時還是選擇動態代理方式。

  下面簡單介紹下動態代理怎么實現我們的需求。我們需要實現RPCProxyClient代理類,代理類的invoke方法中封裝了與遠端服務通信的細節,消費方首先從RPCProxyClient獲得服務提供方的接口,當執行helloWorldService.sayHello("test")方法時就會調用invoke方法。

復制代碼
 1 public class RPCProxyClient implements java.lang.reflect.InvocationHandler{ 2     private Object obj; 3  4     public RPCProxyClient(Object obj){ 5         this.obj=obj; 6     } 7  8     /** 9      * 得到被代理對象;10      */11     public static Object getProxy(Object obj){12         return java.lang.reflect.Proxy.newProxyInstance(obj.getClass().getClassLoader(),13                 obj.getClass().getInterfaces(), new RPCProxyClient(obj));14     }15 16     /**17      * 調用此方法執行18      */19     public Object invoke(Object proxy, Method method, Object[] args)20             throws Throwable {21         //結果參數;22         Object result = new Object();23         // ...執行通信相關邏輯24         // ...25         return result;26     }27 }復制代碼
1 public class Test {2     public static void main(String[] args) {3         HelloWorldService helloWorldService = (HelloWorldService)RPCProxyClient.getProxy(HelloWorldService.class);4         helloWorldService.sayHello("test");5     }6 }

 

1.2  怎么對消息進行編碼和解碼?

1.2.1 確定消息數據結構

  上節講了invoke里需要封裝通信細節,而通信的第一步就是要確定客戶端和服務端相互通信的消息結構??蛻舳说恼埱笙⒔Y構一般需要包括以下內容:

1)接口名稱

  在我們的例子里接口名是“HelloWorldService”,如果不傳,服務端就不知道調用哪個接口了;

2)方法名

  一個接口內可能有很多方法,如果不傳方法名服務端也就不知道調用哪個方法;

3)參數類型&參數值

  參數類型有很多,比如有bool、int、long、double、string、map、list,甚至如struct(class);

  以及相應的參數值;

4)超時時間

5)requestID,標識唯一請求id,在下面一節會詳細描述requestID的用處。

  同理服務端返回的消息結構一般包括以下內容。

1)返回值

2)狀態code

3)requestID 

1.2.2 序列化

  一旦確定了消息的數據結構后,下一步就是要考慮序列化與反序列化了。

  什么是序列化?序列化就是將數據結構或對象轉換成二進制串的過程,也就是編碼的過程。

  什么是反序列化?將在序列化過程中所生成的二進制串轉換成數據結構或者對象的過程。

  為什么需要序列化?轉換為二進制串后才好進行網絡傳輸嘛!

  為什么需要反序列化?將二進制轉換為對象才好進行后續處理!

  現如今序列化的方案越來越多,每種序列化方案都有優點和缺點,它們在設計之初有自己獨特的應用場景,那到底選擇哪種呢?從RPC的角度上看,主要看三點:1)通用性,比如是否能支持Map等復雜的數據結構;2)性能,包括時間復雜度和空間復雜度,由于RPC框架將會被公司幾乎所有服務使用,如果序列化上能節約一點時間,對整個公司的收益都將非??捎^,同理如果序列化上能節約一點內存,網絡帶寬也能省下不少;3)可擴展性,對互聯網公司而言,業務變化飛快,如果序列化協議具有良好的可擴展性,支持自動增加新的業務字段,而不影響老的服務,這將大大提供系統的靈活度。

  目前互聯網公司廣泛使用Protobuf、Thrift、Avro等成熟的序列化解決方案來搭建RPC框架,這些都是久經考驗的解決方案。

1.3  通信

  消息數據結構被序列化為二進制串后,下一步就要進行網絡通信了。目前有兩種常用IO通信模型:1)BIO;2)NIO。一般RPC框架需要支持這兩種IO模型,原理可參考:一個故事講清楚NIO。

  如何實現RPC的IO通信框架呢?1)使用java nio方式自研,這種方式較為復雜,而且很有可能出現隱藏bug,但也見過一些互聯網公司使用這種方式;2)基于mina,mina在早幾年比較火熱,不過這些年版本更新緩慢;3)基于netty,現在很多RPC框架都直接基于netty這一IO通信框架,省力又省心,比如阿里巴巴的HSF、dubbo,Twitter的finagle等。

1.4  消息里為什么要有requestID?

  如果使用netty的話,一般會用channel.writeAndFlush()方法來發送消息二進制串,這個方法調用后對于整個遠程調用(從發出請求到接收到結果)來說是一個異步的,即對于當前線程來說,將請求發送出來后,線程就可以往后執行了,至于服務端的結果,是服務端處理完成后,再以消息的形式發送給客戶端的。于是這里出現以下兩個問題:

1)怎么讓當前線程“暫?!保冉Y果回來后,再向后執行?

2)如果有多個線程同時進行遠程方法調用,這時建立在client server之間的socket連接上會有很多雙方發送的消息傳遞,前后順序也可能是隨機的,server處理完結果后,將結果消息發送給client,client收到很多消息,怎么知道哪個消息結果是原先哪個線程調用的?

  如下圖所示,線程A和線程B同時向client socket發送請求requestA和requestB,socket先后將requestB和requestA發送至server,而server可能將responseA先返回,盡管requestA請求到達時間更晚。我們需要一種機制保證responseA丟給ThreadA,responseB丟給ThreadB。

 

  怎么解決呢?

1)client線程每次通過socket調用一次遠程接口前,生成一個唯一的ID,即requestID(requestID必需保證在一個Socket連接里面是唯一的),一般常常使用AtomicLong從0開始累計數字生成唯一ID;

2)將處理結果的回調對象callback,存放到全局ConcurrentHashMap里面put(requestID, callback);

3)當線程調用channel.writeAndFlush()發送消息后,緊接著執行callback的get()方法試圖獲取遠程返回的結果。在get()內部,則使用synchronized獲取回調對象callback的鎖,再先檢測是否已經獲取到結果,如果沒有,然后調用callback的wait()方法,釋放callback上的鎖,讓當前線程處于等待狀態。

4)服務端接收到請求并處理后,將response結果(此結果中包含了前面的requestID)發送給客戶端,客戶端socket連接上專門監聽消息的線程收到消息,分析結果,取到requestID,再從前面的ConcurrentHashMap里面get(requestID),從而找到callback對象,再用synchronized獲取callback上的鎖,將方法調用結果設置到callback對象里,再調用callback.notifyAll()喚醒前面處于等待狀態的線程。

復制代碼
1 public Object get() {2         synchronized (this) { // 旋鎖3             while (!isDone) { // 是否有結果了4                 wait(); //沒結果是釋放鎖,讓當前線程處于等待狀態5             }6         }7     }復制代碼復制代碼
1 private void setDone(Response res) {2         this.res = res;3         isDone = true;4         synchronized (this) { //獲取鎖,因為前面wait()已經釋放了callback的鎖了5             notifyAll(); // 喚醒處于等待的線程6         }7     }復制代碼

 

2 如何發布自己的服務?

  如何讓別人使用我們的服務呢?有同學說很簡單嘛,告訴使用者服務的ip以及端口就可以了啊。確實是這樣,這里問題的關鍵在于是自動告知還是人肉告知。

  人肉告知的方式:如果你發現你的服務一臺機器不夠,要再添加一臺,這個時候就要告訴調用者我現在有兩個ip了,你們要輪詢調用來實現負載均衡;調用者咬咬牙改了,結果某天一臺機器掛了,調用者發現服務有一半不可用,他又只能手動修改代碼來刪除掛掉那臺機器的ip。現實生產環境當然不會使用人肉方式。

  有沒有一種方法能實現自動告知,即機器的增添、剔除對調用方透明,調用者不再需要寫死服務提供方地址?當然可以,現如今zookeeper被廣泛用于實現服務自動注冊與發現功能!

  簡單來講,zookeeper可以充當一個服務注冊表(Service Registry),讓多個服務提供者形成一個集群,讓服務消費者通過服務注冊表獲取具體的服務訪問地址(ip+端口)去訪問具體的服務提供者。如下圖所示:

  具體來說,zookeeper就是個分布式文件系統,每當一個服務提供者部署后都要將自己的服務注冊到zookeeper的某一路徑上: /{service}/{version}/{ip:port}, 比如我們的HelloWorldService部署到兩臺機器,那么zookeeper上就會創建兩條目錄:分別為/HelloWorldService/1.0.0/100.19.20.01:16888  /HelloWorldService/1.0.0/100.19.20.02:16888。

  zookeeper提供了“心跳檢測”功能,它會定時向各個服務提供者發送一個請求(實際上建立的是一個 Socket 長連接),如果長期沒有響應,服務中心就認為該服務提供者已經“掛了”,并將其剔除,比如100.19.20.02這臺機器如果宕機了,那么zookeeper上的路徑就會只剩/HelloWorldService/1.0.0/100.19.20.01:16888。

  服務消費者會去監聽相應路徑(/HelloWorldService/1.0.0),一旦路徑上的數據有任務變化(增加或減少),zookeeper都會通知服務消費方服務提供者地址列表已經發生改變,從而進行更新。

  更為重要的是zookeeper與生俱來的容錯容災能力(比如leader選舉),可以確保服務注冊表的高可用性。

3 小結

  RPC幾乎是每一個從學校進入互聯網公司的同學都要首先學習的框架,之前面試過一個在大型互聯網公司工作過兩年的同學,對RPC還是停留在使用層面,這是不應該的,希望大家不僅要會用而且要知道內部的原理。本文也僅是對RPC的一個比較粗糙的描述,希望對大家有所幫助,錯誤之處也請指出修正。

4 一些開源的RPC框架

https://github.com/alibaba/dubbo

http://thrift.apache.org/?cm_mc_uid=87762817217214314008006&cm_mc_sid_50200000=1444181090

 

轉載請標明源地址:http://www.cnblogs.com/LBSer
發表評論 共有條評論
用戶名: 密碼:
驗證碼: 匿名發表
亚洲香蕉成人av网站在线观看_欧美精品成人91久久久久久久_久久久久久久久久久亚洲_热久久视久久精品18亚洲精品_国产精自产拍久久久久久_亚洲色图国产精品_91精品国产网站_中文字幕欧美日韩精品_国产精品久久久久久亚洲调教_国产精品久久一区_性夜试看影院91社区_97在线观看视频国产_68精品久久久久久欧美_欧美精品在线观看_国产精品一区二区久久精品_欧美老女人bb
久色乳综合思思在线视频| 2019亚洲日韩新视频| 中文字幕视频一区二区在线有码| 精品亚洲精品福利线在观看| 亚洲国产私拍精品国模在线观看| 欧美精品激情blacked18| 亚洲精品91美女久久久久久久| 亚洲一区二区三区xxx视频| 国产精品美女呻吟| 欧美激情视频播放| 欧美精品国产精品日韩精品| 欧美精品成人91久久久久久久| 亚洲成人久久一区| 欧美国产日韩一区二区三区| 国模极品一区二区三区| 国产精品视频免费在线观看| 亚洲成人黄色网| 97人人爽人人喊人人模波多| 久久久亚洲影院| 亚洲欧美成人一区二区在线电影| 欧美日韩成人在线播放| 国产精品激情av在线播放| 久久久久久久久久久91| 国产精品久久久久福利| 2019日本中文字幕| 激情久久av一区av二区av三区| 国产精品久久久久77777| 在线视频中文亚洲| 操91在线视频| 亚洲综合色av| 欧美午夜宅男影院在线观看| 国产精品视频地址| 欧美激情视频三区| 久久精品视频99| 色偷偷噜噜噜亚洲男人的天堂| 国产欧美一区二区白浆黑人| 精品国产乱码久久久久酒店| 久久精品99久久久香蕉| 美日韩精品免费观看视频| 亚洲深夜福利在线| 中文字幕欧美在线| 亚洲电影在线看| 伊人成人开心激情综合网| 亚洲精品成人av| 久久久久北条麻妃免费看| 久久亚洲国产精品成人av秋霞| 久久激情视频免费观看| 中文字幕精品影院| 国产成人精品一区二区| 黑人狂躁日本妞一区二区三区| 超在线视频97| 91精品久久久久久久久中文字幕| 国产一区二区在线播放| 日韩av大片免费看| 久久久久久综合网天天| 欧美疯狂做受xxxx高潮| 日韩高清电影免费观看完整| 欧美激情视频播放| 欧美理论片在线观看| 日韩欧美精品网址| 丝袜美腿亚洲一区二区| 亚洲在线观看视频网站| 国产伦精品一区二区三区精品视频| 国产精品视频在线播放| 热99久久精品| 91禁国产网站| 欧美专区国产专区| 精品日本美女福利在线观看| 亚洲国产成人精品久久久国产成人一区| 国产在线观看不卡| 国产精品欧美激情| 欧美床上激情在线观看| 国产精品v片在线观看不卡| 最近2019好看的中文字幕免费| 91精品国产高清久久久久久91| 久久久人成影片一区二区三区观看| 91精品国产91久久久久久最新| 中文字幕亚洲第一| 久久理论片午夜琪琪电影网| 亚洲精品一区二三区不卡| 欧美日韩国产在线| 中文字幕v亚洲ⅴv天堂| 国产精品偷伦免费视频观看的| 日韩在线欧美在线国产在线| 国产精品第3页| 91中文精品字幕在线视频| 国产精品美女久久久久av超清| 国产精品入口日韩视频大尺度| 5252色成人免费视频| 久久69精品久久久久久国产越南| 日韩一区视频在线| 国产一区二区三区视频在线观看| 浅井舞香一区二区| 久久av资源网站| 欧美一区二区大胆人体摄影专业网站| 欧美专区日韩视频| 亚洲天堂男人天堂| 欧美成人免费小视频| 国产成人精品网站| 成人精品一区二区三区电影免费| 欧美日韩国产黄| 欧美美女操人视频| 国产区精品视频| 欧美在线视频网站| 久久久精品亚洲| 清纯唯美亚洲综合| 91亚洲精品在线观看| 中文国产成人精品久久一| 国产精品第一区| 亚洲乱码一区二区| 国产精品久久久av久久久| 欧美大奶子在线| 亚洲区一区二区| 国产偷亚洲偷欧美偷精品| 亚洲人午夜色婷婷| 久久频这里精品99香蕉| 日韩av影院在线观看| 91精品国产乱码久久久久久久久| 国产91色在线免费| 日本一区二区三区在线播放| 中文字幕av一区中文字幕天堂| 欧美另类极品videosbestfree| 久久福利网址导航| 欧美日韩国产中文精品字幕自在自线| 欧美在线影院在线视频| 日韩欧美成人区| 992tv成人免费影院| 日韩欧美在线国产| 96精品久久久久中文字幕| 久久精品国产亚洲| 91免费看视频.| 欧美亚洲国产日韩2020| 欧美一乱一性一交一视频| 在线免费观看羞羞视频一区二区| 精品国产91久久久久久| 日韩成人av在线| 国产噜噜噜噜噜久久久久久久久| 97精品视频在线观看| 成人乱人伦精品视频在线观看| 久久精品91久久久久久再现| 国产精品99久久久久久白浆小说| 国产精品成人aaaaa网站| 亚洲欧美日韩精品| 日韩成人久久久| 国产91精品高潮白浆喷水| 精品福利在线视频| 日韩av大片在线| 亚洲老板91色精品久久| 成人动漫网站在线观看| 国产精品久久色| 97视频在线观看视频免费视频| 国产成人精品日本亚洲专区61| 亚洲永久在线观看| 亚洲精品一区二区三区不| 久久夜色精品国产欧美乱| 国产精品白嫩美女在线观看| 日韩成人黄色av| 美女视频黄免费的亚洲男人天堂| 欧美一性一乱一交一视频| 亚洲精品国产美女| 色狠狠av一区二区三区香蕉蜜桃| 国产精品成人播放| 久久亚洲一区二区三区四区五区高| 色婷婷综合久久久久|