版权声明:本文为博主原创文章,未经博主允许不得转载

本文是基于hadoop 2.7.1,以及kafka 0.11.0.0。kafka-connect是以单节点模式运行,即standalone。

一. 首先,先对kafka和kafka connect做一个简单的介绍

  kafka:Kafka是一种高吞吐量的分布式发布订阅消息系统,它可以处理消费者规模的网站中的所有动作流数据。比较直观的解释就是其有一个生产者(producer)和一个消费者(consumer)。可以将kafka想象成一个数据容器,生产者负责发送数据到这个容器中,而消费者从容器中取出数据,在将数据做处理,如存储到hdfs。

  kafka connect:Kafka Connect是一种用于在Kafka和其他系统之间可扩展的、可靠的流式传输数据的工具。它使得能够快速定义将大量数据集合移入和移出Kafka的连接器变得简单。即适合批量数据导入导出操作。

二. 下面将介绍如何用kafka connect将数据写入到hdfs中。包括在这个过程中可能碰到的一些问题说明。

首先启动kafka-connect:

  1.  
  1. bin/connect-standalone.sh config/connect-standalone.properties config/connector1.properties
  1. 这个命令后面两个参数,
      第一个是指定启动的模式,有分布式和单节点两种,这里是单节点。kafka自带,放于config目录下。
      第二个参数指向描述connector的属性的文件,可以有多个,这里只有一个connector用来写入到hdfs。需要自己创建。
  2.  
  3. 接下来看看connector1.properties的内容,
  1. name="test" #该connector的名字
    #将自己按connect接口规范编写的代码打包后放在kafka/libs目录下,再根据项目结构引用对应connector
    connector.class=hdfs.HdfsSinkConnector
  1. #Task是导入导出的具体实现,这里是指定多少个task来并行运行导入导出作业,由多线程实现。由于hdfs中一个文件每次只能又一个文件操作,所以这里只能是1
  1. tasks.max=1
  1. #指定从哪个topic读取数据,这些其实是用来在connector或者task的代码中读取的。
    topics=test
  1. #指定key以那种方式转换,需和Producer发送方指定的序列化方式一致
    key.converter=org.apache.kafka.connect.converters.ByteArrayConverter
    value.converter=org.apache.kafka.connect.json.JsonConverter #同上
  1. hdfs.url=hdfs://127.0.0.1:9000  #hdfs的url路径,在Connector中会被读取
    hdfs.path=/test/file  #hdfs文件路径,同样Connector中被读取
  2.  
  3. key.converter.schemas.enable=true  #稍后介绍,可以true也可以false,影响传输格式
    value.converter.schemas.enable=true  #稍后介绍,可以true也可以false
  1.  
  2. 三. 接下来看代码,connect主要是导入导出两个概念,导入是source,导出时Sink。这里只使用Sink,不过SourceSink的实现其实基本相同。
    实现Sink其实不难,实现对应的接口,即SinkConnectorSinkTask两个接口,再打包放到kafka/libs目录下即可。其中SinkConnector只有一个,而Task可以有多
  1. 先是Connector
  1. public class HdfsSinkConnector extends SinkConnector {
  2. //这两项为配置hdfs的urlh和路径的配置项,需要在connector1.properties中指定
  3. public static final String HDFS_URL = "hdfs.url";
  4. public static final String HDFS_PATH = "hdfs.path";
  5. private static final ConfigDef CONFIG_DEF = new ConfigDef()
  6. .define(HDFS_URL, ConfigDef.Type.STRING, ConfigDef.Importance.HIGH, "hdfs url")
  7. .define(HDFS_PATH, ConfigDef.Type.STRING, ConfigDef.Importance.HIGH, "hdfs path");
  8. private String hdfsUrl;
  9. private String hdfsPath;
  10. @Override
  11. public String version() {
  12. return AppInfoParser.getVersion();
  13. }
      //start方法会再初始的时候执行一次,这里主要用于配置
  14. @Override
  15. public void start(Map<String, String> props) {
  16. hdfsUrl = props.get(HDFS_URL);
  17. hdfsPath = props.get(HDFS_PATH);
  18. }
  19.   //这里指定了Task的类
  20. @Override
  21. public Class<? extends Task> taskClass() {
  22. return HdfsSinkTask.class;
  23. }
  24.   //用于配置Task的config,这些都是会在Task中用到
  25. @Override
  26. public List<Map<String, String>> taskConfigs(int maxTasks) {
  27. ArrayList<Map<String, String>> configs = new ArrayList<>();
  28. for (int i = 0; i < maxTasks; i++) {
  29. Map<String, String> config = new HashMap<>();
  30. if (hdfsUrl != null)
  31. config.put(HDFS_URL, hdfsUrl);
  32. if (hdfsPath != null)
  33. config.put(HDFS_PATH, hdfsPath);
  34. configs.add(config);
  35. }
  36. return configs;
  37. }
  38.   //关闭时的操作,一般是关闭资源。
  39. @Override
  40. public void stop() {
  41. // Nothing to do since FileStreamSinkConnector has no background monitoring.
  42. }
  43.  
  44. @Override
  45. public ConfigDef config() {
  46. return CONFIG_DEF;
  47. }
  48.  
  49. }
  1.  

