Storm Kafka Integration (0.10.x+)
兼容性
Apache Kafka版本0.10以上
向kafka写数据作为拓扑的一部分
你可以创建一个org.apache.storm.kafka.bolt.KafkaBolt
的实例,并将其作为一个组件添加到你的拓扑上,或者如果你正在使用trident
你可以使用
org.apache.storm.kafka.trident.TridentState
, org.apache.storm.kafka.trident.TridentStateFactory
和org.apache.storm.kafka.trident.TridentKafkaUpdater
.
你需要实现下面两个接口:
TupleToKafkaMapper
和 TridentTupleToKafkaMapper
这些接口有两个方法定义:
K getKeyFromTuple(Tuple/TridentTuple tuple);
V getMessageFromTuple(Tuple/TridentTuple tuple);
顾名思义,这些方法被调用映射一个tuple到一个kafka key和kafka message。
如果你只需要一个字段作为key,一个字段作为value,那么你可以使用提供的FieldNameBasedTupleToKafkaMapper.java
实现。
- 在
KafkaBolt
里,如果使用默认构造函数构造FieldNameBasedTupleToKafkaMapper
,则实现始终会查找具有字段名称“key”和“message”的字段,以实现向后兼容性的原因。
或者,您也可以使用非默认构造函数指定不同的key和message字段。 - 在
TridentKafkaState
中,您必须指定key和message的字段名称,因为没有默认构造函数。
在构造FieldNameBasedTupleToKafkaMapper
的实例时应指定这些。
KafkaTopicSelector
和trident KafkaTopicSelector
这个接口只有一个方法
public interface KafkaTopicSelector {
String getTopics(Tuple/TridentTuple tuple);
}
这个接口的实现应该返回要发布tuple的key/message映射的主题。您可以返回一个null,该消息将被忽略。如果你有一个静态主题名称,那么你可以
使用DefaultTopicSelector.java并在构造函数中设置主题的名称。
FieldNameTopicSelector 和 FieldIndexTopicSelector可以被使用来选择一个topic应该去发布一个tuple到哪。(select the topic should to publish a tuple to.)
用户只需要在tuple本身中指定topic名称的字段名称或字段索引。
当topic名称未找到时,Field * TopicSelector将会将消息写入默认topic。请保证默认的topic已经被创建。
指定Kafka生产者属性
您可以通过调用KafkaBolt.withProducerProperties()
和TridentKafkaStateFactory.withProducerProperties()
来提供Storm拓扑中的所有生产者属性。
生产者的重要的配置属性包括:
- metadata.broker.list
- request.required.acks
- producer.type
- serializer.class
这些也被定义在org.apache.kafka.clients.producer.ProducerConfig
里
使用通配符topic匹配(Using wildcard kafka topic match)
您可以通过添加以下配置来进行通配符主题匹配
Config config = new Config(); config.put("kafka.topic.wildcard.match",true);
之后你可以制定一个通配符主题去匹配。例如clickstream.*.log. 这将会匹配如下所有的流 clickstream.my.log, clickstream.cart.log 等等。
综上:
for the bolt:
TopologyBuilder builder = new TopologyBuilder();
Fields fields = new Fields("key", "message");
FixedBatchSpout spout = new FixedBatchSpout(fields, 4,
new Values("storm", "1"),
new Values("trident", "1"),
new Values("needs", "1"),
new Values("javadoc", "1")
);
spout.setCycle(true);
builder.setSpout("spout", spout, 5);
//set producer properties.
Properties props = new Properties();
props.put("bootstrap.servers", "localhost:9092");
props.put("acks", "1");
props.put("key.serializer", "org.apache.kafka.common.serialization.StringSerializer");
props.put("value.serializer", "org.apache.kafka.common.serialization.StringSerializer");
KafkaBolt bolt = new KafkaBolt()
.withProducerProperties(props)
.withTopicSelector(new DefaultTopicSelector("test"))
.withTupleToKafkaMapper(new FieldNameBasedTupleToKafkaMapper());
builder.setBolt("forwardToKafka", bolt, 8).shuffleGrouping("spout");
Config conf = new Config();
StormSubmitter.submitTopology("kafkaboltTest", conf, builder.createTopology());
For Trident:
Fields fields = new Fields("word", "count");
FixedBatchSpout spout = new FixedBatchSpout(fields, 4,
new Values("storm", "1"),
new Values("trident", "1"),
new Values("needs", "1"),
new Values("javadoc", "1")
);
spout.setCycle(true);
TridentTopology topology = new TridentTopology();
Stream stream = topology.newStream("spout1", spout);
//set producer properties.
Properties props = new Properties();
props.put("bootstrap.servers", "localhost:9092");
props.put("acks", "1");
props.put("key.serializer", "org.apache.kafka.common.serialization.StringSerializer");
props.put("value.serializer", "org.apache.kafka.common.serialization.StringSerializer");
TridentKafkaStateFactory stateFactory = new TridentKafkaStateFactory()
.withProducerProperties(props)
.withKafkaTopicSelector(new DefaultTopicSelector("test"))
.withTridentTupleToKafkaMapper(new FieldNameBasedTupleToKafkaMapper("word", "count"));
stream.partitionPersist(stateFactory, fields, new TridentKafkaUpdater(), new Fields());
Config conf = new Config();
StormSubmitter.submitTopology("kafkaTridentTest", conf, topology.build());
从kafka读数据(Spouts)
配置
spout的实现使用KafkaSpoutConfig
类来配置。这个类使用Build模式,可以通过调用其中一个Builds构造函数或通过调用KafkaSpoutConfig
类中的静态方法构建器来启动。
创建构建器的构造函数或静态方法需要几个key values(稍后可以更改),但是启动一个spout所需的最小配置。
bootstrapServers
与Kafka Consumer Property “bootstrap.servers”是相同的。
spout将消耗的主题可以是特定topic名称(1个或更多)的集合或正则表达式Pattern,它指定匹配该正则表达式的任何主题将被使用。
在构造函数的情况下,您可能还需要指定键解串器和值解串器。这是为了通过使用Java泛型来保证类型安全。默认值为StringDeserializer
,
可以通过调用setKeyDeserializer
和/或 setValueDeserializer
来覆盖。
如果这些设置为null,代码将回退到kafka属性中设置的内容,但是最好在这里明确表示,再次使用泛型来维护类型安全性。
有几个关键配置要注意
setFirstPollOffsetStrategy
:允许您设置从哪里开始使用数据.这在故障恢复和首次启动spout的情况下都被使用。允许的值包括:
EARLIEST
:无论以前的提交如何,kafka spout会轮询从分区的第一个偏移开始的记录LATEST
:无论以前的提交如何,kafka spout轮询(具有大于分区中最后一个偏移量的)偏移量的记录UNCOMMITTED_EARLIEST
(默认):kafka spout从最后提交的偏移量(如果有的话)中轮询记录,如果没有提交任何偏移量,则表现为EARLIEST
。UNCOMMITTED_LATEST
:kafka spout从最后提交的偏移量(如果有的话)中轮询记录,如果没有提交任何偏移量,则表现为LATEST
。
setRecordTranslator
:允许您修改spout如何将Kafka Consume Record转换为tuple,以及将发布该元组的流。
默认情况下,”topic”, “partition”, “offset”, “key” 和 “value”将被发送到“默认”流。
如果要根据主题将条目输出到不同的流,则storm提供ByTopicRecordTranslator
。有关如何使用这些的更多示例,请参见下文。setProp
:可以用来设置没有方便方法的kafka属性。setGroupId
:让你设置kafka消费者组属性“group.id”的id。setSSLKeystore
和setSSLTruststore
:允许您配置SSL身份验证。
用法示例
API是用java 8 lambda表达式写的,它可以与java7及以下版本配合使用。
创建一个简单的不安全Spout
以下将消费所有的发布到“topic”的事件,并发送他们到
MyBolt
有着字段”topic”, “partition”, “offset”, “key”, “value”。
final TopologyBuilder tp = new TopologyBuilder();
tp.setSpout("kafka_spout", new KafkaSpout<>(KafkaSpoutConfig.builder("127.0.0.1:" + port, "topic").build()), 1);
tp.setBolt("bolt", new myBolt()).shuffleGrouping("kafka_spout");
通配符topics
通配符主题将从指定代理列表中存在的所有主题消耗,并匹配该模式。所以在下面的例子中,”topic”, “topic_foo” and “topic_bar”将都会匹配”topic.*”,
但是”not_my_topic”不匹配。
final TopologyBuilder tp = new TopologyBuilder();
tp.setSpout("kafka_spout", new KafkaSpout<>(KafkaSpoutConfig.builder("127.0.0.1:" + port, Pattern.compile("topic.*")).build()), 1);
tp.setBolt("bolt", new myBolt()).shuffleGrouping("kafka_spout");
Multiple Streams(多个流)
这使用了java 8 lambda表达式。
final TopologyBuilder tp = new TopologyBuilder();
//By default all topics not covered by another rule, but consumed by the spout will be emitted to "STREAM_1" as "topic", "key", and "value"
//默认情况下,所有未被其他规则覆盖的主题,但是由spout消耗的所有主题将被发送到“STREAM_1”作为"topic", "key", and "value"
ByTopicRecordTranslator byTopic = new ByTopicRecordTranslator<>( (r) -> new Values(r.topic(), r.key(), r.value()), new Fields("topic", "key", "value"), "STREAM_1");
//For topic_2 all events will be emitted to "STREAM_2" as just "key" and "value"
byTopic.forTopic("topic_2", (r) -> new Values(r.key(), r.value()), new Fields("key", "value"), "STREAM_2");
tp.setSpout("kafka_spout", new KafkaSpout<>(KafkaSpoutConfig.builder("127.0.0.1:" + port, "topic_1", "topic_2", "topic_3").build()), 1);
tp.setBolt("bolt", new myBolt()).shuffleGrouping("kafka_spout", "STREAM_1");
tp.setBolt("another", new myOtherBolt()).shuffleGrouping("kafka_spout", "STREAM_2");
Trident
final TridentTopology tridentTopology = new TridentTopology();
final Stream spoutStream = tridentTopology.newStream("kafkaSpout",
new KafkaTridentSpoutOpaque<>(KafkaSpoutConfig.builder("127.0.0.1:" + port, Pattern.compile("topic.*")).build()))
.parallelismHint(1)
Trident不支持多个流,并将忽略为输出设置的任何流。然而,如果每个输出主题的字段不相同,它将抛出异常,而不会继续。
Custom RecordTranslators(自定义RecordTranslators)(高级)
在大多数情况下,内置的SimpleRecordTranslator和ByTopicRecordTranslator应该能覆盖您的用例。
如果您遇到需要定制的情况,则本文档将介绍如何正确执行此操作,以及一些不太明显的类。
适用的要点是使用ConsumerRecord并将其转换为可以发出的List 。不明显的是如何告诉spout将其发射到特定的流。为此,
您将需要返回一个org.apache.storm.kafka.spout.KafkaTuple
的实例。这提供了一个routedTo
方法,它将说明tuple应该去哪个特定的流。
例如:return new KafkaTuple(1, 2, 3, 4).routedTo("bar");
Will cause the tuple to be emitted on the “bar” stream(将导致元组在“bar”流中发出)
在编写自定义record translators时要小心,因为就像在storm spout中,它需要自我一致。stream
方法应该返回一组完整的流,这个转换器将会尝试发送。
另外getFieldsFor
应为每个流返回一个有效的Fields
对象。
如果您正在为Trident执行此操作,则值必须位于通过应用该流的Fields对象中的每个字段返回的List中,否则trident可能会抛出异常。
手动分区控制(高级)
默认情况下,Kafka将自动将分区分配给当前的一组分支。它处理很多事情,但在某些情况下,您可能需要手动分配分区。
当spout go down 并重新启动时,这可能会导致更少的churn(搅拌),但是如果没有完成,可能会导致很多问题。
这都可以通过子类化Subscription来处理,我们有几个实现,您可以查看有关如何执行此操作的示例。
ManualPartitionNamedSubscription
和 ManualPartitionPatternSubscription
。请使用这些或实现你自己的时候要小心。