Storm+kafka 开发

1、pom.xml

    <!--storm-->
    <dependency>
      <groupId>org.apache.storm</groupId>
      <artifactId>storm-core</artifactId>
      <version>1.1.1</version>
      <scope>provided</scope>
    </dependency>
    <dependency>
      <groupId>org.apache.storm</groupId>
      <artifactId>storm-kafka-client</artifactId>
      <version>1.1.1</version>
    </dependency>

    <!--kafka-->
    <dependency>
      <groupId>org.apache.kafka</groupId>
      <artifactId>kafka_2.11</artifactId>
      <version>RELEASE</version>
    </dependency>
    <dependency>
      <groupId>org.apache.kafka</groupId>
      <artifactId>kafka-clients</artifactId>
      <version>RELEASE</version>
    </dependency>

2、Spout


import com.fasterxml.jackson.databind.ObjectMapper;
import com.util.PropUtils;
import lombok.extern.slf4j.Slf4j;
import org.apache.kafka.clients.consumer.ConsumerConfig;
import org.apache.kafka.clients.consumer.ConsumerRecord;
import org.apache.storm.kafka.spout.Func;
import org.apache.storm.kafka.spout.KafkaSpoutConfig;
import org.apache.storm.tuple.Fields;
import org.apache.storm.tuple.Values;

import java.io.IOException;
import java.util.List;

import static org.apache.storm.kafka.spout.KafkaSpoutConfig.FirstPollOffsetStrategy.LATEST;


@Slf4j
public class SpoutConfig {

    /**
     * kafka消息翻译函数,record-->Test对象
     */
    private static Func<ConsumerRecord<String, String>, List<Object>> record = new Func<ConsumerRecord<String, String>, List<Object>>() {
        ObjectMapper objectMapper = new ObjectMapper();

        @Override
        public List<Object> apply(ConsumerRecord<String, String> record) {
            String message = record.value();
            Test test = null;
            try {
                test = objectMapper.readValue(message, Test.class);
            } catch (IOException e) {
                e.printStackTrace();
                log.error("test转换错误:{}", e.getMessage());
            }
            return new Values(test);
        }
    };

    /**
     * kafka spout配置
     *
     * @return KafkaSpoutConfig
     */
    public static KafkaSpoutConfig<String, String> newKafkaSpoutConfig() {
        return KafkaSpoutConfig
                .builder(PropUtils.getProperty("kafka.servers"), PropUtils.getProperty("kafka.topic"))//设置bootstrapServers和topic
                .setProp(ConsumerConfig.GROUP_ID_CONFIG, "spoutGroup")//设置消费组
                .setProp(ConsumerConfig.MAX_POLL_INTERVAL_MS_CONFIG, 600000)//消费者最大心跳时间间隔
                // .setProp(ConsumerConfig.MAX_POLL_RECORDS_CONFIG, 200)
                .setRecordTranslator(record, new Fields("test"))//设置消息翻译函数
                .setOffsetCommitPeriodMs(10000)//设置提交offset周期,设置 spout 多久向 Kafka commit一次
                .setFirstPollOffsetStrategy(LATEST)//设置第一次拉取消息时offset位置
                // .setMaxUncommittedOffsets(500)//控制在下一次拉取数据之前最多可以有多少数据在等待 commit
                .build();
    }
}
  • offset.commit.period.ms

设置 spout 多久向 Kafka commit一次,在 KafkaSpoutConfig 的 setOffsetCommitPeriodMs 中配置;

  • max.uncommitted.offsets

控制在下一次拉取数据之前最多可以有多少数据在等待 commit,在 KafkaSpoutConfig 的 setMaxUncommittedOffsets 中配置;

  • max.poll.interval.ms

消费者每次poll的数据业务处理时间不能超过kafka的max.poll.interval.ms。如果长时间没有调用poll,且间隔超过这个值时,就会认为这个consumer失败了。

session.timeout.ms用于心跳线程,而max.poll.interval.ms用于处理线程。如果处理线程死掉,则需要max.poll.interval.ms来检测它,但是,如果整个消费者死亡(并且一个垂死的处理线程很可能崩溃包括心跳线程在内的整个消费者),则只需要session.timeout.ms来检测它。

在kafka 0.10.0 之前,consumer消费消息和发送心跳信息这两个功能是在一个线程中进行的。这样就会引发一个问题,如果某条数据process的时间较长,那么consumer就无法给broker发送心跳信息,broker就会认为consumer死了。所以不得不提升session.timeout.ms来解决这个问题。但是这又引入了另外一个问题,如果session.timeout.ms设置得很大,那么检测一个consumer挂掉的时间就会很长,如果业务是实时的,那这就是不能忍受的。所以在 0.10.0 之后,发送心跳信息这个功能被拎出来在单独的线程中做,session.timeout.ms就是针对这个线程到底能不能按时发送心跳的。但是如果这个线程运行正常,但是消费线程挂了呢?这就无法检测了啊。所以就引进了max.poll.interval.ms,用来解决这个问题。

  • max.poll.records

