天天看點

RPC(遠端過程調用)簡介1 如何調用他人的遠端服務?2 如何釋出自己的服務?

RPC(Remote Procedure Call Protocol)——遠端過程調用協定,它是一種通過網絡從遠端計算機程式上請求服務,而不需要了解底層網絡技術的協定。

之前聽過這個名詞,但是也隻是大概記住了“遠端調用”之類的關鍵詞,而其他并沒有太多了解。

來到TX實習,确實如别人所說的那樣,公司内部有自己的開發架構。我所在部門使用的是一個叫做TAF(Tencent Application Framwork)的背景架構,它本質上是一個分布式系統,提供了良好的RPC封裝。

那麼問題來了——RPC到底是什麼?

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

//服務接口
public interface HelloWorldService {
    String sayHello(String msg);
}
           
//服務實作
public class HelloWorldServiceImpl implements HelloWorldService {
    @Override
    public String sayHello(String msg) {
        String result = "hello world " + msg;
        System.out.println(result);
        return result;
    }
}
           
//本地服務調用
public class Test {
     public static void main(String[] args) {
         HelloWorldService helloWorldService = new HelloWorldServiceImpl();
         helloWorldService.sayHello("test");
     }
 }
           

而在大型網際網路公司,公司的系統都由成千上萬大大小小的服務組成,各個服務部署在不同的機器上,由不同的團隊負責。

這時就會遇到兩個問題:

  1. 要搭建一個新服務,免不了需要依賴他人的服務,而現在他人的服務都在遠端(而不是在本地),怎麼調用?
  2. 其它團隊要使用我們的服務,我們的服務該怎麼釋出以便他人調用?

RPC可以解決上面兩個問題。簡單來說,RPC就是說,假設有兩台伺服器A和B,一個應用部署在A伺服器上,想要調用B伺服器上應用提供的函數/方法,由于不在一個記憶體空間,不能直接調用,需要通過網絡來表達調用的語義和傳達調用的資料。

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

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

如果有一種方式能讓我們像調用本地服務一樣調用遠端服務,而讓調用者對網絡通信這些細節透明,那麼将大大提高生産力,比如服務消費方在執行helloWorldService.sayHello(“test”)時,實質上調用的是遠端的服務

RPC的例子有:阿裡巴巴的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這些步驟都封裝起來,讓使用者對這些細節透明。

Q:怎麼做到透明化遠端服務調用?

答:使用代理!

Q:為什麼使用代理呢?

舉個例子:假設你有一套房子要賣,一種方法是你直接去網上釋出出售資訊,然後直接帶要買房子的人來看房子等等,另一種方法是去找中介,中介實際上就是你的代理——本來是你要做的事情,現在中介幫助你一一處理。對于買方來說跟你直接交易跟同中介直接交易沒有任何差異,買方甚至可能覺察不到你的存在,這就是代理最大的好處。(當然,還有另外一個好處就是:在你很忙的時候,你可以把事情交給代理去做!)

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

Q:怎麼對消息進行編碼和解碼?

首先,要确定消息資料結構。通信的第一步就是要确定用戶端和服務端互相通信的消息結構。用戶端的請求消息結構一般需要包括以下内容:

1)接口名稱

比如“HelloWorldService”,如果不傳,服務端就不知道調用哪個接口了;

2)方法名

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

3)參數類型&參數值

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

4)逾時時間

5)requestID,辨別唯一請求id。

同理服務端傳回的消息結構一般包括以下内容:

1)傳回值

2)狀态code

3)requestID

第二步,就是序列化與反序列化。

序列化:将資料結構或對象轉換成二進制串的過程,也就是編碼的過程。

反序列化:将在序列化過程中所生成的二進制串轉換成資料結構或者對象的過程。

為什麼需要序列化:轉換為二進制串後便于網絡傳輸。

序列化方案在選擇的時候,主要看三點:

1)通用性,比如是否能支援Map等複雜的資料結構;

2)性能,包括時間複雜度和空間複雜度,由于RPC架構将會被公司幾乎所有服務使用,如果序列化上能節約一點時間,對整個公司的收益都将非常可觀,同理如果序列化上能節約一點記憶體,網絡帶寬也能省下不少;

3)可擴充性,對網際網路公司而言,業務變化快,如果序列化協定具有良好的可擴充性,支援自動增加新的業務字段,删除老的字段,而不影響老的服務,這将大大提供系統的健壯性。

目前國内各大網際網路公司廣泛使用hessian、protobuf、thrift、avro等成熟的序列化解決方案來搭建RPC架構。

2 如何釋出自己的服務?

如何讓别人使用我們的服務呢?

最基本的,我們需要告訴使用者服務的IP以及端口。但是問題在于,如果是直接通過告知IP+port的方式,将會有一系列問題:如果你發現你的服務一台機器不夠,要再添加一台,這個時候就要告訴調用者我現在有兩個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 與生俱來的容錯容災能力,可以確定服務系統資料庫的高可用性。

繼續閱讀