【原创】一个简单的StreamInsight样例分析:MarketMonitor
MarketMonitor是2009年微软专业开发者大会上StreamInsight小组资深程序经理Torsten Grabs演讲中的第一个StreamInsight Demo。这个Demo演示了如何从一个随机的股票报价源中通过WHERE子句过滤出想要的结果。
准备工作
- 下载安装StreamInsight目前的最新版本StreamInsight 1.1(最新消息:StreamInsight小组预计夏季发布1.2版本,见这里);
- 下载安装Reactive扩展框架;
- 下载样例代码;(我在原有的样例代码的基础上进行了一番重构和修改,并转化成了VS 2010的解决方案,你可以从这里下载到修改后的版本。下面的代码分析将围绕修改后的版本进行。)
- 打开MarketMonitor.sln,确保添加了StreamInsight引用程序集Microsoft.ComplexEventProcessing.dll, Microsoft.ComplexEventProcessing.Adapters.dll以及Microsoft.ComplexEventProcessing.Observable.dll,确保添加了Reactive扩展框架程序集System.Reactive.dll。
代码分析
与传统的StreamInsight项目不同,MarketMonitor并没有循规蹈矩的创造一系列Factory, Config和Event类,而是使用了Reactive框架简化了输入和输出。这里Reactive框架并不是本篇博文的重点,目前你只需要知道它提供了一套非常方便的机制用来实现观察者模式,更多内容请参见这里。样例代码总共由4个类组成:MarkDataInput,MyTextFileWriterObserver ,SimpleMonitor 和 QuoteEvent。下面的介绍部分将按照从易到难的方式逐个介绍每一个类:
QuoteEvent可以算是这4个类中最简单的类了,它定义了股票报价的基本属性,包括Ask,Asksize,Assetclass等等。在StreamInsight中,我们通常把这样的类叫做Payload类——负载类,并作为CepStream中的T类型。
QuoteEvent类中最主要的函数就是
public static QuoteEvent RandomQuote()
它的返回值是一个QuoteEvent对象,其中所有的股票报价值都是随机值,如下:
QuoteEvent quote = new QuoteEvent(); quote.Id = rand.Next(1000); quote.Ticker = PickTicker(rand);quote.Assetclass = PickAssetClass(rand); quote.Market = PickMarket(rand); quote.Country = PickCountry(rand); …… return quote;
MarketDataInput类模拟了StreamInsight类中的输入事件流,它实现了IEnumerable<QuoteEvent>和IEnumerator<QuoteEvent>接口。
public class MarketDataInput : IEnumerable<QuoteEvent>, IEnumerator<QuoteEvent>
MarketDataInput对象每一次MoveNext时会生成一个随机的QuoteEvent对象,这里我将生成对象的总次数硬编码为10次,以方便查看运行结果。此外,大家可以自行修改生成次数或直接移除掉限制以模拟“无限”序列。
public bool MoveNext() { ++_eventCount; if (_eventCount > 10) return false; _current = QuoteEvent.RandomQuote(); Thread.Sleep(100); return true; }
MyTextFileWriterObserver类是一个观察者类,它实现了IObserver<QuoteEvent>接口。实现部分包括由三个函数组成:
OnCompleted():订阅事件完成时调用的函数 OnError(Exception error):订阅过程发生错误时调用的函数 OnNext(QuoteEvent value):订阅过程成功时调用的函数 这里最核心的OnNext函数实现了将QuoteEvent内容写入到输出流的逻辑,如:
_writer.Write(value.Assetclass + "\n"); _writer.Write(value.Ticker + "\n"); ……
SimpleMonitor中定义了主函数Main,它实现了创建StreamInsight服务器和应用,并使用MarketDataInput类作为StreamInsight输入事件流,在定义查询过滤后,使用MyTextFileWriterObserver对象订阅StreamInsight输出事件流,并将结果打印在控制台中。下面的代码具体介绍了SimpleMonitor的代码:
Server server = Server.Create("Default"); (1)
(1)行代码指明在进程内部托管StreamInsight服务器实例“Default”,另外一种方式是使用Server.Connect,该方法
指明连接到StreamInsight的Windows服务,代码看起来如下:
string hostUrl = @”http://localhost/StreamInsight/Default”; var service = server.CreateManagementService(); var cepManagementHost = new System.ServiceModel.ServiceHost(service); cepManagementHost.AddServiceEndpoint( typeof(IManagementService), new WSHttpBinding(SecurityMode.Message), hostUrl); cepManagementHost.Open();
关于选择StreamInsight服务器宿主在进程内,还是独立进程的讨论,可以稍微参考下先前的一篇博文
《了解Microsoft StreamInsight 你需要知道的6件事》。
Application app = server.CreateApplication("MarketMonitor"); (2)
(2)行代码在StreamInsight服务器server中创建了一个叫做"MarketMonitor"的应用程序。不难想象,在一个
server中可以有多个Application。
TraceListener TraceListener tracer = new ConsoleTraceListener(); (3)
(3)行代码定义了一个trace监听器,用来输出一些诊断信息。
ManualResetEvent observerStopSignal = new ManualResetEvent(false); (4)
(4)行代码定义的observerStopSignal主要是用来在主线程中同步订阅线程。当MyTextFileWriterObserver
订阅事件结束后,会调用OnCompleted(),从而调用_observerStopSignal.Set();来告诉主线程订阅事件已经
处理完毕。主线程使用如下代码来等待事件完成:observerStopSignal.WaitOne();
MarketDataInput mymarket = new MarketDataInput(); (5) IObservable<QuoteEvent> observableInput = mymarket.ToObservable(); (6)
(5)行定义了输入数据源,(6)行则利用Reactive的扩展方法将IEnumerable转换成IObservable对象。
var input = from e in observableInput.ToPointStream(app, e => PointEvent.CreateInsert(e.Timestamp, e), AdvanceTimeSettings.IncreasingStartTime) select e; (7)
(7)行将输入的Observable流转变为Cep流,其中每个事件的TimeStamp使用的是负载类中的TimeStamp。
结尾的AdvanceTimeSettings.IncreasingStartTime可以在每个点事件后的同一Tick生成CTI事件。
var output = from e in input where e.Ticker == "IBM" select e; (8)
(8)行定义了一个非常基础的带WHERE子句的查询,即查找IBM的所有股票报价。大家可以修改“IBM”为“MSFT”来查看微软的股票报价。在真实应用场景中,用户可以根据自己的需求定义各种不同的查询
var myTextFileWriterObserver = new MyTextFileWriterObserver("", observerStopSignal); (9)
(9)行定义了观察者对象,并将observerStopSignal传入已在订阅事件完成时通知主线程。
IDisposable consolesubscription =
output.ToObservable().Subscribe(myTextFileWriterObserver); (10)
(10)行是真正的订阅过程,将StreamInsight查询转换为ICepObservable<QuoteEvent >对象,并调用Subscribe添加(9)中定义的观察者。需要注意的是订阅完成后会启动StreamInsight的查询。
最终大家看到的结果会和下面的图类似:
这是我第一次写代码分析的文章,不到之处多多见谅,同时也希望大家多多提点意见。