一. 概述

上一篇我们介绍了如何将数据从mysql抛到kafka,这次我们就专注于利用storm将数据写入到hdfs的过程,由于storm写入hdfs的可定制东西有些多,我们先不从kafka读取,而先自己定义一个Spout数据充当数据源,下章再进行整合。这里默认你是拥有一定的storm知识的基础,起码知道Spout和bolt是什么。

写入hdfs可以有以下的定制策略:

  1. 自定义写入文件的名字
  2. 定义写入内容格式
  3. 满足给定条件后更改写入的文件
  4. 更改写入文件时触发的Action

本篇会先说明如何用storm写入HDFS,写入过程一些API的描述,以及最后给定一个例子:

storm每接收到10个Tuple后就会改变hdfs写入文件,新文件的名字就是第几次改变。

ps:storm版本:1.1.1。Hadoop版本:2.7.4。

接下来我们首先看看Storm如何写入HDFS。

二.Storm写入HDFS

Storm官方有提供了相应的API让我们可以使用。可以通过创建HdfsBolt以及定义相应的规则,即可写入HDFS 。

首先通过maven配置依赖以及插件。


  1. <properties>
  2. <storm.version>1.1.1</storm.version>
  3. </properties>
  4. <dependencies>
  5. <dependency>
  6. <groupId>org.apache.storm</groupId>
  7. <artifactId>storm-core</artifactId>
  8. <version>${storm.version}</version>
  9. <!--<scope>provided</scope>-->
  10. <exclusions>
  11. <exclusion>
  12. <groupId>org.slf4j</groupId>
  13. <artifactId>log4j-over-slf4j</artifactId>
  14. </exclusion>
  15. </exclusions>
  16. </dependency>
  17. <dependency>
  18. <groupId>commons-collections</groupId>
  19. <artifactId>commons-collections</artifactId>
  20. <version>3.2.1</version>
  21. </dependency>
  22. <dependency>
  23. <groupId>com.google.guava</groupId>
  24. <artifactId>guava</artifactId>
  25. <version>15.0</version>
  26. </dependency>
  27. <!--hadoop模块-->
  28. <dependency>
  29. <groupId>org.apache.hadoop</groupId>
  30. <artifactId>hadoop-client</artifactId>
  31. <version>2.7.4</version>
  32. <exclusions>
  33. <exclusion>
  34. <groupId>org.slf4j</groupId>
  35. <artifactId>slf4j-log4j12</artifactId>
  36. </exclusion>
  37. </exclusions>
  38. </dependency>
  39. <dependency>
  40. <groupId>org.apache.hadoop</groupId>
  41. <artifactId>hadoop-hdfs</artifactId>
  42. <version>2.7.4</version>
  43. <exclusions>
  44. <exclusion>
  45. <groupId>org.slf4j</groupId>
  46. <artifactId>slf4j-log4j12</artifactId>
  47. </exclusion>
  48. </exclusions>
  49. </dependency>
  50. <!-- https://mvnrepository.com/artifact/org.apache.storm/storm-hdfs -->
  51. <dependency>
  52. <groupId>org.apache.storm</groupId>
  53. <artifactId>storm-hdfs</artifactId>
  54. <version>1.1.1</version>
  55. <!--<scope>test</scope>-->
  56. </dependency>
  57. </dependencies>
  58. <build>
  59. <plugins>
  60. <plugin>
  61. <groupId>org.apache.maven.plugins</groupId>
  62. <artifactId>maven-compiler-plugin</artifactId>
  63. <version>3.5.1</version>
  64. <configuration>
  65. <source>1.8</source>
  66. <target>1.8</target>
  67. </configuration>
  68. </plugin>
  69. <plugin>
  70. <groupId>org.codehaus.mojo</groupId>
  71. <artifactId>exec-maven-plugin</artifactId>
  72. <version>1.2.1</version>
  73. <executions>
  74. <execution>
  75. <goals>
  76. <goal>exec</goal>
  77. </goals>
  78. </execution>
  79. </executions>
  80. <configuration>
  81. <executable>java</executable>
  82. <includeProjectDependencies>true</includeProjectDependencies>
  83. <includePluginDependencies>false</includePluginDependencies>
  84. <classpathScope>compile</classpathScope>
  85. <mainClass>com.learningstorm.kafka.KafkaTopology</mainClass>
  86. </configuration>
  87. </plugin>
  88. <plugin>
  89. <groupId>org.apache.maven.plugins</groupId>
  90. <artifactId>maven-shade-plugin</artifactId>
  91. <version>1.7</version>
  92. <configuration>
  93. <createDependencyReducedPom>true</createDependencyReducedPom>
  94. </configuration>
  95. <executions>
  96. <execution>
  97. <phase>package</phase>
  98. <goals>
  99. <goal>shade</goal>
  100. </goals>
  101. <configuration>
  102. <transformers>
  103. <transformer
  104. implementation="org.apache.maven.plugins.shade.resource.ServicesResourceTransformer"/>
  105. <transformer
  106. implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
  107. <mainClass></mainClass>
  108. </transformer>
  109. </transformers>
  110. </configuration>
  111. </execution>
  112. </executions>
  113. </plugin>
  114. </plugins>
  115. </build>

