from: http://blog.csdn.net/wzhg0508/article/details/40903919

(五)storm-kafka源码走读之KafkaSpout

原创 2014年11月08日 14:09:06
  • 3458

现在开始介绍KafkaSpout源码了。

开始时,在open方法中做一些初始化,

  1. ........................
  2. _state = new ZkState(stateConf);
  3. _connections = new DynamicPartitionConnections(_spoutConfig, KafkaUtils.makeBrokerReader(conf, _spoutConfig));
  4. // using TransactionalState like this is a hack
  5. int totalTasks = context.getComponentTasks(context.getThisComponentId()).size();
  6. if (_spoutConfig.hosts instanceof StaticHosts) {
  7. _coordinator = new StaticCoordinator(_connections, conf, _spoutConfig, _state, context.getThisTaskIndex(), totalTasks, _uuid);
  8. } else {
  9. _coordinator = new ZkCoordinator(_connections, conf, _spoutConfig, _state, context.getThisTaskIndex(), totalTasks, _uuid);
  10. }
  11. ............

前后省略了一些代码,关于metric这系列暂时不介绍。主要是初始化Zookeeper连接zkstate,把kafka Partition 与broker关系对应起来(初始化DynamicPartitionConnections),在DynamicPartitionConnections构造函数需要传入一个brokerReader,我们是zkHosts,看KafkaUtils代码就知道采用的是ZkBrokerReader,来看下ZkBrokerReader的构造函数代码

  1. public ZkBrokerReader(Map conf, String topic, ZkHosts hosts) {
  2. try {
  3. reader = new DynamicBrokersReader(conf, hosts.brokerZkStr, hosts.brokerZkPath, topic);
  4. cachedBrokers = reader.getBrokerInfo();
  5. lastRefreshTimeMs = System.currentTimeMillis();
  6. refreshMillis = hosts.refreshFreqSecs * 1000L;
  7. } catch (java.net.SocketTimeoutException e) {
  8. LOG.warn("Failed to update brokers", e);
  9. }
  10. }

有一个refreshMillis参数,这个参数是定时更新zk中partition的信息,

  1. //ZkBrokerReader
  2. @Override
  3. public GlobalPartitionInformation getCurrentBrokers() {
  4. long currTime = System.currentTimeMillis();
  5. if (currTime > lastRefreshTimeMs + refreshMillis) { // 当前时间大于和上次更新时间之差大于refreshMillis
  6. try {
  7. LOG.info("brokers need refreshing because " + refreshMillis + "ms have expired");
  8. cachedBrokers = reader.getBrokerInfo();
  9. lastRefreshTimeMs = currTime;
  10. } catch (java.net.SocketTimeoutException e) {
  11. LOG.warn("Failed to update brokers", e);
  12. }
  13. }
  14. return cachedBrokers;
  15. }
  16. // 下面是调用DynamicBrokersReader 的代码
  17. /**
  18. * Get all partitions with their current leaders
  19. */
  20. public GlobalPartitionInformation getBrokerInfo() throws SocketTimeoutException {
  21. GlobalPartitionInformation globalPartitionInformation = new GlobalPartitionInformation();
  22. try {
  23. int numPartitionsForTopic = getNumPartitions();
  24. String brokerInfoPath = brokerPath();
  25. for (int partition = 0; partition < numPartitionsForTopic; partition++) {
  26. int leader = getLeaderFor(partition);
  27. String path = brokerInfoPath + "/" + leader;
  28. try {
  29. byte[] brokerData = _curator.getData().forPath(path);
  30. Broker hp = getBrokerHost(brokerData);
  31. globalPartitionInformation.addPartition(partition, hp);
  32. } catch (org.apache.zookeeper.KeeperException.NoNodeException e) {
  33. LOG.error("Node {} does not exist ", path);
  34. }
  35. }
  36. } catch (SocketTimeoutException e) {
  37. throw e;
  38. } catch (Exception e) {
  39. throw new RuntimeException(e);
  40. }
  41. LOG.info("Read partition info from zookeeper: " + globalPartitionInformation);
  42. return globalPartitionInformation;
  43. }

