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

WCF4 的路由管理器

starIconstarIconstarIconstarIconstarIcon

5.00/5 (37投票s)

2010 年 4 月 29 日

CPOL

18分钟阅读

viewsIcon

113540

downloadIcon

2485

本文介绍了一种用于管理通过 .Net 4 技术内置的路由服务发送消息的自定义路由管理器的设计、实现和用法。

 

目录

 

特点

  • 可管理的路由服务
  • 将物理终结点映射到逻辑终结点
  • 从存储库管理路由消息
  • 服务无中断
  • 动态添加更多出站终结点
  • 动态更改路由规则
  • .Net 4 技术

 

引言

最近发布的 Microsoft .Net 4 技术为元数据驱动的模型应用程序提供了基础。本文重点介绍 WCF 4 模型中用于逻辑连接(如路由服务)的独特组件之一。我将演示如何通过存储在运行时存储库中的元数据来扩展其在企业应用程序中的使用。有关元数据驱动应用程序的概念、策略和实现的更多详细信息,请参阅我之前的文章,如契约模型可管理的服务。   

我将重点介绍路由服务的主要功能,有关更多详细信息,请参阅以下链接[1]、[2]、[3]。

从架构风格上看,路由器代表了入站和出站终结点之间的逻辑连接。这是一个“短连接”的虚拟连接,位于由路由器表中存储的元数据描述的同一appDomain空间内。基于这些规则,消息通过路由器以特定的内置路由器模式进行交换。例如,WCF 路由服务允许使用以下具有契约选项的消息交换模式 (MEP):

  • 单向(SessionMode.Required, SessionMode.Allowed, TrasactionFlowOption.Allowed)
  • 双工(OneWay, SessionMode.Required, CallbackContract, TrasactionFlowOption.Allowed)
  • 请求-响应(SessionMode.Allowed, TrasactionFlowOption.Allowed)

除了标准的 MEP 外,路由服务还具有用于多播消息传递和错误处理的内置模式。

 

 

路由器过程非常直接,入站终结点接收到的未类型化消息会根据消息过滤器类型表示的优先级规则转发到一个或多个出站终结点。评估规则从最高优先级开始。路由器的复杂性取决于入站和出站终结点的数量、消息过滤器的类型、MEP 和优先级。

请注意,路由的入站和出站终结点之间的 MEP 模式必须相同,例如:单向消息只能路由到单向终结点,因此,要将请求/响应消息路由到单向终结点,必须调用服务中介来更改 MEP,然后再返回路由器。

路由服务使应用程序能够将进程物理解耦为面向业务的服务,然后使用声明式编程在中心化存储库中进行逻辑连接(紧密)。从架构角度来看,我们可以将路由服务视为私人和公共通信的中心集成点(集线器)。

下图显示了此架构

 

 

如上图所示,连接的中心点由路由表表示。路由表是路由服务的关键组件,所有连接都在其中声明式地描述。这些元数据在运行时被投影,用于消息在入站点和出站点之间流动。消息以完全透明的方式在终结点之间路由。上图显示了通过路由与 MSMQ 技术进行的服务集成。队列可以像其他服务一样插入到路由表的集成部分。

从元数据驱动模型的角度来看,路由表元数据是逻辑业务模型的一部分,集中在存储库中。下图显示了路由服务到复合应用程序的抽象。

 

 

 

连接的虚拟化允许复合应用程序与物理连接进行封装。这对于模型驱动架构来说是一个巨大的优势,在模型驱动架构内部,所有连接都可以使用众所周知的契约。请注意,私有通道(参见上图 - 逻辑连接)位于众所周知的终结点之间,例如路由服务和复合应用程序。

将源(消费者)与终结点及其由元数据驱动的逻辑连接解耦,将使我们的应用程序集成过程具有以下附加功能:   

  • 将物理终结点映射到逻辑终结点
  • 虚拟化连接
  • 中心化入口点
  • 使用备用终结点的错误处理
  • 服务版本控制
  • 消息版本控制
  • 服务聚合
  • 业务封装
  • 基于优先级的消息过滤路由
  • 元数据驱动模型
  • 协议桥接
  • 事务性消息路由

正如我之前提到的,在模型驱动架构中,路由表是存储在存储库中的元数据的一部分,作为逻辑中心化模型。该模型在设计时创建,然后物理上分散到目标。请注意,部署模型以供运行时投影需要回收主机进程。为了最小化这种中断,路由服务可以通过动态管理路由表来帮助隔离这种故障。

WCF 路由服务具有在运行时更新路由表的内置功能。路由服务的默认引导程序从配置文件(路由部分)加载表。我们需要一个自定义服务来在运行时更新路由表。这就是路由管理器组件的任务,请参见下图。

 

 

路由管理器是一个自定义 WCF 服务,负责从配置文件或存储库刷新路由服务中的表。第一个入站消息(主机进程打开后)将自动从存储库引导表。在运行时,当路由服务激活时,路由管理器必须接收一个查询消息以从存储库加载路由元数据,然后更新表。

基本上,路由管理器使我们的虚拟化复合应用程序能够在不回收主机进程的情况下管理此集成过程。此模型中有一个概念性的架构更改。众所周知,存储库会将其元数据推送(部署)到其运行时投影到主机环境(例如:IIS、WAS 等)。此运行时元数据存储在本地私有存储库中,例如文件系统,并用于引导我们的应用程序、服务。

这是一个标准的推送阶段,例如模型->存储库->部署。路由服务是引入拉取阶段概念的一个很好的选择,例如运行时->存储库,其中在设计时创建的模型可以在处理过程中以透明的方式进行更改。因此,我们也可以在设计时决定运行时元数据的使用(通过拉取模型)。

隔离用于引导投影仪和运行时更新的元数据使我们的存储库能够无中断地管理应用程序,例如,我们可以更改物理终结点、绑定、插入新服务版本、新契约等。当然,我们可以构建更复杂的调整系统,其中运行时元数据可以由分析器等创建和/或修改。在这种情况下,存储库和运行时之间有一个完整的控制回路。

最后,下图是可管理路由服务的一个示例。

 

 

如上图所示,可管理的路由服务代表了工作流服务、服务、队列和 Web 服务之间连接的中心虚拟化。运行时存储库代表了用于引导过程以及运行时更改的路由元数据。路由行为可以在共享数据库(存储库)中动态更改,然后由路由管理器与运行时模型同步。

路由服务还具有一个“隐藏”功能,如上图所示,即可伸缩性。通过路由服务将应用程序分解为小型面向业务的服务并进行组合;我们可以控制应用程序的可伸缩性。我们可以根据路由规则为逻辑终结点分配本地主机或集群地址。

从虚拟化的角度来看,下图显示了一个可管理的复合应用程序。

 

您可以看到,复合应用程序由逻辑终结点驱动,因此可以在逻辑模型中声明,而无需了解它们的物理位置。只有运行时存储库知道这些位置,并且可以根据要求轻松管理,例如:开发、暂存、QA、生产等。

本文重点介绍托管在 IIS/WAS 上的路由管理器。我假设您具有 WCF4 路由服务的一些工作经验或了解其功能。

好了,让我们开始了解可管理路由服务的概念和设计。

 

概念与设计

托管在 IIS/WAS 上的路由管理器的概念和设计基于 WCF4 路由服务的扩展,用于实现从存储库以松散耦合的方式下载元数据等附加功能。管道部分实现为服务(RoutingService 和 RoutingManager)的通用扩展,名为routingManager。将routingManager行为添加到routing行为(参见下面的代码片段)中,我们可以从repositoryEndpointName引导路由服务。

routingManager行为具有与routing行为相同的属性,并增加了用于声明存储库终结点的属性。如您所见,这是启动路由服务非常直接的配置。请注意,routingManager行为与routing行为是一对。

现在,在运行时更新路由行为的情况下,我们需要插入一个路由管理器服务及其服务行为routingManager。下面的代码片段显示了一个在同一应用程序中没有 .svc 文件的激活示例。

及其服务行为

请注意,repositoryEndpointName可以指向与进程启动时不同的存储库。

 

路由管理器契约

路由管理器契约允许在应用程序域外部与路由管理器服务通信。契约操作设计用于广播和点对点模式。操作可以指定给特定目标或未知目标(*),具体取决于机器和应用程序名称。下面的代码片段显示了IRoutingManager契约。

[ServiceContract(Namespace = "urn:rkiss/2010/09/ms/core/routing", 
  SessionMode = SessionMode.Allowed)]
public interface IRoutingManager
{
  [OperationContract(IsOneWay = true)]
  void Refresh(string machineName, string applicationName);

  [OperationContract]
  void Set(string machineName, string applicationName, RoutingMetadata metadata);

  [OperationContract]
  void Reset(string machineName, string applicationName);

  [OperationContract]
  string GetStatus(string machineName, string applicationName);
}

Refresh操作代表一个用于从存储库刷新路由表的查询事件。基于此事件,路由管理器将从存储库(由repositoryEndpointName寻址)获取“最新”路由元数据并更新路由表。

路由元数据契约

这是路由管理器和存储库之间的数据契约。我决定将其用作routing配置节的 XML 格式文本。此选择提供了集成和实现的简洁性以及未来的轻松迁移。下面的代码片段是路由节的一个示例。

以及存储库的数据契约

[DataContract(Namespace = "urn:rkiss/2010/09/ms/core/routing")]
public class RoutingMetadata
{
  [DataMember]
  public string Config { get; set; }

  [DataMember]
  public bool RouteOnHeadersOnly { get; set; }

  [DataMember]
  public bool SoapProcessingEnabled { get; set; }

  [DataMember]
  public string TableName { get; set; }

  [DataMember]
  public string Version { get; set; }
}

好的,概念和设计方面就这些了,除了有一件事是必须弄清楚的,特别是对于 IIS/WAS 上的服务托管。如我们所知[1],在System.ServiceModel.Routing命名空间中有一个RoutingExtension类,它有一个名为ApplyConfiguration的“万能”方法,用于更新路由服务的内部表。

我使用了以下“小技巧”来访问由自己的工厂托管的RoutingManager等其他服务中的这个RoutingExtension

第一个路由消息会将 RoutingExtension 的引用存储在 AppDomain 数据槽中,使用一个众所周知的名称,例如CurrentVirtualPath的值。

serviceHostBase.Opened += delegate(object sender, EventArgs e)
{
    ServiceHostBase host = sender as ServiceHostBase;
    RoutingExtension re = host.Extensions.Find<RoutingExtension>();
    if (configuration != null && re != null)
    {
        re.ApplyConfiguration(configuration);

        lock (AppDomain.CurrentDomain.FriendlyName)
        {
            AppDomain.CurrentDomain.SetData(this.RouterKey, re);
        }
    }
};

请注意,路由服务和路由管理器都托管在相同的虚拟路径下,因此路由管理器可以获取此数据槽值并将其强制转换为RoutingExtension。下面的代码片段显示了这部分内容。

private RoutingExtension GetRouter(RoutingManagerBehavior manager)
{
  // ...
      
  lock (AppDomain.CurrentDomain.FriendlyName)
  {
    return AppDomain.CurrentDomain.GetData(manager.RouterKey) as RoutingExtension;
  }         
}

好的,现在是时候展示路由管理器的用法了。

 

用法与测试

可以通过以下测试解决方案演示托管在 IIS/WAS 上的可管理路由器(路由服务 + 路由管理器)的功能和用法。该解决方案包括路由器以及客户端、服务和本地存储库的模拟器。

重点是路由器,如上图所示。如您所见,没有 .svc 文件等,只有配置文件。没错。设置和配置可管理路由器的所有任务都基于存储在web.config和存储库中的元数据(稍后讨论)。

Router 项目被创建为一个空的 Web 项目,位于 https:///Router/ 虚拟路径下,并添加了来自 RoutingManager 项目的程序集引用,并在 web.config 中声明了以下节。

让我们更详细地描述这些部分。

