这是我参与8月更文挑战的第21天,活动详情查看:8月更文挑战
首先 kafka 从本质来说,是一个存储日志的工具。因为先是有存储能力,然后在这个基础上赋予消息队列的功能。而存储功能,在 kafka 主要的功能是查找 log ,因为不同的 consumer 开始消费的位置都不一样,既然存储下来,就整体查询 log,从指定的地方开始消费。
存储中,kafka 采用 index+log+稀疏索引 的结构:
- OffsetIndex → <logOffset, physics position>
- TimeIndex → <timestamp, offset>
- 稀疏索引 → 新写入 4KB 数据新增一条 index record
和索引有关的代码:
- AbstractIndex ⇒ 最顶层的抽象类,封装了所有的索引的公共操作
- OffsetIndex ⇒ 位移索引
- TimeIndex ⇒ 时间戳索引
- TransactionIndex ⇒ 事务索引
先说 AbstractIndex 结构**:**
abstract class AbstractIndex(
@volatile var file: File,
val baseOffset: Long,
val maxIndexSize: Int = -1,
val writable: Boolean) extends Closeable {
......
}
- file ⇒ 对应磁盘文件中的一个 index file
- baseOffset ⇒ 索引对象对应日志段对象的起始位移值。其实就是 index file name 对应的数字
- maxIndexSize ⇒ 磁盘索引文件的最大长度。默认情况,10MB
- writable ⇒ 打开方式:读写/只读
说完基本结构,那 kafka 中的索引底层的实现原理是什么?
内存映射文件,即 Java 中的 MappedByteBuffer。
好处:
- 对于索引这种小文件。使用内存映射文件的速度快于普通文件读写
- 同时在 Linux 中,内存映射区域实际上是 page cache
在 AbstractIndex 中,mmap 变量就是这个 MappedByteBuffer 类型。先来看看他的使用:
/** The maximum number of entries this index can hold */
@volatile
private[this] var _maxEntries: Int = mmap.limit() / entrySize
/** The number of entries in this index */
@volatile
protected var _entries: Int = mmap.position() / entrySize
def isFull: Boolean = _entries >= _maxEntries
- _entries ⇒ 当前索引对象有多少个索引项
- _maxEntries ⇒ 当前索引对象最多能容纳多少个索引项
- 判断当前索引文件是否写满
所以继承类控制 index entry 的主要逻辑,就是向 mmap 中添加对应的字段即可。
write index entry
向索引文件中写入新的索引项 ⇒ OffsetIndex 中的 append():
def append(offset: Long, position: Int): Unit = {
inLock(lock) {
// 1:判断索引文件未写满
require(!isFull, "Attempt to append to a full index (size = " + _entries + ").")
// 2:满足一个条件即可写入索引项
// _1. 当前索引文件为空
// _2. 写入的offset > 上一个写入索引项
if (_entries == 0 || offset > _lastOffset) {
trace(s"Adding index entry $offset => $position to ${file.getAbsolutePath}")
// 3-1:向mmap中写入相对位移值
mmap.putInt(relativeOffset(offset))
// 3-2:向mmap中写入物理位置信息
mmap.putInt(position)
// 4:更新元数据统计信息,当前索引项计数器: _entries; 当前索引项最新位移值: _lastOffset
_entries += 1
_lastOffset = offset
// 5:执行校验
require(_entries * entrySize == mmap.position(), entries + " entries but file position in index is " + mmap.position() + ".")
} else {
throw new InvalidOffsetException(s"Attempt to append an offset ($offset) to position $entries no larger than" +
s" the last offset appended (${_lastOffset}) to ${file.getAbsolutePath}.")
}
}
}
其实写入就是:
- 判断 offset 是否允许写入
- mmap 写入相对位移值以及物理位置信息