Cloud Design Pattern - Pipes and Filters Pattern(管道及过滤器模式)

1.前言

上一篇我们讨论了云计算设计模式之物化视图模式,介绍了如何构建视图来展示业务数据当前的状态,尤其是在事件溯源模式下,如何提升查询性能。这一篇,我们讨论下多任务处理的应用中,如何提升性能,复用性及扩展性。

2.概念

了解ASP.NET MVC中的Filter的读者都知道,Filter是一种过滤器,比如做请求的认证,参数的校验等等,上一个filter的逻辑执行完之后,下一个filter接着针对请求的数据进行处理,这整个的流程就像一个管道一样。我们在设计多任务处理系统的时候,很多时候设计成多个模块,而且不同的模块往往是独立开发的,而且往往一个子模块负责的任务很多,这就造成了复用性低,而且难以重构,因为模块内部耦合度太高了.

我们能否借鉴ASP.NET MVC 的设计思想,在处理请求的时候也建立一个管道,然后逐个进行处理,然后汇总结果呢?我们希望任务处理如下图所示:

每一个管道负责原来大模块的任务,管道里的各个任务都是可以独立进行重构或者重用的,而且增加任务也更加简单了,这样的模式就是今天我们需要讨论的管道和过滤器模式。这种模式下,一个管道的处理速度取决于速度最慢的那个,那这时候如何提升管道的性能呢?针对每个任务做负载均衡就能够显著提升性能了.

在管道中传输的都是stream形式的数据,第一个filter完成任务之后,直接将结果传给下一个filter,这种形式下,如果某个任务失败,可以retry或者直接用另一个instance来完成任务.

这种模式和前面讨论到的事物修正模式比较相像,但是复杂度比较高,所以,关于使用这种模式需要考虑的事情,官方给出的说法如下:

Complexity. The increased flexibility that this pattern provides can also introduce complexity, especially if the filters in a pipeline are distributed across different servers.

Reliability. Use an infrastructure that ensures data flowing between filters in a pipeline will not be lost.

Idempotency(幂等性). If a filter in a pipeline fails after receiving a message and the work is rescheduled to another instance of the filter, part of the work may have already been completed. If this work updates some aspect of the global state (such as information stored in a database), the same update could be repeated. A similar issue might arise if a filter fails after posting its results to the next filter in the pipeline, but before indicating that it has completed its work successfully. In these cases, the same work could be repeated by another instance of the filter, causing the same results to be posted twice. This could result in subsequent filters in the pipeline processing the same data twice. Therefore filters in a pipeline should be designed to be idempotent. For more information see Idempotency Patterns on Jonathan Oliver’s blog.

Repeated messages. If a filter in a pipeline fails after posting a message to the next stage of the pipeline, another instance of the filter may be run (as described by the idempotency consideration above), and it will post a copy of the same message to the pipeline. This could cause two instances of the same message to be passed to the next filter. To avoid this, the pipeline should detect and eliminate duplicate messages.

Context and state. In a pipeline, each filter essentially runs in isolation and should not make any assumptions about how it was invoked. This means that each filter must be provided with sufficient context with which it can perform its work. This context may comprise a considerable amount of state information.

关于何时使用这种模式,官方说法如下:

1)The processing required by an application can easily be decomposed into a set of discrete, independent steps.

2)The processing steps performed by an application have different scalability requirements.

3)Flexibility is required to allow reordering of the processing steps performed by an application, or the capability to add and remove steps.

4)The system can benefit from distributing the processing for steps across different servers.

5)A reliable solution is required that minimizes the effects of failure in a step while data is being processed.

下面的情形可能不适合使用该模式:

1)The processing steps performed by an application are not independent, or they must be performed together as part of the same transaction.

2)The amount of context or state information required by a step makes this approach inefficient. It may be possible to persist state information to a database instead, but do not use this strategy if the additional load on the database causes excessive contention.

3.案例

在Windows Azure中,我们可以使用Azure Service Bus来实现消息队列的机制.

在Azure Service Bus中,ServiceBusPipeFilter类很好地展示了如何实现消息的读取,消息的处理等等.

public class ServiceBusPipeFilter
{
  ...
  private readonly string inQueuePath;
  private readonly string outQueuePath;
  ...
  private QueueClient inQueue;
  private QueueClient outQueue;
  ...

  public ServiceBusPipeFilter(..., string inQueuePath, string outQueuePath = null)
  {
     ...
     this.inQueuePath = inQueuePath;
     this.outQueuePath = outQueuePath;
  }

