1. 使用IceGrid部署

在这里,我们使用IceGrid的部署工具来扩展示例程序的功能。

2. 部署Ripper架构

经过改进以后的应用程序由一个单一的IceGrid节点组成,这个节点负责MP3编码服务器,运行在ComputeServer上。下图显示了客户端对它间接代理的初始调用,和IceGrid使这个调用成为可能的行为:

Architecture for deployed ripper application
Figure: Architecture for deployed ripper application

与最初的体系结构不同,我们不再需要手动启动服务器。在经过改进的应用程序中,客户端的定位请求会提示注册表去查询节点有关服务器的状态,并在必要时启动它。一旦服务器启动成功,定位请求完成,随后的客户端通信直接与服务器进行。

3. Ripper部署描述符

我们可以使用icegridadmin命令行工具部署我们的应用程序,但是,我们要先在XML中定义描述符,描述符相当简介:

<icegrid>
    <application name="Ripper">
        <node name="Node1">
            <server id="EncoderServer" exe="/opt/ripper/bin/server" activation="on-demand">
                <adapter name="EncoderAdapter" id="EncoderAdapter" endpoints="tcp"/>
            </server>
        </node>
    </application>
</icegrid>

为了使用IceGrid,我们将应用程序命名为Ripper。它由一个分配给Node1节点的EncoderServer服务端组成。

{% em color="#FFE4C4" %}由于计算机通常只运行一个节点进程,你可能会考虑为该节点指定一个主机的名称(如ComputeServerNode)。但是,在你需要将节点迁移到其他主机的时候,这个命名规则就会变得有问题。{% endem %}

服务端的exe属性提供它可执行文件的路径,而activation属性表示必要时应按需激活服务端。

对象适配器的描述符很有意思。 正如你所看到的,nameid属性都指定了EncoderAdapter的值。name的值映射为服务器进程中的适配器名称(即传递给createObjectAdapter的参数),用于配置目的,而id的值唯一标识注册表内的适配器,并用于间接代理。这些属性并不必要具有相同的值。如果我们省略了id属性,则IceGrid将通过组合服务端的名称和适配器的名称组成一个唯一的值来生成以下标识符:

EncoderServer.EncoderAdapter

endpoints属性定义适配器的一个或多个端点。如前所述,这些端点不需要固定的端口。

有关使用XML定义描述符的详细信息,请参阅XML参考

4. Ripper注册表和节点配置

在我们初始注册表的配置中,我们创建了目录/opt/ripper/registry以供注册表使用。节点也需要一个子目录,因此我们使用/opt/ripper/node。 在提醒一次,在启动注册表和节点之前,这些目录必须存在。

我们还需要创建一个Ice配置文件来保存注册表和节点所需的属性。/opt/ripper/config包含以下属性:

# 注册表属性
IceGrid.Registry.Client.Endpoints=tcp -p 4061
IceGrid.Registry.Server.Endpoints=tcp
IceGrid.Registry.Internal.Endpoints=tcp
IceGrid.Registry.AdminPermissionsVerifier=IceGrid/NullPermissionsVerifier
IceGrid.Registry.LMDB.Path=/opt/ripper/registry

# 节点属性
IceGrid.Node.Endpoints=tcp
IceGrid.Node.Name=Node1
IceGrid.Node.Data=/opt/ripper/node
IceGrid.Node.CollocateRegistry=1
Ice.Default.Locator=IceGrid/Locator:tcp -p 4061

注册表和节点可以公用一个配置文件。事实上,通过启用IceGrid.Node.CollocateRegistry,我们已经指出注册表和节点应该运行在同一个进程中。

与我们的初始配置不同的是,我们不再定义IceGrid.Registry.DynamicRegistration。 通过省略此属性,我们强制注册表拒绝注册尚未部署的对象适配器。

节点属性解释如下:

  • IceGrid.Node.Endpoints

该属性指定节点的端点,不需要固定端口。

  • IceGrid.Node.Name

这个属性定义了该节点的唯一名称。它的值必须与我们上面写的描述符相匹配。

  • IceGrid.Node.Data

该属性指定节点的数据目录。

  • Ice.Default.Locator

该属性由icegridadmin定义。如果注册表不在同一位置,节点也需要这个属性。有关此设置的更多信息,请参阅我们对Ripper客户端配置的讨论。

5. 部署Ripper服务端配置

服务端配置是使用描述符完成的。在部署期间,节点为每个服务端创建一个子目录树。在树的内部,节点创建一个配置文件,包含从服务端描述符派生的属性。例如,适配器的描述符在服务端的配置文件中生成以下属性:

