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. Windows Embedded Standard 7 (WES7)系统定制遇到的问题(摄像头,喇叭,无线wifi)

    由于项目需要,需要对WES7系统进行定制,删除所有Windows字样基本没有什么问题,主要遇到如下3个问题: 1. 摄像头在Application模板下不能正常使用,即使安装驱动: 2. Jabra喇 ...

  2. php中的字符串常用函数(五) explode 妙用

    // 示例 2 $data = "foo:*:1023:1000::/home/foo:/bin/sh" ; list( $user , $pass , $uid , $gid , ...

  3. mysql安装中出现的问题,

    花了一天的时间明天mysql的安装方法: 自己的错误: 主要原因: (1):bin文件坏境配置出现了问题,没有重新在系统中配置文件 解决方法:右击电脑——属性——高级系统设置——变量配置——在path ...

  4. Java的主要数据类型(Primitive)

    有一系列类需特别对待:可将它们想象成"基本"."主要"或者"主"(Primitive)类型,进行程序设计时要频繁用到它们.之所以要特别对待, ...

  5. mysq基础一(字段类型)

    本文转自 “旋木的技术博客” 博客,http://mrxiong.blog.51cto.com/287318/1651098 一.数值类型 Mysql支持所有标准SQL中的数值类型,其中包括严格数据类 ...

  6. Android应用开发基础之十一:新特性

    Fragment 用途:在一个Activity里切换界面,切换界面时只切换Fragment里面的内容 生命周期方法跟Activity一致,可以理解把其为就是一个Activity 定义布局文件作为Fra ...

  7. 推荐几个jQuery插件

    jQuery仿京东无限级菜单HoverTree http://www.cnblogs.com/jihua/p/hvtree.html 多级弹出菜单jQuery插件ZoneMenu http://www ...

  8. 关于处理addGiftmoneyAction接口报错问题的总结

    昨天UniUser中AddGiftmoneyAction接口在被调用时抛出异常,曾哥让我来解决这个问题,虽然最后查出是路径问题,但是由于解决问题 的思路不够清晰,导致浪费了大量的时间和精力,也没有给出 ...

  9. 浅析字符串操作方法slice、substr、substring及其IE兼容性

    在截取字符串时常常会用到substr().substring().slice()方法,有时混淆之间的用法,故总结下.   slice() 定义:接受一个或者两个参数,第一个参数指定子字符串的开始位置. ...

  10. 为 MDS 修改 SharePoint 2013组件

    了解如何修改 SharePoint 项目中的组件以在 SharePoint 2013 中利用最少下载策略(MDS).   本文内容 为何修改 SharePoint 组件? 母版页 ASP.NET 页面 ...