淺析「遠程對象調用」

版權聲明:本文由韓偉原創文章,轉載請註明出處: 
文章原文連接:https://www.qcloud.com/community/article/242java

來源:騰雲閣 https://www.qcloud.com/community程序員

 

做者介紹:韓偉,1999年大學實習期加入初創期的網易,成爲第30號員工,8年間從程序員開始,歷任項目經理、產品總監。2007年後創業4年,開發過視頻直播社區,及多款頁遊產品。2011年後就任於騰訊遊戲研發部公共技術中心架構規劃組,專一於通用遊戲技術底層的研發。算法

要說「遠程對象」,必先說「遠程調用」,也就是RPC。比較著名的RPC框架有,最近很火的gRPC,也就是Google開源的RPC。另外還有Facebook開源的Thrift等等……我廠內部也有不少RPC框架,琳琅滿應接不暇。Java在JDK裏面也支持RMI(Remote Method Invoke: 遠程方法請求)功能,也能夠視爲一種RPC,但實際上這個更像咱們如今要討論的「遠程對象調用」。數據庫

在諸多的RPC中,咱們都基本認爲是經過網絡,對運行在另一個進程(或者電腦)裏的某個函數,發起一次調用請求。既然是一次函數調用,那麼咱們天然要傳入參數,而後指望得到返回值。在這個過程當中,咱們每每只須要輸入:函數名+參數,RPC就能找到一個遠程的進程,去執行對應的函數,而後傳入目標參數。在這個過程裏,執行這個函數的進程,會被認爲是無狀態的,全部的輸出,都僅與輸入的參數有關,除非有一部分狀態是記錄在數據庫(持久化設備)上的。所以,計算的過程(算法),和計算的數據,實際上分離的,這些計算所需的數據,要麼來源於參數,要麼是數據庫設備。而被請求的函數,以及裝載這個函數的容器——進程,是不保證任何的狀態維護能力的。編程

而「遠程對象調用」,正是在「狀態」這個環節上,和RPC不一樣——它是由框架去保證某種狀態的。當咱們發起一個遠程對象調用的時候,是須要首先「找到」一個遠程對象,而後再發起「方法」(成員函數)調用。這和RPC就產生了兩個明顯的區別:緩存

  1. 咱們須要用某種手段定位到對象,而不是僅僅用一個函數名。對象是一個更復雜的遠程概念,由於有可能同屬於一個類(class),而存在多個狀態一致或不一致的對象,在遠程的機器上存在。咱們就不能僅僅經過一個固定的路由標誌(好比類名)去找一個這樣的對象。遠程對象的路由方式成爲不一樣「遠程對象調用」框架之間的一個顯著區別。服務器

  2. 咱們並不須要把全部的數據,在每次請求時都經過參數發給遠程對象,由於對於同一個遠程對象來講,它是能夠包含大量過程狀態的。咱們只要找到正確的遠程對象,就能得到以前操做所形成的結果狀態。有遠程對象每每是生存在進程的內存中,因此對於訪問本身的狀態數據,會很是快速,這對於有延遲壓力的程序來講,是很是有用的。網絡

因此,遠程對象調用,最大的特色,就是數據和計算是合併在一塊兒的——這很好的提升了使用面向對象編程的便利性,也大大下降了遠程調用中由於數據拉取產生的延遲。數據結構

遠程對象的優勢:DB壓力、易用性

在傳統的「請求-響應」爲基礎的分佈式服務器中,最多見的數據系統是:接入-邏輯-緩存-數據庫 這樣一個四層結構。爲了讓承擔計算壓力的「邏輯」模塊能分佈到不一樣的進程上,咱們每每會把「邏輯」模塊作成「無狀態」的,這樣咱們就能夠隨意的啓動、中止任何一個邏輯模塊的進程,而不須要擔憂所以丟失用戶數據。可是這樣作,邏輯模塊是輕鬆了,承擔狀態存儲的「緩存-數據庫」哥倆壓力就大了。由於每個數據操做,都須要去從他們這裏讀取數據,而後再回寫結果(若是有數據修改操做的話)。
架構

