編程十年之那些年我見過和用過的RPC

轉自:https://pure-earth-7284.herokuapp.com/2016/02/08/ten-years-rpc/

 

如果從我在Code::Blocks上寫下的第一行代碼開始算起,到現在也快十年了。十年之間,經歷了互聯網的流行,Web 2.0的興起,社交網絡的躥紅,移動互聯網的爆發,以及物聯網時代的前奏。伴隨着每一次IT革命,與編程相關的各種技術也在不斷的演進。各種編程語言,各種IDE,各種框架和技術,對於IT從業者,這是一個最好的時代。我希望能夠用幾篇文章來回顧過去十年,我所經歷過的那些技術變遷。

本文是這個系列的第一篇,回顧過去十年,那些我見過和用過的RPC技術。

那些RPC

在互聯網還沒那麼流行的年代,大家的電腦上裝的都是單機軟件,通信基本靠軟盤和光盤。自從有了互聯網,計算機的世界就不一樣了。計算機上的程序,不再是一個個獨立的小東西,它們開始學會和其他計算機上的程序交流。人與人之間交流,我們管它叫做社交;而程序與程序之間交流,我們稱之爲RPC(Remote Procedure Call,遠程過程調用)。

RAW socket

我參加的第一個真正意義上的項目,是在大學裏和一羣計算機系的學長們,用一個十一長假,做一個叫做KIT的即時聊天軟件,這個KIT取的是Keep in Touch的寓意。客戶端技術棧是C++/Qt,服務端是C++/Linux。那時候,我只知道電腦插上網線,就可以上網,至於兩個電腦之間是如何聯接,數據是如何從一個電腦發出去,另一個電腦如何收到數據,我一竅不通(雖然我的專業是電子信息工程,但我們卻不學計算機網絡)。還好和我一起負責客戶端開發的學長知道有Socket這麼個東西,照着Qt手冊上的例子,我們就這麼艱難的開始了。

我們首先和開發服務端的兩個同學約定好要傳輸什麼數據,比如:一條消息的前兩個字節代表消息來源,後兩個字節代表消息目的地,然後就是消息內容;客戶端獲取在線用戶列表,服務端返回的數據就是一個3*N個字節的數據,每一組3字節是由一個2字節的用戶ID加上1字節的狀態標示。看起來沒什麼問題,照着這個約定開工寫代碼。

大概用了兩天時間,代碼寫好,兩撥人決定測試發個消息看看。不出所料,失敗了。客戶端的消息,服務端是接收到了,但是轉換到代碼裏,卻有兩個小問題:1) 源ID和目的地ID的值不對,2) 消息內容都是亂碼。簡單來說,就是整條消息沒有對的。然後又花了一天時間,大家不停地嘗試發送不同的數據,看看到底是什麼問題。亂碼問題很快確認了,從windows發出的數據是ANSI編碼,Linux接收的時候是一UTF8解碼,自然就錯了。ID錯誤的問題花的時間更久一些,最後發現其實是大端(Big-endian)和小端(Little endian)的問題。

解決了那兩個“小問題”,我們歡欣鼓舞。在繼續開發功能時,發現原來設計的數據格式有問題,發送的消息裏面沒有帶上時間信息。很簡單,加上就完了嘛。但當時在做服務端端的同學正在做其他功能,於是我們客戶端就先做了,在兩個ID之後,也就是第5個字節開始,用4個字節表示時間。過了一天,服務端的同學說他們也做完了,我們可以再測一下。於是,不出所料,又失敗了。這一次是因爲做服務端的同學不知道我們把時間放在了兩個ID之後,他們把時間放在了最前面。

除了上面遇到的幾個問題,我們後面還遇到了Socket池性能優化,公網穿透,大文件傳輸等等問題。總的來說,在解決通信問題時,選擇用原始Socket真的是需要勇氣和能力的。

COM/COM+/DCOM

