代码改变世界

【原】StreamInsight 浅入浅出(二)—— 流与事件

2010-09-06 14:52  拖鞋不脱  阅读(2777)  评论(0编辑  收藏  举报

传统数据库的基本单位是数据行,而在 StreamInsight 或者说 CEP 架构中,基本数据单位是事件(Event)。在传统数据库中,数据行集合成为数据表,而在 StreamInsight 中,相对应的集合就是流(Stream),所有的查询都是针对流的。其实流中的每个基本数据其实并不是一个完整的事件,只是包含了构成一个事件的基础信息,需要经过打包才能成为一个标准的事件。

在 StreamInsight 中,最基本的流是位于两端的输入、输出流,此外,还会有经过各种计算与整理而形成的中间流。

一个最简单的流的声明如下:

var inputStream = CepStream<MyDataType>.Create("input");

其中 MyDataType 由开发者自己定义。而上面这条语句就声明了一个以 MyDataType 为基本类型,名称为"Input"的流。这里看起来很像是创建了一个以 MyDataType 类型对象为元素的集合,类似 List<MyDataType>。但其实它更像一种占位符,一种模板,它申明了这个流中的每个元素的类型,但在此刻,并没有任何元素生成,也没有给集合开辟空间。它只是抽象的存在于整个流的转化过程中,直到整个查询启动时,流才开始形成。

声明一个流之后,可以基于该流进行计算或整理,从而生成以其他类型为基本类型的流。例如:

var newStream = from stream in inputStream
                select new {CountString = stream.Count.ToString()};

假设 MyDataType 类型包含一个名为 Count 的属性,则 newStream 生成一个基本类型包含名为 CountString 属性的新的流。这里使用的是 Linq 语法,而 Linq 的 Provider 包含在 Microsoft.ComplexEventProcessing.Linq 命名空间下。该 Linq 语法的具体情况以后会详细讲解,也可以查看 MSDN :http://technet.microsoft.com/zh-cn/library/ee362394.aspx

事件

关于事件的官方介绍可以查看 MSDN : http://technet.microsoft.com/zh-cn/library/ee391434.aspx,以下是对官方介绍的一些补充说明。

DateTimeOffset vs DateTime

事件的标题部分中包含的时间戳使用的是 DateTimeOffset 类型,如 MSDN 所述:“此类型可分辨时区,并且基于 24 小时时钟。”这种数据结构是 .Net 3.5 提供的。包含了一个 DateTime 值还有一个 Offset 属性,正是由 Offset 属性确定了 DateTimeOffset 类型对象的时区,也就是与 UTC 时间之间的差值。关于 DateTimeOffset 和 DateTime 的比较以及相互间的转化,在 TerryLee 的博文中已有详细介绍:http://www.cnblogs.com/Terrylee/archive/2008/08/29/using-net-framework-new-datetime-data-type.html

另外一点需要注意的是,事件的时间戳与服务器系统时间无关,由源数据提供。也就是说,事件中的时间戳与事件被 StreamInsight 接收的时间或者事件对象创建的时间并无必然联系

事件类型

事件类型中的 Insert 类型很好理解,一般我们创建的事件和最终读取的事件都是这个类型的。而 CTI ( current time increment ) 事件类型一般由系统根据指定的 CTI 生成频率自动生成。

CTI 事件的作用有点类似一种认证。它标志着在其时间戳之前的事件是完整的,也就是在该 CTI 事件插入队列之后,不会再有新的时间戳早于该 CTI 事件的时间戳的事件插入队列。如果有怎么办?会根据你的设置,进行校正(校正为 CTI 时间戳之后的时间)或者抛弃。被标注完整性的事件的时间戳必须早于 CTI 的时间戳。

通过 CTI 事件标志过完整性的事件就可以释放开窗运算或者一些聚合运算,因为这些运算都是基于一批数据而不是单个数据的。也就是说如果一个应用了开窗或者聚合运算的流里面没有包含一个 CTI 事件,则永远不会得到计算结果。

那么我们就可以理解,频繁的 CTI 可以让计算结果更快产生。而且,CTI 的时间戳和之前插入的 Insert 事件的时间戳相关。通过设置 CTI 的延迟时间,我们可以控制 CTI 的时间戳是位于 CTI 事件之前的 Insert 事件的时间戳之前固定间隔的一个时间。

另外需要说明的是,在没有 Insert 事件产生的情况下,系统也不会自动生成 CTI 事件。

事件模型

所有的事件都拥有一个开始时间和结束时间,并且只在开始时间与结束时间内有效。对于点事件来说,结束时间是开始时间后一个时钟周期的时间点,也就是它的有效时间间隔是一个时钟周期。而还没有设置结束边缘的边缘事件的结束时间是 DateTimeOffset.MaxValue,可以认为它在整个过程中有效,直到被设置结束边缘。所以如果需要一些总是有效的事件,可以生成一堆边缘类型为开始的边缘事件

而在使用边缘事件的时候,需要特别注意。当插入一个边缘类型为结束的边缘时,需要确保有相应的边缘类型为开始的事件已经被插入了,这里不仅要保证两个边缘事件的开始时间一致,还要保证其他的各个字段一致。否则会抛出“找不到相应的开始边缘事件”的异常。但是,这里没有要求边缘事件的有序关闭,也就是后开始的边缘事件可以先结束。

事件负载

真正的数据,也就是流里的数据,都被存放在事件的负载中。当然是手动设置,而不是自动对应。这里要注意的有两点:

  1. 负载字段只能是 CLR 标量和初级类型,也就是你不可能让一个负载字段的类型是你自定义的类型。
  2. 负载字段要和流里的类型保持一致。如果是由类型化适配器生成的事件,那么只要让类型化适配器的负载类型和流的基本类型一致就可以了,如果是非类型化适配器,则要注意保证生成的事件的负载字段和流的基本类型里的字段一致。具体情况会在下一篇关于适配器的介绍中细说。