因爲「緩存-數據庫」模塊是有狀態的,通常來講還很難簡單的作分佈式部署,由於若是隨機分佈數據的話,邏輯模塊可能就會找不到狀態所在的緩存進程。從CAP理論能夠知道,咱們要讓狀態能分佈,就必定要犧牲一些一致性或可用性。所以咱們更傾向以NoSQL的存儲系統去充當「緩存-數據庫」模塊。可是,即使是NoSQL,仍是會有兩個缺點:一個是跨進程訪問的延遲;一個是編程上的複雜性。

跨進程訪問的延遲來源於兩方面,一方面是自己跨進程經過socket之類的手段通信,就會有比進程內存訪問高的多的延遲,並且咱們經常會把一個業務流程按數據的類型劃分到不一樣的「邏輯模塊」裏,這樣一個業務請求可能會須要屢次的跨進程訪問才能訪問完所需的數據,這就大大加劇了由於網絡帶來的延遲;另一方面來源於路由查找,雖然咱們能夠用一致性哈希這類算法取代路由查找,可是基於數據的業務特性,咱們卻不太喜歡把全部數據都拆的七零八落,因此經常仍是有一個查詢、或探索數據所在地的過程。

編程的複雜性也是很嚴重的問題。無論是SQL仍是NoSQL,這些數據都是以序列化的方式描述的,而且也按照數據的組織(存放)形式,要求使用者去準備好輸入或者解析讀出這些數據。這些數據和咱們在編程中經常使用的結構體、對象每每徹底是不同的形式。這就形成了咱們不少額外的編碼和調試的工做。這些數據每每仍是「結構敏感」的:若是咱們修改了數據結構,每每須要從新配置數據表結構,修改訪問代碼等等。這讓咱們在快速開發業務邏輯的時候,背上沉重的開發效率包袱。——所以業界纔有不少所謂ORM(對象關係映射)的框架出現。

可是若是咱們使用「遠程對象調用」,就能夠有效的緩解以上兩個問題:

  1. 緩解跨進程延遲。因爲遠程對象自己已經包含了數據,因此對於所需的數據,都是從內存中直接讀寫,這方面的延遲是絕對最快的。另外,因爲遠程對象調用發起以前,已經須要先查找到目地對象,這樣就把查找方法和查找數據的兩個過程合二爲一了,在路由層面也能有效下降延遲。

  2. 極好的易用性。因爲面向對象編程的概念已經深刻人心,因此對於「先找到一個對象」,而後「調用其方法」的過程,是很是天然的。複雜的負載均衡、容災、擴容等問題,實際上都隱藏在「查找對象」這個環節底下,開發者幾乎無需關心,因此用起來會很是方便。而編寫一個遠程對象,也很是簡單,就是寫一個類,實例化一個對象,而後登記到服務器裏而已。這都是面向對象編程的傳統作法。因爲對象自己都是帶數據的,因此編寫這些遠程方法也會比較簡單,大部分的數據都直接在本地內存讀寫,好比從對象成員屬性裏。節省了大量編寫SQL或者定義和使用特別的存儲設備協議的時間。

業界遠程對象方案:EJB/MS-WCF/IBM-ORB

遠程對象調用的框架,在業界也是常見的東西,這裏大概說一下三家的: EJB, MS WCF, IBM ORB。這三家的框架大概的說明如今遠程對象調用的主流用法。

1.EJB

EJB全稱Enterprise Java Bean,是Java的企業分佈式集羣方案的核心(J2EE規範)。能部署在多個服務器上提供遠程對象調用服務的JAVA對象,就稱爲EJB對象。底層的網絡是經過JDK自帶的RMI功能實現。EJB自己只是J2EE規範中的一部分,僅僅是一套接口。具體的實現由相似Weblogic這樣的「EJB容器」軟件提供。EJB之因此不及SSH(Spring Structs Hibernate)流行,很大緣由就是由於這些容器軟件都是商業軟件,須要花很貴的價格購買。但這並不影響EJB做爲一個優秀的遠程對象方案的技術地位。EJB如今已經升級到3.0版本以上了,摒棄了之前配置複雜,功能晦澀的特色,大膽的使用更簡單的生命週期管理、簡單的註解式配置、好用的ORM能力,讓EJB 3.0從新成爲一流的技術。