從上面的例子可以看出,要想讓不同應用程序之間能夠通信,需要很複雜的工作,最基本的是確保通信雙方能夠在網絡中找到對方,初次之外,還要保證通信雙方以同樣的方式理解數據。COM(組件對象模型,Component Object Model)技術是在這個方向上的一次成功嘗試,其中COM/COM+首先嚐試解決後者,他們的分佈式升級版本DCOM則解決了前者。

COM技術的目標是讓使用不同語言編寫、運行在不同進程甚至不同計算機中的程序可以通過組件對象模型來進行統一建模,使得不同程序之間可以用同樣的方式來調用彼此。在調用COM接口時,調用者除了需要知道調用的方法和參數,不需要知道任何被調用者的細節。面向對象技術在當時剛剛興起,微軟在設計COM時就非常成功地利用了面向對象強大的抽象能力,將RPC抽象成了簡單的方法調用,讓RPC用起來就像是本地方法調用一樣。儘管COM技術後來逐漸衰落,但它給其繼任者指出了一條陽光大道。

COM概念的提出,應該說也是很有超前意識的,但微軟的問題就就在於,總是希望用一種技術作爲標準,統一所有其他技術。COM本應是一次很好的嘗試,但是爲了成爲大一統技術,可能也受到了當時IT領域追求完全面向對象的過渡煽動,結果結果就導致了過度抽象。

畢業工作後,我參加的第一個項目,是一個用來替換windows原生桌面的應用軟件。當時我選擇界面部分使用HTML/JavaScript來渲染,底層通過C++和操作系統進行交互。JS引擎和C++程序之間的通信,就是COM。舉一個例子,用戶在界面上雙擊一個圖標,這個程序就會被打開,雙擊操作是在JS代碼中捕獲,打開程序是通過C++調用系統API來執行。按照COM設計的初衷,在JS代碼中應該看起來像是在執行本地代碼:


 

1

2

3


 

icon.on("click", function (app) {

sys.open(app)

})

而實際上,sys.open這個方法是C++提供的,通過COM導入到JS中的。在COM中,所有的方法調用,都被建模成一個對象的invoke方法調用,調用時需要傳入方法名,和參數列表。app這個對象包含了要打開的應用程序信息,比如路徑,啓動參數等等。但問題在於,這個參數是JavaScript對象,在C++這種靜態語言中中如何匹配這個動態語言的對象呢?COM中引入了個叫做variant_t的類型,實際上就是在靜態語言中對動態類型數據的一種封裝,這樣就可以在運行時將數據轉換成需要的類型。微軟的工程師們確實強大,在那個連C++都還沒形成標準的年代,就實現了類似現代C++中RTTI的特性。但帶來的問題就是,我花費了大量的精力在數據格式映射、接口參數調試等工作上。而最終我放棄了讓JS對象直接映射成C++對象的嘗試,選擇在傳遞參數時先序列化成字符串,然後在C++中進行字符串解析。

作爲COM的分佈式版本,DCOM在使用者看起來,和COM沒有區別,只是在部署時需要比較複雜的配置,將各個組件註冊到各個客戶機中。DCOM在剛剛推出時,算是引領潮流的一項技術,甚至很多Unix產品以及IBM大型服務器中都開始使用DCOM。但沒過幾年,微軟就轉而去推廣其基於XML的分佈式解決方案,先是SOAP,接着是WCF,這些我們後文會講到。

以上對於COM的評價只是基於我個人的使用經驗,如果要深入理解COM,可以看看侯捷老師的《COM本質論》。

CORBA

除了COM,在RPC技術領域的另一個先驅,是OMG組織提出的CORBA標準。作爲跨主機的分佈式解決方案,CORBA比DCOM的出現還要早幾年。在那個時代,大家對於計算機之間如何通信還沒有一個確定的方案,TCP/IP協議棧也不過是衆多協議棧中的一個選擇。看看維基百科上CORBA的目標:

CORBA enables communication between software written in different languages and running on different computers.

以當時的技術背景,“different computers”不是我們現在理解的不同計算機,這裏的不同,是真的不同,包括架構不同,系統不同,協議棧不同等等。

