Kafka知识点
1.kafka是什么?
2.kafka中的概念有哪些?
3. kafka中Controller的主要作用?
管理和协调Kafka集群,比如:
- Topic分区leader选举:比如broker挂了,controller立即为受影响分区选举leader
- 更新元数据:Topic分区leader选举或副本集合变化等发生后,controller会将元数据广播给所有broker
- 创建、删除Topic请求:做法几乎都是在Zookeeper的/brokers/topics下创建znode,而Controller监听Path下的变更来执行真正的“创建topic”逻辑
- 增加Topic分区数:通过kafka-reassign-partitions脚本
- 扩展集群:Controller自动完成服务发现的工作
脑裂问题:kafka中只有一个Controller负责分区的leader选举、同步broker新增或删除消息等,但由于网络问题,可能同时有两个broker认为自己是controller,这时候就会出现脑裂,其他broker不知道该听从谁的。
如何解决脑裂问题:Controller epoch,当新的controller产生的时候就会在zk中生成一个新的、数值更大的controller epoch标识,并同步给其他的broker进行保存,这样当旧的controller发送指令时,其他broker就会忽略。
4. kafka中zookeeper的主要作用(0.8及以前)?
- broker注册:每个Broker启动时,都会到zookeeper上进行注册,在/brokers/ids目录下创建临时节点,然后将IP地址和端口信息记录到该节点中,检测broker存活;
- 维护分区信息与Broker的对应关系:比如临时节点/brokers/topics/login/3->2,表示Broker ID为3的一个broker,提供了2个分区用于对"login"这个Topic的消息存储;
- 存储消费的状态信息(低版本的 kafka):比如group的配置信息、consumer信息、分区的消费进度Offset,Offset会定时地被记录到Zookeeper上,该消息分区在消费者下次重新消费时,保证能从之前的进度开始进行消费,然而由于Zookeeper自身存在一些问题比如单点故障,高版本的Kafka已经弱化了Zookeeper的这项功能,使用Kafka单独创建一个Topic存储消费信息;
5. kafka如何保证数据的完全生产?
6. kafka如何做到高可靠性?
从Topic的分区副本、producer发送到broker(ACK机制)、leader选举三个方面考虑:
- 分区副本
Kafka为了保证消息高可用、不丢失,一般会做3个备份,备份日志文件被称为replica,有leader replica 和follower replica,而follower replica存在的唯一目的就是防止消息丢失,不参与具体业务逻辑的交互。只有leader 才参与服务,follower只是候补,平时只是信息同步。
- ACK机制
上面讲到的ack机制
- leader选举
在每个分区的leader都会维护一个ISR列表(下面有讲),在leader挂了的时候,并且unclean.leader.election.enable=false(关闭不完全的leader选举)的情况下,会从ISR列表中选取第一个follower作为新的leader,来保证消息的高可靠性。
综上所述,要保证kafka消息的可靠性,至少需要配置一下参数:
- topic级别:replication-factor>=3;
- producer级别:acks=-1;同时发送模式设置producer.type=sync;
- broker级别:关闭不完全的leader选举,即unclean.leader.election.enable=false;
7. Kafka中的ISR列表(in-sync replica)
ISR (in-sync replica)是与leader保持同步的replica集合,我们要保证不丢消息,要保证ISR中至少一个备份存活。就是说不仅需要机器正常,还跟上leader的消息进度,当达到一定程度的时候就会认为“非存活”状态。在0.9.0.0之前,Kafka提供replica lag.max.messages来控制follower最多落后leader的消息数量,超过该follower失效的,就被踢出ISR,常见的导致同步跟不上的原因主要是下面几个:
- 新的副本在进行信息同步的追赶时期
- 网络IO等原因,某些机器处理速度变慢所导致持续消费落后
- 进程卡住(比如Full GC、高频次GC)
0.9.0.0 之后采用Kafka 落后于消费进度的时间长度来判断是否踢出ISR,即如果长时间落后于leader就会被踢出,有效地避免了突发流量偶然落后于leader被不合理的踢出ISR的情况,就有效避免了ISR反复移进移出带来的代价。
8. Kafka中如何保证数据不丢和数据不重复的
数据丢失的原因(对于producer端):
同步模式下ack = 0时,broker接收到还没有写入磁盘,当broker故障时有可能丢失数据;ack=-1时,在follower同步成功之前leader故障,数据也可能丢失。
异步模式下通过buffer控制数据的发送,有两个值来进行控制(时间阈值与消息的数量阈值),如果buffer满了数据还没发送出去,如果立即清理的话风险很大。
数据丢失的解决办法:
producer有丢数据的可能,但是可以通过配置保证消息的不丢失:
同步模式下,设置ack=1;异步模式下当缓冲区满时让生产者一直处于阻塞状态。
对于consumer的话,通过offset提交来保证数据的不丢失,kafka自己记录了offset值,下次消费的时候,接着上次的offset消费。
数据重复的原因:
由于是定期提交offset,线程挂掉或者partition断开连接发生rebalance,导致消费后的数据offset没有提交,下次会重复消费
数据重复的解决办法:
可以将Topic Partation的消费位移保存到外部介质(数据库或者redis)中,比如是一个Map,Map的key是Topic+Partition,value是Offset,定期更新Offset,当Consumer线程关闭时记录Offset,下一次启动Consumer,读取上一次的Offset信息,然后使用consumer.seek()方法指定到上次的offset位置
9. Kafka中数据一致性怎么做的?
10. customer应该从brokes拉消息还是brokers将消息推到consumer?
11. kafka怎么体现消息顺序性?
12. kafka如何做到全局有序
比如,有100条有序数据,生产者发送到kafka集群,kafka的partition有4个,可能的情况就是一个partition保存0-25,一个保存25-50......这样消息在kafka中存储是局部有序了,所以说,kafka是无法保证全局消息有序的,只能局部有序。
两种方案:
方案一,kafka topic只设置一个partition;
方案二,producer基于某个字段作Hash,将消息发送到指定partition
解析:
方案一:kafka默认保证partition内部的消息是有序的,所以设置topic只使用一个partition,这样消息就是全局有序,缺点是一个partition只能被组内一个消费者消费,降低了性能;
方案二:producer可以在发送消息时可以指定需要保证顺序的几条消息发送到同一个partition,这样消费者消费时,消息也是有序的。
producer发送消息时具体到topic的哪一个partition,提供了三种方式:
- 指定partition
- 不指定partition,有指定key 则根据key的hash值与partition数进行运算后确定发送到哪个partition
- 不指定partition,不指定key,则轮询各个partition发送
- 也可以自定义分区器(没试过)
13. Kafka的分区策略
- Range策略
首先对同一个Topic里面的Partition按序号排序,消费者也按字母顺序进行排序,然后两者相除来决定每个消费者线程消费几个分区,如果除不尽,前面的几个消费者将会多消费分区。
- RoundRobin(轮询分配)
首先对同一个Topic里面的Partition按序号排序,消费者也按字母顺序进行排序,按顺序依次把Topic的Partition分配给订阅该Topic的Consumer,缺点是还不够均匀
- sticky策略(Kafka 0.11x以后的)
为了减少资源消耗和异常情况,本次分配时有两个目标:分区的分配要尽可能的均匀;分区的分配尽可能的与上次分配的保持相同(上次C1消费T1P1,这次尽量也是)
14. Coordinator-重点讨论Group Coordinator
- GroupCoordinator:broker端的,每个kafka server都有一个实例,
两大作用:存储Group的元信息(包括Topic列表、Group的配置信息、Consumer信息比如主机名、Partation的消费Offset)和Consumer Rebalance;
如何选举:首先, Kafka 内部有一个 topic专门来存储 group 消费的情况: __consumer_offsets topic ,默认有50个 partition,每个 partition 默认有三个副本,而具体一个 group 的消费情况要存储到哪一个 partition 上,是根据 abs(GroupId.hashCode()) % NumPartitions来计算的(其中,NumPartitions 是 __consumer_offsets 的 partition 数,默认是50个),即其 group.id进行hash得到对应的partition值,该partition leader所在Broker即为Group对应的GroupCoordinator。
- WorkerCoordinator:broker端的,管理GroupCoordinator程序,主要管理workers的分配
- ConsumerCoordinator:consumer端的,只负责与GroupCoordinator通信
15. rebalance
缺点:在Rebalance期间,Group中所有Consumer都不能消费消息
- 消费者加入或者退出;
- Topic的分区数发生变化;
- 订阅Topic的数量发生变化(比如新建的Topic,消费者通过正则匹配到了)
- 选取Coordinator即协调者。对于每1个消费组,要从集群中选择一个broker作为协调者;
- 加入组。这一步中,所有成员都向协调者发送JoinGroup(加入消费组)请求,然后coordinator会从中选择一个consumer担任leader,并把组成员信息以及订阅信息发给leader;
- 同步更新分配方案。leader制定分配方案:根据分配策略决定consumer负责topic的哪些分区,然后把分配方案封装进SyncGroup请求发送给coordinator,coordinator把每个consumer的消费方案返回给每个consumer,这样所有组成员就都知道自己消费哪些分区了
Rebalance Generation:
16. kafka Topic的分区数可以增加或者减少么?
17. 高并发下消息堆积的处理方式(Lag比较高)
- 适当地增大分区:提升生产和消费的并行度;
- 增大线程数:如果有1个消费者线程消费多个分区的情况,建议增加消费者线程,尽量1个消费者线程对应1个分区,从而发挥现有分区数下的最大并行度。
18. consumerGroup中的组员和partition之间如何做负载均衡?
19. Kafka精确回溯到某个时间段的数据
Kafka 0.10以后数据都带有自己的时间戳,所以可以精确回溯
20. Kafka Leader选举过程
Kafka在Zookeeper上针对每个Topic都维护了一个ISR(in-sync replica---已同步的副本)的集合,集合的增减Kafka都会更新该集合,如果某分区的Leader不可用,Kafka就从ISR集合中选择一个副本作为新的Leader。如果ISR中副本都不可用,有两种处理方法:
- 等待一个 ISR 的副本重新恢复正常服务,并选择这个副本作为领 leader (它有极大可能拥有全部数据)。
- 选择第一个重新恢复正常服务的副本(不一定是 ISR 中的)作为leader,默认是这种,不保证已包含所有已commit的消息,不符合强一致性。
21. Kafka的存储机制
22. kafka的幂等性
- 只能保证Producer在单个会话内不丟不重,如果Producer意外挂掉再重启是无法保证的;
- 幂等性不能跨多个Topic-Partition,只能保证单个partition内的幂等性,当涉及多个Topic-Partition时,这中间的状态并没有同步。
系统需要有能力鉴别一条数据到底是不是重复的数据,常用的手段是通过唯一id判断,系统一般需要缓存已经处理的唯一键记录,这样才能更有效率地判断一条数据是不是重复,对于Kafka而言,唯一键是在分区的维度上去做,重复数据的判断让partition的leader判断处理;另外,考虑到一个Partition有多个client写入的情况,client之间很难用同一个唯一键。Kafka Producer在实现时有以下两个重要机制:
- PID:每个Producer在初始化时都会被分配一个唯一的PID,这个PID对用户是不可见的。这里的 PID 是全局唯一的,Producer 故障后重新启动后会被分配一个新的 PID,这也是幂等性无法做到跨会话的一个原因。
- sequence numbers:对于每个PID,Producer发送数据的每个<Topic, Partition>给每条消息对应一个从0开始单调递增的Sequence Number,可以使用更少的空间和更快的查询效率,Server也就是通过这个sequence number来验证数据是否重复。
Server端在缓存中保存了这个seq number,对于接收的每条消息,如果其序号比Server缓存中序号大于1则接受它,否则将其丢弃。 此外,generation必须等于server存储的generation或更大,增加generation阻止来自“僵尸”生成者的任何消息。
23. kafka工具
24. kafka常用命令(来自官网)
- 创建topic
1 bin/kafka-topics.sh --create --zookeeper localhost:2181 --replication-factor 1 --partitions 1 --topic test
- 显示topic
1 bin/kafka-topics.sh --list --zookeeper localhost:2181
- 开启生产者
1 bin/kafka-console-producer.sh --broker-list localhost:9092 --topic my-replicated-topic
- 开启消费者
1 bin/kafka-console-consumer.sh --bootstrap-server localhost:9092 --from-beginning --topic my-replicated-topic
-
显示每个分区lag和偏移量信息
1 kafka-consumer-offset-checker --zookeeper 1.1.1.1:2181/aaa --topic xxxx --group group_id
结果显示为:
1 Group Topic Pid Offset logSize Lag Owner
- 查看单个分区的修改时间、偏移量(另外还有创建时间ctime、修改时间mtime)
1 get /目录/consumers/group_id/offsets/topic名/分区名
设置的话直接用
1 set /目录/consumers/group_id/offsets/topic名/分区名 值
25. kafka的一些特点
- kafka没有提供额外的索引机制来存储offset,所以在kafka中几乎不允许对消息进行“随机读写”;
- kafka和JMS(Java Message Service)实现(activeMQ)不同的是:即使消息被消费,消息仍然不会被立即删除。将会根据broker中的配置要求,保留一定的时间之后删除,释放磁盘空间,比如:
1 log.retention.hours=12 #数据最多保存12小时 2 log.retention.bytes=1073741824 #数据最多1G
26. kafka消息传递的语义
- At most once - 消息传递过程中有可能丢失,丢失的消息也不会重新传递,其实就是保证消息不会重复发送或者重复消费
- At least once - 消息在传递的过程中不可能会丢失,丢失的消息会重新传递,其实就是保证消息不会丢失,但是消息有可能重复发送或者重新被消费
- Exactly once - 这个是大多数场景需要的语义,其实就是保证消息不会丢失,也不会重复被消费,消息只传递一次
27. storm整合kafka(参见博客)
- storm消费kafka
1 TopologyBuilder builder = new TopologyBuilder(); 2 BoltDeclarer generateDeclarer = 3 builder.setBolt("generate", new CommonGenerateBolt(), generateParallelHint); 4 5 final SpoutConfig viewSpout = getSpoutConfig(viewTopic, yconfig, ComponentType.VIEW); 6 KafkaSpout viewKafkaSpout = new KafkaSpout(viewSpout); 7 builder.setSpout("view-spout", viewKafkaSpout, viewSpoutParallelHint);
- storm写入kafka
1 public class ToKafkaBolt extends BaseRichBolt{ 2 private static final Logger Log = LoggerFactory.getLogger(ToKafkaBolt.class); 3 4 public static final String TOPIC = "topic"; 5 public static final String KAFKA_BROKER_PROPERTIES = "kafka.broker.properties"; 6 7 private Producer producer; 8 private OutputCollector collector; 9 private TupleToKafkaMapper Mapper; 10 private KafkaTopicSelector topicselector; 11 12 public ToKafkaBolt withTupleToKafkaMapper(TupleToKafkaMapper mapper){ 13 this.Mapper = mapper; 14 return this; 15 } 16 17 public ToKafkaBolt withTopicSelector(KafkaTopicSelector topicSelector){ 18 this.topicselector = topicSelector; 19 return this; 20 } 21 22 @Override 23 public void prepare(Map stormConf, TopologyContext context, 24 OutputCollector collector) { 25 26 if (Mapper == null) { 27 this.Mapper = new FieldNameBasedTupleToKafkaMapper(); 28 } 29 30 if (topicselector == null) { 31 this.topicselector = new DefaultTopicSelector((String)stormConf.get(TOPIC)); 32 } 33 34 Map configMap = (Map) stormConf.get(KAFKA_BROKER_PROPERTIES); 35 Properties properties = new Properties(); 36 properties.putAll(configMap); 37 ProducerConfig config = new ProducerConfig(properties); 38 producer = new Producer(config); 39 this.collector = collector; 40 } 41 42 @Override 43 public void execute(Tuple input) { 44 // String iString = input.getString(0); 45 K key = null; 46 V message = null; 47 String topic = null; 48 try { 49 key = Mapper.getKeyFromTuple(input); 50 message = Mapper.getMessageFromTuple(input); 51 topic = topicselector.getTopic(input); 52 if (topic != null) { 53 producer.send(new KeyedMessage(topic,message)); 54 }else { 55 Log.warn("skipping key = "+key+ ",topic selector returned null."); 56 } 57 } catch ( Exception e) { 58 // TODO: handle exception 59 Log.error("Could not send message with key = " + key 60 + " and value = " + message + " to topic = " + topic, e); 61 }finally{ 62 collector.ack(input); 63 } 64 } 65 @Override 66 public void declareOutputFields(OutputFieldsDeclarer declarer) { 67 } 68 }