基本内容:消息中间件优点、pull消费模型优点、分区模型意义、reblance分区分配算法(range,round,sticky)、reblance触发条件、消费者如何优雅退出、可靠性(精确一次语义、副本集)、物理存储
1.消息中间件优点:解耦,削峰,异步。
2.pull消费模型优点:
a.push模型与poll模型对比
1.push模型:服务端主动推送数据给客户端。
优点:
1.实时性好,服务端受到数据后可以立即推送给客户端。
2.客户端无状态。
缺点:
1.无法动态适应消费端消费能力。
2.服务端有状态,需要保存消费进度,增大服务端压力。
2.pull模型: 客户端主动去服务端拉数据。
优点:
1.客户端可以根据自己的消费能力去拉取数据。
2.服务端不需要维护消费状态,消费状态由客户端自己维护。目前消费进度可以保存在kafka内部主题或者zk中。如果采用push模型,需要引入额外的消息确认机制:1.服务端发送消息等待确认;2.客户端成功消费完消息向服务端发送确认;3.服务端收到确认,一条消息消费完成。
缺点:
1.实时性比较差,取决于客户端pull的时间间隔。
2.客户端的请求可能很多无效或者没有数据可供传输,浪费带宽和服务器处理能力。
3.服务端无状态,客户端需要自己保存消费进度。
缺点解决方案:
1.push模型服务端压力大问题,可以对状态做缓存,典型redis;
2.pull实时性差问题,可以对服务端引入push模型,push少量通知消息,通知客户端来主动pull数据;
3.pull无效请求问题,可以设置逐渐延长pull间隔时间的策略,以及合理设计协议尽量缩小请求数据包来节省带宽。
3.分区模型意义
1.提高并发写能力;
2.提高消费端并行处理能力;
3.一个主题的多个分区散布在集群的各个节点上,很好的伸缩性;
4.数据存储、冗余的基本单位。
4.消费者组内分区分配算法
1.RangeAssignor
逐个主题做平均分配,每个消费者消费该主题某个区间的分片。每个消费者i消费的某个主题的区间为[p/c * i + min(i,p%c),p/c*(i+1) + min(i+1,p%c) - 1],消费的分区数量为:p/c + p%c > i ? 1 : 0。p为该主题的分区数,c为消费者数量。
该算法可能出现部分消费者过载情况:消费者组g有10个消费者,他们共同订阅了主题t1,t2,主题t1,t2均有六个分区,如果采用该分配算法,那么前六个消费者每个都会分配到两个分区(t1pi,t2pi),后面的4个消费者将都不会被分配到分区。
2.RoundRobinAssignor
轮训分配。1.对所有topic的所有分区按照topic+partition转string之后的hash进行排序;2.对消费者按字典序排序;3.以轮训的方式将分区分配给消费者
3.StickyAssignor
消费者,分区均从0开始编号
5.reblance触发条件
1.组成员发生变更,比如新的consumer加入组,或是已有的consumer离开组,或是已有的consumer发生崩溃。
2.组订阅topic数发生变化,比如使用基于正则表达式的订阅,当匹配正则式的新topic被创建时则会触发reblance。
3.组订阅topic的分区数发生了变化,比如使用命令行脚本增加了订阅topic的分区数。
6.消费者优雅退出
1.在外部线程里面调用consumer.wakeup()方法。调用consumer.wakeup()可以使消费者线程退出poll(),并抛出WakeupException;如果调用consumer.wakeup()时线程没有等待轮训,那么异常将在下一次poll()时抛出。
2.在消费者线程的finally块里面调用consumer.close(),它会提交任何还没有提交的东西,并向协调器发送消息,告知自己要离开组群,接下来就会触发reblance,而不需要等待会话超时。
//1.消费者伪码:循环拉取消息、遍历处理消息。位移提交具体看(分段提交、分批提加、一消息以提交、同步|异步)
try{
while(true){
ConsumerRecords<String,String> records=consumer.poll(1000);
for(ConsumerRecords<String,String> record : recoeds){
...处理逻辑
...位移提交
}
...唯一提交
}
}catch(WakeupException){
//忽略关闭异常
}finally{
...位移提交
consumer.close();
System.out.println("Closed consumer and we are done");
}
7.可靠性:一个消息只会被写入一次broker,不会重复写入,已经写入的消息不会丢,对于已经写入的消息只会消费一次。于是涉及精确一次投递、副本集、精确一次消费这三个问题。
8.精确一次语义
精确一次投递(一个消息只会被写入一次broker,不会重复写入)0.11之后:
1.单分区的幂等性:
[每个分区]中[精确一次]且[有序](Idempotence: Exactly-once in order semantics per partition)。一个幂等性的操作就是一种被执行多次造成的影响和只执行一次造成的影响一样的操作。现在生产者发送的操作是幂等的了。如果出现导致生产者重试的错误,同样的消息,仍由同样的生产者发送多次,将只被写到kafka broker的日志中一次。对于单个分区,幂等生产者不会因为生产者或broker故障而发送多条重复消息。想要开启这个特性,获得每个分区内的精确一次语义,也就是说没有重复,没有丢失,并且有序的语义,只需要设置producer配置中的”enable.idempotence=true”。
原理:类似于tcp,每一批发送到Kafka的消息都将包含一个序列号,broker将使用这个序列号来删除重复的发送。和只能在瞬态内存中的连接中保证不重复的TCP不同,这个序列号被持久化到副本日志,所以,即使分区的leader挂了,其他的broker接管了leader,新leader仍可以判断重新发送的是否重复了。
2.事务:跨分区(多分区)原子写入(Transactions: Atomic writes across multiple partitions)。
Kafka现在通过新的事务API支持跨分区原子写入。这将允许一个生产者发送一批到不同分区的消息,这些消息要么全部对任何一个消费者可见,要么对任何一个消费者都不可见。这个特性也允许你在一个一个事务中处理消费数据和提交消费偏移量,从而实现端到端的精确一次语义。下面是的代码片段演示了事务API的使用:
producer.initTransactions();
try {
producer.beginTransaction();
producer.send(record1);
producer.send(record2);
producer.commitTransaction();
} catch(ProducerFencedException e) {
producer.close();
} catch(KafkaException e) {
producer.abortTransaction();
}
精确一次消费(对于已经写入的每一条消息,确保只消费一次):把偏移量和记录的处理结果都保存到外部系统,提交时采用事务。
伪精确一次消费(消费处理多次,外部系统看起来好像只消费了一次):[主题+分区+偏移量]做唯一约束,就算重复消费也只会写入外部系统一次。
9.物理存储
kafka的日志设计以分区为单位,每个分区有自己的日志,称为分区日志(partition log),简称日志。
每个日志由一系列日志段文件和对应的索引文件组成,每个日志段文件(.log)有其对应的索引文件(.index)。
日志段文件(.log)保存着真实的kafka记录。每个.log文件都包含了一段位移范围的kafka记录,kafka使用该文件的第一条记录对应的offset来命名此.log文件。broker端的log.segment.bytes限制日志段文件的大小,当日志段文件的大小超过限制后,就建新的日志段文件及其对应的索引文件。
索引文件便于快速定位记录所在的物理位置,与对应的日志文件同名,后缀为.index。索引文件是稀疏索引文件,kafka不会为每条记录都保存对应的索引项(使索引文件小一点),默认是写入若干条记录后才增加一个索引项,默认是至少写入4k的数据后才会在索引文件中添加一个索引项,由broker端log.index.interval.bytes指定。索引文件中的索引项都是按某种规律有序的,因此随机定位记录时,虽然是稀疏的,但二分查找大致保证logN。
生产者写入消息时采用追加的方式,顺序写磁盘的性能是很高的。消费者一般情况下也是顺序读取消息,顺序读磁盘的效率也很高。但有些场景下,消费者可能要随机读取分区,比如定位到某个指定的偏移量位置,这时可以利用索引文件快速定位读取的位置。
参:
kafka权威指南
apache kafka实战
http://jm.taobao.org/2011/04/30/918/
https://cloud.tencent.com/developer/article/1032490
https://blog.youkuaiyun.com/u013256816/article/details/81123600
https://blog.youkuaiyun.com/u013256816/article/details/81123625
https://blog.youkuaiyun.com/lzufeng/article/details/81906031