代码改变世界

【原】StreamInsight 浅入浅出(三)—— 适配器

2010-09-08 13:56  拖鞋不脱  阅读(2064)  评论(3编辑  收藏  举报

适配器,顾名思义,就是将一种格式的数据转换成另一种格式符合操作需求的数据。在 StreamInsight 程序中,适配器起着举足轻重的作用,它们掌管着系统的入口和出口,控制着事件流的运动。没有适配器, StreamInsight 系统只是由查询模板支起来的空骨架,是适配器让整个系统运动起来的。

适配器的分类

适配器可以以多种方式分类:

1.以适配器功能分类:

输入适配器:将外部的数据转换成 StreamInsight 服务器可以使用的格式的数据,提供流入的事件。

输出适配器:将经过 StreamInsight 服务器处理过的事件转换成外部设备需要的格式,提供输出的数据。

系统中输入适配器和输出适配器缺一不可,但一个系统可以有多个输入适配器和多个输出适配器。

2.以事件类型分类:

类型化适配器:类型化的适配器所生成(输入适配器)或者接收到(输出适配器)的事件负载始终是确定类型的。一般当你确定输入数据的类型时,使用此中适配器,因为它能提供更方便的事件生成机制。

非类型化适配器:与类型化适配器相对,生成或接收到的事件负载类型不确定。这种适配器更为灵活,可以应付各种在设计时无法预知的输入和输出的需求,但在应用时也相对复杂,需要预先配置事件的负载字段,并手动填充各个字段。

3.以事件模型分类:

如上文所述,StreamInsight 里包含三种事件模型:间隔、点和边缘。那么与之相对的,就有针对三种事件事件模型的三类适配器。

综上所述,适配器可以按照三种方式分别分成二、二、三种类别。那么细化的适配器的种类就是2*2*3=12种,这正是 StreamInsight 中适配器基类的数量:

image

基类的命名规范遵循模式[类型化][点 | 间隔 | 边缘][输入 | 输出],非类型化适配器不具有类型化的前缀。那么如果想生成某一类的适配器,则只要继承相对应的基类即可。这里面有一点问题的是,由于一个系统可能往往同时需要间隔、点和边缘三种事件模型的适配器,那么在创建相应适配器的时候,由于只能各自继承各自相应的基类,大量相同的代码只能重复的写在不同的适配器里,管理和维护起来比较麻烦。

适配器工厂

适配器工厂作为适配器到服务器运行时之间的网关,提供了资源共享、适配器生成、提供配置信息和 CTI 事件生成等一系列工程。可以认为它是枢纽,配置各个适配器,并指挥各个事件通过相应的适配器,同时还利用对 CTI 事件的控制,影响所有事件流。

适配器工厂同样可以按照输入、输出或者类型化、非类型化分类,相应的,有四个接口:

image

一个典型的适配器工厂的实现代码是:

public sealed class TextFileReaderFactory : IInputAdapterFactory<TextFileReaderConfig>,IDeclareAdvanceTimeProperties<TextFileReaderConfig>
{
    public InputAdapterBase Create(TextFileReaderConfig configInfo, EventShape eventShape, CepEventType cepEventType)
    {
        InputAdapterBase adapter = default(InputAdapterBase);

        if (eventShape == EventShape.Point)
        {
            adapter = new TextFilePointInput(configInfo, cepEventType);
        }
        else if (eventShape == EventShape.Interval)
        {
            adapter = new TextFileIntervalInput(configInfo, cepEventType);
        }
        else if (eventShape == EventShape.Edge)
        {
            adapter = new TextFileEdgeInput(configInfo, cepEventType);
        }
        else
        {
            throw new ArgumentException(
                string.Format(
                    CultureInfo.InvariantCulture,
                    "TextFileReaderFactory cannot instantiate adapter with event shape {0}",
                    eventShape.ToString()));
        }

        return adapter;
    }

    public void Dispose()
    {
    }


    public AdapterAdvanceTimeSettings DeclareAdvanceTimeProperties(TextFileReaderConfig configInfo, EventShape eventShape, CepEventType cepEventType)
    {

        var atgs = new AdvanceTimeGenerationSettings(configInfo.CtiFrequency, TimeSpan.FromTicks(-1), true);
        return new AdapterAdvanceTimeSettings(atgs, AdvanceTimePolicy.Adjust);
    }
}

其中 TextFileReaderConfig 是适配器的配置信息类型,每个通过此适配器工厂生成的适配器都会被该类型的实例配置。而且由于配置信息需要使用 DataContractSerialization 进行序列化,所以配置信息类型的各字段必须是基本类型或者通过 DataContract 特性标识。

工厂中的两个方法 Create 和 Dispose 是 IInputAdapterFactory 接口需要的,也是所有其他三个适配器工厂接口所要求的。在查询启动时,会调用 Create 方法,针对不同的事件模型生成相对应的适配器。而在查询关闭时,会调用适配器工厂的 Dispose 方法。

此外的 DeclareAdvanceTimeProperties 方法则由 IDeclareAdvanceTimeProperties 接口申明。通过返回一个 AdvanceTimeSettings 类型的对象,控制 CTI 事件的产生频率和时间戳延迟策略。具体可以查看 AdvanceTimeSettings 类的说明 http://msdn.microsoft.com/en-us/library/ff518491.aspx

适配器的状态机

这无疑是适配器中最重要的一部分,只有理解了适配器和服务器之间的状态转化,才能明白适配器的工作原理:

image image

这里中文版可以理解各个状态以及转换的意义,而英文版可以直接和方法名以及状态名对应上。具体的解释可以参见 http://technet.microsoft.com/en-us/library/ee378877.aspx 下面的说明。这里依然只是做一些补充:

  1. 无论对于何种类型的适配器,上图,也就是状态机是一致的。
  2. 需要由适配器实现的方法只有 Start 和 Resume,另外还有一个图中没有显示的 Dispose 方法。一般来说,为了避免阻塞 I/O 在 Start 和 Resume 中会以单独线程执行相应的操作,而且这两个方法往往是同样的实现。
  3. Enqueue、Dequeue、Ready 和 Stopped 方法由适配器调用,但实际是由服务器提供的,用于提示服务器适配器的状态改变。而 Start 和 Resume 方法由适配器实现,但不会在适配器内部被调用。
  4. 适配器调用 Ready 方法后就会由服务器调用 Resume 方法。
  5. 要注意在某些状态下,调用不想关的方法,比如在“Stopped”状态下调用 Ready 方法,会触发异常。
  6. 由于可以由服务器异步发起停止查询的命令,从而令适配器处于 Stopping 的状态下。所以需要在适配器内部检查当前的状态,也就是在 Start 和 Resume 中检查当前状态是否为 Stopping 。