原文链接:http://my.oschina.net/SnifferApache/blog/338550
StreamInsight开发指南(https://technet.microsoft.com/zh-cn/library/ee391564(v=sql.111).aspx)在“输入和输出适配器”后面标注了“旧模型”。
适配器模型遵循如下的状态转移图:
其体系结构如图1:
{可以看出,StreamInsight主要包括三个部分:输入适配器(Input Adapter)、输出适配器(Output Adapter)以及CEP服务器。}
StreamInsight示例采用的新模型。该示例中创建和使用五个基本实体类型:源、接收器、主题、绑定和处理。其体系结构如图2:
{对比图1,可以看出新旧模型的原理没变}
新模型“使用事件源和事件接收器”过程中用到可枚举和可观察的源和接收器,其中IQbservable可以理解为Observable的“远程查询”版本{by赵姐夫blog.zhaojie.me/2010/09/async-programming-and-reactive-framework.html}
HelloInsight系列样例是基于适配器模型的,包括三个项目:
HelloInsight:流中只有一个简单事件(其payload为字符串类型)便停止了,输出这个事件payload的内容;
HelloInsightObservable:事件流中payload为数字,找出流中大于某个值的事件;
HelloInsightEnumerable:在HelloInsight基础上,InputAdapter把txt文件中每行字符串“包装”为事件push到流中,OutputAdapter输出流中每个事件的payload;
手札系列的内容相对权威、深入,在这里本文会从初学者角度来提到一些点。
至于开发环境请参考上一篇(http://my.oschina.net/SnifferApache/blog/324541)
Demo下载地址(http://pan.baidu.com/s/1qWqKe5Y的CEP目录)
HelloInsight和HelloInsightObservable本地直接运行成功,而HelloInsightObservable运行出现如下问题:
类型“System.Reactive.Linq.IQbservable`1<T0>”在未被引用的程序集中定义。必须添加对程序集“System.Reactive.Providers, Version=1.0.10621.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35”的引用。 HelloInsightObservable
按照“使用IObservable接口创建StreamInsight程序”这篇教程,第一步需要安装Reactive Extension for .Net 4,接着添加System.CoreEx和System.Reactive程序集引用。但是最新版Rx安装之后,程序集列表中没有System.CoreEx,在只导入System.Reactive .dll的情况下运行出现上面的问题。{!安装Rx的目的仅仅是提供两个dll文件,略显逗逼;}
事实上,安装StreamInsight2.1之后,Reactive会自动安装上,VS中下文program.cs本来出现的错误提示和引用中的黄色叹号立即消失了,这个项目调试正常输出;
推测五星教程以及其他教程中之所以提到的单独安装Reactive Extension for .Net 4,可能是其StreamInsight版本较旧;
BTW,安装Reactive Extension for .Net 4安装之后默认在C盘创建两个文件夹Microsoft Reactive Extensions SDK和Microsoft SDKs,VS2013添加引用栏会自动识别到这些东西,Reactive主要路径如下
路径1:C:\Program Files (x86)\Microsoft Reactive Extensions SDK\v1.0.10621\Binaries\.NETFramework\v4.0
路径2:C:\Program Files (x86)\Microsoft SDKs\Reactive Extensions\v2.0\Binaries\.NETFramework\v4.5
从下图可看出VS自动将V2.0和V1.0的dll混到一起了,所以引用的时候不要重复,而且最好同一版本(否则两个版本的方法名相同,可能会出现方法不明确的错误)。
前面提到的System.CoreEx在V2.0中已经集成到SystemCore中了,所以没有这个dll文件
该项目是基于适配器模型的。回顾前面的状态转移图,适配器就是将一种格式的数据转换成另一个格式的数据。输入适配器把传感器、web服务器和数据库等等多样数据转换成事件(Event)格式,push到事件流(Event Stream)中,输出适配器则与之进行相反的操作。
我们首先看这个项目的InputAdapters包括:
HelloInputConfig.cs //配置类
HelloInputFactory.cs //适配器工厂
HelloPointInput.cs //点事件适配器
理论上,适配器工厂会根据情况把任务交给不同类型的适配器,比如Point,Interval或者Edge适配器。样例为了简单期间,只有点事件一种适配器,要不然怎么叫“麻雀虽小,五脏俱全”呢,哈哈;
适配器处理的各类信息肯定要有一定的格式才规范,我们把格式信息抽象为配置类,这便是HelloInputConfig;
具体到HelloPointInput.cs,其处理流程则和状态转移图对照起来了,其中最重要的“生产”环节
private void ProduceEvents() { var pendingEvent = CreateInsertEvent(); pendingEvent.StartTime = DateTime.Now; pendingEvent.Payload = new HelloPayload { str = _config.inputString }; EnqueueOperationResult result = Enqueue(ref pendingEvent); EnqueueCtiEvent(DateTime.Now); Stopped(); }
构造Insert事件,配置其StartTime和Payload,Enqueue到StreamInsight引擎,在Enqueue一个Cti事件,遍把引擎关掉了,关掉了,关掉了。。。。。
所以呢,功能非常简单,我们再来看看输出适配器(暂时不关心引擎到底会对stream做什么事情。。),同样的,只看最核心的如何消费流中的事件
private void ConsumeEvents() { PointEvent<HelloPayload> currEvent; DequeueOperationResult result; while (true) { if (AdapterState.Running == base.AdapterState) { result = Dequeue(out currEvent); if (result == DequeueOperationResult.Empty) { Ready(); return; } else { if (currEvent.EventKind == EventKind.Insert) { Console.WriteLine("Output: " + currEvent.Payload.str ); } ReleaseEvent(ref currEvent); } } else if (AdapterState.Stopping == AdapterState) { Stopped(); } else { return; } } }
大体上,只要适配器还在Running状态,就Dequeue一个事件,输出Insert类型事件的payload。适配器要不在Running状态了,那就结束了。
最后主程序要做的就是搭建Query模版,启动Query
Query query = filteredCepStream.ToQuery(application,//Guid.NewGuid().ToString() "HelloInsightQuery", "创建查询,并绑定到输出适配器", typeof(HelloOutputFactory), outputConfig, EventShape.Point, StreamEventOrder.ChainOrdered); query.Start();
没错,就是这两句,其中application是CEP服务器上的一个应用程序,定义如下
Server server = Server.Create("Default");
Application application = server.CreateApplication("HelloInsight");
filteredCepStream为Microsoft.ComplexEventProcessing.Linq.CepStream<TPayload>类型;
这个类用到了泛型,就好比你明天有一个快递到,快递具体是什么不重要,我们关注你和快递员的沟通、协作过程。
这个强大的类型可以使用linq表达式来定义好流的处理方式(这里就一个字符串没什么好处理的),我们现在是在构建Query模版,启动之后才会生效
var filteredCepStream = from e in cepStream select e;cepStream怎么构造呢?
var inputConfig = new HelloInputConfig { inputString = "Hello StreamInsight!" }; Microsoft.ComplexEventProcessing.Linq.CepStream<HelloPayload> inputStream = CepStream<HelloPayload>.Create("InputStream", typeof(HelloInputFactory), inputConfig, EventShape.Point);BTW,CepStream<TPayload>是一个牛逼的静态类,有很多静态方法可以用~~
查询启动之后,输入适配器用另外的线程执行相应的方法(ProduceEvents),主线程需要等待适配器线程执行结束。结束之后一定要记得关闭Query哦
那问题来了,我们怎么在主线程获取适配器线程的运行状态呢?
答案是使用DiagnosticView 诊断报告每,具体原理你不用管,你只要知道我们一定会成功就好了,科科
//流队列为空的时候,适配器会停止运行 DiagnosticView dv = query.Application.Server.GetDiagnosticView(query.Name); //Start()异步启动之后,主程序每隔一秒就询问一下输出适配器的状态,如果不是“Running”,就可以输出统计结果了 while ((string)dv[DiagnosticViewProperty.QueryState] == "Running") { Thread.Sleep(1000); dv = query.Application.Server.GetDiagnosticView(query.Name); } // 此函数输出监视到的参数,比如总事件数、查询时间等 RetrieveDiagnostics(query.Application.Server.GetDiagnosticView(new Uri("cep:/Server/EventManager")), Console.Out); query.Stop();
让我们看一下效果:
创建Query模版,启动Query的方法除了上面提到的,还可以使用QueryTemplate来替代(上一篇提到的复杂样例TrafficJoinQuery就是使用这种方法)
var queryTemplate = application.CreateQueryTemplate("ExampleTemplate", "Description...", cepStream); var queryBinder = new QueryBinder(queryTemplate); queryBinder.BindProducer<HelloPayload>("input", inputAdapter, inputConfig, EventShape.Point); queryBinder.AddConsumer<HelloPayload>("output", outputAdapter, outputConfig, EventShape.Point, StreamEventOrder.ChainOrdered); var query = application.CreateQuery("ExampleQuery", "Description...", queryBinder);
和HelloInsight相比,你只要修改一下输入适配器就ok了(配置类字段改成了txt路径)
构造函数改成读完txt全部内容。。
private HelloInputConfig _config; private List<string> strings; private IEnumerator<string> stringEnumerator; public HelloPointInput(HelloInputConfig config) { _config = config; var streamReader = new StreamReader(config.fileName); strings = new List<string>(); while (!streamReader.EndOfStream) { strings.Add(streamReader.ReadLine()); } stringEnumerator = strings.GetEnumerator(); streamReader.Close(); }
再改改ProduceEvents
private void ProduceEvents() { while (AdapterState != AdapterState.Stopping) { if (stringEnumerator.MoveNext()) { try { var line = stringEnumerator.Current; var pendingEvent = CreateInsertEvent(); pendingEvent.StartTime = DateTime.Now; pendingEvent.Payload = new HelloPayload { str = line }; EnqueueOperationResult result= Enqueue(ref pendingEvent); if (result == EnqueueOperationResult.Full) { Thread.Sleep(1000); //Ready(); return; } EnqueueCtiEvent(DateTime.Now); //Thread.Sleep(1000); } catch { //error handling should go here } } else { break; } } Stopped(); }
搞定
手札四无法解决海量数据读取的问题,如果某一行文本没有换行,超长,有可能塞爆string空间。还有一次性全部读取可能也不太合适。
LINQ和Rx都是用来对集合进行操作。LINQ操作的集合实现了IEnumerable接口,能够使用foreach语句遍历集合。而Rx操作的集合实现了IEnumerable,IQueryable集合,这样的集合称之为Observable集合。大家对Enumerable集合可能很熟悉,他是foreach语句的基础,我的另一篇文章对这个有详细介绍,这里就不多说了,下面主要来看看Observable集合。
Rx对Observable集合进行操作,这个集合的命名是从观察者设计模式得来的,观察者模式的基础是委托和事件,所以要了解这一模式需要理解委托和事件,在这里推荐张子阳的文章C#中的委托和事件。Enumebrable集合中所有的元素在集合中都已经填充好了,是静态的,用户可以使用“拉”的方式从集合中遍历元素进行处理。而Observable集合则不同,在创建该集合时,集合中的元素可能会在以后的某个时间才能添加进去。由于集合注册了事件,一旦集合中的元素到达,就会触发这一事件,将信息“推”到注册者哪里去。
HelloInsightObservable项目中,过滤这一步使用了LINQ中的查询表达式
var query = from e in stream
where e.value > 50
select e;
参考(LINQ之路http://www.cnblogs.com/lifepoem/archive/2011/10/28/2227735.html)
方法语法和查询表达式语法互为补充。
方法语法中,Where产生一个经过过滤的sequence;OrderBy生成输入sequence的排序版本;Select得到的序列中的每个元素都经过了给定lambda表达式的转换。
查询表达式语法中,查询表达式总是以from子句开始,以select或者group子句结束。From子句定义了查询的范围变量(range variable),可以认为该变量是对输入sequence的一个遍历,就像foreach做的那样。下面这幅图描述了查询表达式的完整语法:
嗯,理论上说会有很多篇……
①重构和改进HelloInsightObservable
(http://my.oschina.net/SnifferApache/blog/360563)