分区管理
分区副本机制
- 一个topic可以有多个partition , 合理的分区规则设置, 可以均匀分散消息, 实现负载均衡和水平扩展
- 多个订阅者可以从一个或多个分区中同时消费数据, 以支撑海量数据处理能力 ( 一个partition只能被同组的一个consumer消费 )
- 消息以追加的方式存储到分区中, 多个分区顺序写磁盘的总效率要比随机写内存还要高, 是高吞吐率的重要保证之一
分区leader选举
- 如果某个partition的leader挂了, followers会产生一个新leader,所以的读写都会转移到新leader
- 并没有采用多数投票赢得选举的机制
- zookeeper上针对每个topic会维护一个ISR的集合 ( in-sync replica 已同步的副本, 消息落后leader在容忍范围内 )
- 在ISR集合中, 才有资格被选为leader, 先选第一个做为leader, 如果不行依次类推
- 假设topic有f+1个副本, kafka可以容忍f个不可用
- 当然如果ISR里面的副本都不可用, 那么还可以选择OSR ( out-sync replica 消息同步落后超过设置的阈值 ), 但是会存在数据的不一致
分区重新分配
修改副本因子
副本数量
.
kafka存储
kafka是基于scala和java实现的, 均需要在jvm上运行,高吞吐量会造成频繁的GC影响性能
考虑到这些因素, kafka是使用磁盘而不是broker进程内存来进行数据存储, 并且基于磁盘顺序读写和MMAP技术来实现高性能
存储结构介绍
kafka一个topic下可以存在很多个分区, 不考虑分区副本的情况下, 一个分区对应一个日志.
为了防止单个Log文件过大, Kafka又引入日志分段LogSegment的概念, 将Log切分为多个LogSegment, 相当于一个巨型文件被平均分配为多个相对较小的文件, 这样也便于消息的维护和清理
事实上, Log和LogSegment对应于磁盘上的一个日志文件和两个索引文件, 以及可能的其他文件
1. *.log 日志文件
存储完整的内容, 没啥可讲的
为了提高查找消息的性能,为每一个日志文件添加2个索引索引文件:OffsetIndex 和 TimeIndex, 分别对应着磁盘上两个索引文件,与FileMessageSet共同构成一个LogSegment对象。
2. *.index 偏移量索引文件
- 每一个索引项为8字节,其中相对offset占用4字节,消息的物理地址(position)占用4个字节
- 这样就实现了相对offset与物理地址的映射,相对offset表示消息相对于baseOffSet的偏移量,例如分段后的一个日志文件的baseOffset是32450,它的文件名就是32450.log,那么offset为32455的消息在相对offset就是32455-32450 = 5。
- 另外OffsetIndex是稀疏索引,也就是说不会存储所有的消息的相对offset和position
3. *.timeindex 时间戳索引文件
它是映射时间戳和相对offset, 时间戳和相对offset作为entry,供占用12字节,时间戳占用8字节,相对offset占用4字节,这个索引也是稀疏索引,没有保存全部的消息的entry
消息顺序写入 Producer
不允许删除和修改已写入的消息, 在kafka中只能在日志文件的末尾追加新的消息
通过消费位移offset来决定从哪个位置开始进行消息的消费
页缓存 Producer
为了再一次提高吞吐量, kafka采用了Memory Mapped Files ( mmap 内存映射文件)
操作系统本身有一层缓存, 叫做page cache, 是在内存里的缓存, 也可以称之为os cache
写入磁盘的时候, 直接写入这个os cache这个内存里, 就可以返回方法了, 接下来由os自己决定什么时候把os cache里的数据真的输入磁盘中(每5秒检查一次是否需要将页缓存数据同步到磁盘文件), 仅仅这一个步骤, 性能就可以提升很多
我的理解是, kafka为什么会有极高的性能:
- 直接写内存, 写partition线程就可以返回了, 后续输入磁盘的事 , 交给os线程去做
- 每5秒刷一次磁盘, 避免了频繁刷磁盘, 并且能把5秒内累计的数据, 尽可能的顺序写入磁盘
另外, 网友对于mmap可靠性的分析
从上面实验结果可以判断:
- 如果使用mmap的进程挂了,写到mmap的数据不会丢失,因为已经写到page-cache;page-cache里面的数据对所有进程都是可见的。
- 如果是机器挂掉,比如掉电,page-cache里的数据会丢掉;mmap会最多丢失30s左右的数据。
由于机器挂掉概率较小,而且增量时可以自己做一些msync策略,比如更新1K次之后,主动调用一次msync,这样就可以减少数据丢失。
有理解不对的,还请大家补充。
零拷贝技术 consumer
普通人的思路
假设什么优化都不做, 那么broker要把消息发送给consumer, 大致要走以下几步
- 先看看要读的数据在不在os cache里, 如果不在, 就从磁盘里读取数据放到os cache里
- 把 os cache里的数据拷贝到用户态缓存
- 用户态缓存再拷贝到操作系统层面的socket缓存里
- socket缓存的数据发送到网卡, 最后发给consumer
优化后的流程
os cache缓存里的数据, 直接发送给网卡, socket缓存中仅仅会拷贝一个描述符过去, 不会拷贝数据到socket缓存
简化了两次拷贝的过程
- 从os cache拷贝到用户态缓存
- 从用户态缓存拷贝到socket缓存
而且为了这两次拷贝, 需要两个线程, 线程的切换也加剧了性能的损耗