shuttle介绍

http://shuttle.github.io/shuttle-esb/architecture/
该网址的翻译

概念
本页提供的阳历代码并不代表着一个样例或者解决方案,但是,确实演示了一些概念在 Shuttle ESB中的应用。为了帮助整合你的第一个实施,你可以看下这一页 http://shuttle.github.io/shuttle-esb/getting-started/index.html

Shuttle ESB的基本组成部分有:
消息(Message)
队列(Queues)
服务总线(Service bus)
每个服务总线实例被关联并且为此运行着仅一个输入队列。这是收件箱。所有收件箱收到的消息都被关联的服务总线实例所处理。

消息
Shuttle 是基于消息的。消息是实现了特性消息接口的数据传输对象:
    public class ActivateMemberCommand
    {
        string MemberId { get; set; }
    }

    public class MemberActivatedEvent
    {
        string MemberId { get; set; }
    }

队列

消息是被 Shuttle ESB上回调的消息处理器( message handler)所处理的。当一个服务总线被启动,它就开始监听一个消息输入队列的消息。所以消息之中都会在相关的队列中被处理。收件箱配置是在应用程序配置文件中被指明的。采取的方法是,至少投递一次( an  at-least-once  delivery )。这和只投递一次( an  exactly-once  delivery )的区别是,边界情况可能导致一个消息被执行一次以上(这可能很少发生)。无论如何,由于其它机制( mechanism   )的边界情况可能导致一个消息在不可能到达节点的时候遗失(一个复制的消息对于节点来说比根本没有消息要容易处理得多)。
所有的队列都是没有破坏性的,这很重要,而且应该总是在实现的思路中被承认。所以,当一个消息被从队列中恢复,它应该既可以被确认也可以释放该消息返回队列。

服务总线

一个服务总线实例在每个连接服务总线应用程序中都是必须的。用来配置服务总线,需要联合使用代码,应用程序配置文件和自定义组件。
    public class ServiceBusHost : IHost, IDisposable
    {
        private static IServiceBus bus;

        public void Start()
        {
            bus = ServiceBus.Create().Start();
        }

        public void Dispose()
        {
            bus.Dispose();
        }
    }