控制单次调用call方法能够返回的记录数量,帮助控制在轮询里需要处理的数据量。

参考:Storm1.1.1 对 0.10.x 版 Kafka之commit offsets_weixin_30755393的博客-CSDN博客

java - Kafka学习笔记 - 大数据学习笔记 - SegmentFault 思否

PropUtils.java

import lombok.extern.slf4j.Slf4j;

import java.io.IOException;
import java.util.Properties;

@Slf4j
public class PropUtils {

    // 本地环境
    private static final String devMode = "development";
    // 正式环境
    // private static final String devMode = "production";
    // 测试环境
    // private static final String devMode = "test";


    private static Properties prop = new Properties();

    static {
        try {
            Properties kafka = new Properties();

            kafka.load(PropUtils.class.getClassLoader().getResourceAsStream("profile/" + devMode + "/kafka.properties"));

            prop.putAll(kafka);

        } catch (IOException e) {
            log.error("加载配置文件失败!", e);
            System.exit(1);
        }
    }

    public static String getProperty(String p) {
        return prop.getProperty(p);
    }

    public static int getInt(String p) {
        return Integer.parseInt(prop.getProperty(p));
    }

    public static boolean getBoolean(String p) {
        return Boolean.parseBoolean(prop.getProperty(p));
    }

}

kafka.properties

kafka.servers=node1:6667,node2:6667,node3:6667
kafka.topic=test

3、Bolt

public class TestBolt1 extends BaseWindowedBolt
public class TestBolt2 extends BaseBasicBolt

4、创建拓扑

import lombok.extern.slf4j.Slf4j;
import org.apache.storm.Config;
import org.apache.storm.LocalCluster;
import org.apache.storm.StormSubmitter;
import org.apache.storm.generated.StormTopology;
import org.apache.storm.kafka.spout.KafkaSpout;
import org.apache.storm.topology.TopologyBuilder;
import org.apache.storm.topology.base.BaseWindowedBolt;

import java.util.List;
import java.util.concurrent.TimeUnit;



@Slf4j
public class TopologyBuilder {
    static final String TOPOLOGY_NAME = "TEST_TOPOLOGY";

    public static void main(String[] args) throws Exception {
        Config config = new Config();

        config.setMessageTimeoutSecs(3600); //设置消息过期时间
        config.setNumAckers(0); //设置表示acker的并发数
        config.setNumWorkers(2); //表示整个topology将使用几个worker

        TopologyBuilder topologyBuilder = new TopologyBuilder();
        StormTopology topology = topologyBuilder.buildTopology();

        if (args.length == 0) {
            //本地模式
            LocalCluster cluster = new LocalCluster();
            //提交topo
            cluster.submitTopology(TOPOLOGY_NAME, config, topology);
            log.info("topology submit...");
            //运行一段时间后关闭程序
            TimeUnit.HOURS.sleep(1);
            cluster.killTopology(TOPOLOGY_NAME);
            cluster.shutdown();
            log.info("topology shutdown...");
            System.exit(0);
        } else {
            //集群模式
            config.put(Config.STORM_CLUSTER_MODE, "distributed");
            //提交topo
            StormSubmitter.submitTopology(args[0], config, topology);
        }
    }

    /**
     * 构建topology
     *
     * @return StormTopology
     */
    private StormTopology buildTopology() {
        TopologyBuilder builder = new TopologyBuilder();
        //设置spout
        builder.setSpout("KafkaSpout", new KafkaSpout<>(SpoutConfig.newKafkaSpoutConfig()), 3);
        //设置bolt
        //滚动窗口
        builder.setBolt("TestBolt1", new TestBolt1().withTumblingWindow(new BaseWindowedBolt.Duration(5, TimeUnit.MINUTES)), 1).localOrShuffleGrouping("KafkaSpout");
        //普通窗口
        builder.setBolt("TestBolt2", new TestBolt2(), 1).localOrShuffleGrouping("KafkaSpout");

        return builder.createTopology();

    }


}

遇到的问题记录:

1、一个topolopy多个bolt都没抛异常,spout有failed

解决:

conf.setMaxSpoutPending(100000);   //  这个设置一个spout task上面最多有多少个没有处理(ack/fail)的tuple,防止tuple队列过大, 只对可靠任务起作用
conf.setMessageTimeoutSecs(1000);//设置消息过期时间,默认是30s

或者

conf.setNumAckers(0); // 设置acker并发数,关闭Storm应答,可靠性有关

2、异常:org.apache.kafka.clients.consumer.CommitFailedException: Commit cannot be completed since the group has already rebalanced and assigned the partitions to another member. This means that the time between subsequent calls to poll() was longer than the configured max.poll.interval.ms, which typically implies that the poll loop is spending too much time message processing. You can address this either by increasing the session timeout or by reducing the maximum size of batches returned in poll() with max.poll.records.
解决:

a)调大max.poll.interval,ms,默认300000(300s)

b)调小max.poll.records,默认500

参考:Kafka消费异常处理

Storm1.1.1 对 0.10.x 版 Kafka之commit offsets

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值