这里要提一下,如果要打包部署到集群上的话,打包的插件需要使用maven-shade-plugin这个插件,然后使用maven Lifecycle中的package打包。而不是用Maven-assembly-plugin插件进行打包。

因为使用Maven-assembly-plugin的时候,会将所有依赖的包unpack,然后在pack,这样就会出现,同样的文件被覆盖的情况。发布到集群上的时候就会报No FileSystem for scheme: hdfs的错。

然后是使用HdfsBolt写入Hdfs。这里来看看官方文档中的例子吧。

  1. // 使用 "|" 来替代 ",",来进行字符分割
  2. RecordFormat format = new DelimitedRecordFormat()
  3. .withFieldDelimiter("|");
  4. // 每输入 1k 后将内容同步到 Hdfs 中
  5. SyncPolicy syncPolicy = new CountSyncPolicy(1000);
  6. // 当文件大小达到 5MB ,转换写入文件,即写入到一个新的文件中
  7. FileRotationPolicy rotationPolicy = new FileSizeRotationPolicy(5.0f, Units.MB);
  8. //当转换写入文件时,生成新文件的名字并使用
  9. FileNameFormat fileNameFormat = new DefaultFileNameFormat()
  10. .withPath("/foo/");
  11. HdfsBolt bolt = new HdfsBolt()
  12. .withFsUrl("hdfs://localhost:9000")
  13. .withFileNameFormat(fileNameFormat)
  14. .withRecordFormat(format)
  15. .withRotationPolicy(rotationPolicy)
  16. .withSyncPolicy(syncPolicy);
  17. //生成该 bolt
  18. topologyBuilder.setBolt("hdfsBolt", bolt, 5).globalGrouping("randomStrSpout");

到这里就结束了。可以将HdfsBolt当作一个Storm中特殊一些的bolt即可。这个bolt的作用即使根据接收信息写入Hdfs。

而在新建HdfsBolt中,Storm为我们提供了相当强的灵活性,我们可以定义一些策略,比如当达成某个条件的时候转换写入文件,新写入文件的名字,写入时候的分隔符等等。

如果选择使用的话,Storm有提供部分接口供我们使用,但如果我们觉得不够丰富也可以自定义相应的类。下面我们看看如何控制这些策略吧。

RecordFormat

这是一个接口,允许你自由定义接收到内容的格式。

  1. public interface RecordFormat extends Serializable {
  2. byte[] format(Tuple tuple);
  3. }

Storm提供了DelimitedRecordFormat,使用方法在上面已经有了。这个类默认的分割符是逗号",",而你可以通过withFieldDelimiter方法改变分隔符。

如果你的初始分隔符不是逗号的话,那么也可以重写写一个类实现RecordFormat接口即可。

FileNameFormat

同样是一个接口。

  1. public interface FileNameFormat extends Serializable {
  2. void prepare(Map conf, TopologyContext topologyContext);
  3. String getName(long rotation, long timeStamp);
  4. String getPath();
  5. }