一个服务总线实例在应用程序配启动的时候创建和开启,释放的时候退出。一个服务总线可以承载在任何类型的应用程序中,但是,大多数的电影场景是承载他们作为服务。虽然你可以写你自己的服务去承载你的服务总线,但是,那并不是必须的,因为你或许想要用一般服务宿主( http://shuttle.github.io/shuttle-esb/generic-host/index.html

消息类型

命令消息
一个命令消息用来告诉另一个系统或者组件要做什么。这说明主叫系统(calling system)知道被叫系统(called system)的行为。因此,这是一种高度的行为耦合。
一个命令消息总是属于一个终结点(endpoint)。发送一个命令永远不会导致消息进到超过一个消息队列。
虽然在某些情况下,我们应该最小化消息所需要的消息的类型,这些我们将在接下来的部分进行讨论。

开始一个过程
存在这样的一些情况,我们需要以某系事情为开始。让我们来用这个例子从客户端接收一个订单。在我们的应用程序中,我们应该发送一个 CreateOrderCommand  到我们的订单服务,这将开启相关的流程。
这是客户端的代码:
   bus.Send(new CreateOrderCommand("ClientName", "ProductXYZ"));
如果没有地方发送这个消息的话,调用将会失败。
现在我们可以公布一个事件,如: OrderReceivedEvent  并且,我们的订单服务可以订阅这个事件并开始一切。
 bus.Publish(new OrderOrderReceivedEvent("ClientName", "ProductXYZ"));
即使没有订阅者,这个请求也不会失败。
所以,其中的区别单纯是消息的目的。当我们可以使用事件(event)时,这应该是首选,但是,当某一动作在一个系统中是必须的话,那么一个命令(command)或许会更加合适。当然,即使使用一个命令来处理,依然可能会有其它系统对获知一个订单被接收了感兴趣,所以,订单系统应该在接下来发布一个事件。

更加底层的方法( RPC(Remote Procedure Call Protocol)—— 远程过程调用 协议
在某些情况下,一个事件无法传达一个特定方法的意图。比如,我们需要发送一个e-mail给一个管理者,只要一个订单被创建(或者订单的总金额超出限额)。e-mail服务负责发送e-mail,通过我们的smtp服务器。但是e-mail服务不能订阅 OrderReceivedEvent  ,因为e-mail系统需要另外一个系统的规则。
有鉴于此,e-mail系统负责发送邮件。任何一个需要发送邮件的系统将会需要决定,什么时候去做这件事。因此,订单系统将会发送一个命令到e-mail系统:
 bus.Send(new SendMailCommand
                 {
                     To = "[email protected]",
                     From = "[email protected]",
                     Subject = "Important Order Received",
                     Body = "Order Details"
                 });

事件消息
一个事件消息被用来通知任意订阅的组件有什么业务上重要的事情发生了。每个订阅了事件的组件将会获得一份事件消息的副本。一个事件如果没有订阅者存在,那么发布这个事件也不会有任何效果。

文档消息(document message)
一个文档消息单纯得被用于向某个终结点发送数据。相较于事件消息,它并不存在任何意图,并且接收者可以随意处置它。这并不代表着数据会被没有原因得发送到终结点。一个终结点可能需要一个文档从几个服务,或者数据是自动送到一些终结点的,因为它可能是必须的。

耦合

行为耦合(behavioural coupling)

行为耦合是用来指这样的一种耦合,一个系统知道另一个系统的行为。当一个命令被发送到一个系统,你期望它会被进行一种制定的处理。这表现出了一种高度的行为耦合。当一个事件消息被公布,并没有期待从订阅者获得什么预期的反馈。这是一种较低的行为耦合。
可以预见,如果预期某个事件将会有指定的结果,这将会再次增加行为耦合。

时间耦合(temporal coupling)

时间耦合泛指服务在被需要时的可用性。
服务A要求服务B可用,以使服务A可以执行某个方法,这里就存在一个高度的时间耦合。相反的,如果服务A可以继续,即使ServiceB是不可用的,那么他们就只有低的时间耦合。
一个同步的web服务请求是一个高度的时间耦合的情况。
现在你可能疑惑服务A怎么能够继续进行,在它需要服务B一些操作的情况下。答案是用队列异步通信。有些人或许认为一个web服务请求就可以实现异步,但是,这里有些区别。服务A可能在收到一个回应的要求之前就停止了,而这可能导致一个web服务请求失败。
伴随着消息,事物总是在同一时间向一个方向移动。服务A向服务B是一个操作,并且总是会结束。服务B向服务A是另一个活动,并且总是会结束。

模式

请求/响应(request/response)
关于请求/响应通信模式的背景,你可以看下这个链接http://en.wikipedia.org/wiki/Request-response
shuttle介绍_第1张图片
请求一个终结点,表现为一个确定方法,这里,你发送一个命令消息:
    bus.Send(new RequestMessage());
尽管这是一个非常简单的模式,他将导致非常紧密的行为耦合。这不一定是坏事,在很多情况下,这是被明确要求的。
典型的处理命令消息的消息处理器和一些业务逻辑打交道,并处理消息。但是,有时还是需要响应的。
响应(response)可以是一个命令消息,也可以是一个事件消息,你可以单纯的请求服务总线的回复(reply)方法:
bus.Send(new ResponseMessage(), c => c.Reply());
响应或许,当然可以,用事件教习解耦,但是这取决于实现者决定用哪种方法。这将不再是请求/响应,但也不是发布/订阅。请求/响应模式的优势是,它提供给请求提供了直接获得响应的能力,而发布的消息将会导致所有的订阅方收到一份消息的副本。

发布/订阅

关于发布/订阅消息模式的背景,你可以访问这个链接 http://en.wikipedia.org/wiki/Publish/subscribe
shuttle介绍_第2张图片
这种模式将会导致在发布者和订阅者之间没有行为耦合。事实上,可能对于一个特定的事件消息,可能没有任何一个订阅者,但是或许也没有一个这样的场景,需要处于某种业务需求发布一个事件,这意味着至少会有一个订阅者。要发布一个事件消息,你可以像下面这样:
   bus.Publish(new EventMessage());
每个订阅者收到它自己的那份消息拷贝来进行处理。
这从本质上区别了需要被发送给一个工作者(worker)处理的那些消息的分配。

消息分配

可以预见,一个中间点如果收到过多的工作就可能会它的处理过程可能会开始落后。这种情况下,它可能改变消息分配的工作者节点。
shuttle介绍_第3张图片
一个终结点将会自动分配消息收到可用消息的工作者(worker)。一个终结点可以被配置为知分配消息,不处理,通过吧inbox标签的distribute属性设置为true来实现。
由于消息分发哦被集成到收件箱中处理,相同的节点只需要根据需要在不同的机器上作为工作者被安装多次即可。你希望用来处理分配消息的终结点将会需要一个控制收件箱的配置,因为所有的Shuttle的消息需要被处理而不去在一个队列中等待,就像是收件箱背后隐藏着成千的消息。每个工作者在配置中这样被定义,终结点的控制收件箱的分配策略是必须的:
<configuration>
   <configSections>
      <section name="serviceBus" type="Shuttle.ESB.Core.ServiceBusSection, Shuttle.ESB.Core"/>
   </configSections>

   <serviceBus>
      <control 
          workQueueUri="msmq://./control-inbox-work" 
          errorQueueUri="msmq://./shuttle-error"/>
      <inbox 
          distribute="true"
          workQueueUri="msmq://./inbox-work" 
          errorQueueUri="msmq://./shuttle-error"/>
   </serviceBus>
</configuration>
任何接收消息的终结点可以被配置消息分发策略。
你接下来可以安装任意你需要的数量的工作者在任意你数量你希望的机器上,配置他们和一个分配者通信。物理的分配者连通相关的所有工作者,构成一个消息的逻辑的终结点。工作者配置如下:
<configuration>
   <configSections>
      <section name="serviceBus" type="Shuttle.ESB.Core.ServiceBusSection, Shuttle.ESB.Core"/>
   </configSections>

   <serviceBus>
      <worker
         distributorControlWorkQueueUri="msmq:///control-inbox=work" />
      <inbox
         workQueueUri="msmq://./workerN-inbox-work"
         errorQueueUri="msmq://./shuttle-error"
         threadCount="15">
      </inbox>
   </serviceBus>
</configuration>
一旦应用程序配置文件包含了worker标签,每个线程变为空闲都会向分配者发送一个消息,表名一个线程可用了。分配者将会发送消息向每个可用的线程。

消息分配异常(Message Distribution Exceptions)

一些队列技术不需要消息分配。用来替代工作者,另一个终结点可以消费同一个输入队列。这一方案需要中间人。由于中间人其中的管理队列,消息被消费者的每个正常运行的线程消费着。消费者被创建的地点是无所谓的,所以队列可以被各种各样的过程所消费。
中间人风格和msmq或者sql-based队列不一样,中间人的消息处理这是被承载着消费者线程的宿主所管理的。这里,过程A不知道消息正在被过程B所消费,导致一个过程偷了另一个过程的消息。

消息路由(message routing)

通常来说,每发送一个条消息,消息是一个命令。它并没有必要非是一个命令,并且你可以发送一个事件消息到一个特定的终结点,但是更多的往往不是,你将会发送一个命令。消息被发送,通过调用服务总线实例上加载的一个相关的方法:
        TransportMessage Send(object message);
        TransportMessage Send(object message, Action<TransportMessageConfigurator> configure);
仅没有RecipientInboxWorkQueueUri 设置的消息,将会被服务总线所路由。
如果你需要访问任何可用消息的元数据, TranssportMessage外壳将会被返回。
Shuttle ESB使用一个IMessageRoutProvider接口的实现来决定在那里发送消息。
  public interface IMessageRouteProvider
    {
        IEnumerable<string> GetRouteUris(object message);   
    }
消息路由提供者的使用是在构造服务总线时被指定的:
bus = ServiceBus
        .Create(c => c.MessageRouteProvider(new DefaultForwardingRouteProvider())
        .Start();
DefaultMessageRouteProvider使用应用程序配置文件来决定向哪里发送消息:
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
   <configSections>
      <section name="serviceBus" type="Shuttle.ESB.Core.ServiceBusSection, Shuttle.ESB.Core"/>
   </configSections>

   <serviceBus>
      <messageRoutes>
         <messageRoute uri="msmq://serverA/inbox">
            <add specification="StartsWith" value="Shuttle.Messages1" />
            <add specification="StartsWith" value="Shuttle.Messages2" />
         </messageRoute>
         <messageRoute uri="sql://serverB/inbox">
            <add specification="TypeList" value="DoSomethingCommand, Assembly" />
         </messageRoute>
         <messageRoute uri="msmq://serverC/inbox">
            <add specification="Regex" value=".+[Cc]ommand.+" />
         </messageRoute>
         <messageRoute uri="sql://serverD/inbox">
            <add specification="Assembly" value="TheAssemblyName" />
         </messageRoute>
      </messageRoutes>
   </serviceBus>
</configuration>
每个IMessageRouteProvider的实现可以决定路由,然而,它需要从消息中获得目的地。一个典型的场景,也是DefaultMessageRouteProvider的工作方式,是用类型全称来决定目的地。
请注意:每个消息类型可能仅仅被送到一个终结点(使用send)。


来自为知笔记(Wiz)


你可能感兴趣的:(shuttle介绍)