ZeroC Ice框架介紹


概述

  Ice是一個開源的綜合性RPC框架,以高性能和原生支持微服務的架構而著稱。提供了很多可以直接使用的組件,如注冊中心IceGrid,部署工具IcePatch2,防火牆穿透Glacier2,發布訂閱服務IceStorm等。這樣的好處就是降低了學習成本和基於中間件的二次開發工作量。同時弊端也很明顯就是生態比較封閉,缺乏像Spring Cloud那樣全面的技術棧,如服務網關,鏈路追蹤等,因此替換引入組件成本會比較高。
  在國內開源社區和技術論壇的討論很少見,資料也比較少,基本參考就是官方英文技術文檔和源碼。有人可能頭已經大了,話說看官方技術文檔和閱讀源碼不也是程序猿應該修煉的基本功嘛,英文不行只能邊看邊學了。

以下是Ice框架的特點:

  • 可壓縮,高效的二進制協議
  • 支持同步,異步調用和多種通信協議,如TCP,UDP,WebSockets,Buletooth;
  • 支持多語言實現,如C++,C#,Java,Python等,和跨操作系統,跨平台部署;
  • 支持基於原生的SSL/TLS的安全加密通信;
  • 支持自動的服務發現;
  • 支持面向對象編程模型;

架構

重要抽象概念

Ice有很多抽象概念和術語,基本上這些概念都是繼承現有的概念,新增加的抽象很少。所以學習其他RPC架構的同學很容易理解這些概念。

客戶端服務端(Client and Server)

客戶端和服務端都是相對,可以轉換的;

在一次請求交互中,發請求一方為客戶端,處理請求的一方為服務端,同時服務端也能是請求的發起方,這時它充當的角色就是客戶端。

Ice對象(Ice Object)

這里的Ice對象是特指提供接口服務的對象,而不是我們面向對象編程時的泛指對象。

它有幾個特征:

  • 一個Ice對象可能有多個接口類(interface),一個接口類可能包含一系列操作(operation)即函數。
  • 一個Ice對象應該有全局唯一的標識。

對象適配器(Object Adaptor)

可以看做一個Ice對象集合,維護對象ID與對象的映射關系。

代理(Proxies)

是Ice對象在客戶端本地的代理,包含向下調用接口和服務地址信息,客戶端通過調用接口將請求發送到指定服務端的Ice對象。

一般代理信息以“對象ID:服務地址”的形式表示,其中服務地址是以協議+地址+端口表示。如

SimplePrinter:default -p 10000

上面這種固定ip端口的形式,又稱為直接代理(Direct Proxies)

還有一種間接代理(Indirect Proxies)是通過定位服務來獲取服務的路由信息。定位服務類似於DNS的域名服務,間接代理的代理信息就相當於URL。

間接代理有兩種形式,對象ID或者對象適配器ID;這種通過對象ID就可以訪問的對象又稱為眾所周知的對象(well-known object)

SimplePrinter
SimplePrinter@PrinterAdapter

服務者(Servants)

服務端的人工實現的實際服務對象

復制(Replication)

Ice的復制就是將對象適配器(Object Adaptor)與多個地址關聯。就是同個服務在多個機器上運行着實例,以避免單機故障。

客戶端的代理支持復制的格式,一個對象ID對應多個連接端點。如下

SimplePrinter:tcp -h server1 -p 10001:tcp -h server2 -p 10002

復制組(Replica Groups)

上面基於直接代理的復制,缺點很明顯,對象到服務端點的映射關系是固定。Ice支持一種更有用的復制形式--復制組。

復制組可以看做是一個有動態復制功能的適配器集合。動態復制的能力來自於定位服務,因為定位服務提供了復制組與路由的動態映射關系查詢。

復制組有唯一的標識符,由一系列對象適配器組成,一個對象適配器最多屬於一個復制組。當一個復制組創建之后,它的標識符可以用來替換間接代理中的適配器標識符。

例如一個標識符為PrinterAdapters的復制組用在代理中:

SimplePrinter@PrinterAdapters

至多一次語義(At-Most-Once Semantics)

就是說一個請求操作至多被執行一次,如果請求傳遞失敗,將會拋一個異常,而不會去重試。只有確認失敗了,才會重試。

這個語義對於不是冪等性的操作是安全的,比如++i,操作一次和操作兩次得到結果是不一樣的。

同步方法調用

調用RPC時,線程被掛起,直到結果返回。

異步方法調用(AMI)

調用RPC時,立即返回,線程可以處理其他工作,等結果返回時會通知該線程

異步方法處理

相當於服務端的AMI,這個請求可能不會被立即處理。適用於需要等待數據,耗時較長的調用過程。