在解決不同應用程序之間如何找到對方,並可靠地將數據傳輸過去方面,CORBA做了非常有意義的嘗試。CORBA提出了IDL(接口定義語言)的概念,通過一箇中間語言來定義接口,屏蔽了不同編程語言的異構性;CORBA的另一個重要概念是ORB(對象請求代理),應用程序只需要向ORB發起本地請求,ORB負責找到目標對象,並完成消息傳遞。後來的很多使用中間語言來描述通信接口的技術,估計都是受了CORBA的啓發;而消息代理模式,在後來的大多數消息中間件中,都成了標配。

關於CORBA,大多是從我的一些同事那裏道聽途說過的一些故事。同事的項目是一個由40多個不同的節點組成的分佈式系統,由CORBA負責節點之間的通信,而描述各種消息格式,文檔寫了幾十頁。而每次啓動整個系統,需要至少5個人,分別在40多個節點之間穿梭,按照順序啓動每個節點上的程序。

另外一個值得一提的事情是,CORBA的衰亡,和它流行的速度一樣快。

The Rise and Fall of CORBA一文中,比較詳細的介紹了爲什麼CORBA會衰落。總結起來有幾點:

  1. 標準不夠完善,導致各個商業版本的實現不一致(後來的Web其實也有這個問題);
  2. 功能跟不上時代,互聯網的快速發展對於安全通信提出了需求,但CORBA並沒有提供這方面的支持;
  3. 接口定義複雜,暴露過多細節,對開發人員要求高,容易出現bug;

其實,出現這些問題,並不是CORBA的設計者能力不足,而是因爲在那個時代,業務發展快過技術發展,對於分佈式系統的通信,大家並沒有很好的全局感。在一個通信系統中,哪些是穩定不變的,哪些是隨業務不斷變化的,該如何抽象和分層,程序員們還沒來的及很好地思考這些問題,就已經有客戶付錢要買了。所以CORBA作爲一個分佈式系統框架的半成品,在沒有經過足夠驗證的情況下,就快速推向市場,最終導致了失敗。不過,CORBA也有其積極意義,後來的一些分佈式系統框架,或多或少都有些CORBA的影子。

SOAP

在學校期間,我就用過SOAP做項目了,但是直到現在,我對於SOAP那些巨大、複雜的XML,仍然望而生畏。

IT技術的發展,總是伴隨着一個又一個“銀彈”的提出和破滅。SOAP是其中之一。

COM和CORBA在九十年代以及21世紀初都是RPC的主流技術,但是他們配置複雜,運維困難,授權費用高昂,尤其是他們號稱能夠解決編程語言和平臺的異構性,但實際上,即使是同一種編程語言,同一種平臺,要使用COM和CORBA進行通信,在開發過程中依然存在數據格式匹配失敗的問題。就像我在用COM進行開發,最終放棄二進制數據格式直接映射,而是選擇用字符串作爲傳輸媒介一樣,大家也逐漸意識到,直接在二進制數據上進行數據轉換,太容易出錯,而且靈活性非常差。

就在這個時候,XML被提出來,給所有程序員帶來除二進制之外的另一種數據描述方式。基於XML的通信標準SOAP也順勢被提出來。可能是類似COM和CORBA這樣的技術給大家帶來的痛苦過於深刻,也可能是IT從業者從來都是好了傷疤忘了疼,大家根本來不及詳細評估SOAP是否真的如其宣傳的那麼美好,依然迫不及待的送上自己的膝蓋,即使它有明顯的性能問題。使用基於XML的SOAP協議,不論數據類型,還是方法描述,都有XML來定義;大家再也不用糾結於不同編程語言之間數據如何映射,因爲都是XML,各個語言只需要實現各自的XML解析器就可以了;一切都是XML,多麼美好。在IBM(Java)和微軟(.NET)兩大技術體系的強力推動下,再輔以SOA概念的美好願景,SOAP很快就成了企業級開發的標準實踐。

