65.9K
CodeProject 正在变化。 阅读更多。
Home

Windows Azure 开发深度剖析:处理配置

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.93/5 (9投票s)

2010年3月8日

CPOL

13分钟阅读

viewsIcon

80913

学习有效利用云应用程序中的配置,而无需锁定 Azure 平台

引言

在任何应用程序中,您都必须考虑配置。在 Windows 和 Web Forms 中,我们有 *.config 文件来帮助在应用程序启动前进行配置。它们是存储诸如 提供程序 配置、IOC 容器 配置、连接字符串、服务终结点等内容的好地方。坦白说——我们经常使用配置文件。

在本文中,我将讨论 Windows Azure 中可用的不同类型的配置,它们如何在您的应用程序中得到利用,以及配置项如何在不导致应用程序角色重新启动的情况下在运行时进行更改。

云配置中的问题

Windows Azure 应用程序中,配置可以与标准 .NET 应用程序完全相同地工作。如果您有一个 Web 角色,那么您就有一个 web.config。如果您有一个 工作器角色,您将获得一个 app.config。这允许您在角色启动时向其提供配置信息。

但是,对于您希望在应用程序部署并运行 **之后** 更改的配置值呢?在将 web.config 部署到云生产环境后,要对其进行修改肯定要困难得多。您真的想在其中包含新 web.config 文件的应用程序包的整个新版本都上传一遍吗?

或者,能否一次性更改所有正在运行的实例的配置方面,并且 **不** 需要停止它们运行?为什么配置更改需要重新启动,就像 web.configapp.config 文件那样?

在 Windows Azure 中,我们有了一种配置角色的新方法,它为我们的应用程序提供了灵活性和一致性。

服务配置和文件服务定义

在 Windows Azure 中,我们可以通过 服务配置 文件:ServiceConfiguration.cscfg 来获得上述配置的灵活性和一致性。

此文件包含有关您应用程序的信息,这些信息可以在启动时使用,也可以在运行时更改,而无需重新上传。这包括正在运行的实例数、证书信息以及特定的应用程序配置设置,例如连接字符串、端口号、REST 终结点等。

服务配置与服务定义 文件:ServiceDefinition.csdef 协同工作。

这两个文件都位于您的主云项目中。服务定义是“老板”。它在运行时无法更改。当您上传 Azure 应用程序时,服务配置是应用程序包的一部分,但服务定义是单独上传的。这是因为服务定义包含了有关应用程序应如何部署的所有说明,包括:每个角色的详细信息、它们的故障和升级域、要打开的 TCP 端口(用于工作器角色)、可用的本地存储、使用的 VM 大小、信任级别等等。上传后,服务定义将发送到 fabric controller,它将解析它并弄清楚如何处理您的云包。

我们将重点关注服务定义文件中您还会发现的另一项内容——有关配置项的信息。本质上,服务定义声明了服务配置文件中每个角色可用的配置值。它看起来有点像这样

<workerrole name="ImageSearch.Cloud.Overlord">
  <configurationsettings>
    <setting name="ImageSearchSettings" />
    <setting name="ImageSearchDBC" />
    <setting name="SearchMultiplier" />
    <setting name="MatchTolerance" />
  </configurationsettings>
</workerrole>

如果服务定义中未指定配置设置名称,则无法在服务配置文件中使用它来配置您的应用程序。将其视为对fabric controller 的指示:“我的应用程序可以使用这 4 个值进行配置”。此外,一旦上传了应用程序,您就无法添加或删除设置,因为服务定义文件不可编辑;您只能更改配置值。我稍后将向您展示如何操作。

在服务配置文件中,我们的条目将如下所示

<Role name="ImageSearch.Cloud.Overlord">
  <Instances count="1" />
  <ConfigurationSettings>
    <Setting name="ImageSearchSettings" value="UseDevelopmentStorage=true" />
    <Setting name="ImageSearchDBC" value="Data Source=..." />
    <Setting name="SearchMultiplier" value="3" />
    <Setting name="MatchTolerance" value="50" />
  </ConfigurationSettings>
</Role>

它与您可能熟悉的 <appSettings> 元素非常相似。您的云项目中的每个角色都有一个元素,而 <ConfigurationSettings> 子元素包含您应用程序的所有设置,作为“名称/值”对。

设置配置值

在服务配置和定义文件中直接设置值时,您将获得完整的 IntelliSense,这使得确定正确的值非常容易。也可以通过工具设置配置值。

image

在您的主云项目中(其中包含指向所有角色的链接),您可以调出角色的属性窗口。在“设置”选项卡下,您可以添加新的配置值。

image

添加新设置时,IDE 将在服务定义中插入相关的占位符,并在服务配置中插入值。这使得 Visual Studio IDE 方法成为一个省时器,并有助于确保您的定义和配置文件保持同步。在上图所示的屏幕截图中,您可以看到我们有两个配置项:第一个是云存储 连接字符串,第二个是数据库连接字符串。本质上,所有配置项都是字符串,但 IDE 在通过此连接字符串类型创建存储帐户的配置值时提供了一些快捷方式。单击省略号 […] 会弹出一个新的模态窗口,您可以在其中指定存储帐户的详细信息。

image

最终,这只会创建一个可供应用程序使用的另一个字符串配置值。

<Setting
  name="ImageSearchSettings"
  value="DefaultEndpointsProtocol=https;AccountName=ImageSearch;AccountKey=3ob...UY=="
/>

此连接字符串的结构使其对 API 有用,我们稍后会看到。

在 Azure 中运行时更改配置值

要更改已部署到 Azure 的应用程序的配置值,请单击项目中的 **配置** 按钮,这将带您进入一个带有文本框的屏幕,您可以在其中直接编辑所有值,或者可以选择上传另一个配置文件。保存后,新的配置将生效。Windows Azure 的默认行为是使用新配置重新启动您的实例。但这并不理想,我们将探讨处理此问题的不同方法。

image

代码中配置值的简单处理

在代码中利用配置项的最简单方法是使用 Cloud 工具自带的 API。它提供了一组有用的程序集,可以为您提供对应用程序想要执行的所有操作的强类型访问。在 Microsoft.WindowsAzure.ServiceRuntime 程序集(和命名空间)中,有一个名为 RoleEnvironment 的密封类,它提供了一个静态方法,用于在运行时访问配置值。

var tolerance = RoleEnvironment.GetConfigurationSettingValue("MatchTolerance”);

这将以 string 的形式返回指定的值。如果您请求一个不存在的配置设置,将抛出 RoleEnvironmentException ,并附带一个极其详细的错误消息:“error”。

image

代码中处理存储连接字符串

前面我们看到,您可以使用 IDE 创建特殊的连接字符串配置值。这些连接字符串可用于创建 CloudStorageAccount 实例,这是一个 Azure SDK API 中的特殊类,它封装了有关特定存储帐户的信息,并允许您轻松连接到存储帐户的各个方面(队列、Blob 和表)。

CloudStorageAccount 可以正常实例化,并要求您将帐户凭据信息传递给其构造函数。如果您要硬编码这些凭据,这是可以的,但当然这不是一个好主意。

或者(并且更推荐)您可以使用一些 static 构造函数和属性来构建您的存储帐户客户端。最简单的上手方法是使用 FromConfigurationSetting 静态方法,如下所示。

public class StorageAccountFactory
{
  public static CloudStorageAccount Create()
  {
    return CloudStorageAccount.FromConfigurationSetting("ImageSearchSettings");
  }
}

这将使用配置设置“ImageSearchSettings”中指定的连接字符串,这与我们之前使用 IDE 指定的连接字符串相同。这是自动加载 CloudStorageAccount 实例的最简单方法。但是……

使用存储 API 中配置方法的陷阱

利用配置值的 API 方法,例如 CloudStorageAccount.FromConfigurationSetting 方法,要求您提前告知 API 如何检索配置值。目前,如果您只是“按原样”使用 CloudStorageAccount.FromConfigurationSetting 方法,当您运行应用程序时,您将收到一个 InvalidOperationException ,错误消息为

SetConfigurationSettingPublisher 需要在 FromConfigurationSetting 使用之前调用。

这试图告诉您的是,您需要指示 CloudStorageAccount 使用标准的配置定义文件来加载配置设置。起初这似乎有点愚蠢……您的配置设置还能从哪里来?您不是刚刚花了半个小时阅读有关 Azure 配置文件了吗?

当在 Azure 中运行应用程序时,您的配置肯定会来自 Azure 服务配置文件。但是,Microsoft 的工具团队希望您创建的应用程序仍然可以轻松地切换回在私有基础结构上运行,而无需删除所有与 Azure 相关的集成点;不幸的是,服务配置文件是 Azure 独有的集成点。我们需要能够将其分解为抽象,并使我们的应用程序在配置设置源方面透明。

让我通过一个例子进一步说明。考虑以下场景:您有一个 Web 角色,它将项目放入队列,而一个工作器角色从中获取项目并对其进行处理。您的 Web 角色设置为使用 CloudStorageAccount.FromConfigurationSetting 从服务配置文件加载存储客户端。但是稍后,您决定将 Web 角色迁移到本地,而将其他部分留在 Azure 中运行。您将 Web 角色与云服务项目分离;它现在可以独立运行并在 IIS 上运行。但这也意味着您不再拥有服务配置文件;您又回到了普通的 web.config。理想情况下,您仍然应该能够使用相同的 static 方法从配置设置加载存储帐户,只是这些配置设置现在位于不同的位置。

错误引用的 SetConfigurationSettingsPublisher 方法是 CloudStorageAccount 类上的另一个 static 方法,它允许我们指定在哪里加载配置。根据 MSDN 文档

应调用此方法一次以设置环境。环境可以是 Windows Azure 运行时,在这种情况下,发布者是配置读取和更改事件的简单包装器。环境也可以是 .NET 环境,在这种情况下,开发人员可以挂接自定义配置读取器和更改通知。

如文档所述,您应该只设置一次配置发布者,因此这最好在您的角色的 OnStart 事件中进行。用于告知 CloudStorageAccount 使用配置定义的代码使用一个委托,该委托被存储并在每次请求配置时调用。

CloudStorageAccount.SetConfigurationSettingPublisher(
    (configName, configSetter) =>
        configSetter(RoleEnvironment.GetConfigurationSettingValue(configName))
    );

本质上,配置设置发布者只是一个 Action ——一个在尝试获取配置设置名称的值时被调用的 委托。在上面的代码中,我们告诉存储帐户客户端,每当它需要获取配置项(configName)时,就调用标准的 RoleEnvironment.GetConfigurationSettingValue 静态方法并传入该名称来查找并返回该值。

如何使我的应用程序利用此抽象

一个很好的问题!假设我们实际上想让我们的代码更灵活,能够从 Azure 中运行时的配置定义切换到本地服务器上纯 IIS 运行时的 web.config appSettings。我们希望我们的应用程序检测到它不再运行在 Azure 世界中,并且有一个属性可以做到这一点:RoleEnvironment.IsAvailable。我们可以使用它来定制我们的设置发布者。请考虑我创建的一个名为 StorageAccountFactory 的工厂类中的以下 static 方法。

public static Action<string, Func<string,bool>> GetConfigurationSettingPublisher()
{
    if (RoleEnvironment.IsAvailable)
      return (configName, configSetter) => 
	configSetter(RoleEnvironment.GetConfigurationSettingValue(configName));
    return (configName, configSetter) => 
	configSetter(ConfigurationManager.AppSettings[configName]);
}

然后您可以这样调用新方法。

CloudStorageAccount.SetConfigurationSettingPublisher(
    StorageAccountFactory.GetConfigurationSettingPublisher()
);

瞧!我们的应用程序现在可以感知它是否正在 Azure 中运行,并根据需要切换其设置提供程序,同时仍然提供使用 CloudStorageAccount API 从配置加载存储帐户的好处。

运行时处理配置更改

正如我之前提到的,您可以在应用程序已经在生产环境中正常运行时更改配置值。需要进行更改的原因可能有很多;也许您想调整性能设置或更改日志文件输出的格式。

最重要的是,您不希望应用程序在配置更改时总是停止和重新启动。Azure 角色的默认行为是在任何配置更改时重新启动,因此如果您想阻止此行为,请继续阅读!

您可以通过处理 RoleEnvironment 类上提供的以下 2 个 static 事件来检测角色环境的变化。

RoleEnvironment.Changing += RoleEnvironmentChanging;
RoleEnvironment.Changed += RoleEnvironmentChanged;

Changing 事件在应用配置更改之前发生,而 Changed 事件在应用更改之后发生。当您创建新的 Web 或工作器角色时,Changing 事件会与事件处理程序代码一起插入。

private void RoleEnvironmentChanging(object sender, RoleEnvironmentChangingEventArgs e)
{
    if (e.Changes.Any(change => change is RoleEnvironmentConfigurationSettingChange))
    {
        e.Cancel = true;
    }
}

此代码的默认行为是在任何配置更改时“取消”事件。取消事件有点误导;实际意思是角色将被回收,应用配置更改,然后角色将重新启动。因此,“e.Cancel”实际上意味着“e.Reboot”。

理所当然,这是我们可以进行更改的事件处理程序。是否需要重新启动角色取决于您。我个人喜欢创建一个 static 数组,其中包含我不想导致重新启动的配置项名称,如下所示。

private static readonly string[] ExemptConfigurationItems = 
  new[] { "MatchTolerance", "SearchMultiplier" };

然后在 RoleEnvironmentChanging 事件处理程序中,我们可以决定是否需要重新启动,如果不需要重新启动,则在 RoleEnvironmentChanged 事件中应用新的配置更改。

private void RoleEnvironmentChanging(object sender, RoleEnvironmentChangingEventArgs e)
{
    Func<RoleEnvironmentConfigurationSettingChange, bool> changeIsExempt =
       x => !ExemptConfigurationItems.Contains(x.ConfigurationSettingName);
    var environmentChanges = e.Changes
       .OfType<RoleEnvironmentConfigurationSettingChange>();
    e.Cancel = environmentChanges.Any(changeIsExempt);

    if (!e.Cancel)
    {
        var newMultiplier = RoleEnvironment
           .GetConfigurationSettingValue("SearchMultiplier");
    }
} 
private void <roleenvironmentconfigurationsettingchange>RoleEnvironmentChanged
	(object sender, RoleEnvironmentChangedEventArgs e)
{<roleenvironmentconfigurationsettingchange>
    var newMultiplier = RoleEnvironment
           .GetConfigurationSettingValue("SearchMultiplier");

}

RoleEnvironmentChangingEventArgs 提供了一个 Changes 属性,这是一个导致此事件触发的所有更改的列表。通常,这些将是角色运行实例数量的更改,或配置项目的更改。我们请求所有类型为 RoleEnvironmentConfigurationSettingChange 的更改,它仅代表设置更改。然后,我们将更改的项目与我们的 static 配置名称列表进行比较,如果任何更改不在列表中,我们将重新启动。如果我们不重新启动(这意味着所有更改都在我们的豁免列表中),我们将在 Changed 事件处理程序中应用这些新更改。

摘要

到此为止,关于配置的内容就讲完了。这里有几点总结了我们学到的内容。

  • 配置值可以在应用程序运行时更改。
  • 在这种情况下,默认行为是重启角色。
  • 配置只是 XML,但 Visual Studio IDE 提供了很好的设计时体验。
  • 连接字符串是一种特殊的配置字符串,用于存储 Windows Azure 存储帐户的详细信息。
  • 使用 API,我们可以抽象出我们的设置发布者,从而在 Azure 和非 Azure 应用程序中实现使用上的灵活性。
  • 可以检查运行时配置更改,并在必要时避免实例回收。

请记住,这在撰写本文时是有效的,并且 Azure 平台在不断发展,因此可能已经有更好的技术来满足您的需求。

附加链接

© . All rights reserved.