http://blog.csdn.net/zhanyuanlin/article/details/76695481
Kakfa MirrorMaker是Kafka 官方提供的跨数据中心的流数据同步方案。其实现原理,其实就是通过从Source Cluster消费消息然后将消息生产到Target Cluster,即普通的消息生产和消费。用户只要通过简单的consumer配置和producer配置,然后启动Mirror,就可以实现准实时的数据同步。
1. Kafka MirrorMaker基本特性
Kafka Mirror的基本特性有:
- 在Target Cluster没有对应的Topic的时候,Kafka MirrorMaker会自动为我们在Target Cluster上创建一个一模一样(Topic Name、分区数量、副本数量)一模一样的topic。如果Target Cluster存在相同的Topic则不进行创建,并且,MirrorMaker运行Source Cluster和Target Cluster的Topic的分区数量和副本数量不同。
- 同我们使用Kafka API创建KafkaConsumer一样,Kafka MirrorMaker允许我们指定多个Topic。比如,TopicA|TopicB|TopicC。在这里,|其实是正则匹配符,MirrorMaker也兼容使用逗号进行分隔。
- 多线程支持。MirrorMaker会在每一个线程上创建一个Consumer对象,如果性能允许,建议多创建一些线程
- 多进程任意横向扩展,前提是这些进程的consumerGroup相同。无论是多进程还是多线程,都是由Kafka ConsumerGroup的设计带来的任意横向扩展性,具体的分区分派,即具体的TopicPartition会分派给Group中的哪个Topic负责,是Kafka自动完成的,Consumer无需关心。
我们使用Kafka MirrorMaker完成远程的AWS(Source Cluster)上的Kafka信息同步到公司的计算集群(Target Cluster)。由于我们的大数据集群只有一个统一的出口IP,因此,Kafka MirrorMaker部署在本地(Target Cluster),它负责从远程的Source Cluster上的AWS Kafka 上拉取数据,然后生产到本地的Kafka。
Kafka MirrorMaker的官方文档一直没有更新,因此新版Kafka为MirrorMaker增加的一些参数、特性等在文档上往往找不到,需要看Kafka MirrorMaker的源码。Kafka MirrorMaker的主类位于kafka.tools.MirrorMaker,尤其是一些参数的解析逻辑和主要的执行流程,会比较有助于我们理解和运维Kafka MirrorMaker。
2. 新旧Consumer API的使用问题
从Kafka 0.9版本开始引入了new consumer API。相比于普通的old consumer api,new Conumser API有以下主要改变:
- 统一了旧版本的High-Level和Low-Level Consumer API;-
- new consumer API消除了对zookeeper的依赖,修改了ConsumerGroup的管理等等协议
- new Consumer API完全使用Java实现,不再依赖Scala环境
- 更好了安全认证只在new consumer中实现,在old consumer中没有。
Kakfa MirrorMaker同时提供了对新旧版本的Consumer API的支持。
默认是旧版API,当添加–new.consumer,MirrorMaker将使用新的Consumer进行消息消费:
// Create consumers
val mirrorMakerConsumers = if (!useNewConsumer) {//如果用户没有配置使用new consumer,则使用旧的consumer
val customRebalanceListener = { val customRebalanceListenerClass = options.valueOf(consumerRebalanceListenerOpt) if (customRebalanceListenerClass != null) { val rebalanceListenerArgs = options.valueOf(rebalanceListenerArgsOpt) if (rebalanceListenerArgs != null) { Some(CoreUtils.createObject[ConsumerRebalanceListener](customRebalanceListenerClass, rebalanceListenerArgs)) } else { Some(CoreUtils.createObject[ConsumerRebalanceListener](customRebalanceListenerClass)) } } else { None } } if (customRebalanceListener.exists(!_.isInstanceOf[ConsumerRebalanceListener])) throw new IllegalArgumentException("The rebalance listener should be an instance of kafka.consumer.ConsumerRebalanceListener") createOldConsumers(//创建旧的consumer numStreams, options.valueOf(consumerConfigOpt), customRebalanceListener, Option(options.valueOf(whitelistOpt)), Option(options.valueOf(blacklistOpt))) } else {//用户指定使用new consumer val customRebalanceListener = { val customRebalanceListenerClass = options.valueOf(consumerRebalanceListenerOpt) if (customRebalanceListenerClass != null) { val rebalanceListenerArgs = options.valueOf(rebalanceListenerArgsOpt) if (rebalanceListenerArgs != null) { Some(CoreUtils.createObject[org.apache.kafka.clients.consumer.ConsumerRebalanceListener](customRebalanceListenerClass, rebalanceListenerArgs)) } else { Some(CoreUtils.createObject[org.apache.kafka.clients.consumer.ConsumerRebalanceListener](customRebalanceListenerClass)) } } else { None } } if (customRebalanceListener.exists(!_.isInstanceOf[org.apache.kafka.clients.consumer.ConsumerRebalanceListener])) throw new IllegalArgumentException("The rebalance listener should be an instance of" + "org.apache.kafka.clients.consumer.ConsumerRebalanceListner") createNewConsumers(//创建new consumer numStreams, options.valueOf(consumerConfigOpt), customRebalanceListener, Option(options.valueOf(whitelistOpt))) }
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
这是我启动Kakfa MirrorMaker 的命令:
nohup ./bin/kafka-mirror-maker.sh --new.consumer --consumer.config config/mirror-consumer.properties --num.streams 40 --producer.config config/mirror-producer.properties --whitelist 'ABTestMsg|AppColdStartMsg|BackPayMsg|WebMsg|GoldOpenMsg|BoCaiMsg' &
- 1
mirror-consumer.properties配置文件如下:
#新版consumer摈弃了对zookeeper的依赖,使用bootstrap.servers告诉consumer kafka server的位置
bootstrap.servers=ip-188-33-33-31.eu-central-1.compute.internal:9092,ip-188-33-33-32.eu-central-1.compute.internal:9092,ip-188-33-33-33.eu-central-1.compute.internal:9092 #如果使用旧版Consumer,则使用zookeeper.connect #zookeeper.connect=ip-188-33-33-31.eu-central-1.compute.internal:2181,ip-188-33-33-32.eu-central-1.compute.internal:2181,ip-188-33-33-33.eu-central-1.compute.internal:2181 1.compute.internal:2181 #change the default 40000 to 50000 request.timeout.ms=50000 #hange default heartbeat interval from 3000 to 15000 heartbeat.interval.ms=30000 #change default session timeout from 30000 to 40000 session.timeout.ms=40000 #consumer group id group.id=africaBetMirrorGroupTest partition.assignment.strategy=org.apache.kafka.clients.consumer.RoundRobinAssignor #restrict the max poll records from 2147483647 to 200000 max.poll.records=20000 #set receive buffer from default 64kB to 512kb receive.buffer.bytes=524288 #set max amount of data per partition to override default 1048576 max.partition.fetch.bytes=5248576 #consumer timeout #consumer.timeout.ms=5000
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
mirror-producer.properties的配置文件如下:
bootstrap.servers=10.120.241.146:9092,10.120.241.82:9092,10.120.241.110:9092 # name of the partitioner class for partitioning events; default partition spreads data randomly #partitioner.class= # specifies whether the messages are sent asynchronously (async) or synchronously (sync) producer.type=sync # specify the compression codec for all data generated: none, gzip, snappy, lz4. # the old config values work as well: 0, 1, 2, 3 for none, gzip, snappy, lz4, respectively compression.codec=none # message encoder serializer.class=kafka.serializer.DefaultEncoder
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
同时,我使用kafka-consumer-groups.sh循环监控消费延迟:
bin/kafka-consumer-groups.sh --bootstrap-server ip-188-33-33-31.eu-central-1.compute.internal:9092,ip-188-33-33-32.eu-central-1.compute.internal:9092,ip-188-33-33-33.eu-central-1.compute.internal:9092 --describe --group africaBetMirrorGroupTest --new-consumer
- 1
当我们使用new KafkaConsumer进行消息消费,要想通过kafka-consumer-groups.sh获取整个group的offset、lag延迟信息,也必须加上–new-consumer,告知kafka-consumer-groups.sh,这个group的消费者使用的是new kafka consumer,即group中所有consumer的信息保存在了Kafka上的一个名字叫做__consumer_offsets
的特殊topic上,而不是保存在zookeeper上。我在使用kafka-consumer-groups.sh的时候就不知道还需要添加–new-consumer,结果我启动了MirrorMaker以后,感觉消息在消费,但是就是在zookeeper的/consumer/ids/上找不到group的任何信息。后来在stack overflow上问了别人才知道。
3. 负载不均衡原因诊断以及问题解决
在我的另外一篇博客《Kafka为Consumer分派分区:RangeAssignor和RoundRobinAssignor》中,介绍了Kafka内置的分区分派策略:RangeAssignor和RoundRobinAssignor。由于RangeAssignor是早期版本的Kafka的唯一的分区分派策略,因此,默认不配置的情况下,Kafka使用RangeAssignor进行分区分派,但是,在MirrorMaker的使用场景下,RoundRobinAssignor更有利于均匀的分区分派。甚至在KAFKA-3831中有人建议直接将MirrorMaker的默认分区分派策略改为RoundRobinAssignor。那么,它们到底有什么区别呢?我们先来看两种策略下的分区分派结果。在我的实验场景下,有6个topic:ABTestMsg|AppColdStartMsg|BackPayMsg|WebMsg|GoldOpenMsg|BoCaiMsg
,每个topic有两个分区。由于MirrorMaker所在的服务器性能良好,我设置--num.streams 40
,即单台MirrorMaker会用40个线程,创建40个独立的Consumer进行消息消费,两个MirrorMaker加起来80个线程,80个并行Consumer。由于总共只有6 * 2=12
个TopicPartition,因此最多也只有12个Consumer会被分派到分区,其余Consumer空闲。
我们来看基于RangeAssignor分派策略的kafka-consumer-groups.sh 的结果:
TOPIC PARTITION CURRENT-OFFSET LOG-END-OFFSET LAG CONSUMER-ID HOST CLIENT-ID ABTestMsg 0 780000 820038 49938 africaBetMirrorGroupTest-4-cf330e66-1319-4925-9605-46545df13453/114.113.198.126 africaBetMirrorGroupTest-0 ABTestMsg 1 774988 820038 55000 africaBetMirrorGroupTest-19-c77523e7-7b87-472b-9a26-cd902888944d/114.113.198.126 africaBetMirrorGroupTest-1 AppColdStartMsg 0 774000 820039 55938 africaBetMirrorGroupTest-19-674d8ad4-39d2-40cc-ae97-f4be9c1bb154/114.113.198.126 africaBetMirrorGroupTest-0 AppColdStartMsg 1 774100 820045 56038 africaBetMirrorGroupTest-15-91c67bf8-0c1c-42ac-97f0-5369794c2d1b/114.113.198.126 africaBetMirrorGroupTest-1 BackPayMsg 0 780000 820038 49938 africaBetMirrorGroupTest-4-cf330e66-1319-4925-9605-46545df13453/114.113.198.126 africaBetMirrorGroupTest-0 BackPayMsg 1 774988 820038 55000 africaBetMirrorGroupTest-19-c77523e7-7b87-472b-9a26-cd902888944d/114.113.198.126 africaBetMirrorGroupTest-1 WebMsg 0 774000 820039 55938 africaBetMirrorGroupTest-19-674d8ad4-39d2-40cc-ae97-f4be9c1bb154/114.113.198.126 africaBetMirrorGroupTest-0 WebMsg 1 774100 820045 56038 africaBetMirrorGroupTest-15-91c67bf8-0c1c-42ac-97f0-5369794c2d1b/114.113.198.126 africaBetMirrorGroupTest-1 GoldOpenMsg 0 780000 820038 49938 africaBetMirrorGroupTest-4-cf330e66-1319-4925-9605-46545df13453/114.113.198.126 africaBetMirrorGroupTest-0 GoldOpenMsg 1 774988 820038 55000 africaBetMirrorGroupTest-19-c77523e7-7b87-472b-9a26-cd902888944d/114.113.198.126 africaBetMirrorGroupTest-1 BoCaiMsg 0 774000 820039 55938 africaBetMirrorGroupTest-19-674d8ad4-39d2-40cc-ae97-f4be9c1bb154/114.113.198.126 africaBetMirrorGroupTest-0 BoCaiMsg 1 774100 820045 56038 africaBetMirrorGroupTest-15-91c67bf8-0c1c-42ac-97f0-5369794c2d1b/114.113.198.126 africaBetMirrorGroupTest-1 - -