接下来是Task

  1. public class HdfsSinkTask extends SinkTask {
  2. private static final Logger log = LoggerFactory.getLogger(HdfsSinkTask.class);
  3.  
  4. private String filename;
  5.  
  6. public static String hdfsUrl;
  7. public static String hdfsPath;
  8. private Configuration conf;
  9. private FSDataOutputStream os;
  10. private FileSystem hdfs;
  11.  
  12. public HdfsSinkTask(){
  13.  
  14. }
  15.  
  16. @Override
  17. public String version() {
  18. return new HdfsSinkConnector().version();
  19. }
  20.   //Task开始会执行的代码,可能有多个Task,所以每个Task都会执行一次
  21. @Override
  22. public void start(Map<String, String> props) {
  23. hdfsUrl = props.get(HdfsSinkConnector.HDFS_URL);
  24. hdfsPath = props.get(HdfsSinkConnector.HDFS_PATH);
  25. System.out.println("----------------------------------- start--------------------------------");
  26.  
  27. conf = new Configuration();
  28. conf.set("fs.defaultFS", hdfsUrl);
  29. //这两个是与hdfs append相关的设置
  30. conf.setBoolean("dfs.support.append", true);
  31. conf.set("dfs.client.block.write.replace-datanode-on-failure.policy", "NEVER");
  32. try{
  33. hdfs = FileSystem.get(conf);
  34. // connector.hdfs = new Path(HDFSPATH).getFileSystem(conf);
  35. os = hdfs.append(new Path(hdfsPath));
  36. }catch (IOException e){
  37. System.out.println(e.toString());
  38. }
  39.  
  40. }
  41.   //核心操作,put就是将数据从kafka中取出,存放到其他地方去
  42. @Override
  43. public void put(Collection<SinkRecord> sinkRecords) {
  44. for (SinkRecord record : sinkRecords) {
  45. log.trace("Writing line to {}: {}", logFilename(), record.value());
  46. try{
  47. System.out.println("write info------------------------" + record.value().toString() + "-----------------");
  48. os.write((record.value().toString()).getBytes("UTF-8"));
  49. os.hsync();
  50. }catch(Exception e){
  51. System.out.print(e.toString());
  52. }
  53. }
  54. }
  55.  
  56. @Override
  57. public void flush(Map<TopicPartition, OffsetAndMetadata> offsets) {
  58. try{
  59. os.hsync();
  60. }catch (Exception e){
  61. System.out.print(e.toString());
  62. }
  63.  
  64. }
      //同样是结束时候所执行的代码,这里用于关闭hdfs资源
  65. @Override
  66. public void stop() {
  67. try {
  68. os.close();
  69. }catch(IOException e){
  70. System.out.println(e.toString());
  71. }
  72. }
  73.  
  74. private String logFilename() {
  75. return filename == null ? "stdout" : filename;
  76. }
  77.  
  78. }
  1. 这里重点提一下,因为在connector1.propertise中设置了key.converter=org.apache.kafka.connect.converters.ByteArrayConverter,所以不能用命令行形式的
    producer发送数据,而是要用程序的方式,并且在producer总也要设置key的序列化形式为org.apache.kafka.common.serialization.ByteArraySerializer
  1. 编码完成,先用idea以开发程序与依赖包分离的形式打包成jar包,然后将程序对应的jar包(一般就是“项目名.jar”)放到kafka/libs目录下面,这样就能被找到。
  2.  
  1. 四. 接下来对这个过程的问题做一个汇总。
  1. 1.connector1.properties中的key.converter.schemas.enable=falsevalue.converter.schemas.enable=false的问题。
  1. 这个选项默认在connect-standalone.properties中是true的,这个时候发送给topicJson格式是需要使用avro格式,具体情况可以百度,这里给出一个样例。
  1. {
  2. "schema": {
  3. "type": "struct",
  4. "fields": [{
  5. "type": "int32",
  6. "optional": true,
  7. "field": "c1"
  8. }, {
  9. "type": "string",
  10. "optional": true,
  11. "field": "c2"
  12. }, {
  13. "type": "int64",
  14. "optional": false,
  15. "name": "org.apache.kafka.connect.data.Timestamp",
  16. "version": 1,
  17. "field": "create_ts"
  18. }, {
  19. "type": "int64",
  20. "optional": false,
  21. "name": "org.apache.kafka.connect.data.Timestamp",
  22. "version": 1,
  23. "field": "update_ts"
  24. }],
  25. "optional": false,
  26. "name": "foobar"
  27. },
  28. "payload": {
  29. "c1": 10000,
  30. "c2": "bar",
  31. "create_ts": 1501834166000,
  32. "update_ts": 1501834166000
  33. }
  34. }
  1.  

