您好,登錄后才能下訂單哦!
??本文描述一個(gè)通過C++可變參數(shù)模板實(shí)現(xiàn)C++反射機(jī)制的方法。該方法非常實(shí)用,在Nebula高性能網(wǎng)絡(luò)框架中大量應(yīng)用,實(shí)現(xiàn)了非常強(qiáng)大的動(dòng)態(tài)加載動(dòng)態(tài)創(chuàng)建功能。
??C++11的新特性--可變模版參數(shù)(variadic templates)是C++11新增的最強(qiáng)大的特性之一,它對(duì)參數(shù)進(jìn)行了高度泛化,它能表示0到任意個(gè)數(shù)、任意類型的參數(shù)。關(guān)于可變參數(shù)模板的原理和應(yīng)用不是本文重點(diǎn),不過通過本文中的例子也可充分了解可變參數(shù)模板是如何應(yīng)用的。
??熟悉Java或C#的同學(xué)應(yīng)該都知道反射機(jī)制,很多有名的框架都用到了反射這種特性,簡(jiǎn)單的理解就是只根據(jù)類的名字(字符串)創(chuàng)建類的實(shí)例。 C++并沒有直接從語言上提供反射機(jī)制給我們用,不過無所不能的C++可以通過一些trick來實(shí)現(xiàn)反射。Bwar也是在開發(fā)Nebula框架的時(shí)候需要用到反射機(jī)制,在網(wǎng)上參考了一些資料結(jié)合自己對(duì)C++11可變參數(shù)模板的理解實(shí)現(xiàn)了C++反射。
??Nebula框架是一個(gè)高性能事件驅(qū)動(dòng)通用網(wǎng)絡(luò)框架,框架本身無任何業(yè)務(wù)邏輯實(shí)現(xiàn),卻為快速實(shí)現(xiàn)業(yè)務(wù)提供了強(qiáng)大的功能、統(tǒng)一的接口。業(yè)務(wù)邏輯通過從Nebula的Actor類接口編譯成so動(dòng)態(tài)庫,Nebula加載業(yè)務(wù)邏輯動(dòng)態(tài)庫實(shí)現(xiàn)各種功能Server,開發(fā)人員只需專注于業(yè)務(wù)邏輯代碼編寫,網(wǎng)絡(luò)通信、定時(shí)器、數(shù)據(jù)序列化反序列化、采用何種通信協(xié)議等全部交由框架完成。
??開發(fā)人員編寫的業(yè)務(wù)邏輯類從Nebula的基類派生,但各業(yè)務(wù)邏輯派生類對(duì)Nebula來說是完全未知的,Nebula需要加載這些動(dòng)態(tài)庫并創(chuàng)建動(dòng)態(tài)庫中的類實(shí)例就需要用到反射機(jī)制。第一版的Nebula及其前身Starship框架以C++03標(biāo)準(zhǔn)開發(fā),未知類名、未知參數(shù)個(gè)數(shù)、未知參數(shù)類型、更未知實(shí)參的情況下,Bwar沒想到一個(gè)有效的加載動(dòng)態(tài)庫并創(chuàng)建類實(shí)例的方式。為此,將所有業(yè)務(wù)邏輯入口類的構(gòu)造函數(shù)都設(shè)計(jì)成無參構(gòu)造函數(shù)。
??Bwar在2015年未找到比較好的實(shí)現(xiàn),自己想了一種較為取巧的加載動(dòng)態(tài)庫并創(chuàng)建類實(shí)例的方法(這還不是反射機(jī)制,只是實(shí)現(xiàn)了可以或需要反射機(jī)制來實(shí)現(xiàn)的功能)。這個(gè)方法在Nebula的C++03版本中應(yīng)用并實(shí)測(cè)通過,同時(shí)也是在一個(gè)穩(wěn)定運(yùn)行兩年多的IM底層框架Starship中應(yīng)用。下面給出這種實(shí)現(xiàn)方法的代碼:
CmdHello.hpp:
#ifdef __cplusplus
extern "C" {
#endif
// @brief 創(chuàng)建函數(shù)聲明
// @note 插件代碼編譯成so后存放到plugin目錄,框架加載動(dòng)態(tài)庫后調(diào)用create()創(chuàng)建插件類實(shí)例。
neb::Cmd* create();
#ifdef __cplusplus
}
#endif
namespace im
{
class CmdHello: public neb::Cmd
{
public:
CmdHello();
virtual ~CmdHello();
virtual bool AnyMessage();
};
} /* namespace im */
CmdHello.cpp:
#include "CmdHello.hpp"
#ifdef __cplusplus
extern "C" {
#endif
neb::Cmd* create()
{
neb::Cmd* pCmd = new im::CmdHello();
return(pCmd);
}
#ifdef __cplusplus
}
#endif
namespace im
{
CmdHello::CmdHello()
{
}
CmdHello::~CmdHello()
{
}
bool CmdHello::AnyMessage()
{
std::cout << "CmdHello" << std::endl;
return(true);
}
}
??實(shí)現(xiàn)的關(guān)鍵在于create()函數(shù),雖然每個(gè)動(dòng)態(tài)庫都寫了create()函數(shù),但動(dòng)態(tài)庫加載時(shí)每個(gè)create()函數(shù)的地址是不一樣的,通過不同地址調(diào)用到的函數(shù)也就是不一樣的,所以可以創(chuàng)建不同的實(shí)例。下面給出動(dòng)態(tài)庫加載和調(diào)用create()函數(shù),創(chuàng)建類實(shí)例的代碼片段:
void* pHandle = NULL;
pHandle = dlopen(strSoPath.c_str(), RTLD_NOW);
char* dlsym_error = dlerror();
if (dlsym_error)
{
LOG4_FATAL("cannot load dynamic lib %s!" , dlsym_error);
if (pHandle != NULL)
{
dlclose(pHandle);
}
return(pSo);
}
CreateCmd* pCreateCmd = (CreateCmd*)dlsym(pHandle, strSymbol.c_str());
dlsym_error = dlerror();
if (dlsym_error)
{
LOG4_FATAL("dlsym error %s!" , dlsym_error);
dlclose(pHandle);
return(pSo);
}
Cmd* pCmd = pCreateCmd();
??對(duì)應(yīng)這動(dòng)態(tài)庫加載代碼片段的配置文件如下:
{"cmd":10001, "so_path":"plugins/CmdHello.so", "entrance_symbol":"create", "load":false, "version":1}
??這些代碼實(shí)現(xiàn)達(dá)到了加載動(dòng)態(tài)庫并創(chuàng)建框架未知類實(shí)例的目的。不過沒有反射機(jī)制那么靈活,用起來也稍顯麻煩,開發(fā)人員寫好業(yè)務(wù)邏輯類之后還需要實(shí)現(xiàn)一個(gè)對(duì)應(yīng)的全局create()函數(shù)。
??Bwar曾經(jīng)用C++模板封裝過一個(gè)基于pthread的通用線程類。下面是這個(gè)線程模板類具有代表性的一個(gè)函數(shù)實(shí)現(xiàn),對(duì)設(shè)計(jì)C++反射機(jī)制實(shí)現(xiàn)有一定的啟發(fā):
template <typename T>
void CThread<T>::StartRoutine(void* para)
{
T* pT;
pT = (T*) para;
pT->Run();
}
??與之相似,創(chuàng)建一個(gè)未知的類實(shí)例可以通過new T()的方式來實(shí)現(xiàn),如果是帶參數(shù)的構(gòu)造函數(shù),則可以new T(T1, T2)來實(shí)現(xiàn)。那么,通過類名創(chuàng)建類實(shí)例,建立"ClassName"與T的對(duì)應(yīng)關(guān)系,或者建立"ClassName"與包含了new T()語句的函數(shù)的對(duì)應(yīng)關(guān)系即可實(shí)現(xiàn)無參構(gòu)造類的反射機(jī)制。考慮到new T(T1, T2)的參數(shù)個(gè)數(shù)和參數(shù)類型都未知,應(yīng)用C++11的可變參數(shù)模板解決參數(shù)問題,即完成帶參構(gòu)造類的反射機(jī)制。
??研究C++反射機(jī)制實(shí)現(xiàn)最重要是Nebula網(wǎng)絡(luò)框架中有極其重要的應(yīng)用,而Nebula框架在實(shí)現(xiàn)并應(yīng)用了反射機(jī)制之后代碼量精簡(jiǎn)了10%左右,同時(shí)易用性也有了很大的提高,再考慮到應(yīng)用反射機(jī)制后給基于Nebula的業(yè)務(wù)邏輯開發(fā)帶來的好處,可以說反射機(jī)制是Nebula框架僅次于以C++14標(biāo)準(zhǔn)重寫的重大提升。
??Nebula的Actor為事件(消息)處理者,所有業(yè)務(wù)邏輯均抽象成事件和事件處理,反射機(jī)制正是應(yīng)用在Actor的動(dòng)態(tài)創(chuàng)建上。Actor分為Cmd、Module、Step、Session四種不同類型。業(yè)務(wù)邏輯代碼均通過從這四種不同類型時(shí)間處理者派生子類來實(shí)現(xiàn),專注于業(yè)務(wù)邏輯實(shí)現(xiàn),而無須關(guān)注業(yè)務(wù)邏輯之外的內(nèi)容。Cmd和Module都是消息處理入庫,業(yè)務(wù)開發(fā)人員定義了什么樣的Cmd和Module對(duì)框架而言是未知的,因此這些Cmd和Module都配置在配置文件里,Nebula通過配置文件中的Cmd和Module的名稱(字符串)完成它們的實(shí)例創(chuàng)建。通過反射機(jī)制動(dòng)態(tài)創(chuàng)建Actor的關(guān)鍵代碼如下:
Actor類:
class Actor: public std::enable_shared_from_this<Actor>
Actor創(chuàng)建工廠(注意看代碼注釋):
template<typename ...Targs>
class ActorFactory
{
public:
static ActorFactory* Instance()
{
if (nullptr == m_pActorFactory)
{
m_pActorFactory = new ActorFactory();
}
return(m_pActorFactory);
}
virtual ~ActorFactory(){};
// 將“實(shí)例創(chuàng)建方法(DynamicCreator的CreateObject方法)”注冊(cè)到ActorFactory,注冊(cè)的同時(shí)賦予這個(gè)方法一個(gè)名字“類名”,后續(xù)可以通過“類名”獲得該類的“實(shí)例創(chuàng)建方法”。這個(gè)實(shí)例創(chuàng)建方法實(shí)質(zhì)上是個(gè)函數(shù)指針,在C++11里std::function的可讀性比函數(shù)指針更好,所以用了std::function。
bool Regist(const std::string& strTypeName, std::function<Actor*(Targs&&... args)> pFunc);
// 傳入“類名”和參數(shù)創(chuàng)建類實(shí)例,方法內(nèi)部通過“類名”從m_mapCreateFunction獲得了對(duì)應(yīng)的“實(shí)例創(chuàng)建方法(DynamicCreator的CreateObject方法)”完成實(shí)例創(chuàng)建操作。
Actor* Create(const std::string& strTypeName, Targs&&... args);
private:
ActorFactory(){};
static ActorFactory<Targs...>* m_pActorFactory;
std::unordered_map<std::string, std::function<Actor*(Targs&&...)> > m_mapCreateFunction;
};
template<typename ...Targs>
ActorFactory<Targs...>* ActorFactory<Targs...>::m_pActorFactory = nullptr;
template<typename ...Targs>
bool ActorFactory<Targs...>::Regist(const std::string& strTypeName, std::function<Actor*(Targs&&... args)> pFunc)
{
if (nullptr == pFunc)
{
return (false);
}
bool bReg = m_mapCreateFunction.insert(
std::make_pair(strTypeName, pFunc)).second;
return (bReg);
}
template<typename ...Targs>
Actor* ActorFactory<Targs...>::Create(const std::string& strTypeName, Targs&&... args)
{
auto iter = m_mapCreateFunction.find(strTypeName);
if (iter == m_mapCreateFunction.end())
{
return (nullptr);
}
else
{
return (iter->second(std::forward<Targs>(args)...));
}
}
動(dòng)態(tài)創(chuàng)建類(注意看代碼注釋):
template<typename T, typename...Targs>
class DynamicCreator
{
public:
struct Register
{
Register()
{
char* szDemangleName = nullptr;
std::string strTypeName;
#ifdef __GNUC__
szDemangleName = abi::__cxa_demangle(typeid(T).name(), nullptr, nullptr, nullptr);
#else
// 注意:這里不同編譯器typeid(T).name()返回的字符串不一樣,需要針對(duì)編譯器寫對(duì)應(yīng)的實(shí)現(xiàn)
//in this format?: szDemangleName = typeid(T).name();
szDemangleName = abi::__cxa_demangle(typeid(T).name(), nullptr, nullptr, nullptr);
#endif
if (nullptr != szDemangleName)
{
strTypeName = szDemangleName;
free(szDemangleName);
}
ActorFactory<Targs...>::Instance()->Regist(strTypeName, CreateObject);
}
inline void do_nothing()const { };
};
DynamicCreator()
{
m_oRegister.do_nothing(); // 這里的函數(shù)調(diào)用雖無實(shí)際內(nèi)容,卻是在調(diào)用動(dòng)態(tài)創(chuàng)建函數(shù)前完成m_oRegister實(shí)例創(chuàng)建的關(guān)鍵
}
virtual ~DynamicCreator(){};
// 動(dòng)態(tài)創(chuàng)建實(shí)例的方法,所有Actor實(shí)例均通過此方法創(chuàng)建。這是個(gè)模板方法,實(shí)際上每個(gè)Actor的派生類都對(duì)應(yīng)了自己的CreateObject方法。
static T* CreateObject(Targs&&... args)
{
T* pT = nullptr;
try
{
pT = new T(std::forward<Targs>(args)...);
}
catch(std::bad_alloc& e)
{
return(nullptr);
}
return(pT);
}
private:
static Register m_oRegister;
};
template<typename T, typename ...Targs>
typename DynamicCreator<T, Targs...>::Register DynamicCreator<T, Targs...>::m_oRegister;
??上面ActorFactory和DynamicCreator就是C++反射機(jī)制的全部實(shí)現(xiàn)。要完成實(shí)例的動(dòng)態(tài)創(chuàng)建還需要類定義必須滿足(模板)要求。下面看一個(gè)可以動(dòng)態(tài)創(chuàng)建實(shí)例的CmdHello<a name="CmdHello"></a>類定義(注意看代碼注釋):
// 類定義需要使用多重繼承。
// 第一重繼承neb::Cmd是CmdHello的實(shí)際基類(neb::Cmd為Actor的派生類,Actor是什么在本節(jié)開始的描述中有說明);
// 第二重繼承為通過類名動(dòng)態(tài)創(chuàng)建實(shí)例的需要,與template<typename T, typename...Targs> class DynamicCreator定義對(duì)應(yīng)著看就很容易明白第一個(gè)模板參數(shù)(CmdHello)為待動(dòng)態(tài)創(chuàng)建的類名,其他參數(shù)為該類的構(gòu)造函數(shù)參數(shù)。
// 如果參數(shù)為某個(gè)類型的引用,作為模板參數(shù)時(shí)應(yīng)指定到類型。比如: 參數(shù)類型const std::string&只需在neb::DynamicCreator的模板參數(shù)里填std::string
// 如果參數(shù)為某個(gè)類型的指針,作為模板參數(shù)時(shí)需指定為類型的指針。比如:參數(shù)類型const std::string*則需在neb::DynamicCreator的模板參數(shù)里填std::string*
class CmdHello: public neb::Cmd, public neb::DynamicCreator<CmdHello, int32>
{
public:
CmdHello(int32 iCmd);
virtual ~CmdHello();
virtual bool Init();
virtual bool AnyMessage(
std::shared_ptr<neb::SocketChannel> pChannel,
const MsgHead& oMsgHead,
const MsgBody& oMsgBody);
};
??再看看上面的反射機(jī)制是怎么調(diào)用的:
template <typename ...Targs>
std::shared_ptr<Cmd> WorkerImpl::MakeSharedCmd(Actor* pCreator, const std::string& strCmdName, Targs... args)
{
LOG4_TRACE("%s(CmdName \"%s\")", __FUNCTION__, strCmdName.c_str());
Cmd* pCmd = dynamic_cast<Cmd*>(ActorFactory<Targs...>::Instance()->Create(strCmdName, std::forward<Targs>(args)...));
if (nullptr == pCmd)
{
LOG4_ERROR("failed to make shared cmd \"%s\"", strCmdName.c_str());
return(nullptr);
}
...
}
??MakeSharedCmd()方法的調(diào)用:
MakeSharedCmd(nullptr, oCmdConf["cmd"][i]("class"), iCmd);
??至此通過C++可變參數(shù)模板實(shí)現(xiàn)C++反射機(jī)制實(shí)現(xiàn)已全部講完,相信讀到這里已經(jīng)有了一定的理解,這是Nebula框架的核心功能之一,已有不少基于Nebula的應(yīng)用實(shí)踐,是可用于生產(chǎn)的C++反射實(shí)現(xiàn)。
??這個(gè)C++反射機(jī)制的應(yīng)用容易出錯(cuò)的地方是
??注意以上兩點(diǎn),基本就不會(huì)有什么問題了。
??上面為了說明C++反射機(jī)制給出的代碼全都來自Nebula框架。最后再提供一個(gè)可執(zhí)行的例子加深理解。
DynamicCreate.cpp:
#include <string>
#include <iostream>
#include <typeinfo>
#include <memory>
#include <unordered_map>
#include <cxxabi.h>
namespace neb
{
class Actor
{
public:
Actor(){std::cout << "Actor construct" << std::endl;}
virtual ~Actor(){};
virtual void Say()
{
std::cout << "Actor" << std::endl;
}
};
template<typename ...Targs>
class ActorFactory
{
public:
//typedef Actor* (*ActorCreateFunction)();
//std::function< Actor*(Targs...args) > pp;
static ActorFactory* Instance()
{
std::cout << "static ActorFactory* Instance()" << std::endl;
if (nullptr == m_pActorFactory)
{
m_pActorFactory = new ActorFactory();
}
return(m_pActorFactory);
}
virtual ~ActorFactory(){};
//Lambda: static std::string ReadTypeName(const char * name)
//bool Regist(const std::string& strTypeName, ActorCreateFunction pFunc)
//bool Regist(const std::string& strTypeName, std::function<Actor*()> pFunc)
bool Regist(const std::string& strTypeName, std::function<Actor*(Targs&&... args)> pFunc)
{
std::cout << "bool ActorFactory::Regist(const std::string& strTypeName, std::function<Actor*(Targs... args)> pFunc)" << std::endl;
if (nullptr == pFunc)
{
return(false);
}
std::string strRealTypeName = strTypeName;
//[&strTypeName, &strRealTypeName]{int iPos = strTypeName.rfind(' '); strRealTypeName = std::move(strTypeName.substr(iPos+1, strTypeName.length() - (iPos + 1)));};
bool bReg = m_mapCreateFunction.insert(std::make_pair(strRealTypeName, pFunc)).second;
std::cout << "m_mapCreateFunction.size() =" << m_mapCreateFunction.size() << std::endl;
return(bReg);
}
Actor* Create(const std::string& strTypeName, Targs&&... args)
{
std::cout << "Actor* ActorFactory::Create(const std::string& strTypeName, Targs... args)" << std::endl;
auto iter = m_mapCreateFunction.find(strTypeName);
if (iter == m_mapCreateFunction.end())
{
return(nullptr);
}
else
{
//return(iter->second());
return(iter->second(std::forward<Targs>(args)...));
}
}
private:
ActorFactory(){std::cout << "ActorFactory construct" << std::endl;};
static ActorFactory<Targs...>* m_pActorFactory;
std::unordered_map<std::string, std::function<Actor*(Targs&&...)> > m_mapCreateFunction;
};
template<typename ...Targs>
ActorFactory<Targs...>* ActorFactory<Targs...>::m_pActorFactory = nullptr;
template<typename T, typename ...Targs>
class DynamicCreator
{
public:
struct Register
{
Register()
{
std::cout << "DynamicCreator.Register construct" << std::endl;
char* szDemangleName = nullptr;
std::string strTypeName;
#ifdef __GNUC__
szDemangleName = abi::__cxa_demangle(typeid(T).name(), nullptr, nullptr, nullptr);
#else
//in this format?: szDemangleName = typeid(T).name();
szDemangleName = abi::__cxa_demangle(typeid(T).name(), nullptr, nullptr, nullptr);
#endif
if (nullptr != szDemangleName)
{
strTypeName = szDemangleName;
free(szDemangleName);
}
ActorFactory<Targs...>::Instance()->Regist(strTypeName, CreateObject);
}
inline void do_nothing()const { };
};
DynamicCreator()
{
std::cout << "DynamicCreator construct" << std::endl;
m_oRegister.do_nothing();
}
virtual ~DynamicCreator(){m_oRegister.do_nothing();};
static T* CreateObject(Targs&&... args)
{
std::cout << "static Actor* DynamicCreator::CreateObject(Targs... args)" << std::endl;
return new T(std::forward<Targs>(args)...);
}
virtual void Say()
{
std::cout << "DynamicCreator say" << std::endl;
}
static Register m_oRegister;
};
template<typename T, typename ...Targs>
typename DynamicCreator<T, Targs...>::Register DynamicCreator<T, Targs...>::m_oRegister;
class Cmd: public Actor, public DynamicCreator<Cmd>
{
public:
Cmd(){std::cout << "Create Cmd " << std::endl;}
virtual void Say()
{
std::cout << "I am Cmd" << std::endl;
}
};
class Step: public Actor, DynamicCreator<Step, std::string, int>
{
public:
Step(const std::string& strType, int iSeq){std::cout << "Create Step " << strType << " with seq " << iSeq << std::endl;}
virtual void Say()
{
std::cout << "I am Step" << std::endl;
}
};
class Worker
{
public:
template<typename ...Targs>
Actor* CreateActor(const std::string& strTypeName, Targs&&... args)
{
Actor* p = ActorFactory<Targs...>::Instance()->Create(strTypeName, std::forward<Targs>(args)...);
return(p);
}
};
}
int main()
{
//Actor* p1 = ActorFactory<std::string, int>::Instance()->Create(std::string("Cmd"), std::string("neb::Cmd"), 1001);
//Actor* p3 = ActorFactory<>::Instance()->Create(std::string("Cmd"));
neb::Worker W;
neb::Actor* p1 = W.CreateActor(std::string("neb::Cmd"));
p1->Say();
//std::cout << abi::__cxa_demangle(typeid(Worker).name(), nullptr, nullptr, nullptr) << std::endl;
std::cout << "----------------------------------------------------------------------" << std::endl;
neb::Actor* p2 = W.CreateActor(std::string("neb::Step"), std::string("neb::Step"), 1002);
p2->Say();
return(0);
}
??Nebula框架是用C++14標(biāo)準(zhǔn)寫的,在Makefile中有預(yù)編譯選項(xiàng),可以用C++11標(biāo)準(zhǔn)編譯,但未完全支持C++11全部標(biāo)準(zhǔn)的編譯器可能無法編譯成功。實(shí)測(cè)g++ 4.8.5不支持可變參數(shù)模板,建議采用gcc 5.0以后的編譯器,最好用gcc 6,Nebula用的是gcc6.4。
??這里給出的例子DynamicCreate.cpp可以這樣編譯:
g++ -std=c++11 DynamicCreate.cpp -o DynamicCreate
??程序執(zhí)行結(jié)果如下:
DynamicCreator.Register construct
static ActorFactory* Instance()
ActorFactory construct
bool ActorFactory::Regist(const std::string& strTypeName, std::function<Actor*(Targs... args)> pFunc)
m_mapCreateFunction.size() =1
DynamicCreator.Register construct
static ActorFactory* Instance()
ActorFactory construct
bool ActorFactory::Regist(const std::string& strTypeName, std::function<Actor*(Targs... args)> pFunc)
m_mapCreateFunction.size() =1
static ActorFactory* Instance()
Actor* ActorFactory::Create(const std::string& strTypeName, Targs... args)
static Actor* DynamicCreator::CreateObject(Targs... args)
Actor construct
DynamicCreator construct
Create Cmd
I am Cmd
----------------------------------------------------------------------
static ActorFactory* Instance()
Actor* ActorFactory::Create(const std::string& strTypeName, Targs... args)
static Actor* DynamicCreator::CreateObject(Targs... args)
Actor construct
DynamicCreator construct
Create Step neb::Step with seq 1002
I am Step
??完畢,周末花了6個(gè)小時(shí)才寫完,找了個(gè)合適的時(shí)間發(fā)布出來。
??Nebula框架系列技術(shù)分享 之 《C++反射機(jī)制:可變參數(shù)模板實(shí)現(xiàn)C++反射》。 如果覺得這篇文章對(duì)你有用,如果覺得Nebula框架還可以,幫忙到Nebula的Github或碼云給個(gè)star,謝謝。Nebula不僅是一個(gè)框架,還提供了一系列基于這個(gè)框架的應(yīng)用,目標(biāo)是打造一個(gè)高性能分布式服務(wù)集群解決方案。
參考資料:
免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。