第一部分 - 激活

这些部分对于任何路由器都是必需的,例如激活路由服务和路由管理器服务,为routingManager提供行为扩展,以及声明用于存储库连接的客户端终结点。下图显示了这些部分。

注意,此部分还声明了我们路由器的相对地址。在上面的示例中,路由消息的入口点是~/Pilot.svc,访问路由管理器地址是~/PilotManager.svc

第二部分 - 服务终结点

在这些部分中,我们必须为路由服务和路由管理器这两个服务声明终结点。路由服务终结点具有在System.ServiceModel.Routing命名空间中定义的非类型化契约。在此测试解决方案中,我们使用了两个契约:一个用于通知(单向),另一个用于请求-回复消息交换。绑定使用 basicHttpBinding,但它可以是任何标准或自定义绑定,具体取决于要求。

路由管理器服务配置了简单的basicHttpBinding终结点,但在生产版本中,它应该使用自定义 UDP 通道来广播消息,以触发从群集中的存储库拉取元数据过程。

 

第三部分 - 将路由服务和管理器连接在一起

此部分会将路由服务附加到路由管理器,以便在运行时访问其RoutingExtension

第一个extBehavior部分是路由引导过程的配置。第二个部分是在运行时下载路由元数据的部分。

 好的,这就是创建可管理路由器的一切。

下图显示了解决方案的全貌。

 

 

如您所见,有用于集成复合应用程序的可管理路由器(托管在 IIS/WAS 中)。左侧是客户端模拟器,用于根据路由规则生成两个操作(通知和回显)到 Service1 和/或 Service2。客户端和服务是常规的 WCF 自托管应用程序,客户端可以直接或通过路由器与服务通信。

本地存储库

本地存储库代表元数据的存储,例如逻辑中心化应用程序模型、部署模型、运行时模型等。模型在设计时创建,并基于部署模型推送到(物理上分散到)目标,在那里它们在运行时进行投影。例如:RouterManager 程序集和 web.config 是从存储库部署模型到 IIS/WAS 目标进行投影的元数据。

在运行时,某些组件能够根据从存储库拉取的新元数据更新其行为。例如,此组件是可管理路由器(路由服务 + 路由管理器)。构建企业存储库和工具并非易事,有关 Microsoft 策略的更多详细信息,请参阅[6],以及有趣的示例[7]、[8]。

在本篇文章中,我包含了一个非常简单的本地存储库用于路由元数据,并带有自托管服务,以演示路由管理器服务的功能。路由元数据由system.serviceModel节描述。下图显示了远程路由元数据的配置根节。

 

您可以看到,system.ServiceModel节的内容与目标 web.config 相似。请注意,存储库仅包含这些节。它们与路由元数据相关。通过选择“路由表”选项卡,路由规则将以表单显示。

路由表中的任何更改都将在按下“完成”按钮时更新本地路由元数据,但运行时路由器必须通过按下“刷新”按钮来通知此更改。在此场景中,LocalRepository 将向RoutingManager发送一个查询消息以拉取新的路由元数据。您可以在“状态”选项卡中看到此操作。

路由规则

上图显示了路由表,它代表映射到配置元数据中routing节的路由规则。此测试解决方案具有预先构建的路由规则集,包含四条规则。让我们描述这些规则。请注意,我们对消息正文使用 XPath 过滤器,因此RouteOnHeadersOnly选项必须取消选中。否则,路由器将抛出异常。

路由器部署了两个入站终结点,分别是SimplexDatagramRequestReply,因此接收到的消息将根据以下规则进行路由:

请求/回复规则

首先,作为最高优先级(级别 3),消息会根据 xpath 正文表达式进行缓冲。

starts-with(/s11:Envelope/s11:Body/rk:Echo/rk:topic, 2)

如果 xpath 表达式为 true,则复制的消息将被路由到出站终结点 TE_Service1,否则复制的消息将被转发到 TE_Service2(参见过滤器 aa)。

SimplexDatagram 规则

