WordCount在大数据领域就像学习一门语言时的hello world,得益于Storm的开源以及Storm.Net.Adapter,现在我们也可以像Java或Python一样,使用Csharp创建原生支持的Storm Topologies。下面我将通过介绍wordcount来展示如何使用Csharp开发Storm拓扑。

上篇博客已经介绍了如何部署Storm开发环境,本文所讲述demo已包含在Storm.Net.Adapter中,如果你觉得对你有帮助,欢迎Star和Fork,让更多人看到来帮助完善这个项目。

首先,我们创建一个控制台应用程序(使用控制台是方便调用) StormSimple;使用Nuget添加添加Storm.Net.Adapter(该类库的namespace为Storm)。

STEP1:通过继承ISpout创建一个Spout:Generator,实现ISpout的四个方法:

void Open(Config stormConf, TopologyContext context);
void NextTuple();
void Ack(long seqId);
void Fail(long seqId);

在实现这4个方法之前,我们还需要创建一些变量和方法来初始化这个类:

private Context ctx;

public Generator(Context ctx)
{
Context.Logger.Info("Generator constructor called");
this.ctx = ctx; // Declare Output schema
Dictionary<string, List<Type>> outputSchema = new Dictionary<string, List<Type>>();
outputSchema.Add("default", new List<Type>() { typeof(string) });
this.ctx.DeclareComponentSchema(new ComponentStreamSchema(null, outputSchema));
}

  

我使用了一个私有变量ctx来保存实例化时传入的Context对象,Context有一个静态的Logger,用于日志的发送,我们无需实例化即可使用它。根据日志级别不同,包含 Trace Debug Info Warn Error 五个级别,另外我们在实例化方法里还需要定义输入和输出的参数的数量和类型,本例子中输入为null,输出为一个字符串。另外我们还创建一个方法来直接返回实例化后的类:

/// <summary>
/// Implements of delegate "newPlugin", which is used to create a instance of this spout/bolt
/// </summary>
/// <param name="ctx">Context instance</param>
/// <returns></returns>
public static Generator Get(Context ctx)
{
return new Generator(ctx);
}

其中Open在该类第一次任务调用前执行,主要用于预处理和一些配置信息的传入,大多数情况下,我们并不需要做什么;NextTuple方法用于生成Tuple,会不断被调用,因此如果没什么任务要向下发送,可以使用Thread.Sleep(50);来减少CPU的消耗(具体休息时间与Topology设置有关,只要不超过超时时间就没有问题)。

本例子中NextTuple主要用于从一个包含英语句子的数组中随机取出一条句子,并把它发送到下一个环节,为了能够保证所有的任务都被成功执行一遍,我们将发送的消息缓存起来,并且限制正在执行中的任务数量为20。

private const int MAX_PENDING_TUPLE_NUM = ;
private long lastSeqId = ;
private Dictionary<long, string> cachedTuples = new Dictionary<long, string>(); private Random rand = new Random();
string[] sentences = new string[] {
"the cow jumped over the moon",
"an apple a day keeps the doctor away",
"four score and seven years ago",
"snow white and the seven dwarfs",
"i am at two with nature"};
/// <summary>
/// This method is used to emit one or more tuples. If there is nothing to emit, this method should return without emitting anything.
/// It should be noted that NextTuple(), Ack(), and Fail() are all called in a tight loop in a single thread in C# process.
/// When there are no tuples to emit, it is courteous to have NextTuple sleep for a short amount of time (such as 10 milliseconds), so as not to waste too much CPU.
/// </summary>
public void NextTuple()
{
Context.Logger.Info("NextTuple enter");
string sentence; if (cachedTuples.Count <= MAX_PENDING_TUPLE_NUM)
{
lastSeqId++;
sentence = sentences[rand.Next(, sentences.Length - )];
Context.Logger.Info("Generator Emit: {0}, seqId: {1}", sentence, lastSeqId);
this.ctx.Emit("default", new List<object>() { sentence }, lastSeqId);
cachedTuples[lastSeqId] = sentence;
}
else
{
// if have nothing to emit, then sleep for a little while to release CPU
Thread.Sleep();
}
Context.Logger.Info("cached tuple num: {0}", cachedTuples.Count); Context.Logger.Info("Generator NextTx exit");
}