一個客戶端程序,想要訪問一個EJB對象,通常須要使用一個叫作JNDI的API,來具體鏈接到EJB對象上。JNDI的全稱是Java Naming and Directory Inerface,基本等於咱們常說的名字、目錄服務接口。Java經過一套API規範,來統一各類目錄服務器的使用方法。全部的J2EE容器,都必須提供一個JNDI服務,而客戶端程序則經過使用J2EE容器提供的JNDI來訪問容器內的EJB對象。JNDI的使用方法,基本上就是輸入一個字符串,而後API會返回給你一個對象。在J2EE的環境裏,這個對象就是EJB對象的Home接口對象(對應遠程EJB對象的一個映像,也叫樁對象)。代碼相似:

Context ctx = new InitialContext(env);
Object ejbHome = ctx.lookup(「java:comp/env/ejb/HelloBean」);
HelloHome empHome = (HelloHome) PortableRemoteObject.narrow (ejbHome,  HelloHome.class);

輸入lookup()函數的字符串,是用戶能夠本身定義的任何內容,只要在對應的EJB容器裏面登記了這個對應關係便可。從這個代碼咱們能夠看到,若是EJB想要作容災、負載均衡等功能,是徹底能夠經過ctx.lookup()這個接口來實現的。另外,遠程對象的Home接口(樁代碼)是須要預先部署在客戶端測,在上面的例子裏是HelloHome.class這個類。而EJB對象的這個Home接口類,是由EJB工具,自動經過來源的EJB對象類定義生成的。對比CORBA,Thrift等技術,EJB能夠直接用.java源代碼代替IDL定義,而後自動生成樁代碼,這確實是簡便不少。

EJB規範把遠程對象定義爲三種:無狀態會話Bean,有狀態會話Bean,消息驅動Bean。這意味着EJB容器對於EJB對象的生命週期是有管理的。其中無狀態會話Bean和消息驅動Bean的聲明週期是相似的,都是來一個請求(消息驅動的意思是每來一個JMS消息),就可能new一個Bean對象。固然也可能不是每次請求都新建對象,總之容器不保證會保持Bean對象的生存週期,這樣容器能夠根據負載壓力,靈活的管理衆多的Bean對象。而最特別的是「有狀態會話Bean」,容器會根據客戶端的會話狀態(和客戶端的context對象對應),來保持Bean對象,也就是說,每一個客戶端context對應一個有狀態Bean。若是你用這個客戶端context,發起屢次lookup()查找,訪問的那個EJB對象都將會是同一個。這對於須要保持登陸狀態的服務,就很是方便了。客戶無需本身去維持一個遠程對象的生命週期,而能獲得狀態保存的功能。

最後說說EJB的部署配置,之前的EJB容器部署異常複雜。除了須要寫一個繼承於特定基類的業務JAVA類外,還要配置不少細節。而EJB3.0以後,經過JAVA註解功能(Annotation),這些配置均可以和源代碼寫到一塊兒,而業務JAVA類也無需集成特定的接口和類型,能夠是任何一個普通的類(POJO),只是須要加上一些特定的註釋便可。EJB容器提供工具對這些加了EJB註釋的JAVA類進行處理,一方面把這個JAVA類自動部署到容器中,另外一方面生成客戶端的Home接口類文件,供用戶發佈(拷貝)到須要使用的客戶方服務器上去。而一些EJB容器(如Weblogic)還提供了Eclipse(IDE)的圖形界面工具,讓整個過程幾乎都不在須要編寫額外的配置和命令行操做。

2.MS WCF

WCF全稱Windows Communication Foundation,是微軟發佈的用於構建面向服務的應用程序框架。這套框架的底層是Windows的COM+技術,而編程接口則更多的使用C#語言/VB語言和.Net平臺。這和EJB有必定的相似,差異就是WCF中的遠程對象,不須要一個像JVM那樣的虛擬機,而是結合在WINDOWS操做系統裏。