主要就是schema和payload这两个,不按照这个格式会报错如下

  1.  
  1. org.apache.kafka.connect.errors.DataException: JsonConverter with schemas.enable requires "schema" and "payload" fields and may not contain additional fields. If you are trying to deserialize plain JSON data, set schemas.enable=false in your converter configuration.
  2.  
  3. at org.apache.kafka.connect.json.JsonConverter.toConnectData(JsonConverter.java:308)
  1.  

如果想发送普通的json格式而不是avro格式的话,很简单key.converter.schemas.enable和value.converter.schemas.enable设置为false就行。这样就能发送普通的json格式数据。

2.在启动的过程中出现各种各样的java.lang.ClassNotFoundException。

在启动connector的时候,一开始总是会报各个各样的ClassNotFoundException,不是这个包就是那个包,查找问题一直说要么缺少包要么是包冲突。这个是什么原因呢?

其实归根结底还是依赖冲突的问题,因为kafka程序自定义的类加载器加载类的目录是在kafka/libs中,而写到hdfs需要hadoop的包。

我一开始的做法是将hadoop下的包路径添加到CLASSPATH中,这样子问题就来了,因为kafka和hadoop的依赖包是有冲突的,比如hadoop是guava-11.0.2.jar,而kafka是guava-20.0.jar,两个jar包版本不同,而我们是在kafka程序中调用hdfs,所以当jar包冲突时应该优先调用kafka的。但是注意kafka用的是程序自定义的类加载器,其优先级是低于CLASSPATH路径下的类的,就是说加载类时会优先加载CLASSPATH下的类。这样子就有问题了。

我的解决方案时将kafka和hadoop加载的jar包路径都添加到CLASSPATH中,并且kafka的路径写在hadoop前面,这样就可以启动connector成功。

---
推荐阅读:
大数据存储的进化史 --从 RAID 到 Hdfs
贝叶斯分类算法实例 --根据姓名推测男女
从分治算法到 MapReduce

  1.  