GlobalPartitionInformation是一个Iterator类,存放了paritition与broker之间的对应关系,DynamicPartitionConnections中维护Kafka Consumer与parittion之间的关系,每个Consumer读取哪些paritition信息。这个COnnectionInfo信息会在storm.kafka.ZkCoordinator中会被初始化和更新,需要提到的一点是一个KafkaSpout包含一个SimpleConsumer

  1. //storm.kafka.DynamicPartitionConnections
  2. static class ConnectionInfo {
  3. SimpleConsumer consumer;
  4. Set<Integer> partitions = new HashSet();
  5. public ConnectionInfo(SimpleConsumer consumer) {
  6. this.consumer = consumer;
  7. }
  8. }

再看ZkCoordinator类,看其构造函数

  1. //storm.kafka.ZkCoordinator
  2. public ZkCoordinator(DynamicPartitionConnections connections, Map stormConf, SpoutConfig spoutConfig, ZkState state, int taskIndex, int totalTasks, String topologyInstanceId, DynamicBrokersReader reader) {
  3. _spoutConfig = spoutConfig;
  4. _connections = connections;
  5. _taskIndex = taskIndex;
  6. _totalTasks = totalTasks;
  7. _topologyInstanceId = topologyInstanceId;
  8. _stormConf = stormConf;
  9. _state = state;
  10. ZkHosts brokerConf = (ZkHosts) spoutConfig.hosts;
  11. _refreshFreqMs = brokerConf.refreshFreqSecs * 1000;
  12. _reader = reader;
  13. }

_refreshFreqMs就是定时更新zk partition到本地的操作,在kafkaSpout中nextTuple方法中每次都会去调用ZkCoordinator的getMyManagedPartitions方法。该方法根据_refreshFreqMs参数定时更新partition信息

  1. //storm.kafka.ZkCoordinator
  2. @Override
  3. public List<PartitionManager> getMyManagedPartitions() {
  4. if (_lastRefreshTime == null || (System.currentTimeMillis() - _lastRefreshTime) > _refreshFreqMs) {
  5. refresh();
  6. _lastRefreshTime = System.currentTimeMillis();
  7. }
  8. return _cachedList;
  9. }
  10. @Override
  11. public void refresh() {
  12. try {
  13. LOG.info(taskId(_taskIndex, _totalTasks) + "Refreshing partition manager connections");
  14. GlobalPartitionInformation brokerInfo = _reader.getBrokerInfo();
  15. List<Partition> mine = KafkaUtils.calculatePartitionsForTask(brokerInfo, _totalTasks, _taskIndex);
  16. Set<Partition> curr = _managers.keySet();
  17. Set<Partition> newPartitions = new HashSet<Partition>(mine);
  18. newPartitions.removeAll(curr);
  19. Set<Partition> deletedPartitions = new HashSet<Partition>(curr);
  20. deletedPartitions.removeAll(mine);
  21. LOG.info(taskId(_taskIndex, _totalTasks) + "Deleted partition managers: " + deletedPartitions.toString());
  22. for (Partition id : deletedPartitions) {
  23. PartitionManager man = _managers.remove(id);
  24. man.close();
  25. }
  26. LOG.info(taskId(_taskIndex, _totalTasks) + "New partition managers: " + newPartitions.toString());
  27. for (Partition id : newPartitions) {
  28. PartitionManager man = new PartitionManager(_connections, _topologyInstanceId, _state, _stormConf, _spoutConfig, id);
  29. _managers.put(id, man);
  30. }
  31. } catch (Exception e) {
  32. throw new RuntimeException(e);
  33. }
  34. _cachedList = new ArrayList<PartitionManager>(_managers.values());
  35. LOG.info(taskId(_taskIndex, _totalTasks) + "Finished refreshing");
  36. }

其中每个Consumer分配partition的算法是KafkaUtils.calculatePartitionsForTask(brokerInfo, _totalTasks, _taskIndex);

主要做的工作就是获取并行的task数,与当前partition做比较,得出一个COnsumer要负责哪些parititons的读取,具体算法去kafka文档吧

以上在KafkaSpout中做完了初始化操作,下面开始取数据发射数据了,来看nextTuple方法

  1. // storm.kafka.KafkaSpout
  2. @Override
  3. public void nextTuple() {
  4. List<PartitionManager> managers = _coordinator.getMyManagedPartitions();
  5. for (int i = 0; i < managers.size(); i++) {
  6. try {
  7. // in case the number of managers decreased
  8. _currPartitionIndex = _currPartitionIndex % managers.size();
  9. EmitState state = managers.get(_currPartitionIndex).next(_collector);
  10. if (state != EmitState.EMITTED_MORE_LEFT) {
  11. _currPartitionIndex = (_currPartitionIndex + 1) % managers.size();
  12. }
  13. if (state != EmitState.NO_EMITTED) {
  14. break;
  15. }
  16. } catch (FailedFetchException e) {
  17. LOG.warn("Fetch failed", e);
  18. _coordinator.refresh();
  19. }
  20. }
  21. long now = System.currentTimeMillis();
  22. if ((now - _lastUpdateMs) > _spoutConfig.stateUpdateIntervalMs) {
  23. commit();
  24. }
  25. }