無獨有偶,WCF的遠程接口定義,也是直接使用C#/VB代碼,加上相似註解的「特性」(Attribute)功能註釋,標註在一個定義好的接口(Interface)上來組成的。具體的業務實現類,只要「實現」定義的這個接口就能夠了,和一個普通的類沒有任何差異。和EJB的差異是,咱們仍是須要寫一段XML配置,把這個遠程對象的接口和查找字符串,註冊到萬能的IIS服務器裏面。一旦註冊完成,就能夠經過URL:http://xx.xx.xx.xx/servicesname/service.svc這樣的字符串去訪問了。同時,若是客戶端想要訪問這個遠程對象,則須要使用svcuitl.exe這個工具,輸入剛剛註冊的那個URL,就能夠生成對應的客戶端樁代碼庫。客戶端能夠直接new這個新創建的樁類型對象,而後直接調用其方法,就和調用本地對象的方法同樣。

// Create a client.
CalculatorClient client = new CalculatorClient();

// Call the Add service operation.
double value1 = 100.00D;
double value2 = 15.99D;
double result = client.Add(value1, value2);
Console.WriteLine("Add({0},{1}) = {2}", value1, value2, result);

固然,若是你想鏈接不一樣的服務器,仍是有機會的,一位內生成的客戶端代碼,會使用一個配置文件。在裏面能夠修改遠程服務器的地址(仍是那個註冊的URL)。

<client>
     <endpoint
         address="http://localhost/servicemodelsamples/service.svc" 
         binding="wsHttpBinding" 
         contract=" Microsoft.ServiceModel.Samples.ICalculator" />
</client>

你除了能夠經過IIS來提供WCF的遠程對象服務外,還能夠本身寫一個單獨的程序,經過定義main()來徹底的控制這些遠程對象,從而提供服務。另外,WCF除了經過URL直接對應一個遠程對象外,還能夠經過編寫「路由服務」,來對同一個URL的遠程對象調用進行靈活的路由。雖然WCF沒有提供相似EJB的遠程對象生命週期管理功能,可是你徹底能夠經過WCF的服務API和路由服務,來本身編碼實現任何形式的遠程對象生命週期管理。

3.IBM RMI-IIOP

IBM公司的RMI-IIOP服務,是以JAVA技術爲基礎的,可是又不一樣於EJB的另一套遠程對象技術。這套技術更接近於以JAVA爲基礎實現的CORBA體系。這個技術的使用標準的JAVA RMI接口(RMIInterface)做爲遠程對象的接口,使用JAVA的序列化、反序列化能力做爲編碼能力。而後本身寫一個main()函數,創建一個org.omg.CORBA.ORB對象來構造一個遠程服務器。而客戶端則是經過一個字符串來定位想要訪問的遠程對象。這個字符串相似:corbaloc:iiop:1.2@localhost:8080/OurLittleClient 。咱們能夠看到這裏面有IP和端口,還有一個編寫服務器遠程對象時註冊的字符串OurlLittleClient。咱們經過rmic –iiop Server這樣的命令行部署遠程對象,而後用start java Server啓動服務器,用start java Client啓動客戶機。這些命名,都是包含在IBM Developer Kit for Java technology v1.3.1裏面的。咱們能夠發現,RMI-IIOP是一個更加原始的遠程對象方案,基本上就是一個CORBA的API實現的組合。使用起來有點繁瑣,可是好處是不須要學習和部署複雜的容器服務,能夠徹底本身編碼去實現一套遠程對象服務。這裏沒有限定你使用什麼方法去定位查找遠程對象,也沒有限定你怎麼管理遠程對象的生命週期,一切都由開發者本身去編寫實現。

小結

規範 遠程對象定位 遠程對象生命週期管理 服務器部署
EJB JNDI路徑字符串查找 自動管理,帶會話狀態對象 使用容器服務
WCF URL、路由服務 部署到IIS或自寫main()
RMI-IIOP COBRA URL定位 自寫main()

在對象定位的選擇上,經過字符串查找已是標準,而複雜的自定義路由也能夠隱藏在這個查找操做下面。遠程對象的生命週期管理,其實是對服務器資源的管理,除了EJB有容器支持之外,其餘的方案都比較少提供這樣的能力,說明這一塊是比較困難的。服務器部署方面,可讓用戶以API本身寫main()去構建服務器,提供了極大的靈活性。

遠程對象的挑戰:生命週期管理、數據一致性