然而,現實並沒有想象的那麼美好。和CORBA相比,SOAP的簡單體現在:

  1. SOAP隔離了傳輸相關細節和業務相關細節,WSDL中定義的是服務的地址、方法以及參數,關於如何傳輸,要麼通過HTTP直接傳輸,要麼通過ESB傳輸,但SOAP本身並不關心;
  2. 基於標準化的XML,避免在解析數據時產生歧義,程序員再也不同糾結,到底哪個字節纔是數據的開始,或者這個字節到底是什麼類型;
  3. SOAP具有自描述能力,程序員只需要閱讀WSDL定義,就可以瞭解服務如何調用,很多框架甚至提供根據WSDL自動生成客戶端的工具。

然而,SOAP也不過是解決了CORBA和COM遺留的一些嚴重問題,作爲分佈式系統的通信標準,SOAP依然只是個半成品。因爲:

  1. SOAP以XML描述數據,雖然XML是文本方式,但對於每一個數據,SOAP還是要爲其標記類型,因此SOAP依然存在數據映射的問題,尤其是SOAP還可以定義自定義類型;
  2. 因爲SOAP通信構建在HTTP之上,所以只支持點對點通信,不能做廣播和通知;
  3. 一個很簡單的字段,以XML描述數據,可能會佔用十倍以上的空間,導致每一次數據交互,都包含大量的冗餘信息,性能非常差;
  4. 儘管SOAP相比CORBA和COM簡化了很多,而且利用了HTTP,所以其配置的大部分複雜性都交給Web Server去處理,但SOAP本身用來描述服務的結構,依然很複雜。

SOAP能夠在企業級軟件開發中生存很久,很重要的一個原因是,大家在工作中用到的OA系統、報表工具、CRM系統等,即使慢如蝸牛,大家也不會太多抱怨,趁着網頁卡在那裏的時間,正好喝喝茶,聊聊天;而CTO們更不會有抱怨了,因爲他們只負責買,又不負責用。

ESB

僅僅一個SOAP,要想賣給企業CTO,看起來還是單薄了一些,畢竟把所有基於COM或者CORBA的系統都換成SOAP服務,僅僅是減輕了程序員的負擔,對於經理們沒有任何幫助嘛。這時就需要功能更強大,願景更美好的解決方案來給CTO們一個掏錢的理由。

ESB(企業服務總線)藉着SOA概念的流行,在2000年之後快速興起。很多公司在宣傳ESB時都描繪了這樣一種未來:公司裏的各個業務都做成服務,接到服務總線上,當你需要定製一個新的業務時,只需要拖拖拽拽、勾勾選選,就可以實現。很多公司都被這個美好的大餅所迷惑,不惜巨資,購買ESB,改造IT系統。

ESB確實在一定程度上做到了它所承諾的事情。比如,一個銀行IT系統有個人存款查詢和轉賬兩個服務,都接入到ESB上,通過ESB提供的圖形界面,將兩個服務的接口對接,就可以實現轉賬功能。更神奇的是,這兩個服務可以是用不同語言編寫,互相不需要知道對方部署在哪裏。聽起來很不錯,不是麼?

當年的我們,也是被這個大餅迷住了。我們設想了把成百上千個服務接入到ESB中,讓不懂技術的領導們,只需要動動鼠標,就可以想做什麼就做什麼。但是,隨着系統逐漸複雜,服務越來越多,我們最終發現,童話裏都是騙人的。爲了編排那麼多服務,我們甚至用上了BPEL(如果你不知道BPEL是什麼,你很幸運,最好永遠不要知道它是什麼),簡直就是災難。

使用ESB的另一個問題,是協議轉換。這本來是ESB的一大賣點,但是最後也成制約我們項目使用ESB的地方。我們使用的ESB有一箇中間描述語言,XML格式的,在將開發好的服務對接到ESB之前,需要將服務以這個中間描述語言描述出來。如果服務本身就是SOAP,很簡單,因爲這個中間描述語言本來就是參照SOAP標準中的WSDL(Web服務描述語言)設計的。但是如果這個服務是一些非標準化的,比如服務本身是基於TCP傳輸的,描述起來就很困難。導致我們不得不改造很多已有服務,而這些成本,在引入ESB時,供應商並不會告訴你。