看完上述代码可知,所有的操作都是在PartitionManager中进行的,PartitionManager中会读取message信息,然后进行发射,主要逻辑在PartitionManager的next方法中

  1. //returns false if it's reached the end of current batch
  2. public EmitState next(SpoutOutputCollector collector) {
  3. if (_waitingToEmit.isEmpty()) {
  4. fill();
  5. }
  6. while (true) {
  7. MessageAndRealOffset toEmit = _waitingToEmit.pollFirst();
  8. if (toEmit == null) {
  9. return EmitState.NO_EMITTED;
  10. }
  11. Iterable<List<Object>> tups = KafkaUtils.generateTuples(_spoutConfig, toEmit.msg);
  12. if (tups != null) {
  13. for (List<Object> tup : tups) {
  14. collector.emit(tup, new KafkaMessageId(_partition, toEmit.offset));
  15. }
  16. break;
  17. } else {
  18. ack(toEmit.offset);
  19. }
  20. }
  21. if (!_waitingToEmit.isEmpty()) {
  22. return EmitState.EMITTED_MORE_LEFT;
  23. } else {
  24. return EmitState.EMITTED_END;
  25. }
  26. }

如果_waitingToEmit列表为空,则去读取msg,然后进行逐条发射,每发射一条,break一下,返回EMIT_MORE_LEFT给KafkaSpout的nextTuple方法中,,然后进行判断是否该paritition读取的一次读取的message buffer size是否已发射完毕,如果发射完毕就进行下一个partition 数据读取和发射,

注意的一点是,并不是一次把该partition的所有待发射的msg都发射完再commit offset到zk,而是发射一条,判断一下是否到了该commit的时候了(开始时设置的定时commit时间间隔),笔者认为这样做的原因是为了好控制fail

KafkaSpout中的ack,fail,commit操作全部交给了PartitionManager来做,看代码

  1. @Override
  2. public void ack(Object msgId) {
  3. KafkaMessageId id = (KafkaMessageId) msgId;
  4. PartitionManager m = _coordinator.getManager(id.partition);
  5. if (m != null) {
  6. m.ack(id.offset);
  7. }
  8. }
  9. @Override
  10. public void fail(Object msgId) {
  11. KafkaMessageId id = (KafkaMessageId) msgId;
  12. PartitionManager m = _coordinator.getManager(id.partition);
  13. if (m != null) {
  14. m.fail(id.offset);
  15. }
  16. }
  17. @Override
  18. public void deactivate() {
  19. commit();
  20. }
  21. @Override
  22. public void declareOutputFields(OutputFieldsDeclarer declarer) {
  23. declarer.declare(_spoutConfig.scheme.getOutputFields());
  24. }
  25. private void commit() {
  26. _lastUpdateMs = System.currentTimeMillis();
  27. for (PartitionManager manager : _coordinator.getMyManagedPartitions()) {
  28. manager.commit();
  29. }
  30. }

所以PartitionManager是KafkaSpout的核心,很晚了,都3点多了,后续会不上PartitionManager的分析,晚安

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