这是到两个出站终结点 TE_Service1 和 TE_Service2 的多播路由(相同优先级 2)。如果消息无法传递到 TE_Service2,则使用备份列表中的备用终结点,例如 Test1(队列)。请注意,消息不会被缓冲,它直接(filterType = EndpointName)从endpointNotify终结点传递。

 

测试

测试路由器是一个非常简单的过程。启动客户端、Service1、Service2 和本地存储库程序,为 https:///Router 项目在 IIS/WAS 中创建虚拟目录,然后解决方案就可以进行测试了。以下是说明步骤。

  1. 在客户端窗体上,按“回显”按钮。您应该看到消息被路由到 Service2。
  2. 再次按“回显”按钮,消息被路由到 Service1。
  3. 再次按“回显”按钮,消息被路由到 Service2。
  4. 将路由器组合框更改为:https:///Router/Pilot.svc/Notify
  5. 按“事件”按钮,在两个服务中查看通知消息。

您可以随意使用本地存储库更改路由规则,以查看路由器如何处理传递的消息。

要测试备份规则,请创建一个事务性队列 Test1 并关闭 Service2 控制台程序,然后按照步骤 2 和 3 操作。您应该在 Service1 和队列中看到消息。

 

故障排除

可管理路由器有两种故障排除方法。第一种是内置的标准 WCF 跟踪日志,并由 Microsoft Service Trace Viewer 程序查看。Routerweb.config文件已为此诊断指定了此节,但已注释掉。

第二种故障排除路由器的方法是使用内置的自定义消息跟踪检查器,重点关注消息流。此检查器由路由管理器及其服务行为自动注入。我们可以使用来自Windows Sysinternals的 DebugView 工具程序来查看来自路由器的跟踪输出。

 

一些路由器技巧

1. 将物理出站连接集中到一个路由器中,可以使多个应用程序使用逻辑连接(别名地址)。下图显示了此模式。

我们不需要为每个应用程序路由器使用物理出站终结点,而是可以创建一个主路由器来虚拟化所有公共出站终结点。在这种情况下,我们只需要管理每个物理出站连接的主路由器。相同的策略可用于物理入站终结点。这种路由器层次结构的另一个优点是集中了预处理和后处理服务。

2. 正如我之前提到的,路由器允许将业务工作流分解为小型面向业务的服务,例如:管理器、工作者等。业务工作流的组合由路由表声明,该表代表某种将消息分派到正确服务的调度程序。我们应该使用 ws 绑定和自定义标头,以便仅根据标头通过路由器简化消息分派。

 

实现

根据概念和设计,实现有两个部分:路由管理器服务及其行为扩展。这两个模块都使用相同的自定义配置库,其中包含有用的静态方法,用于从 XML 格式化资源中存储的元数据中获取 CLR 类型。我在之前的文章(.Net 3)中使用了这个库,并为新的路由节扩展了它。

在下面的代码片段中,我将向您展示实现是多么的直接。

下面的代码片段演示了路由管理器服务的 Refresh 实现。我们需要从routingManager行为中获取一些可配置属性,并访问RouterExtension。一旦我们获得了它们,就会调用 RepositoryProxy.GetRouting 方法以从存储库获取路由元数据。

我们获得的配置节是 XML 格式的文本,就像从应用程序配置文件中获取的一样。现在,使用“万能”配置库,我们可以将文本资源反序列化为 CLR 对象,例如MessageFilterTable<IEnumerable<ServiceEndpoint>>

然后创建RoutingConfiguration实例并将其传递给router.ApplyConfiguration过程。其余的魔力由路由服务完成。