ESB的初衷是好的,它希望能夠在SOAP的基礎上,解決其只能點對點路由的問題,同時還可以兼容不同通信協議。但是,爲了解決一個問題,它引入了另一個問題,就是業務邏輯與ESB綁定,導致ESB變得越來越不“單純”,最後ESB越來越大,越來越複雜,越來越難以維護,最後就成了整個系統的痼疾。

WCF

WCF是微軟在實現大一統夢想的路上,推出的又一力作。WCF繼承了COM的衣鉢,希望以一種統一的方式,將TCP通信、HTTP服務、SOAP服務、REST服務等等,都封裝起來,在開發者看來,都是一個本地方法調用。

在學校實驗室裏,我做過一個小項目,在城市裏佈置一些帶有傳感器的嵌入式設備,收集到的數據,通過GSM模塊發給中心服務器。爲了技術嚐鮮,我當時用了WCF來做各個組件之間的通信。和SOAP一樣,WCF也是通過XML來描述服務,框架很重,描述文件複雜難懂,但事實上這東西很好用,因爲Visual Studio實在是太強大了。Visual Studio會根據服務生產者代碼中的標籤,自動生成XML描述文件;而在開發服務消費者時,只要指定一個服務端地址,Visual Studio就會自動訪問這個地址,獲取服務描述,生成客戶端代碼,甚至還可以選擇是生成同步方法,還是異步方法。程序員要做的,就是在需要的地方調用一下方法就可以了。

WCF其實就是更高級好用的SOAP,但本質上還是SOAP。除了可以通過封裝TCP通信在某些場景提高了性能外,其他SOAP的問題,它都存在。而WCF還有一個SOAP不存在的問題,就是和.NET平臺綁定。像很多微軟的其他技術一樣,雖然走在時代前列,但非微軟系的程序員並沒有廣泛接受它。在那個以黑微軟爲樂的年代,大家對微軟推出的技術總是嘴上不屑一顧,身體卻很誠實的借鑑它的思想,用各自的方式,重新實現一遍。我的同事們做的MicroBuilder是這類嘗試中比較成功的一個,類似Visual Studio上對WCF的支持,MicroBuilder是對REST服務提供了自動生成中間語言描述文件,以及自動生成調用客戶端代碼的能力。

DDS

CORBA的快速隕落,讓CORBA的提出者OMG痛定思痛,重新提出了一個標準——DDS(Data Distribution Service,數據分發服務)。這個標準最早在2001年開始成形,2003年DDS 1.0標準正式提出。DDS的基礎是以發佈訂閱(Publishi-subscribe)的方式進行無中心實時數據傳輸,支持QoS定義,可水平擴展等能力。這是一個工業級的數據協議,早期是用在儀器設備之間進行實時數據通信,在美國有些軍用設備上也有使用,後來開始用在金融、航空領域,近些年,隨着物聯網的流行,DDS已經將重點轉向這個領域,2015年發佈1.4版本發佈,就是針對物聯網應用做了很多改進。

作爲實時數據傳輸協議,商用的DDS實現可以做到微秒級別的延時,同時還可以支撐大規模併發通信。時至今日,互聯網技術圈中以性能著稱的消息服務Kafka,在性能指標上依然難以望其項背。其實互聯網看起來很熱鬧,其實很多時候都是在重新發明/發現那些在其他領域早已成熟的技術。DDS之所以能夠做到這麼快,是因爲它的RTPS協議(Real-Time Publish-Subscribe Protocol,實時發佈訂閱協議),這個協議是基於UDP的,通過ACK心跳和消息接收回執來保證消息的送達,避免了TCP協議漫長的三次握手。但用UDP也有一個問題,就是不能跨網段,於是有公司提出通過一箇中心的橋接器,以TCP連接將兩個私有網絡連接起來,私有網絡內部採用UDP通信。