this.ctx.Emit 即用来把Topology发送给下一个Bolt。

Ack()和Fail()方法分别在整个Topology执行成功和Topology失败时被调用。本例中Ack主要是移除缓存,Fail主要是用于取出缓存数据并重新发送Tuple。

/// <summary>
/// Ack() will be called only when ack mechanism is enabled in spec file.
/// If ack is not supported in non-transactional topology, the Ack() can be left as empty function.
/// </summary>
/// <param name="seqId">Sequence Id of the tuple which is acked.</param>
public void Ack(long seqId)
{
Context.Logger.Info("Ack, seqId: {0}", seqId);
bool result = cachedTuples.Remove(seqId);
if (!result)
{
Context.Logger.Warn("Ack(), remove cached tuple for seqId {0} fail!", seqId);
}
} /// <summary>
/// Fail() will be called only when ack mechanism is enabled in spec file.
/// If ack is not supported in non-transactional topology, the Fail() can be left as empty function.
/// </summary>
/// <param name="seqId">Sequence Id of the tuple which is failed.</param>
public void Fail(long seqId)
{
Context.Logger.Info("Fail, seqId: {0}", seqId);
if (cachedTuples.ContainsKey(seqId))
{
string sentence = cachedTuples[seqId];
Context.Logger.Info("Re-Emit: {0}, seqId: {1}", sentence, seqId);
this.ctx.Emit("default", new List<object>() { sentence }, seqId);
}
else
{
Context.Logger.Warn("Fail(), can't find cached tuple for seqId {0}!", seqId);
}
}

至此,一个Spout就算完成了,下面我们继续分析Bolt。

STEP2:通过继承IBasicBolt创建Bolt:Splitter、Counter。

Splitter是一个通过空格来拆分英语句子为一个个独立的单词,Counter则用来统计各个单词出现的次数。我们只详细分析Splitter,Counter类仅贴出全部源码。

和Generator相同,我们首先也要构造一个实例化方法方便使用者传参和调用:

private Context ctx;
private int msgTimeoutSecs; public Splitter(Context ctx)
{
Context.Logger.Info("Splitter constructor called");
this.ctx = ctx; // Declare Input and Output schemas
Dictionary<string, List<Type>> inputSchema = new Dictionary<string, List<Type>>();
inputSchema.Add("default", new List<Type>() { typeof(string) });
Dictionary<string, List<Type>> outputSchema = new Dictionary<string, List<Type>>();
outputSchema.Add("default", new List<Type>() { typeof(string), typeof(char) });
this.ctx.DeclareComponentSchema(new ComponentStreamSchema(inputSchema, outputSchema)); // Demo how to get stormConf info
if (Context.Config.StormConf.ContainsKey("topology.message.timeout.secs"))
{
msgTimeoutSecs = Convert.ToInt32(Context.Config.StormConf["topology.message.timeout.secs"]);
}
Context.Logger.Info("msgTimeoutSecs: {0}", msgTimeoutSecs);
} /// <summary>
/// Implements of delegate "newPlugin", which is used to create a instance of this spout/bolt
/// </summary>
/// <param name="ctx">Context instance</param>
/// <returns></returns>
public static Splitter Get(Context ctx)
{
return new Splitter(ctx);
}

在这个实例化方法中,我们增加了一个没有使用的变量msgTimeoutSecs用来展示如何获取Topology的配置。

由于继承了IBasicBolt,我们需要实现以下两个方法:

void Prepare(Config stormConf, TopologyContext context);
void Execute(StormTuple tuple);

这和IBolt是一致的,IBasicBolt和IBolt的区别仅仅在于后者需要自己处理何时向Storm发送Ack或Fail,IBasicBolt则不需要关心这些,如果你的Execute没有抛出异常的话,总会在最后向Storm发送Ack,否则则发送Fail。Prepare则是用于执行前的预处理,此例子里同样什么都不需要做。

/// <summary>
/// The Execute() function will be called, when a new tuple is available.
/// </summary>
/// <param name="tuple"></param>
public void Execute(StormTuple tuple)
{
Context.Logger.Info("Execute enter"); string sentence = tuple.GetString(); foreach (string word in sentence.Split(' '))
{
Context.Logger.Info("Splitter Emit: {0}", word);
this.ctx.Emit("default", new List<StormTuple> { tuple }, new List<object> { word, word[] });
} Context.Logger.Info("Splitter Execute exit");
} public void Prepare(Config stormConf, TopologyContext context)
{
return;
}

Counter和上述的代码类似:

using Storm;
using System;
using System.Collections.Generic; namespace StormSample
{
/// <summary>
/// The bolt "counter" uses a dictionary to record the occurrence number of each word.
/// </summary>
public class Counter : IBasicBolt
{
private Context ctx; private Dictionary<string, int> counts = new Dictionary<string, int>(); public Counter(Context ctx)
{
Context.Logger.Info("Counter constructor called"); this.ctx = ctx; // Declare Input and Output schemas
Dictionary<string, List<Type>> inputSchema = new Dictionary<string, List<Type>>();
inputSchema.Add("default", new List<Type>() { typeof(string), typeof(char) }); Dictionary<string, List<Type>> outputSchema = new Dictionary<string, List<Type>>();
outputSchema.Add("default", new List<Type>() { typeof(string), typeof(int) });
this.ctx.DeclareComponentSchema(new ComponentStreamSchema(inputSchema, outputSchema));
} /// <summary>
/// The Execute() function will be called, when a new tuple is available.
/// </summary>
/// <param name="tuple"></param>
public void Execute(StormTuple tuple)
{
Context.Logger.Info("Execute enter"); string word = tuple.GetString();
int count = counts.ContainsKey(word) ? counts[word] : ;
count++;
counts[word] = count; Context.Logger.Info("Counter Emit: {0}, count: {1}", word, count);
this.ctx.Emit("default", new List<StormTuple> { tuple }, new List<object> { word, count }); Context.Logger.Info("Counter Execute exit");
} /// <summary>
/// Implements of delegate "newPlugin", which is used to create a instance of this spout/bolt
/// </summary>
/// <param name="ctx">Context instance</param>
/// <returns></returns>
public static Counter Get(Context ctx)
{
return new Counter(ctx);
} public void Prepare(Config stormConf, TopologyContext context)
{
return;
}
}
}

STEP3:修改Program.cs来方便使用Java调用。

using Storm;
using System;
using System.Linq; namespace StormSample
{
class Program
{
static void Main(string[] args)
{
if (args.Count() > )
{
string compName = args[]; try
{
if ("generator".Equals(compName))
{
ApacheStorm.LaunchPlugin(new newPlugin(Generator.Get));
}
else if ("splitter".Equals(compName))
{
ApacheStorm.LaunchPlugin(new newPlugin(Splitter.Get));
}
else if ("counter".Equals(compName))
{
ApacheStorm.LaunchPlugin(new newPlugin(Counter.Get));
}
else
{
throw new Exception(string.Format("unexpected compName: {0}", compName));
}
}
catch (Exception ex)
{
Context.Logger.Error(ex.ToString());
}
}
else
{
Context.Logger.Error("Not support local model.");
}
}
}
}

我们在Main方法里使用参数来确定具体调用的是哪个Spout/Bolt,ApacheStorm是一个包含主要方法的类,之所以不使用Storm只是因为命名空间占用了它。Csharp端的代码到此就全部结束了,Java端的代码与部署发布将在下一篇详细介绍,敬请期待!下面让我们来看一看整个Topology的流程吧!

Storm系列文章

(一):搭建dotNet开发Storm拓扑的环境

(二):使用Csharp创建你的第一个Storm拓扑(wordcount)

(三):创建Maven项目打包提交wordcount到Storm集群

Storm系列(二):使用Csharp创建你的第一个Storm拓扑(wordcount)的更多相关文章

  1. Storm系列二: Storm拓扑设计

    Storm系列二: Storm拓扑设计 在本篇中,我们就来根据一个案例,看看如何去设计一个拓扑, 如何分解问题以适应Storm架构,同时对Storm拓扑内部的并行机制会有一个基本的了解. 本章代码都在 ...

  2. Storm系列(二)系统结构及重要概念

    在Storm的集群里面有两种节点:控制节点和工作节点,控制节点上面运行Nimbus进程,Nimbus负责在集群里面分配计算任务,并且监控状态.每一个工作节点上面运行Supervisor进程,Super ...

  3. C# Socket系列二 简单的创建 socket 通信

    看了系列一 我们开启了对socket tcp的监听状态,那么这一章我们来讲解怎么创建socket的通信代码 我新建一个类 TSocketBase public abstract class TSock ...

  4. Storm系列(二十)分区事务PartitionTransaction及示例

    在Storm中分区事务的处理,Spout端需要实现IPartitionedTransactionalSpout接口,用于对批次及偏移量的控制,而Bolt都必须实现IBatchBolt接口,通常继承至B ...

  5. Storm系列(三):创建Maven项目打包提交wordcount到Storm集群

    在上一篇博客中,我们通过Storm.Net.Adapter创建了一个使用Csharp编写的Storm Topology - wordcount.本文将介绍如何编写Java端的程序以及如何发布到测试的S ...

  6. Storm系列(一):搭建dotNet开发Storm拓扑的环境

    上篇博客比较了目前流行的计算框架特性,如果你是 Java 开发者,那么根据业务场景选择即可:但是如果你是 .Net 开发者,那么三者都不能拿来即用,至少在这篇文章出现之前是如此.基于上篇文章的比较发现 ...

  7. Storm系列三: Storm消息可靠性保障

    Storm系列三: Storm消息可靠性保障 在上一篇 Storm系列二: Storm拓扑设计 中我们已经设计了一个稍微复杂一点的拓扑. 而本篇就是在上一篇的基础上再做出一定的调整. 在这里先大概提一 ...

  8. Storm 系列(二)实时平台介绍

    Storm 系列(二)实时平台介绍 本章中的实时平台是指针对大数据进行实时分析的一整套系统,包括数据的收集.处理.存储等.一般而言,大数据有 4 个特点: Volumn(大量). Velocity(高 ...

  9. [Unity3D插件]2dtoolkit系列二 动画精灵的创建以及背景图的无限滚动

    经过昨天2dtoolkit系列教程一的推出,感觉对新手还有有一定的启发作用,引导学习使用unity 2dToolKit插件的使用过程,今天继续系列二——动画精灵的创建,以及背景图的无限循环滚动,在群里 ...

随机推荐

  1. objective-c IOS应用更新

    当前苹果已经禁止了,通过IOS应用直接跳转APP下载链接的方法.但是仍然可以使用另外一种方法直接跳转AppStore. 这种方法需要增加一个类库StoreKit.framework. 这里使用这功能是 ...

  2. java servlet手机app访问接口(二)短信验证

    今天找了几个短信平台,其实最想使用的一个是sharesdk,使用它上面http api短信功能,不仅价格低,而且最少可以充值100RMB,但是审核过于严格,对应APP还必须集成他们的短信功能,而且要上 ...

  3. HDU 4293---Groups(区间DP)

    题目链接 http://acm.split.hdu.edu.cn/showproblem.php?pid=4293 Problem Description After the regional con ...

  4. cnodejs社区论坛1--登陆

  5. 不可小觑的SQL语句

    在前面学的我们通过点鼠标给数据表插数据,虽然这种方法很靠谱,但是有那么的一些缺点,就是比较麻烦和效率不高.所以现在我们的好好学SQL语句,来弥补这么的一个漏洞,能提高我们工作的效率. SQL语句能做什 ...

  6. jquery图片查看插件,支持旋转、放大、缩小、拖拽、缩略图(仿qq图片查看)

    最近做了一个jquery图片查看的插件,目的是能精确查看图片的详情,插件支持图片旋转.放大.缩小.拖拽.缩略图显示,界面效果是按照window的qq查看图片功能写的,当然不尽相同. 具体功能: 1. ...

  7. windows 下配置 nginx的问题

    1.存放nginx的目录最好是英文目录,如果存放到中文目录下启动时有问题: 未完,待续...

  8. Atitit.木马病毒强制强行关闭360 360tray.exe的方法

    Atitit.木马病毒强制强行关闭360 360tray.exe的方法 1. taskkill /im 进程名称1 2. 用 wmic process where name="进程名称&qu ...

  9. Hosting socket.io WebSocket apps in IIS using iisnode

    In this post I explain how to configure a socket.io node.js application to use of WebSockets when ho ...

  10. switch-枚举

    在swift中,如果switch的枚举,可以不写default,因为系统知道有多少种情况,如果不是枚举,必须要写default enum WBComposeToolBarButtonType:Int ...