1. 使用IceGrid会话进行资源分配
IceGrid提供了一个资源分配工具,来协调访问IceGrid应用程序的对象和服务端。要分配资源以供独占使用,首先,客户端必须通过使用IceGrid注册表或Glacier2路由器进行身份验证来建立会话,然后,客户端可以保留应用程序指示可分配的对象和服务器。客户端应该在不再需要的时候释放资源,否则,当客户端的会话终止或由于不活动而超时时,IceGrid会回收它。
一个可分配的服务端提供至少一个可分配的对象。当第一个可分配对象被声明时,对应服务端被认为是被分配的,并且直到所有分配的对象被释放才被释放。当服务端由客户端分配时,其他客户端不能分配其对象。
2. 创建一个IceGrid会话
客户端必须先创建一个IceGrid会话,然后才能分配对象。如果你已经配置了Glacier2路由,来进行IceGrid的会话管理器,则客户端的路由会话满足此要求。
在没有Glacier2的情况下,IceGrid客户端调用IceGrid注册表接口createSession或createSessionFromSecureConnection来创建一个会话:
module IceGrid
{
exception PermissionDeniedException
{
string reason;
}
interface Session extends Glacier2::Session
{
idempotent void keepAlive();
...
}
interface Registry
{
Session* createSession(string userId, string password)
throws PermissionDeniedException;
Session* createSessionFromSecureConnection()
throws PermissionDeniedException;
idempotent int getSessionTimeout();
idempotent int getACMTimeout();
...
}
}
createSession方法需要用户名和密码,并且如果客户端有权限创建会话,则返回会话代理。默认情况下,IceGrid不允许创建会话。你必须定义注册表的属性IceGrid.Registry.PermissionsVerifier,然后,使用权限验证对象的代理,通过createSession启用会话创建。
createSessionFromSecureConnection操作不需要用户名和密码,因为它使用SSL连接提供的凭据来认证客户端。与createSession一样,必须通过配置权限验证对象的代理来启用会话创建,以便客户端可以使用createSessionFromSecureConnection创建会话。在这种情况下,属性则是IceGrid.Registry.SSLPermissionsVerifier。
要创建会话,客户端通过将知名的代理字符串“IceGrid/Registry”转换为communicator的代理对象来获取注册表代理,将代理向下转换到IceGrid::Registry接口,并调用其中一个方法。以下示例代码演示了如何在C++中执行此操作。该代码在其他语言映射中看起来非常相似。
//C++11
auto base = communicator->stringToProxy("IceGrid/Registry");
auto registry = Ice::checkedCast<IceGrid::RegistryPrx>(base);
string username = ...;
string password = ...;
shared_ptr<IceGrid::SessionPrx> session;
try
{
session = registry->createSession(username, password);
}
catch(const IceGrid::PermissionDeniedException& ex)
{
cout << "permission denied:\n" << ex.reason << endl;
}
//C++98
Ice::ObjectPrx base = communicator->stringToProxy("IceGrid/Registry");
IceGrid::RegistryPrx registry = IceGrid::RegistryPrx::checkedCast(base);
string username = ...;
string password = ...;
IceGrid::SessionPrx session;
try
{
session = registry->createSession(username, password);
}
catch(const IceGrid::PermissionDeniedException& ex)
{
cout << "permission denied:\n" << ex.reason << endl;
}
注册表对象的ID可能会根据它的配置设置而更改。
创建会话后,客户端必须保持活动状态,以防止它过期。你有两个选择让会话保持活着:
- 调用
Registry::getSessionTimeout,并定期每1(或更少)秒钟调用Session::keepAlive - 调用
Registry::getACMTimeout,并在连接上配置ACM设置
我们推荐使用第二种方法,你可以按如下方式执行:
//C++11
int acmTimeout = registry->getACMTimeout();
if(acmTimeout > 0)
{
auto conn = session->ice_getCachedConnection();
conn->setACM(acmTimeout, Ice::nullopt, Ice::ACMHeartbeat::HeartbeatAlways);
}
//C++98
int acmTimeout = registry->getACMTimeout();
if(acmTimeout > 0)
{
Ice::ConnectionPtr conn = session->ice_getCachedConnection();
conn->setACM(acmTimeout, IceUtil::None, Ice::HeartbeatAlways);
}
在连接上启用心跳,Ice会按照给定的超时值,定期自动发送心跳消息。服务端忽略这些消息,但它们的作用是保持会话活着。
如果会话超时,或者客户端通过调用destroy方法来显式终止会话,则IceGrid会自动释放使用该会话分配的所有对象。
3. 控制IceGrid会话的访问
如上所述,你必须使用至少一种权限验证对象的代理来配置IceGrid注册表,以启用会话创建:
- IceGrid.Registry.PermissionsVerifier
该属性提供实现了接口Glacier2::PermissionsVerifier对象的代理。定义这个属性允许客户使用createSession创建会话。
- IceGrid.Registry.SSLPermissionsVerifier
该属性提供实现了接口Glacier2::SSLPermissionsVerifier对象的代理。定义这个属性允许客户使用createSessionFromSecureConnection创建会话。
IceGrid提供了内置权限验证对象:
- 用于
TCP/IP的空权限验证程序。该对象接受任何用户名和密码,只能用于不需要访问控制的安全环境。通过定义以下配置属性,来选择验证对象:
IceGrid.Registry.PermissionsVerifier=<instance-name>/NullPermissionsVerifier
请注意,你必须将正确的实例名称替换为对象ID类别。
- SSL的空权限验证程序,类似于
TCP/IP的权限验证程序。通过定义以下配置属性,来选择验证对象:
IceGrid.Registry.SSLPermissionsVerifier=<instance-name>/NullSSLPermissionsVerifier
- 基于文件的权限验证程序。此对象在包含
username-password对的文件中使用访问控制列表。密码文件的格式与Glacier2密码文件的格式相同。 通过使用密码文件的路径名,定义配置属性IceGrid.Registry.CryptPasswords,来启用验证实现。请注意,如果你使用IceGrid.Registry.PermissionsVerifier指定权限验证者对象的代理,则忽略此属性。
你也可以实现你自己的权限验证器对象。
4. 使用IceGrid会话分配对象
客户端使用从createSession或createSessionFromSecureConnection返回的会话代理来分配对象。代理支持如下所示的Session接口:
module IceGrid
{
exception ObjectNotRegisteredException
{
Ice::Identity id;
}
exception AllocationException
{
string reason;
}
exception AllocationTimeoutException extends AllocationException
{
}
interface Session extends Glacier2::Session
{
idempotent void keepAlive();
Object* allocateObjectById(Ice::Identity id)
throws ObjectNotRegisteredException, AllocationException;
Object* allocateObjectByType(string type)
throws AllocationException;
void releaseObject(Ice::Identity id)
throws ObjectNotRegisteredException, AllocationException;
idempotent void setAllocationTimeout(int timeout);
}
}
如前所述,客户端负责通过定期调用keepAlive使会话保持活动状态。
allocateObjectById方法分配并返回给定ID可分配对象的代理。如果给定的ID中没有可分配的对象已经注册,则客户端将收到ObjectNotRegisteredException。如果对象不能分配,客户端会收到AllocationException。分配尝试可能会失败,原因如下:
- 该对象已被会话分配
- 该对象由另一个会话分配,在配置的分配超时期限内不可用
- 回话被销毁了。
allocateObjectByType方法分配并返回用给定类型注册的可分配对象的代理。如果有多个可分配对象已使用给定类型注册了,则注册表将随机选择一个。如果没有给定类型的对象可以分配,客户端会收到AllocationException。分配尝试可能会失败,原因如下:
- 没有对象与给定的类型注册
- 给定类型的所有对象都已被分配(通过此会话或其他会话),并且在配置的分配超时期间内没有任何对象可用
- 回话被销毁了。
releaseObject方法释放会话分配的对象。如果给定的ID中没有可分配的对象注册,客户端会收到ObjectNotRegisteredException;如果对象没有被会话分配,则抛出AllocationException。会话销毁后,IceGrid会自动释放所有分配的对象。
setAllocationTimeout用来配置分配方法的超时。如果客户端调用allocateObjectById或allocateObjectByType时没有对象可用,则IceGrid会等待指定的超时时间,等待可分配对象可用。如果超时过期,则客户端收到AllocationTimeoutException。
5. 使用IceGrid会话分配服务端
客户端不需要明确分配服务端。如果一个服务端是可分配的,IceGrid将隐式地分配给声明服务端为可分配对象中的第一个客户端。同样,当它的所有可分配对象被释放时,IceGrid会释放服务端。
服务端分配在两种情况下很有用:
- 只有可分配的服务端才能使用会话激活模式,在这种模式下,客户端分配服务端时按需激活服务端,并在释放时停用服务端。
- 一个可分配的服务端可以用
IceSSL或Glacier2进行保护,以便其对象只能由分配它的客户端调用。
6. 分配资源的安全考虑
IceGrid的资源分配工具允许客户端协调对对象和服务端的访问,但是不会对客户端调用分配的对象有任何限制;任何具有分配对象的代理的客户端都可以在它上面调用一个方法。IceGrid假定客户端正在相互合作,并且尊重分配语义。
为了防止未经授权的客户端调用分配的对象或服务端上的方法,可以使用IceSSL或Glacier2:
使用IceSSL,你可以使用属性
IceSSL.TrustOnly.Server或IceSSL.TrustOnly.Server.AdapterName来保护对服务端或特定对象适配器的访问。例如,如果使用会话激活模式配置服务器,则可以将IceSSL.TrustOnly属性之一设置为${session.id}变量,该变量在为会话激活服务端时被替换为会话ID。如果IceGrid会话是从安全连接创建的,则会话ID将是与安全连接关联的专有名称,这将有效的限制服务端或通过IceGrid建立回话的客户端的适配器的访问。使用Glacier2,你可以使用Glacier2过滤机制来保护对已分配对象或已分配服务端的对象适配器的访问。默认情况下,使用Glacier2路由器创建的IceGrid会话会自动访问分配的对象、可分配的对象、某些知名对象以及分配服务端的对象适配器。
7. 部署可分配资源
Allocatable对象使用类似于知名对象描述符的描述符进行注册。Allocatable对象不能被复制,因此只能在对象适配器描述符中指定。
通过设置服务端描述符的allocatable属性,可以将服务端指定为可分配的。
作为一个例子,下面的应用程序定义了一个可分配的服务端和一个可分配的对象:
<icegrid>
<application name="Ripper">
<node name="Node1">
<server id="EncoderServer"
exe="/opt/ripper/bin/server"
activation="on-demand"
allocatable="true">
<adapter name="EncoderAdapter" id="EncoderAdapter" endpoints="tcp">
<allocatable identity="EncoderFactory" type="::Ripper::MP3EncoderFactory"/>
</adapter>
</server>
</node>
</application>
</icegrid>
8. 在Ripper中使用资源分配
我们可以使用MP3编码器对象工厂的分配工具来协它的访问。首先,我们需要修改描述符来定义一个可分配的对象:
<icegrid>
<application name="Ripper">
<server-template id="EncoderServerTemplate">
<parameter name="index"/>
<server id="EncoderServer${index}"
exe="/opt/ripper/bin/server"
activation="on-demand">
<adapter name="EncoderAdapter" endpoints="tcp">
<allocatable identity="EncoderFactory${index}"
type="::Ripper::MP3EncoderFactory"/>
</adapter>
</server>
</server-template>
<node name="Node1">
<server-instance template="EncoderServerTemplate" index="1"/>
</node>
<node name="Node2">
<server-instance template="EncoderServerTemplate" index="2"/>
</node>
</application>
</icegrid>
接下来,客户端需要创建一个会话并分配一个对象工厂:
//C++11
auto obj = session->allocateObjectByType(Ripper::MP3EncoderFactory::ice_staticId());
try
{
auto encoder = factory->createEncoder();
// 使用编码器编码一个文件
}
catch(const Ice::LocalException& ex)
{
// 编码时有问题,我们捕捉这个异常,并释放对象工厂
}
session->releaseObject(obj->ice_getIdentity());
//C++98
Ice::ObjectPrx obj = session->allocateObjectByType(Ripper::MP3EncoderFactory::ice_staticId());
try
{
Ripper::MP3EncoderPrx encoder = factory->createEncoder();
// 使用编码器编码一个文件
}
catch(const Ice::LocalException& ex)
{
// 编码时有问题,我们捕捉这个异常,并释放对象工厂
}
session->releaseObject(obj->ice_getIdentity());
当不再需要的时候,释放一个分配的对象是非常重要的,这样其他客户端就可以使用它。如果你忘记释放一个对象,它将保持分配,直到会话被销毁。