Storm所提供的默认的是org.apache.storm.hdfs.format.DefaultFileNameFormat。默认人使用的转换文件名有点长,格式是这样的:

{prefix}{componentId}-{taskId}-{rotationNum}-{timestamp}{extension}

例如:

MyBolt-5-7-1390579837830.txt

默认情况下,前缀是空的,扩展标识是".txt"。

SyncPolicy

同步策略允许你将buffered data缓冲到Hdfs文件中(从而client可以读取数据),通过实现org.apache.storm.hdfs.sync.SyncPolicy接口:

  1. public interface SyncPolicy extends Serializable {
  2. boolean mark(Tuple tuple, long offset);
  3. void reset();
  4. }

FileRotationPolicy

这个接口允许你控制什么情况下转换写入文件。

  1. public interface FileRotationPolicy extends Serializable {
  2. boolean mark(Tuple tuple, long offset);
  3. void reset();
  4. }

Storm有提供三个实现该接口的类:

  • 最简单的就是不进行转换的org.apache.storm.hdfs.bolt.rotation.NoRotationPolicy,就是什么也不干。

  • 通过文件大小触发转换的org.apache.storm.hdfs.bolt.rotation.FileSizeRotationPolicy。

  • 通过时间条件来触发转换的org.apache.storm.hdfs.bolt.rotation.TimedRotationPolicy。

如果有更加复杂的需求也可以自己定义。

RotationAction

这个主要是提供一个或多个hook,可加可不加。主要是在触发写入文件转换的时候会启动。

  1. public interface RotationAction extends Serializable {
  2. void execute(FileSystem fileSystem, Path filePath) throws IOException;
  3. }

三.实现一个例子

了解了上面的情况后,我们会实现一个例子,根据写入记录的多少来控制写入转换(改变写入的文件),并且转换后文件的名字表示当前是第几次转换。

首先来看看HdfsBolt的内容:

  1. RecordFormat format = new DelimitedRecordFormat().withFieldDelimiter(" ");
  2. // sync the filesystem after every 1k tuples
  3. SyncPolicy syncPolicy = new CountSyncPolicy(1000);
  4. // FileRotationPolicy rotationPolicy = new FileSizeRotationPolicy(1.0f, FileSizeRotationPolicy.Units.KB);
  5. /** rotate file with Date,every month create a new file
  6. * format:yyyymm.txt
  7. */
  8. FileRotationPolicy rotationPolicy = new CountStrRotationPolicy();
  9. FileNameFormat fileNameFormat = new TimesFileNameFormat().withPath("/test/");
  10. RotationAction action = new NewFileAction();
  11. HdfsBolt bolt = new HdfsBolt()
  12. .withFsUrl("hdfs://127.0.0.1:9000")
  13. .withFileNameFormat(fileNameFormat)
  14. .withRecordFormat(format)
  15. .withRotationPolicy(rotationPolicy)
  16. .withSyncPolicy(syncPolicy)
  17. .addRotationAction(action);

然后分别来看各个策略的类。

FileRotationPolicy

  1. import org.apache.storm.hdfs.bolt.rotation.FileRotationPolicy;
  2. import org.apache.storm.tuple.Tuple;
  3. import java.text.SimpleDateFormat;
  4. import java.util.Date;
  5. /**
  6. * 计数以改变Hdfs写入文件的位置,当写入10次的时候,则更改写入文件,更改名字取决于 “TimesFileNameFormat”
  7. * 这个类是线程安全
  8. */
  9. public class CountStrRotationPolicy implements FileRotationPolicy {
  10. private SimpleDateFormat df = new SimpleDateFormat("yyyyMM");
  11. private String date = null;
  12. private int count = 0;
  13. public CountStrRotationPolicy(){
  14. this.date = df.format(new Date());
  15. // this.date = df.format(new Date());
  16. }
  17. /**
  18. * Called for every tuple the HdfsBolt executes.
  19. *
  20. * @param tuple The tuple executed.
  21. * @param offset current offset of file being written
  22. * @return true if a file rotation should be performed
  23. */
  24. @Override
  25. public boolean mark(Tuple tuple, long offset) {
  26. count ++;
  27. if(count == 10) {
  28. System.out.print("num :" +count + " ");
  29. count = 0;
  30. return true;
  31. }
  32. else {
  33. return false;
  34. }
  35. }
  36. /**
  37. * Called after the HdfsBolt rotates a file.
  38. */
  39. @Override
  40. public void reset() {
  41. }
  42. @Override
  43. public FileRotationPolicy copy() {
  44. return new CountStrRotationPolicy();
  45. }
  46. }

FileNameFormat


  1. import org.apache.storm.hdfs.bolt.format.FileNameFormat;
  2. import org.apache.storm.task.TopologyContext;
  3. import java.util.Map;
  4. /**
  5. * 决定重新写入文件时候的名字
  6. * 这里会返回是第几次转换写入文件,将这个第几次做为文件名
  7. */
  8. public class TimesFileNameFormat implements FileNameFormat {
  9. //默认路径
  10. private String path = "/storm";
  11. //默认后缀
  12. private String extension = ".txt";
  13. private Long times = new Long(0);
  14. public TimesFileNameFormat withPath(String path){
  15. this.path = path;
  16. return this;
  17. }
  18. @Override
  19. public void prepare(Map conf, TopologyContext topologyContext) {
  20. }
  21. @Override
  22. public String getName(long rotation, long timeStamp) {
  23. times ++ ;
  24. //返回文件名,文件名为更换写入文件次数
  25. return times.toString() + this.extension;
  26. }
  27. public String getPath(){
  28. return this.path;
  29. }
  30. }

RotationAction


  1. import org.apache.hadoop.fs.FileContext;
  2. import org.apache.hadoop.fs.FileSystem;
  3. import org.apache.hadoop.fs.Path;
  4. import org.apache.storm.hdfs.common.rotation.RotationAction;
  5. import org.slf4j.Logger;
  6. import org.slf4j.LoggerFactory;
  7. import java.io.IOException;
  8. import java.net.URI;
  9. /**
  10. 当转换写入文件时候调用的 hook ,这里仅写入日志。
  11. */
  12. public class NewFileAction implements RotationAction {
  13. private static final Logger LOG = LoggerFactory.getLogger(NewFileAction.class);
  14. @Override
  15. public void execute(FileSystem fileSystem, Path filePath) throws IOException {
  16. LOG.info("Hdfs change the written file!!");
  17. return;
  18. }
  19. }

OK,这样就大功告成了。通过上面的代码,每接收到10个Tuple后就会转换写入文件,新文件的名字就是第几次转换。

完整代码包括一个随机生成字符串的Spout,可以到我的github上查看。

StormHdfsDemo:https://github.com/shezhiming/StormHdfsDemo


推荐阅读:

Mysql 流增量写入 Hdfs(一) --从 mysql 到 kafka

Spark SQL,如何将 DataFrame 转为 json 格式