單向調用

就是沒有出參的RPC,調用方不需要知道執行的結果。

描述性語言Slice

這是專門為ICE提供的描述性語言,主要是為了定義接口,數據類型。使客戶端服務端的開發獨立於語言,

可以通過slice工具生成你想要使用的語言,如C++,C#,Java等,生成的這些代碼使你只需要關注接口的實現,而不需要關注底層邏輯。

客戶端和服務端的結構

Ice客戶端和服務端的內部邏輯結構:

客戶端和服務端程序是由應用代碼、Ice庫代碼和由slice生成的代碼構成的。

  • Ice Core包含支撐通訊的運行時代碼,對應用開發屏蔽了底層的網絡通信,線程,數據編解碼,以及很多網絡相關的問題。Ice Core是以一系列庫的形式提供給客戶端,服務端使用。Ice Core的配置管理是通過Ice API來操作的。
  • Proxy Code代理代碼由slice定義產生,是服務端Ice對象的代理。它主要有兩個功能,一是為客戶端應用提供向下調用接口,以請求消息的形式發給服務端,然后服務端調用相關聯的接口;二是數據結構的編解碼,以便於網絡傳輸。
  • Skeleton Code 骨架代碼由slice定義產生,提供向上調用接口,允許Ice運行時將客戶端請求傳遞到你實現的服務端代碼。同時也包含了對參數的解碼,返回結果合異常的編碼。

環境部署

參考官方文檔:https://doc.zeroc.com/ice/3.7/release-notes/using-the-linux-binary-distributions

HelloWord

以下簡單demo是以c++11實現的。完成源碼見:https://github.com/GodMonking/ice-demo/tree/main/simple

定義slice文件

slice文件命名必須以.ice結尾。如Printer.ice

module Demo
{
    interface Printer
    {
        void printString(string s);
    }
}

編譯slice文件生成c++代碼:

slice2cpp Printer.ice

服務端代碼

#include <Ice/Ice.h>
#include <Printer.h>
using namespace std;
using namespace Demo;
class PrinterI : public Printer
{
public:
virtual void printString(string s, const Ice::Current&) override;
};
void
PrinterI::printString(string s, const Ice::Current&)
{
cout << s << endl;
}
int
main(int argc, char* argv[])
{
try
{
//初始化通信器
Ice::CommunicatorHolder ich(argc, argv);
//根據名稱、連接端點創建一個對象適配器
auto adapter = ich->createObjectAdapterWithEndpoints("SimplePrinterAdapter", "default
-p 10000");
//創建一個servant對象,將其添加到對象適配器
auto servant = make_shared<PrinterI>();
adapter->add(servant, Ice::stringToIdentity("SimplePrinter"));
//激活對象適配器,監聽服務請求
adapter->activate();
ich->waitForShutdown();
}
catch(const std::exception& e)
{
cerr << e.what() << endl;
return 1;
}
return 0;
}

由Printer.ice生成的Printer.h包含骨架代碼(Skeleton Code),一個接口類包含一個純虛接口如下,詳細代碼省略:

#include <Ice/Ice.h>
#include <Printer.h>
using namespace std;
using namespace Demo;
…
namespace Demo
{
class Printer : public virtual Ice::Object
{
public:
virtual void printString(std::string, const Ice::Current&) = 0;
};
}

編譯服務端代碼:

c++ -I. -DICE_CPP11_MAPPING -c Printer.cpp Server.cpp
c++ -o server Printer.o Server.o -lIce++11

客戶端代碼

#include <Ice/Ice.h>
#include <Printer.h>
#include <stdexcept>
using namespace std;
using namespace Demo;
int
main(int argc, char* argv[])
{
try
{
//初始化通信器
Ice::CommunicatorHolder ich(argc, argv);
//通過 “對象ID:對象適配器連接端點” 獲取代理對象
auto base = ich->stringToProxy("SimplePrinter:default -p 10000");
//轉類型校驗是否合法
auto printer = Ice::checkedCast<PrinterPrx>(base);
if(!printer)
{
throw std::runtime_error("Invalid proxy");
}
//通過代理對象調用接口
printer->printString("Hello World!");
}
catch(const std::exception& e)
{
cerr << e.what() << endl;
return 1;
}
return 0;
}

編譯客戶端代碼:

c++ -I. -DICE_CPP11_MAPPING -c Printer.cpp Client.cpp
c++ -o client Printer.o Client.o -lIce++11

運行程序

運行服務端程序:

./server

執行客戶端程序:

./client

服務顯示結果:

 

結尾

這篇文章主要是介紹了Ice框架的優缺點,架構組成以及一個簡單Demo。后續文章會介紹幾種服務部署方案。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM