.NET开源的服务总线:Shuttle(飞梭)

jopen 12年前

Shuttle(飞梭)服务总线是一个免费的.NET开源软件项目,它为开发面向消息的事件驱动架构(EDA[1])系统提供了一种新方法。尽管它仍处于起步阶段,不过它已被应用于生产系统之中。

相关要点如下:

  • 用C#(基于.NET 3.5)开发而成
  • 核心功能不依赖于任何第三方产品或项目
  • 既支持命令消息,又支持事件消息(Pub/Sub)
  • 具有集成的消息分发功能
  • 包含一个命令行管理程序,以便轻松应对各种操作要求
  • 广泛使用接口,以便替换或扩展功能
  • 通过自动重试提供容错能力

为何要使用服务总线?

尽管在使用服务总线(service bus)时需要做一些思维模式的转变,不过这么做却是大有裨益的。通过在你的系统中设计一条服务总线,让其在特定终结点(endpoint)上执行非常明确的功能,这样你就可以专心设计在隔离环境下运行的那一小部分软件。可以对此类终结点进行独立地版本控制及维护,而前提是你对此类终结点所做的解耦工作已达到所需的程度。

基本上是说,你最终会在不同组件之间发送消息,并对这些消息进行异步处理。由于这会导致出现“即发即弃(fire-and-forget)”的情况,因此需要认真考虑系统的工作方式。而由此带来的用户体验可能完全不同于传统实现,即那种立即处理对用户所请求的全部操作的方式。

比如就是简单地发送一封电子邮件。你可以先设置好某个终结点去处理与之有关的命令消息类型(command message type)。然后,当你需要发送电子邮件时,你就可以使用如下代码:

 Bus.Send(new SendEMailCommand                 {                       From = "someone@from.com",                       To = "someone@to.com",                       Subject = "testing e-mail",                       Body = "Hello"                 });

你可能想知道,这么做与你自己直接发送此邮件的到底有何不同之处:

  • 这条消息发送调用是即时的,而我们不用等着此调用执行完成
  • 由于请求会被排入队列,因此就避免了瓶颈
  • 要是此邮件发送失败,就会自动重发此邮件
  • 只要得到的数据正确无误,就可以保证此邮件最终会被发出去;或者,至少在发生问题时可以采取手动操作进行处理

因此,客户端代码不用关心该终结点到底是如何发送数据的。而终结点可能会用简单邮件传输协议(SMTP)发送数据,甚至还可能会用某种自定义的网络服务(web-service)发送数据。

Shuttle是如何发挥其魔力的?

Shuttle服务总线依赖于两件事:

  • 消息(Message),及
  • 队列(Queue)

队列基础设施可以是任何实现。通常情况下,你会想使用某种真正的队列技术,例如微软消息队列(MSMQ)。目前Shuttle提供直接现成支持的队列有,微软消息队列(MSMQ)及Sql Server基于表的队列(table-based queues)。如果你想使用任何其他实现,要解决的问题就是实现有关接口,因此你会干得不错。Shuttle使用统一资源标识符(URI)的结构去表示队列,例如:

  • msmq://{machine}/{queue}
  • sql://{connection-name}/{table}

为了实现你自己的队列,你只需简单挑选一种方案,并在你的队列实现中解析这种结构。

为了组织你的终结点,Shuttle使用了通用服务主机去简化部署。让一个新的终结点生效,就是解决该终结点所用队列的配置问题,然后启动你的服务即可,正如以下配置文件及代码片段所示:

<?xml version="1.0"?> <configuration>   <configSections>     <section       name="serviceBus"        type="Shuttle.ESB.Core.ServiceBusSection, Shuttle.ESB.Core"/>   </configSections>   <serviceBus>     <inbox       workQueueUri="msmq://./inbox-work"        journalQueueUri="msmq://./inbox-journal"        errorQueueUri="msmq://./shuttle-error"        />   </serviceBus> </configuration>     public class ServiceBusHost : IHost, IDisposable     {          private IServiceBus bus;     public void Dispose()   {       bus.Dispose();   }     public void Start()   {       bus = ServiceBus               .Default()                .Start();   }      }

应该注意的是,通用主机既能以控制台应用程序的方式运行,又能作为服务去安装。这样就让调试终结点变得特别容易,因为当你在Visual Studio中调试时,你可以将通用主机指定为启动应用程序。

一旦通用主机从收件箱队列中获取到一条特定的消息,通用主机就会尝试找到处理程序,并将消息传送给此处理程序以供处理。至于那些找不到处理程序的消息,或被移至错误队列,或被丢弃,采取何种处理方式就要根据配置文件中所指定的信息而定了。

命令消息与事件消息

由于命令(command)是一个要求执行特定功能的明确请求,因此命令会被发送至单独的终结点。这意味着,为了发送消息,你需要知道某个终结点实现了特定的行为。因此,命令导致了更高程度的行为耦合。而如果在发送消息时,那个接收消息的终结点是可配置的,那么便可随时更改终结点。

譬如,你可能有如下命令:

  • SendEMailCommand
  • ConvertDocumentCommand
  • DeleteFileCommand
  • CancelOrderCommand

刚好相反,事件(event)可能没有或有多个订阅者(subscriber)。通常情况下,事件应该被明确定义,因为从业务角度看必需这么做。因此,按理说,除非是那种根据未来需求定义的事件,否则事件至少会有一个订阅者。一旦事件被发布出来,每个订阅者都会收到一份事件消息副本。

这不同于消息分发(message distribution),因为一条分布式消息只会被发送至一个工作者的收件箱队列。

譬如,你可能有如下事件:

  • EMailSentEvent
  • DocumentConvertedEvent
  • FileDeletedEvent
  • OrderCancelledEvent

为了在消息中添加任何必需的即席数据(ad-hoc[2] data),Shuttle服务总线允许你为消息指定消息头。此外,还可以使用相关ID(correlation ID)去对有关消息进行分组。

可伸缩性(Scalability)

Shuttle使你获得了不少的可伸缩性(scalability[3]),由于消息被排入队列,因此也就不会出现刻不容缓的紧迫瓶颈。即使有些终结点可能会被配置成多线程的,但是这并不意味着某个特定的终结点不会由于接收过量消息而导致性能下降。每个终结点都要有将消息分发给其他终结点的内置能力,一旦任何其他终结点通知分配器(distributor)它有可执行工作的空闲线程,就可以运用此能力分发消息。而要做到这一点,只需配置一下就好了。

模块(Modules)

Shuttle使用了一种可观测的管道(pipeline)结构。各种事件以特定顺序被注册到管道中,而观察者(observer)也可以被注册到管道中去响应各自的事件。

为了保持可扩展性(extensibility[4]),你可以把自己的模块实现注册到Shuttle之中。通过添加响应各自管道事件的观察者,这些模块通常会与特定的管道相连。而且为了随需应变,你甚至可以把自定义事件添加到管道中。

对于依赖注入及日志记录的支持怎么样?

由于Shuttle为了解耦而如此广泛地使用了接口,因此你就可以根据个人喜好,自由接入任何依赖注入(DI)或日志记录的实现。默认实现不依赖于任何第三方组件,不过目前还有作为依赖注入实现的Castle Windsor及作为日志记录实现的Log4Net可供选用。

生产环境下的Shuttle案例

Shuttle在南非某大型短期保险公司的实施已大获成功,取代了陈旧的文档索引系统。

客户通过电子邮件发送与索赔有关的文档,而邮件会被Lotus Domino电子邮件系统接收到。然后,FileNet Email Manager(电子邮件管理器)应用程序会将这些电子邮件提取出来,并放置到IBM FileNet Content Engine(内容引擎)中。内容引擎经过配置,用以响应任何电子邮件分类为已提交的新文档。内容引擎被设计用于处理这些送达邮件,并写出一份包含相关数据的XML文件。

从那里开始,Shuttle Content Engine(内容引擎)终结点会拾取这些XML文件,然后发布事件以表明新内容已被放置到了内容引擎中。由于此终结点的结构并没有具体到索引过程,因此就有可能重用此终结点,用于发布任何新内容。而且应由此内容引擎负责简单写出相关的XML文档。

Shuttle Indexing(索引)终结点订阅那些新的内容消息,而且消息一送达,终结点就开始跟踪与特定邮件一起送达的所有文档,因为每封邮件都有唯一标示符,而且此标示符已被附加到每份文档的内容引擎元数据中。一旦搜集到与一封邮件相关的所有文档,命令消息就会被发送至Shuttle Document Conversion(文档转换)终结点,用以将HTML格式邮件正文及全部JPG格式文件转换为TIFF格式文档。

文档转换终结点对于索引过程一无所知,而仅仅执行各种文档转换。一旦某个文档转换完成,就会发布事件以通知某个文档转换已成功或失败。任何系统需要文档转换的系统都会订阅这些事件消息。为了既能建立针对于特定系统的消息,也能建立不针对于特定系统的消息,请求转换的系统可以在转换请求命令中使用相关 ID(correlation ID),和/或将名/值对头添加到传出的转换请求命令消息中。这些头信息始终会被附加到由Shuttle所发送的任何相关消息中。

一旦完成了所有需要的文档转换,就会给Shuttle OvaFlo终结点发送一条命令消息,用以在IBM FileNet Process Engine(流程引擎)中创建索引工作流实例。OvaFlo是由Ovations Group公司开发的一款元工作流(meta-workflow)框架产品。

为了执行实际的索引,用户会访问基于网络的索引应用程序。此应用程序会从OvaFlo终结点中得到下一个可用索引工作流实例,并显示相关文档的分类。每份文档会被链接到一个具体的索赔、及输入的任何其他的相关索引数据。此外,也可以用于处理系统用户所提出的将某些文档转换为TIFF格式的请求。当把各种分门别类的文档放置到一个文件中时,此功能就特别有用。一旦用户觉得对数据很满意,就可以提交该任务以示完成。这一步是通过发出一条命令消息后异步处理的,以便让用户可以立即继续处理下一索引任务。

在某些情况下,会发现来自web前端的工作却在后台系统工作中排队。譬如文档转换就是这种情况,其中在从前端发起请求之前,来自送达邮件的所有需要的转换都在被处理中。我们通过通用主机,使用相同的编译程序集 ,就可以简单安装一个单独的Shuttle Document Conversion(文档转换)终结点,不过我们要修改此终结点的配置,以便使用其自身队列。然后前端将转换请求发送给这个高优先级终结点,接着那些转换会得到及时地处理。而所有的后台转换仍会被发送至原有的终结点。

因此,在这个例子中,Shuttle被用于将不同的系统粘合到一起。由于该系统具有所需的内置容错能力,因此它比前一系统要稳定得多。而且由于没有积压工作被创建出来,因此系统性能非常出色。

结论

Shuttle为你在实现企业服务总线(ESB)[5]时提供了另一种自由选择。这个项目托管在CodePlex上:

优点:

  • 新项目
  • 高度可扩展
  • 免费开源软件
  • 命令行管理程序

缺点:

  • 新项目
  • 必须手动处理进程状态数据

随着时间的推移和社区的支持,出现了各种不同的实现可用于扩展,其中包括更多的队列、依赖注入(DI)、及其他选择。

来自:http://www.infoq.com/cn/articles/Shuttle-Service-Bus