DDS看起來如此強大,爲什麼沒有火起來呢?首先,DDS在工業領域其實很火,在互聯網領域只是大家沒有聽說;其次,目前大部分DDS的實現都是商用版本,要想使用,需要付高額的授權費用,之前我們在考慮用RTI的DDS來代替CORBA,所以瞭解過一點它的價格,真心不是一般公司用得起的;最後,DDS因爲需要在計算機上安裝RTPS協議棧,對於服務器的侵入性比較強。對於喜歡在通用硬件上使用開源軟件的互聯網公司,這幾點都是比較致命的,而且其帶來的從毫秒級別到微秒級別的提升,對於互聯網公司的服務對象(主要是人)來說,感受並不明顯,所以DDS沒有進入消費級互聯網的技術視野也是可以理解的。不過可以預期的是,在物聯網領域,以Kafka爲首的互聯網數據通信解決方案和以DDS爲首的工業級數據通信解決方案,會有一戰。

REST

最近兩年,REST(Representational State Transfer,表述性狀態轉移)幾乎成了Web API的默認標準。SOAP僅僅將HTTP作爲一種可選的數據傳輸方式,REST則是帶着HTTP基因出生的。首先,REST將服務間通信進行了一次抽象,所有的服務交互都建模成對資源的CRUD操作;其次,它充分利用了HTTP協議中豐富的動詞,遵循約定大於配置的互聯網開發哲學,避免了複雜和冗餘的方法定義;再次,由於通過HTTP的動詞可以明確區分讀寫操作,對於GET類的讀操作,還可以通過緩存或者CDN進行加速;最後,採用JSON或者XML等通用格式來描述數據,簡單,直接。基於這幾點,REST開始在互聯網領域大行其道。我最近兩年做過的所有Web項目,幾乎都是以REST方式進行通信。

如此說來,REST不就是真正的銀彈了麼?非也。

REST看起來簡單易懂,但是要想設計出合理的REST API,也是需要很多經驗和技巧的。很多人覺得,我用JSON作爲數據格式了,就是REST API;或者我用POST和GET來區分讀寫操作了,就是REST了。到底一個REST API設計好壞如何評判?Martin Fowler的Richardson Maturity Model有比較詳細的介紹,至今我所見到的REST API中,能夠做到這個成熟度模型中最高等級的,寥寥無幾。

REST的核心是Resource,對Resource的CRUD操作構成了整個業務系統。對於簡單應用,這種抽象足矣。但對於稍微複雜一些的業務,僅僅是對於Resource的CRUD就不夠用了。舉一個簡單的例子,對於一個雲計算服務,虛擬機是一個Resource,用戶可以對虛擬機執行創建、啓動、關機、重啓、強制重啓、銷燬等等操作。其中,創建虛擬機的操作可以通過發起POST請求給/VirtualMachines來實現;查看虛擬機,可以發送GET請求給/VirtualMachines/{vm id}。但是,如果是要啓動、重啓或是強制重啓一個虛擬機,這個請求應該怎麼設計?比較直接的想法是,發送一個POST請求到/VirtualMachines/{vm id}?operation=boot|reboot|force-reboot,或是將operation=boot/reboot/force-reboot作爲POST請求的body發給/VirtualMachines/{vm id},但是,這樣的設計並不符合REST的設計理念。在REST中,所有的行爲,都應該是對資源的CRUD,而POST正是對應於資源的創建。上面這種做法,發送了POST請求,卻沒有創建任何資源,這是違反REST初衷的,對於服務消費者來說,這樣的API也可能產生疑惑。如果是用資源的角度來看,不同的操作,其實是資源的狀態轉換,比如啓動操作,最終結果是將虛擬機的狀態變成“running”。那發起一個PUT請求給/VirtualMachines/{vm id},將status字段改爲running,就可以了。但是,重啓和強制重啓怎麼辦呢?因爲從狀態轉移上看,虛擬機最終對變成了running,也許可以通過發送兩次請求達到目的,一次將虛擬機狀態設置爲“stop”,另一次將虛擬機狀態設置成“running”,但這種實現任誰看起來都會覺得很荒謬。在這個場景中,通過更新(Update)資源屬性來實現複雜功能,會導致丟失很多重要的業務信息。這就是REST的簡單帶來的侷限性,它的思想非常簡單,就是對業務中出現的名詞建模,因此很難表達複雜業務中出現的動詞語義。解決這個問題的一種方案,就是將動詞轉換爲名詞,比如上面的bootrebootforce-reboot等操作,可以轉換爲BootOperationRebootOperationForceRebootOperation等資源,這樣就可以給/VirtualMachines/{vm id}/BootOperations/VirtualMachines/{vm id}/RebootOperations/VirtualMachines/{vm id}/ForceRebootOperations等資源發送POST請求,來實現啓動虛擬機。