Mysql增量写入Hdfs(二) --Storm+hdfs的流式处理的更多相关文章

  1. Mysql增量写入Hdfs(一) --将Mysql数据写入Kafka Topic

    一. 概述 在大数据的静态数据处理中,目前普遍采用的是用Spark+Hdfs(Hive/Hbase)的技术架构来对数据进行处理. 但有时候有其他的需求,需要从其他不同数据源不间断得采集数据,然后存储到 ...

  2. Storm:分布式流式计算框架

    Storm是一个分布式的.高容错的实时计算系统.Storm适用的场景: Storm可以用来用来处理源源不断的消息,并将处理之后的结果保存到持久化介质中. 由于Storm的处理组件都是分布式的,而且处理 ...

  3. Storm简介——实时流式计算介绍

    概念 实时流式计算: 大数据环境下,流式数据将作为一种新型的数据类型,这种数据具有连续性.无限性和瞬时性.是实时数据处理所面向的数据类型,对这种流式数据的实时计算就是实时流式计算. 特征 实时流式计算 ...

  4. flume-ng+Kafka+Storm+HDFS 实时系统搭建

    转自:http://www.tuicool.com/articles/mMrQnu7 一 直以来都想接触Storm实时计算这块的东西,最近在群里看到上海一哥们罗宝写的Flume+Kafka+Storm ...

  5. 大数据架构:flume-ng+Kafka+Storm+HDFS 实时系统组合

    http://www.aboutyun.com/thread-6855-1-1.html 个人观点:大数据我们都知道hadoop,但并不都是hadoop.我们该如何构建大数据库项目.对于离线处理,ha ...

  6. [转]flume-ng+Kafka+Storm+HDFS 实时系统搭建

    http://blog.csdn.net/weijonathan/article/details/18301321 一直以来都想接触Storm实时计算这块的东西,最近在群里看到上海一哥们罗宝写的Flu ...

  7. 转:大数据架构:flume-ng+Kafka+Storm+HDFS 实时系统组合

    虽然比较久,但是这套架构已经很成熟了,记录一下 一般数据流向,从“数据采集--数据接入--流失计算--数据输出/存储”<ignore_js_op> 1).数据采集 负责从各节点上实时采集数 ...

  8. 浅谈Storm流式处理框架(转)

    Hadoop的高吞吐,海量数据处理的能力使得人们可以方便地处理海量数据.但是,Hadoop的缺点也和它的优点同样鲜明——延迟大,响应缓慢,运维复杂. 有需求也就有创造,在Hadoop基本奠定了大数据霸 ...

  9. 浅谈Storm流式处理框架

    Hadoop的高吞吐,海量数据处理的能力使得人们可以方便地处理海量数据.但是,Hadoop的缺点也和它的优点同样鲜明——延迟大,响应缓慢,运维复杂. 有需求也就有创造,在Hadoop基本奠定了大数据霸 ...

随机推荐

  1. [Swift]LeetCode135. 分发糖果 | Candy

    There are N children standing in a line. Each child is assigned a rating value. You are giving candi ...

  2. [Swift]LeetCode239. 滑动窗口最大值 | Sliding Window Maximum

    Given an array nums, there is a sliding window of size k which is moving from the very left of the a ...

  3. [Swift]LeetCode376. 摆动序列 | Wiggle Subsequence

    A sequence of numbers is called a wiggle sequence if the differences between successive numbers stri ...

  4. [Swift]LeetCode983. 最低票价 | Minimum Cost For Tickets

    In a country popular for train travel, you have planned some train travelling one year in advance.  ...

  5. [Swift]LeetCode1027. 最长等差数列 | Longest Arithmetic Sequence

    Given an array A of integers, return the length of the longest arithmetic subsequence in A. Recall t ...

  6. [Abp 源码分析]十二、多租户体系与权限验证

    0.简介 承接上篇文章我们会在这篇文章详细解说一下 Abp 是如何结合 IPermissionChecker 与 IFeatureChecker 来实现一个完整的多租户系统的权限校验的. 1.多租户的 ...

  7. 获取Ip所在城市名与详细

    //获取ip和地理信息 string url = "http://pv.sohu.com/cityjson"; WebRequest wRequest = WebRequest.C ...

  8. 使用ML.NET预测纽约出租车费

    有了上一篇<.NET Core玩转机器学习>打基础,这一次我们以纽约出租车费的预测做为新的场景案例,来体验一下回归模型. 场景概述 我们的目标是预测纽约的出租车费,乍一看似乎仅仅取决于行程 ...

  9. 带着新人学springboot的应用12(springboot+Dubbo+Zookeeper 下)

    上半节已经下载好了Zookeeper,以及新建了两个应用provider和consumer,这一节我们就结合dubbo来测试一下分布式可不可以用. 现在就来简单用一下,注意:这里只是涉及最简单的部分, ...

  10. 前端笔记之JavaScript(四)关于函数、作用域、闭包那点事

    一.自定义函数function 函数就是功能.方法的封装.函数能够帮我们封装一段程序代码,这一段代码会具备某一项功能,函数在执行时,封装的这一段代码都会执行一次,实现某种功能.而且,函数可以多次调用. ...