# 服务端配置
Ice.Admin.ServerId=EncoderServer
Ice.Admin.Endpoints=tcp -h 127.0.0.1
Ice.ProgramName=EncoderServer
# 对象适配器EncoderAdapter
EncoderAdapter.Endpoints=tcp
EncoderAdapter.AdapterId=EncoderAdapter
Ice.Default.Locator=IceGrid/Locator:default -p 4061

正如你所看到的,IceGrid从描述符中生成的配置文件类似于初始配置,另外还有两个属性:

  • Ice.Admin.ServerId
  • Ice.Admin.Endpoints

Ice.Admin.Endpoints属性启用管理工具,除其他功能外,还允许IceGrid节点正常停用服务器。

使用我们为Ripper建立的目录结构,EncoderServer的配置文件的文件名如下所示:

/opt/ripper/node/servers/EncoderServer/config/config

请注意,不要直接编辑此文件,因为下次节点启动时会重新生成该文件。将属性添加到文件的正确方法是:在服务端的描述符中包含属性定义。 例如,我们可以通过修改服务端描述符来添加属性Ice.Trace.Network=1,如下所示:

<icegrid>
    <application name="Ripper">
        <node name="Node1">
            <server id="EncoderServer" exe="/opt/ripper/bin/server" activation="on-demand">
                <adapter name="EncoderAdapter" id="EncoderAdapter" endpoints="tcp"/>
                <property name="Ice.Trace.Network" value="1"/>
            </server>
        </node>
    </application>
</icegrid>

当一个节点激活服务器时,它使用--Ice.Config命令行参数传递服务器配置文件的位置。如果从命令提示符手动启动服务器,则必须提供此参数。

6. 为Ripper启动一个节点

现在配置文件和目录结构都准备好了,我们准备启动IceGrid注册表和节点。使用注册表和节点,我们只需要使用一个命令:

$ icegridnode --Ice.Config=/opt/ripper/config

支持其他命令行选项,包括允许节点作为Windows服务或Unix守护进程的选项。

7. 部署Ripper

随着注册表的启动和运行,现在是部署我们的应用程序的时候了。和我们的客户端一样,icegridadmin也需要定义Ice.Default.Locator的属性。我们可以用下面的命令启动该实用程序:

$ icegridadmin --Ice.Config=/opt/ripper/config

在确认它可以访问注册表后,icegridadmin提供了一个命令提示符,我们在该命令提示符下部署我们的应用程序。假设我们的描述符存储在/opt/ripper/app.xml中,那么部署命令如下所示:

>>> application add "/opt/ripper/app.xml"

下一步,确认应用程序已被部署:

>>> application list
Ripper

你可以使用以下命令启动服务器:

>>> server start EncoderServer

最后,您可以检索对象适配器的当前端点:

>>> adapter endpoints EncoderAdapter

如果你想使用icegridadmin进行进一步的实验,请使用帮助命令并查看可用的选项

8. Ripper进度回顾

我们已经部署了我们的第一个IceGrid应用程序,但是你可能会质疑这些工作是否值得。即使在这个早期阶段,我们已经获得了一些好处:

  • 在启动客户端之前,我们不再需要手动启动编码的服务端,因为如果IceGrid节点在客户端需要时没有激活,它将自动启动。如果服务端由于任何原因(例如IceGrid管理操作或服务端编程错误)而终止,节点将重新启动它,而不需要我们干预。

  • 我们可以使用IceGrid管理工具来远程管理应用程序。远程修改应用程序,启动和停止服务端以及检查配置的每个方面的能力都是一个显着的优势。

坦白地讲,作为在单台计算机上的可选方案,在我们所宣称能提高Ripper性能的目标上,我们还没有取得太多的进步。我们的客户端现在可以轻松地将编码任务委托给在另一台计算机上的服务端,但是还没有实现我们真正需要的并行。例如,如果客户端创建了多个编码器,并在多个线程中同时使用它们,编码性能可能比直接在客户端做数据编码更差,因为当尝试在多个处理器密集型的任务之间做任务切换时,计算机会变得很缓慢。

9. 给Ripper增加节点

通过增加更多的节点来分配编码负载给更多的服务器。使用我们目前所学的,我们来调查一下添加一个节点对描述符、配置和客户端的影响。

9.1. 描述符更改

添加节点主要是剪切和粘贴操作:

<icegrid>
    <application name="Ripper">
        <node name="Node1">
            <server id="EncoderServer1" exe="/opt/ripper/bin/server" activation="on-demand">
                <adapter name="EncoderAdapter" endpoints="tcp"/>
            </server>
        </node>
        <node name="Node2">
            <server id="EncoderServer2" exe="/opt/ripper/bin/server" activation="on-demand">
                <adapter name="EncoderAdapter" endpoints="tcp"/>
            </server>
        </node>
    </application>