storm-kafka源码走读之KafkaSpout的更多相关文章

  1. twitter storm 源码走读之5 -- worker进程内部消息传递处理和数据结构分析

    欢迎转载,转载请注明出处,徽沪一郎. 本文从外部消息在worker进程内部的转化,传递及处理过程入手,一步步分析在worker-data中的数据项存在的原因和意义.试图从代码实现的角度来回答,如果是从 ...

  2. kafka源码分析之一server启动分析

    0. 关键概念 关键概念 Concepts Function Topic 用于划分Message的逻辑概念,一个Topic可以分布在多个Broker上. Partition 是Kafka中横向扩展和一 ...

  3. Apache Spark源码走读之23 -- Spark MLLib中拟牛顿法L-BFGS的源码实现

    欢迎转载,转载请注明出处,徽沪一郎. 概要 本文就拟牛顿法L-BFGS的由来做一个简要的回顾,然后就其在spark mllib中的实现进行源码走读. 拟牛顿法 数学原理 代码实现 L-BFGS算法中使 ...

  4. Apache Spark源码走读之16 -- spark repl实现详解

    欢迎转载,转载请注明出处,徽沪一郎. 概要 之所以对spark shell的内部实现产生兴趣全部缘于好奇代码的编译加载过程,scala是需要编译才能执行的语言,但提供的scala repl可以实现代码 ...

  5. Apache Spark源码走读之13 -- hiveql on spark实现详解

    欢迎转载,转载请注明出处,徽沪一郎 概要 在新近发布的spark 1.0中新加了sql的模块,更为引人注意的是对hive中的hiveql也提供了良好的支持,作为一个源码分析控,了解一下spark是如何 ...

  6. Apache Spark源码走读之7 -- Standalone部署方式分析

    欢迎转载,转载请注明出处,徽沪一郎. 楔子 在Spark源码走读系列之2中曾经提到Spark能以Standalone的方式来运行cluster,但没有对Application的提交与具体运行流程做详细 ...

  7. Kakfa揭秘 Day3 Kafka源码概述

    Kakfa揭秘 Day3 Kafka源码概述 今天开始进入Kafka的源码,本次学习基于最新的0.10.0版本进行.由于之前在学习Spark过程中积累了很多的经验和思想,这些在kafka上是通用的. ...

  8. Kafka 源码剖析

    1.概述 在对Kafka使用层面掌握后,进一步提升分析其源码是极有必要的.纵观Kafka源码工程结构,不算太复杂,代码量也不算大.分析研究其实现细节难度不算太大.今天笔者给大家分析的是其核心处理模块, ...

  9. apache kafka & CDH kafka源码编译

    Apache kafka编译 前言 github网站kafka项目的README.md有关于kafka源码编译的说明 github地址:https://github.com/apache/kafka ...

随机推荐

  1. Linux设备驱动程序加载/卸载方法 insmod和modprobe命令

    linux加载/卸载驱动有两种方法. 1.modprobe 注:在使用这个命令加载模块前先使用depmod -a命令生成modules.dep文件,该文件位于/lib/modules/$(uname ...

  2. Microsoft.VisualStudio.Web.PageInspector.Loader

    未能加载文件或程序集"Microsoft.VisualStudio.Web.PageInspector.Loader, Version=1.0.0.0, Culture=neutral, P ...

  3. POJ 1442 优先队列

    题意:有一些ADD和GET操作.n次ADD操作,每次往序列中加入一个数,由ADD操作可知序列长度为1-n时序列的组成.GET操作输入一个序列长度,输出当前长度序列第i大的元素的值.i初始为0,每次GE ...

  4. Java控制语句——分支、循环、跳转

    分支语句(if语句,switch语句): 循环语句(for,while,do...while); 跳转语句(break,continue,return): 分支语句(if语句,switch语句) if ...

  5. JavaWeb -- Struts2 模型驱动

    1. 模型驱动 示例: 注册表单reg.jsp <%@ page language="java" contentType="text/html; charset=u ...

  6. tech| kafka入门书籍导读

    J梳理了一下自己在入门 kafka 时读过的一些书, 希望能帮助到对 kafka 感兴趣的小伙伴. 涉及到的书籍: kafka 权威指南 Kafka: The Definitive Guide (ka ...

  7. Android 中Json解析的几种框架(Gson、Jackson、FastJson、LoganSquare)使用与对比

    介绍 移动互联网产品与服务器端通信的数据格式,如果没有特殊的需求的话,一般选择使用JSON格式,Android系统也原生的提供了JSON解析的API,但是它的速度很慢,而且没有提供简介方便的接口来提高 ...

  8. 使用ssm整合是创建Maven项目报错Failure to transfer com.thoughtworks.xstream:xstream:pom:1.3.1

    Description Resource Path Location TypeFailure to transfer com.thoughtworks.xstream:xstream:pom:1.3. ...

  9. 百度之星2017初赛A-1006-度度熊的01世界

    度度熊的01世界 Accepts: 967 Submissions: 3064 Time Limit: 2000/1000 MS (Java/Others) Memory Limit: 32768/3 ...

  10. Light oj 1074 spfa

    https://vjudge.net/problem/LightOJ-1074 首先吐槽一个单词,directional是有方向的,undirectional是无向的,这个unidirectional ...