經過上面的分析,咱們能夠發現,遠程對象的生命週期管理,是一個比較重大且複雜的課題。咱們要保證這樣的生命週期管理程序,能有一個通用的策略,來保持各類業務狀況下的服務器資源穩定,是比較困難的。並且在分佈式系統的狀況下,爲了負載均衡,還要把一樣類型的遠程對象,部署到不一樣的進程上,這就引入了一個新的問題:數據一致性。

遠程對象的生命週期,除了佔用服務器的內存資源外,還會佔用記錄其地址的路由空間,檢查維護生命週期的CPU運算時間。若是咱們提供自動化的對象生命週期管理,勢必就須要在客戶使用的時候,提供這方面的教育,以及防止客戶使用錯誤、過載等狀況下對象管理失效的防護性策略。因此即使是EJB容器,也僅僅提供了很是簡單的生命週期管理策略:會話狀態、無狀態這兩種。

對於通常的互聯網應用,只有EJB這兩種生命週期管理的遠程對象,基本上是夠用的。由於通常的互聯網應用,大部分數據都是持久化數據,須要讀寫數據庫。臨時狀態數據通常來講很少,主要是用戶登陸後的產生的一些過程數據,有一個「會話(Session)」類型的生命週期就足夠了。可是,若是咱們的業務是網絡遊戲,那麼這麼簡單的生命週期就是徹底不夠的,由於遊戲中有大量的臨時狀態,好比組隊的狀態,玩家所在房間的狀態,關卡副本的狀態等等。這些臨時狀態,都是須要咱們經過業務邏輯代碼,來控制和管理所對應的對象生命週期的。因此一個適合遊戲的遠程對象系統,須要提供讓客戶端程序來選擇,「新建/初始化」和「銷燬」遠程對象的能力。

在對遠程對象進行管理的時候,咱們經常會用到一種叫「對象池」的技術,使用這種技術避免頻繁的新建和銷燬對象。可是若是這些對象的是帶狀態的,那麼咱們的「池」就必須帶索引,而且對象也必須有一個key。同時咱們的對象還須要有一個「reset」的重置方法,用來讓對象迴歸到初始化狀態。

在分佈式的系統下,咱們的對象池由於是分別存放在不一樣的機器上,因此其一致性的維護每每是比較困難的。可是,咱們能夠把這個問題,轉換成構建一個「分佈式對象池」的問題。假如每一個對象池,都按KEY的某個規律,如一致性哈希,存放不一樣的對象。那麼只要在遠程調用發起的時候,也就是經過lookup()查找遠程對象的時候,把請求導向到對象所在進程,那麼就能很方便的從本地進程對象池中得到對象。遠程對象的「定位」和「一致性」在查找對象這個環節結合起來,是一個很是好的想法。這樣能讓遠程狀態對象的使用進一步簡化,用戶徹底無需關心遠程對象在什麼地方,又能快速的訪問到正確的對象。

[擴容下的遠程對象遷移]

當分佈式的對象容器出現部分進程故障,或者須要動態擴容的時候,只要咱們針對對象查找的數據作某種程度的數據搬遷,或者緩存清理,就能很容易的實現對象的從新分佈。若是對象同時可以支持持久化,那麼這種數據搬遷,只須要簡單的讓對象寫入持久化。而後在新的機器上,經過緩存創建的策略,從持久化設備讀取出對象便可。

總結

遠程對象調用,是一種業界成熟的分佈式服務器系統模型。這套模型提供了強大的分佈式程序架構能力,而且能方便的置入統一的運維特性能力:容災、擴容、負載均衡。

它比遠程方法調用,增長了對數據位置的指向,能有效的提升系統的響應速度。同時面向對象的形態,也能顯著下降複雜邏輯的開發成本。

遠程對象的生命週期管理,實際上一種分佈式緩存系統的管理。良好的遠程對象系統,能提升豐富的生命週期管理功能,以適合網絡遊戲,這種須要處理豐富臨時狀態的行業需求。

若是咱們把遠程對象的尋址和數據一致性維護結合起來,而且提供對象的持久化支持,那麼遠程對象調用將是一個高度自動化,且具備自我維護能力的強大分佈式計算系統。

相關文章
相關標籤/搜索