使用kafka connect,将数据批量写到hdfs完整过程的更多相关文章

  1. 【转】reduce端缓存数据过多出现FGC,导致reduce生成的数据无法写到hdfs

    转自  http://blog.csdn.net/bigdatahappy/article/details/41726389 转这个目的,是因为该贴子中调优思路不错,值得学习 搜索推荐有一个job,1 ...

  2. kafka产生的数据通过Flume存到HDFS中

    试验目标: 把kafka的生产者发出的数据流经由Flume放到HDFS来存储. 试验环境: java:1.8 kafka:2.11 flume:1.6 hadoop:2.8.5 试验流程: 1.进入z ...

  3. 打造实时数据集成平台——DataPipeline基于Kafka Connect的应用实践

    导读:传统ETL方案让企业难以承受数据集成之重,基于Kafka Connect构建的新型实时数据集成平台被寄予厚望. 在4月21日的Kafka Beijing Meetup第四场活动上,DataPip ...

  4. Kafka connect快速构建数据ETL通道

    摘要: 作者:Syn良子 出处:http://www.cnblogs.com/cssdongl 转载请注明出处 业余时间调研了一下Kafka connect的配置和使用,记录一些自己的理解和心得,欢迎 ...

  5. 基于Kafka Connect框架DataPipeline可以更好地解决哪些企业数据集成难题?

    DataPipeline已经完成了很多优化和提升工作,可以很好地解决当前企业数据集成面临的很多核心难题. 1. 任务的独立性与全局性. 从Kafka设计之初,就遵从从源端到目的的解耦性.下游可以有很多 ...

  6. Kafka connect in practice(3): distributed mode mysql binlog ->kafka->hive

    In the previous post Kafka connect in practice(1): standalone, I have introduced about the basics of ...

  7. 以Kafka Connect作为实时数据集成平台的基础架构有什么优势?

    Kafka Connect是一种用于在Kafka和其他系统之间可扩展的.可靠的流式传输数据的工具,可以更快捷和简单地将大量数据集合移入和移出Kafka的连接器.Kafka Connect为DataPi ...

  8. Kafka Connect使用入门-Mysql数据导入到ElasticSearch

    1.Kafka Connect Connect是Kafka的一部分,它为在Kafka和外部存储系统之间移动数据提供了一种可靠且伸缩的方式,它为连接器插件提供了一组API和一个运行时-Connect负责 ...

  9. SQL Server CDC配合Kafka Connect监听数据变化

    写在前面 好久没更新Blog了,从CRUD Boy转型大数据开发,拉宽了不少的知识面,从今年年初开始筹备.组建.招兵买马,到现在稳定开搞中,期间踏过无数的火坑,也许除了这篇还很写上三四篇. 进入主题, ...

随机推荐

  1. Effective Java 第三版——34. 使用枚举类型替代整型常量

    Tips <Effective Java, Third Edition>一书英文版已经出版,这本书的第二版想必很多人都读过,号称Java四大名著之一,不过第二版2009年出版,到现在已经将 ...

  2. 转载 git Unknown SSL protocol error in connection to github.com:443

    1.执行命令:git pull –progress –no-rebase -v "origin",报错,如图1 fatal: unable to access 'https://g ...

  3. 常用校验码(奇偶校验,海明校验,CRC)学习总结

    常用校验码(奇偶校验,海明校验,CRC)学习总结 一.为什么要有校验码? 因为在数据存取和传送的过程中,由于元器件或者噪音的干扰等原因会出现错误,这个时候我们就需要采取相应的措施,发现并纠正错误,对于 ...

  4. The Windows account sa does not exist and cannot be provisioned as a SQL Server system administrator

    今天遇到一个案例,在使用命令修改一个测试服务器(SQL Server 2014标准版)的服务器排序规则时,遇到了下面错误信息 (具体账号信息脱敏处理,随机生成一个账号密码) The Windows a ...

  5. 如何将ubuntu控制台输出到串口?

    如何将ubuntu控制台输出到串口? Linux使用ubuntu14.04发行版本 操作步骤: 1.修改/etc/default/grub ## Modify this line by leekwen ...

  6. android自定义View的绘制原理

    每天我们都会使用很多的应用程序,尽管他们有不同的约定,但大多数应用的设计是非常相似的.这就是为什么许多客户要求使用一些其他应用程序没有的设计,使得应用程序显得独特和不同. 如果功能布局要求非常定制化, ...

  7. Android HotFix动态加载框架介绍

    HotFix(Deprecated) https://github.com/dodola/HotFix 请关注 RocooFix 我重新写了一个RocooFix框架,解决了Nuwa因为Gradle1. ...

  8. Action写法心得

    最近一段时间,一直在忙着做项目,这个项目的运用的是SSH2三大框架,页面是用dojo技术. 我之前对dojo有所了解,但是好长时间都在弄Flex和JSP写页面,dojo没有得到运用,导致有所生疏:另外 ...

  9. 图解MBR分区无损转换GPT分区+UEFI引导安装WIN8.1

    确定你的主板支持UEFI引导.1,前期准备,WIN8.1原版系统一份(坛子里很多,自己下载个),U盘2个其中大于4G一个(最好 准备两个U盘)2,大家都知道WIN8系统只支持GPT分区,传统的MBR分 ...

  10. 使用EFI引导从硬盘(U盘)安装Win7的图文教程

    目前仅支持vista后的64位系统 大部分使用EFI引导安装Win7的教程都是采用光盘启动安装,虽然光盘安装比较简单,但是对于没有光驱的朋友来说还是相当不便,更不用说光盘安装的两大缺点了,一速度慢,二 ...