另一個設計REST API的問題,在於多大的粒度纔是合適的。粒度大了,服務不夠靈活,每次通信發送的數據冗餘;粒度小了,客戶端要調用多次才能API才能完成一個操作,導致通信次數過多;如果一個本該在同一個事務中的操作被拆分成多個細粒度的API調用,可能會導致一致性爲題。比如,一個典型的例子是銀行賬戶進行轉賬,要從用戶A的賬戶中扣款(修改id爲A的UserAccount資源的的money屬性),然後轉入用戶B的賬戶(修改id爲B的UserAccount資源的money屬性),如果API粒度設置的比較細,這一個操作就需要分多次API請求才能完成,而且如果在第二次請求時出現錯誤,數據一致性難以保證。而如果以粗粒度的資源來建模,在增加新的業務時,就不那麼靈活了。

對於REST如何進行有效地資源建模,這篇文章有更詳細的論述。

通信的本質

上面講的幾種通信方式,每一種的提出,都是爲了解決當時的問題,但其實每一種解決方案都帶來了新的問題。我們不斷的提出新的框架、新的技術,去解決上一個框架和技術的問題,最終,我們都忘了我們最初要解決的問題是什麼。

讓我們追本溯源,看看RPC的原始問題——通信到底是在幹什麼?作爲通信專業的研究生,我還記得,在通信原理的課本中,是這麼定義通信的:通信的本質,是將信息信源有效、可靠的發送給信宿

通信基本模型

但是,信源到信宿這個過程,是需要一箇中間媒介來傳輸信息的,這個媒介叫做信道。

通信基本模型

這裏面的信道,可以是同軸電纜、光纖或者是自由空間,因爲信道會對信號產生衰減,導致在信宿收到的信息,可能和信源不一致。爲了讓信息更可靠的傳輸,就需要在信息進入信道之前,進行預處理,在信宿從信道中取出信息時,再進行逆向處理,得到原始信息。這一步驟稱爲信道編碼。

通信基本模型

信道容量有限,也就是說,傳輸數據的速度不是無限大的。爲了儘可能傳輸更多的信息,就需要在信源將信息進行壓縮處理,去除冗餘,這樣就可以利用有限的信道容量提高信息傳輸速率,這一指標也被稱爲通信系統的有效性。將信源在發送前進行預處理,這一步驟稱爲信源編碼。

通信基本模型

以上,是通信專業對於通信系統建立的一個經典模型,現在我們嘗試將這一模型用在計算機的通信系統中:

  1. 信源和信宿是兩個應用程序,功能業務邏輯都在這裏;
  2. 既然TCP/IP協議棧已經成爲默認通信標準,我們就將TCP/IP協議棧的傳輸層以下,都看做信道;
  3. 在傳輸層中實際傳輸的數據,可以看做是經過信道編碼處理過的數據;
  4. 而信源編碼則是應用程序在發送數據前對數據進行的序列化操作。

將上文中提到的技術,對照這個定義,我們來看看各個通信方式分別對應着通信系統中的哪些部分:

通信基本模型

  • raw socket: 直接在socket上編程,相當於應用程序自己將信源編碼與信道編碼都處理了,有點像是本節的第二張圖;
  • COM/COM+/DCOM: COM技術希望屏蔽技術異構性,讓兩個程序之間像是在同一個進程中相互調用,數據類型的轉換,內存的映射,以及通信方式,都在COM中處理了,所以可以將其歸類爲實現了信源編碼與信道編碼的技術;
  • CORBA: CORBA是對信道做了一層封裝,雖然也做了些類似信道編碼的工作,但其實它暴露了很多底層信道的細節;
  • SOAP: SOAP其實就是XML版的COM,傳輸在HTTP之上,所以它和COM一樣,屬於具有信源編碼和信道編碼能力的技術;
  • ESB: ESB在SOAP的基礎上更進一步,還帶有一些業務編排能力,所以ESB做的事情,有一部分是屬於應用程序的;
  • WCF: WCF算是改良的SOAP,和SOAP分在一樣的類別中;
  • DDS: DDS做的事情和CORBA接近,但是比CORBA設計的更合理,它對於信道編碼這一層的封裝,完全屏蔽了底層細節,讓使用DDS的開發者,只需要考慮消息的設計,以及發佈訂閱關係;
  • REST: REST算是構建在HTTP上的一種約定標準,如果僅僅看REST,實際上它是介於信源編碼與信道編碼之間的那薄薄一層,因爲REST並沒有限定其信源編碼必須是JSON還是XML,事實上,你可以用YML表示數據,甚至還可以用自定義的數據格式。但是,因爲其限定了傳輸必須是通過HTTP發生,所以傳輸的信息必須是文本的。如果將HTTP和REST放在一起來看,它就屬於信道編碼加上一點點的信源編碼。

以目前發展的趨勢來看,大一統形式的技術,比如ESB,逐漸沒落,職責單一的技術越來越受歡迎。

微服務系統中的通信

微服務作爲一種架構設計理念,在最近一年多越來越流行,因爲其鬆散耦合的架構特別適合當前IT領域需求變化特別快、技術更新特別快的環境。在進行微服務系統設計時,一個非常重要的問題,就是如何選擇服務之間的通信機制。

本着先定義問題,在解決問題的思路,我們先來看看微服務的系統特點,再來決定那種技術合適。

微服務的一個最直接特點是“微”。因爲一個服務只負責實現一個特定的業務功能,所以一個服務通常都不會很大,在一個代碼量不大的程序中,用於通信的代碼通常也不會很多,所以註定用在微服務上的通信技術,必須是輕量級,同時使用簡單。所以SOAP這種重量級的技術,以及CORBA這種過於複雜的技術在這裏就不是特別適合了。

微服務的提出,是爲了應對業務需求的快速變化,而通信本身和業務無關,爲了滿足業務快速變化導致的服務頻繁迭代,微服務的通信必須和業務剝離。因此,ESB和raw socket就不在選擇之列了。

微服務的另一大特點,是各個服務可以用完全不同的技術棧來實現,因此對於通信的要求,最好是能夠具備平臺無關性。於是COM和WCF就不不行了。

如此篩選下來,還剩下兩個候選技術,REST和DDS。所以說,REST在微服務盛行的今天,被如此推崇,作爲微服務的首選通信技術,也是有其道理的。另外,DDS雖然沒有在微服務中廣泛使用,但是其後輩們,包括RabbitMQ,ZeroMQ,Kafka等技術確實是緊隨REST之後的次選微服務通信技術。

事實上,雖然微服務並不要求實時的通信性能,但是,因爲一個系統拆分成了很多個服務,來自用戶的每一次請求,都可能會在多個服務之間多次交互,爲了保證對用戶的快速響應能力,微服務的通信性能應該是越高越好。但是,一方面,基於消息的通信需要從系統架構層面進行設計,而且對於開發者要求比較高;另一方面,對於用來公開的API,基於HTTP的REST協議,對於一般用戶來說更容易接受。所以目前一種更合理的微服務通信架構,是系統內部各個服務之間,採用基於消息的技術,而對外提供API則是採用REST。

當然,隨着HTTP/2的發展,相信REST的性能也會越來越好,也有可能會出現完全基於HTTP的消息技術,到時候,選擇哪一種,就真的是仁者見仁,智者見智了。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章