public void Refresh(string machineName, string applicationName)
{
  RoutingConfiguration rc = null;
  
  RoutingManagerBehavior manager = 
    OperationContext.Current.Host.Description.Behaviors.Find<RoutingManagerBehavior>();
  
  RoutingExtension router = this.GetRouter(machineName, applicationName, manager);

  try
  {
    if (router != null)
    {
      RoutingMetadata metadata = 
        RepositoryProxy.GetRouting(manager.RepositoryEndpointName, 
         Environment.MachineName, manager.RouterKey, manager.FilterTableName);
         
      string tn = metadata.TableName == null ? 
                  manager.FilterTableName : metadata.TableName;
        
      var ft = ServiceModelConfigHelper.CreateFilterTable(metadata.Config, tn);

      rc = new RoutingConfiguration(ft, metadata.RouteOnHeadersOnly);
      rc.SoapProcessingEnabled = metadata.SoapProcessingEnabled;
         
      router.ApplyConfiguration(rc);

      // insert a routing message inspector
      foreach (var filter in rc.FilterTable)
      {
        foreach (var se in filter.Value as IEnumerable<ServiceEndpoint>)
        {
          if (se.Behaviors.Find<TraceMessageEndpointBehavior>() == null)
            se.Behaviors.Add(new TraceMessageEndpointBehavior());
        }
      }
    }
  }
  catch (Exception ex)
  {
     RepositoryProxy.Event( ...);
  }
}

上述 Refresh 方法中的最后一个操作是注入 TraceMessageEndpointBehavior,用于在跟踪输出设备上对路由服务内的消息进行故障排除。

接下来的代码片段显示了 ConfigHelper 库的一些细节。

public static MessageFilterTable<IEnumerable<ServiceEndpoint>>CreateFilterTable(string config, string tableName)
{
  var model = ConfigHelper.DeserializeSection<ServiceModelSection>(config);
  
  if (model == null || model.Routing == null || model.Client == null)
    throw new Exception("Failed for validation ...");

  return CreateFilterTable(model, tableName);
}

 

下面的代码片段显示了一个通用方法,用于从 XML 格式的文本中反序列化特定类型的节。

public static T DeserializeSection<T>(string config) where T : class
{
  T cfgSection = Activator.CreateInstance<T>();
  byte[] buffer = 
    new ASCIIEncoding().GetBytes(config.TrimStart(new char[]{'\r','\n',' '}));
  XmlReaderSettings xmlReaderSettings = new XmlReaderSettings();
  xmlReaderSettings.ConformanceLevel = ConformanceLevel.Fragment;

  using (MemoryStream ms = new MemoryStream(buffer))
  {
    using (XmlReader reader = XmlReader.Create(ms, xmlReaderSettings))
    {
      try
      {
        Type cfgType = typeof(ConfigurationSection);
        
        MethodInfo mi = cfgType.GetMethod("DeserializeSection", 
        		BindingFlags.Instance | BindingFlags.NonPublic);
        		
        mi.Invoke(cfgSection, new object[] { reader });
      }
      catch (Exception ex)
      {
        throw new Exception("....");
      }
    }
  }
  return cfgSection;
}

注意,上述静态方法是一个非常强大且有用的方法,可以从 XML 格式的文本资源中获取任何类型的配置节,这使得我们能够使用存储在数据库中的元数据,而不是文件系统 - 应用程序配置文件。

 

 

结论

总之,本文描述了一个基于 WCF4 路由服务的可管理路由器。可管理路由器允许动态更改存储在存储库中的集中式逻辑模型中的路由规则。该路由器代表了将逻辑终结点映射到物理终结点的虚拟化组件,并且是模型驱动分布式架构中的一个基本组件。

 

参考文献

[1] http://msdn.microsoft.com/en-us/library/ee517421(v=VS.100).aspx

[2] http://blogs.msdn.com/routingrules/archive/2010/02/09/routing-service-features-dynamic-reconfiguration.aspx

[3] http://weblogs.thinktecture.com/cweyer/2009/05/whats-new-in-wcf4-routing-service---or-look-ma-just-one-service-to-talk-to.html

[4] http://dannycohen.info/2010/03/02/wcf-4-routing-service-multicast-sample/

[5] http://blogs.profitbase.com/tsenn/?p=23

[6] SQL Server Modeling CTP and Model-Driven Applications

[7] Model Driven Content Based Routing using SQL Server Modeling CTP – Part I

[8] Model Driven Content Based Routing using SQL Server Modeling CTP – Part II

[9] Intermediate Routing

© . All rights reserved.