</icegrid>

请注意,我们现在有两个节点元素而不是一个节点元素。你可能会试图使用主机名称作为节点名称。通常来说,这不是一个好的做法。例如,你可能需要在单台机器上运行多个IceGrid节点(例如,用于测试)。同样,你可能有重命名主机的需求,或者需要将节点迁移到其他主机。但是,除非你也重命名该节点,否则会导致这样的情况,当节点没有在与之对应的主机上运行时,您有一个节点(可能已经过时)的名称。显然,这会造成一个令人困惑的配置,因此,最好使用抽象节点名称,比如Node1

除了新的节点元素,请注意:服务端标识符必须是唯一的。但是,适配器名称可以保留为EncoderAdapter,因为该名称仅用于本地服务器进程。实际上,为每个适配器使用不同的名称,会使服务端复杂化,因为它有时候在创建适配器时,需要去寻找他的名称。

我们也从我们的适配器描述符中删除了id属性;IceGrid提供的默认值对我们来说已经足够了。

9.2. 配置文件更改

我们可以继续使用我们之前创建的配置文件,来处理组合注册表和节点一起的进程。我们需要为Node2准备一个单独的配置文件,主要是为IceGrid.Node.Name定义一个不同的值。 但是,我们也不能有两个节点配置IceGrid.Node.CollocateRegistry,因为只允许有一个主注册表,所以我们必须删除这个属性:

IceGrid.Node.Endpoints=tcp
IceGrid.Node.Name=Node2
IceGrid.Node.Data=/opt/ripper/node

Ice.Default.Locator=IceGrid/Locator:tcp -h registryhost -p 4061

我们假设/opt/ripper/node指向托管Node2计算机上的本地文件系统目录,而不是共享卷,因为两个节点不能共享相同的数据目录。

我们还修改了定位器代理,以包含运行注册表主机的地址。

使用IceLocatorDiscovery允许节点在运行时发现其注册表,而不需要定义Ice.Default.Locator。

9.3. 重新部署应用程序

保存新描述符后,你需要重新部署应用程序。使用icegridadmin,执行以下命令:

$ icegridadmin --Ice.Config=/opt/ripper/config
>>> application update "/opt/ripper/app.xml"

如果更新影响当前正在运行程序的任何服务器,则在执行更新之前,IceGrid会自动停止这些服务器,并在更新完成后,再次重新启动它们。我们可以使用application diff命令确定更新是否需要重新启动:

$ icegridadmin --Ice.Config=/opt/ripper/config
>>> application diff --servers "/opt/ripper/app.xml"

为了确保更新不会影响任何活动的服务器,我们可以使用--no-restart选项:

$ icegridadmin --Ice.Config=/opt/ripper/config
>>> application update --no-restart "/opt/ripper/app.xml"

使用此选项,如果有任何服务器需要重新启动,则更新将失败。

9.4. 客户端更改

我们添加了一个新的节点,但是我们仍然需要修改我们的客户端来使用它。就目前而言,我们的客户端可以将一个编码任务委托给两个MP3EncoderFactory对象之一 客户端通过使用正确的间接代理选择一个对象:

为了在两个对象之间分配任务,客户可以使用随机数发生器来决定哪个对象接收下一个任务:

//c++11
string adapter;
if((rand() % 2) == 0)
{
    adapter = "EncoderServer1.EncoderAdapter";
}
else
{
    adapter = "EncoderServer2.EncoderAdapter";
}
auto proxy = communicator->stringToProxy("factory@" + adapter);
auto factory = Ice::checkedCast<Ripper::MP3EncoderFactoryPrx>(proxy);
auto encoder = factory->createEncoder();
//c++98
string adapter;
if((rand() % 2) == 0)
{
    adapter = "EncoderServer1.EncoderAdapter";
}
else
{
    adapter = "EncoderServer2.EncoderAdapter";
}
Ice::ObjectPrx proxy = communicator->stringToProxy("factory@" + adapter);
Ripper::MP3EncoderFactoryPrx factory = Ripper::MP3EncoderFactoryPrx::checkedCast(proxy);
Ripper::MP3EncoderPrx encoder = factory->createEncoder();

这种设计有一些缺点:

  • 客户端必须在每次添加或删除一个新的服务端时进行修改,因为服务端知道所有的适配器标识符。

  • 客户端无法智能分配负载;因为它是一个闲置的计算机,所以将任务分配给负载较重的计算机的可能性也是一样的。

我们在后面的章节中描述了更好的解决方案。

Copyright © github.com/weiofcn 2017 all right reserved,powered by GitbookLast modified time: 2017-12-21 13:30:06

results matching ""

    No results matching ""