1.AMI:AsynchronousMethod Invocation
异步方法调用
2.AMD:AsynchronousMethod Dispatch
异步方法派发
1.1同步模型与异步模型区别
两者的区别:
同步模型中发出调用的线程会阻塞到操作返回。异步模型则正好相反,发出调用的线程不会阻塞的等待调用结果的返回。
Ice 在本质上是一个异步的中间件平台,出于对应用(及其程序员)的考虑而模拟了同步的行为。当 Ice 应用通过代理、向远地对象发出同步的双向调用时,发出调用的线程会阻塞起来,以模拟同步的方法调用。在此期间,Ice run time在后台运行,处理消息,直到收到所需的答复为止,然后发出调用的线程就可以解除阻塞并解编结果。
1.2AMI概述
AMI:
异步方法调用这个术语描述的是客户端的异步编程模型支持。
使用 AMI发出远地调用,在Ice run time 等待答复的同时,发出调用的线程不会阻塞。相反,发出调用的线程可以继续进行各种活动,当答复最终到达时,Ice run time会通知应用。通过回调发给应用对象通知
1.3AMD概述
AMD:
使用 AMD 时,服务器可以接收一个请求,然后挂起其处理,以尽快释放分派线程。当处理恢复、结果已得出时,服务器要使用Ice run time 提供的回调对象,显式地发送响应。
AMD 操作通常会把请求数据 (也就是,回调对象和
操作参数)放入队列,供应用的某个线程 (或线程池)随后处理用。这样,服务器就使分派线程的使用率降到了最低限度,能够高效地支持数千并发客户。
为什么要使用AMD:
(1)默认情况下:一个服务器在同一时刻所能支持的同步请求数受到Ice run time的服务器线程池的尺寸限制。如果所有线程都在忙于分派长时间运行的操作,那么就没有线程可用于处理新的请求,客户就会体验到不可接受的无响应状态。
(2)Icerun time的服务器线程池尺寸受限于主机的CPU数
1.4元数据(metadata)与AMI,AMD
1)ICE自3.4以后,提供了一套全新的AMI(异步调用方式)的API,新的API已不需要在slice文件中标记[“ami”]这样的元数据。
2)ICE提供向下兼容性,支持[“ami”]的语言标记,但已标注为deprecated.
3)服务端使用AMD异步派发方式编程,仍然必须在slice文件中,对slice定义标注[“amd”]元数据。
Tips:
给客户提供某个调用方法的同步和异步版本是有益的,但如果在服
务器中这样做,程序员就必须实现两种版本的分派方法,并不能带来切实的好处,而且还会带来若干潜在的缺陷。
AMD元数据标记方式:
1)接口级(interface)
["amd"]interface I {
bool isValid();
float computeRate();
};
2)操作级(operation)
interface J {
["amd"] void startProcess();
int endProcess();
};
AMD元数据的标记方式
Tips:
在操作层面、而不是接口或类的层面指定元数据,不仅能使所生成的代码的数量降到最低限度,而且,更重要的是,它还能使复杂度降到最低限度。尽管异步模型更为灵活,它的使用也更复杂。因此,最好只把它用于那些能从中获得某种好处的操作;对其他操作则使用更简单的同步模型
2.1基本API
1.异步方法通知(AMI)API
ICE从3.4开始采用新的AMIAPI:
module Demo {
interface Employees {
string getName(intnumber);
};
};
1)不必再显式的注明[“ami”]元数据,slice工具会自动的生产同步调用与异步调用的stub代码。
Ice::AsyncResultPtr begin_getName(Ice::Int number);
Ice::AsyncResultPtr begin_getName(Ice::Int number,
const Ice::Context& __ctx);
std::string end_getName(const Ice::AsyncResultPtr&);
(2)begin_xxxMethod负责向队列中插入一个调用的请求,当请求插入到队列后,便可立即返回,不必阻塞的等待调用完成。
(3)end_xxxMethod负责收集异步调用的结果。当异步调用的结果还未处理完成时,调用end_xxxMethod方法会阻塞到异步调用完成处理结果。换句话说,如果结果已完成,调用end_xxxMethod方法会立即返回。
示例:
EmployeesPrxe = ...;
Ice::AsyncResultPtrr = e->begin_getName(99);
//Continue to do other things here...
stringname = e->end_getName(r);
AsyncResultPtr是AsynResult的智能指针,存储了ICE运行时对于异步调用的状态追踪的信息。在调用begin_xxxMethod方法相关的end_xxxMethod方法时,必须传入相应的AsyncResultPtr对象
(4)异步方法调用中的参数
Slice定义的方法参数在AMI的begin_,end_方法中有相应的变化:
doubleop(intinp1, string inp2, out booloutp1, out long outp2);
AMI:
Ice::AsyncResultPtrbegin_op(Ice::Intinp1,
const::std::string&inp2)
Ice::Doubleend_op(bool&outp1, Ice::Long& outp2,
constIce::AsyncResultPtr&);
注意:
1)slice定义中的输入参数转移为begin_op中的唯一输入参数。
2)slice定义中的输出参数作为end_op的输出参数,end_op的返回值为原slice定义的返回值。
Begin_op返回AsyncResultPtr作为end_op的输入参数。
(5)异常处理(ExceptionHandling)
1)通常情况下,当异步调用过程中发生异常,在end_xxxMethod中抛出,即使异常发生在begin_xxxMethod中。
2)但有2种例外的情况:
ß当销毁了当前的Communicator后,再调用异步方法,会抛出CommunicatorDestroyedException异常。
ß当调用方式出错时,抛出IceUtil::IllegalArgumentException异常(begin_,end_配对错误,调用的方法参数错误)
2.2AsynResult类
AsyncResult类由begin_op方法返回,封装了异步调用的有关信息
classAsyncResult
: virtual public IceUtil::Shared,
private IceUtil::noncopyable{
public:
virtual booloperator==(const AsyncResult&)const;
virtual booloperator<(const AsyncResult&)const;
virtual IntgetHash() const;
virtual CommunicatorPtrgetCommunicator()const;
virtual ConnectionPtrgetConnection()const;
virtual ObjectPrxgetProxy() const;
conststring& getOperation()const;
LocalObjectPtrgetCookie() const;
boolisCompleted() const;
void waitForCompleted();
boolisSent() const;
void waitForSent();
boolsentSynchronously()const;
};
(2)方法说明
booloperator==(const AsyncResult&)const
booloperator<(const AsyncResult&)const
IntgetHash() const
比较函数与取hash值,用于AsynResult对象之间的比较,排序。特别是在外部有大量异步调用请求,在begin_op与end_op之间需要传递AsynResult对象时需要存储AysnResult对象时,这些方法可以将AsynResult对象作为主键存储在类似map这样的关联容器中,存储每个异步调用的状态。
CommunicatorPtrgetCommunicator()const
获取发送当前异步调用的通信器对象。
virtual ConnectionPtrgetConnection()const
获取当前调用使用的连接对象
virtualObjectPrx getProxy()const
获取发送异步调用的代理对象
conststring& getOperation()const
获取当前异步调用方法的名称
LocalObjectPtrgetCookie() const
获取begin_op发送的Cookie对象(后续介绍),如果begin_op没有发送Cookie,则返回Null
boolisCompleted() const
判断当前异步调用是否完成,当异步调用的结果数据的状态为有效时,返回true;反之,若结果数据状态为无效时,返回false
voidwaitForCompleted()
阻塞调用者,直达异步调用的结果数据状态为有效。
boolisSent() const
当begin_op操作被调用时,Ice运行时会将请求从客户侧的发送器发出,如果发送被客户侧的发送器拒绝,则向客户端侧的传输队列插入相关的调用请求。当这个请求成功发送时,返回true,当请求还在队列中时,返回false
voidwaitForSent()
阻塞等待客户侧将调用请求发出
boolsentSynchronously()const
当调用请求在第一次发送的时候,如果没有入队直接发出,返回true;反之返回false
2.3轮询方式的完成通知(Pollingfor Completion)
1)同步方式与异步方式效率上的差距
2)异步方式AsyncResult队列控制
提高传输效率
控制内存占用
2.通用回调函数方式的完成通知
相对于轮询方式的优点:
解除应用逻辑与异步调用机制的耦合性。利用Ice 运行时完成客户端的异步处理逻辑。
Slice提供的重载方法,红色高亮部分是回调完成通知的特征。
Ice::AsyncResultPtrbegin_getName(
Ice::Intnumber,
constIce::CallbackPtr& __del,
const Ice::LocalObjectPtr&__cookie = 0);
Ice::AsyncResultPtrbegin_getName(
Ice::Intnumber,
const Ice::Context&__ctx,
const Ice::CallbackPtr& __del,
const Ice::LocalObjectPtr&__cookie = 0);
Ice::CallbackPtr:Ice运行时提供的回调函数智能指针,存储了自定义的回调函数实例。
当发生回调时,Ice运行时会执行回调函数对象上绑定的方法。
3.自定义的回调对象示例:
classMyCallback :public IceUtil::Shared{
public:
void finished(constIce::AsyncResultPtr&r) {
EmployeesPrxe =
EmployeesPrx::uncheckedCast(r->getProxy());
try {
string name = e->end_getName(r);
cout<< "Name is: " << name << endl;
} catch (constIce::Exception& ex) {
cerr<< "Exception is: " << ex << endl;
}
}
};
typedefIceUtil::Handle<MyCallback>MyCallbackPtr;
回调对象封装了异步调用实质的业务逻辑,异步调用本身是发出非阻塞的调用请求,但重要的是发出了请求以后,应用逻辑该如何执行。
4.回调对象的编写要点
1)继承IceUtil::Shared
2)回调对象中的回调方法,必须符合以下声明方式:
void callback_method(AsyncResultPtr& r)
3)回调对象中的回调方法名称可以为合乎语法的任意名称。
4)回调方法中必须调用end_xxxMethod();
回调方法必须捕获所有可能由end_xxxMethod抛出的异常,如果遗漏了某异常,Ice运行时默认会在日志中记录,但也会忽略这个异常(设置Ice.Warn.AMICallback属性为0,可以不记录日志)。
5.应用程序AMI调用示例:
EmployeesPrxe = ...;
MyCallbackPtrcb =new MyCallback;
Ice::CallbackPtrd = Ice::newCallback(cb,
&MyCallback::finished);
e->begin_getName(99,d);
注意:
1)Ice::newCallback辅助函数,将自定义的回调对象智能指针与回调方法绑定后,返回Ice::CallbackPtr类型的对象。
begin_xxxMethod,需要输入一个Ice::CallbackPtr对象。
6.回调中的Cookie
Ice::AsyncResultPtrbegin_getName(
Ice::Intnumber,
const Ice::CallbackPtr&__del,
const Ice::LocalObjectPtr& __cookie = 0);
Ice::AsyncResultPtrbegin_getName(
Ice::Intnumber,
const Ice::Context&__ctx,
const Ice::CallbackPtr&__del,
const Ice::LocalObjectPtr& __cookie = 0);
Cookie对象在异步方法调用中用于begin_xxxMethod与end_xxxMethod之间传递信息。
应用场景:
用户在界面上点击产生大量的异步调用,当这些异步调用完成后,每个调用需要更新不同用户界面组件。在这种情景下,每个调用需要携带它所需要更新的组件信息才能完成操作。
在这种异步调用与回调函数之间需要交互的情况下,可以通过Cookie在应用中绑定相关信息。
7.Cookie的编写
为了满足不同的应用场景,Cookie对象可以自定义。
Cookie编写的唯一要求就是要继承Ice::LocalObject
示例:
classCookie : public Ice::LocalObject
{
public:
Cookie(WidgetHandleh) : _h(h) {}
WidgetHandlegetWidget() {return _h; }
private:
WidgetHandle_h;
};
typedefIceUtil::Handle<Cookie>CookiePtr;
//
CookiePtrcookie1 = new Cookie(widgetHandle1);
//Make cookie for call to getName(42);
CookiePtrcookie2 = new Cookie(widgetHandle2);
//Invoke the getNameoperation with different cookies.
e->begin_getName(99,getNameCB,cookie1);
e->begin_getName(24,getNameCB,cookie2);
void
MyCallback::getName(constIce::AsyncResultPtr&r)
{
EmployeesPrxe =
EmployeesPrx::uncheckedCast(r->getProxy());
CookiePtrcookie =
CookiePtr::dynamicCast(r->getCookie());
try {
string name = e->end_getName(r);
cookie->getWidget()->writeString(name);
} catch (constIce::Exception& ex) {
handleException(ex);
}
}
1)在应用逻辑中创建了Cookie对象
2)将Cookie对象传递给begin_getName方法
3)在对应的回调方法中重新获得Cookie(需要向下转型)
从Cookie对象中获得需要的信息
类型安全回调函数方式的完成通知
(1)通用回调方法存在一些类型安全方面的隐患
ß在回调方法中调用end_xxxMethod前必须将代理对象向下转型为正确的代理对象类型
EmployeesPrx e = EmployeesPrx::uncheckedCast(r->getProxy());
ß调用的end_xxxMethod必须与begin_xxxMethod配对
ß如果使用了Cookie,那么也需要将Cookie向下转型为正确的Cookie类型。
CookiePtr cookie = CookiePtr::dynamicCast(r->getCookie());
ß在调用end_xxxMethod方法时,必须要捕获所有可能的异常。否则无法获知调用在何时失败
Slice工具提供了一套Callback类API解决上述类型安全问题。
类型安全回调函数方式的完成通知
(2)Slice工具与类型安全的回调
Slice工具在编译slice文件时,生成一系列相关的回调函数类别,在这些自动生成的代码中,将类型转换,调用end_xxxMethod等通用回调方法中的程式化方法以自动生成的方式代替手工编程。(C++中是以模板的方式实现不同回调函数的模板实例)
interfaceFileTransfer
{
void send(intoffset, ByteSeqbytes);
};
//同步发送
FileHandlefile = open(...);
FileTransferPrxft = ...;
constint chunkSize= ...;
Ice::Intoffset = 0;
while(!file.eof()) {
ByteSeqbs;
// Read a chunk
bs= file.read(chunkSize);
// Send the chunk
ft->send(offset, bs);
offset += bs.size();
}
FileHandlefile = open(...);
FileTransferPrxft =...;
const intchunkSize= ...;
Ice::Intoffset = 0;
list<Ice::AsyncResultPtr>results;
const intnumRequests= 5;
while (!file.eof()){
ByteSeqbs;
bs = file.read(chunkSize);
// Send up to numRequests+ 1 chunks asynchronously.
Ice::AsyncResultPtrr = ft->begin_send(offset,bs);
offset += bs.size();
// 等待请求完成发送
r->waitForSent();
results.push_back(r);
//超过了水位标则阻塞等待.
while (results.size()> numRequests){
Ice::AsyncResultPtrr = results.front();
results.pop_front();
r->waitForCompleted();
}
}
//等待剩余的请求完成.
while (!results.empty()){
Ice::AsyncResultPtrr = results.front();
results.pop_front();
r->waitForCompleted();
}
2.4通用的完成通知回调函数(GenericCompletion Callbacks)
2.5类型安全的回调(Type-SafeCompletion Callbacks)
2.6单路调用(OnewayInvocations)
1)通用的回调方式由于回调类别中回调函数具有输入参数,
void callback_method(AsyncResultPtr& r)
因此是双路调用,无法调用单路的代理对象方法。
2)类型安全的回调方式,通过newCallback辅助方法,在回调函数对象中只指定失败的回调函数,可以调用单路方法。
示例:
MyCallbackPtrcb =new MyCallback;
Ice::Callback_Object_ice_pingPtrcallback =
Ice::newCallback_Object_ice_ping(cb,&MyCallback::failureCB);
p->begin_opVoid(callback);
2.7流量控制(FlowControl)
1.流量控制的起因:
客户端并发请求非常频繁,服务端处理能力赶不上客户端发送异步请求的速度。客户端将请求发往客户端Ice运行时请求队列,队列不断增长,在不加流量控制的情况下,最终耗尽内存。
2.流量控制的功能:
Ice提供了一套流量控制管理的API,应用了流量管理的api后,当客户端的请求数超过了限定的阈值,则阻塞客户端发起的新的操作请求直到队列中完成部分请求的处理,有剩余的空间容纳入队的请求。
1)通用回调方式
voidsent(constIce::AsyncResult&);
调用情景1和情景2的判断由程序员调用AsyncResult::sentSynchronously判断,自行编写逻辑。
2)类型安全的回调方式
voidsent(bool sentSynchronously);
voidsent(bool sentSynchronously,const <CookiePtr>&cookie);
调用情景1与情景2的判断由Ice运行时传递的sendSynchronously参数判断。
3.流量控制的编程要点:
流量控制的基本思想是自定义流量控制的方法。
classMyCallback :public IceUtil::Shared{
public:
void finished(const Ice::AsyncResultPtr&);
voidsent(const Ice::AsyncResultPtr&); // 发送请求的方法,名字可以自定义
};
typedefIceUtil::Handle<MyCallback>MyCallbackPtr;
ß情景1:当Ice运行时将请求发送到客户端的发送端上后,由调用begin_xxxMethod的方法的线程接着调用send方法。
ß情景2:当Ice运行时将请求入队时,则由另一线程在Ice运行时将队列中请求发送到发送端后,调用send方法。
ß以上2种情况调用send的const Ice::AsyncResultPtr& 参数的sentSynchronously方法判断。
ß在send方法中对请求数进行计数,当队列的长度达到高水位标,则阻塞,当运行时将请求发往发送端,则减少计数。
2.8批量请求(BatchRequests)
1.批量调用的起因:
对于大量的零散单路调用或数据包请求,服务端在处理时需要频繁的在用户态和内核态之间转换,代价非常高昂。因此引入批量调用。
批量调用的方法:
Ice运行时提供了将客户端对象代理转换为批量调用代理的方法:
//C++
namespaceIceProxy {
namespaceIce {
classObject : /* ... */ {
public:
//将普通代理转换为批量调用的代理
Ice::ObjectPrx ice_batchOneway() const;
Ice::ObjectPrx ice_batchDatagram() const;
void ice_flushBatchRequests();
//...
};
}
}
显式调用与隐式调用:
显式同步调用:
ice_flushBatchRequests
显式异步调用:
begin_ice_flushBatchRequests
end_ice_flushBatchRequests
隐式调用:
Ice.BatchAutoFlush=1(默认)
Ice.MessageSizeMax=1MB(默认)
2.9并发(Concurrency)
ßIce运行时通常情况下由独立的线程调用异步回调方法中的回调函数。因此在回调函数中可以使用非递归锁而不怕出现死锁的情况。
ß
ß但如果定义了流量控制回调函数,则如前所述的规则,在不同的情景下,有不同的线程选择规则。
2.10限制(Limitations)
ß采用了AMI异步回调,则不能采用collocatedoptimization优化。否则抛出CollocationOptimizationException。
ßcollocatedoptimization优化参见ICE手册32.21节
2.11示例
#ifndefHELLO_ICE
#defineHELLO_ICE
moduleDemo
{
exceptionRequestCanceledException
{
};
interfaceHello
{
// 注意元数据定义
["ami", "amd"]idempotent void sayHello(int delay)
throws RequestCanceledException;
void shutdown();
};
};
#endif
int
AsyncClient::run(intargc,char* argv[])
{
if(argc> 1)
{
cerr<< appName()<< ": too many arguments" << endl;
return EXIT_FAILURE;
}
HelloPrxhello = HelloPrx::checkedCast(communicator()->propertyToProxy("Hello.Proxy"));
if(!hello)
{
cerr<< argv[0]<< ": invalid proxy" << endl;
return EXIT_FAILURE;
}
menu();
CallbackPtrcb =new Callback();
char c;
do
{
try
{
cout<< "==> ";
cin>> c;
if(c == 'i')
{
hello->sayHello(0);
}
else if(c == 'd')
{
hello->begin_sayHello(5000,newCallback_Hello_sayHello(cb,&Callback::response, &Callback::exception));
}
else if(c == 's')
{
hello->shutdown();
}
else
{
cout<< "unknown command `" << c << "'"
<< endl;
menu();
}
}
catch(constIce::Exception& ex)
{
cerr<< ex << endl;
}
}
while(cin.good()&& c != 'x');
return EXIT_SUCCESS;
}