  public void Start()
  {
    ...
    // Create the outbound filter queue if it does not exist.
    ...
    this.outQueue = QueueClient.CreateFromConnectionString(...);
    
    ...
    // Create the inbound and outbound queue clients.
    this.inQueue = QueueClient.CreateFromConnectionString(...);
  }

  public void OnPipeFilterMessageAsync(
    Func<BrokeredMessage, Task<BrokeredMessage>> asyncFilterTask, ...) 
  {
    ...

    this.inQueue.OnMessageAsync(
      async (msg) =>
    {
      ...
      // Process the filter and send the output to the 
      // next queue in the pipeline.
      var outMessage = await asyncFilterTask(msg);

      // Send the message from the filter processor 
      // to the next queue in the pipeline.
      if (outQueue != null)
      {
        await outQueue.SendAsync(outMessage);
      }

      // Note: There is a chance that the same message could be sent twice 
      // or that a message may be processed by an upstream or downstream 
      // filter at the same time.
      // This would happen in a situation where processing of a message was
      // completed, it was sent to the next pipe/queue, and then failed 
      // to complete when using the PeekLock method.
      // Idempotent message processing and concurrency should be considered 
      // in a real-world implementation.
    },
    options);
  }

  public async Task Close(TimeSpan timespan)
  {
    // Pause the processing threads.
    this.pauseProcessingEvent.Reset();

    // There is no clean approach for waiting for the threads to complete
    // the processing. This example simply stops any new processing, waits
    // for the existing thread to complete, then closes the message pump 
    // and finally returns.
    Thread.Sleep(timespan);

    this.inQueue.Close();
    ...
  }

  ...
}
在Windows Azure Worker Role中,可以调用这个类来完成操作,下面的代码展示了调用方式。

public class PipeFilterARoleEntry : RoleEntryPoint
{
  ...
  private ServiceBusPipeFilter pipeFilterA;

  public override bool OnStart()
  {
    ...
    this.pipeFilterA = new ServiceBusPipeFilter(
      ...,
      Constants.QueueAPath,
      Constants.QueueBPath);

    this.pipeFilterA.Start();
    ...
  }

  public override void Run()
  {
    this.pipeFilterA.OnPipeFilterMessageAsync(async (msg) =>
    {
      // Clone the message and update it.
      // Properties set by the broker (Deliver count, enqueue time, ...) 
      // are not cloned and must be copied over if required.
      var newMsg = msg.Clone();
      
      await Task.Delay(500); // DOING WORK

      Trace.TraceInformation("Filter A processed message:{0} at {1}", 
        msg.MessageId, DateTime.UtcNow);

      newMsg.Properties.Add(Constants.FilterAMessageKey, "Complete");

      return newMsg;
    });

    ...
  }

  ...
}
在Azure消息队列中,两个类比较重要,一个是 InitialSenderRoleEntry,负责初始化消息队列的Message,另一个是 FinalReceiveRoleEntry,负责终止消息队列操作。

public class FinalReceiverRoleEntry : RoleEntryPoint
{
  ...
  // Final queue/pipe in the pipeline from which to process data.
  private ServiceBusPipeFilter queueFinal;

  public override bool OnStart()
  {
    ...
    // Set up the queue.
    this.queueFinal = new ServiceBusPipeFilter(...,Constants.QueueFinalPath);
    this.queueFinal.Start();
    ...
  }

  public override void Run()
  {
    this.queueFinal.OnPipeFilterMessageAsync(
      async (msg) =>
      {
        await Task.Delay(500); // DOING WORK

        // The pipeline message was received.
        Trace.TraceInformation(
          "Pipeline Message Complete - FilterA:{0} FilterB:{1}",
          msg.Properties[Constants.FilterAMessageKey],
          msg.Properties[Constants.FilterBMessageKey]);

        return null;
      });
    ...
  }

  ...
}
4.相关阅读

The following patterns and guidance may also be relevant when implementing this pattern:

  • Competing Consumers Pattern. A pipeline can contain multiple instances of one or more filters. This approach is useful for running parallel instances of slow filters, enabling the system to spread the load and improve throughput. Each instance of a filter will compete for input with the other instances; two instances of a filter should not be able to process the same data. The Competing Consumers pattern provides more information on this approach.
  • Compute Resource Consolidation Pattern. It may be possible to group filters that should scale together into the same process. The Compute Resource Consolidation pattern provides more information about the benefits and tradeoffs of this strategy.
  • Compensating Transaction Pattern. A filter can be implemented as an operation that can be reversed, or that has a compensating operation that restores the state to a previous version in the event of a failure. The Compensating Transaction pattern explains how this type of operation may be implemented in order to maintain or achieve eventual consistency.





  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值