LogManager
说明,LogManager实例的生成依赖于KafkaScheduler的后台调度管理组件,这个组件用于管理各partition的消息记录下index的信息,包含每个Partition的Log,segment等的管理。
实例创建与启动
在KafkaServer的startup函数中通过调用createLogManager函数来完成实例的函数,
private def createLogManager(zkClient: ZkClient, brokerState: BrokerState)
: LogManager = {
val defaultProps = KafkaServer.copyKafkaConfigToLog(config)
val defaultLogConfig = LogConfig(defaultProps)
从zk中/config/topics找对应的topic的特殊配置的配置信息,如果没有,直接使用默认的配置.
val configs = AdminUtils.fetchAllTopicConfigs(zkUtils).mapValues(
LogConfig.fromProps(defaultProps, _))
这里需要的配置项:
配置项log.cleaner.threads,默认值1.用于配置清理过期日志的线程个数(用于日志合并).
配置项log.cleaner.dedupe.buffer.size,默认值128MB,用于配置清理过期数据的内存缓冲区,这个用于数据清理时,选择的压缩方式时,用于对重复数据的清理排序内存,用于日志合并.
配置项log.cleaner.io.buffer.load.factor,默认值0.9,用于配置清理内存缓冲区的数据装载因子,主要是用于hash,这个因子越小,对桶的重复可能越小,但内存占用越大,用于日志合并.
配置项log.cleaner.io.buffer.size,默认值512KB,用于清理过期数据的IO缓冲区大小,用于日志合并.
配置项message.max.bytes,默认值1000012字节,用于设置单条数据的最大大小.
配置项log.cleaner.io.max.bytes.per.second,用于控制过期数据清理时的IO速度限制,默认不限制速度,用于日志合并.
配置项log.cleaner.backoff.ms,用于定时检查日志是否需要清理的时间间隔(这个主要是在日志合并时使用),默认是15秒.
配置项log.cleaner.enable,是否启用日志的定时清理,默认是启用.
配置项num.recovery.threads.per.data.dir,用于在启动时,用于日志恢复的线程个数,默认是1.
配置项log.flush.scheduler.interval.ms,用于检查日志是否被flush到磁盘,默认不检查.
配置项log.flush.offset.checkpoint.interval.ms,用于定时对partition的offset进行保存的时间间隔,默认值60000ms.
配置项log.retention.check.interval.ms,定期检查保留日志的时间间隔,默认值5分钟.
// read the log configurations from zookeeper
val cleanerConfig = CleanerConfig(numThreads = config.logCleanerThreads,
dedupeBufferSize = config.logCleanerDedupeBufferSize,
dedupeBufferLoadFactor = config.logCleanerDedupeBufferLoadFactor,
ioBufferSize = config.logCleanerIoBufferSize,
maxMessageSize = config.messageMaxBytes,
maxIoBytesPerSecond = config.logCleanerIoMaxBytesPerSecond,
backOffMs = config.logCleanerBackoffMs,
enableCleaner = config.logCleanerEnable)
new LogManager(logDirs = config.logDirs.map(new File(_)).toArray,
topicConfigs = configs,
defaultConfig = defaultLogConfig,
cleanerConfig = cleanerConfig,
ioThreads = config.numRecoveryThreadsPerDataDir,
flushCheckMs = config.logFlushSchedulerIntervalMs,
flushCheckpointMs = config.logFlushOffsetCheckpointIntervalMs,
retentionCheckMs = config.logCleanupIntervalMs,
scheduler = kafkaScheduler,
brokerState = brokerState,
time = time)
}
实例初始化时的默认运行流程:
val RecoveryPointCheckpointFile = "recovery-point-offset-checkpoint"
val LockFile = ".lock"
val InitialTaskDelayMs = 30*1000
private val logCreationOrDeletionLock = new Object
private val logs = new Pool[TopicAndPartition, Log]()
检查日志目录是否被创建,如果没有创建目录,同时检查目录是否有读写的权限.
createAndValidateLogDirs(logDirs)
生成每个目录的.lock文件,并通过这个文件锁定这个目录.
private val dirLocks = lockLogDirs(logDirs)
根据每个目录下的recovery-point-offset-checkpoint文件,生成出checkpoints的集合.这个用于定期更新每个partition的offset记录.
private val recoveryPointCheckpoints = logDirs.map(
dir => (dir, new OffsetCheckpoint(new File(dir, RecoveryPointCheckpointFile)))
).toMap
根据每一个目录,生成一个线程池,线程池的大小是num.recovery.threads.per.data.dir配置的值,
加载每个目录下的offset-checkpoint的文件内容.
读取每个目录下的topic-partitionid的目录,并根据zk中针对此topic的配置文件(或者默认的配置文件),通过offset-checkpoint中记录的此partition对应的offset,生成Log实例.并通过线程池来执行Log实例的加载,也就是日志的恢复.
loadLogs()
如果启用了日志定时清理,生成LogCleaner实例,并根据配置的清理线程个数,生成对应个数的清理线程.
// public, so we can access this from kafka.admin.DeleteTopicTest
val cleaner: LogCleaner =
if(cleanerConfig.enableCleaner)
new LogCleaner(cleanerConfig, logDirs, logs, time = time)
else
null
加载partition的日志的segment的处理流程:
在LogManager实例生成时,会读取每个目录下的topic-partition的目录,并生成Log实例,Log实例初始化时,会读取segment的信息.
/* the actual segments of the log */
private val segments: ConcurrentNavigableMap[java.lang.Long, LogSegment] =
new ConcurrentSkipListMap[java.lang.Long, LogSegment]
这里开始去加载对应的Partition的segments的信息.
loadSegments()
/* Calculate the offset of the next message */
@volatile var nextOffsetMetadata =
new LogOffsetMetadata(activeSegment.nextOffset(), activeSegment.baseOffset,
activeSegment.size.toInt)
val topicAndPartition: TopicAndPartition = Log.parseTopicPartitionName(dir)
info("Completed load of log %s with log end offset %d".format(name, logEndOffset))
val tags = Map("topic" -> topicAndPartition.topic,
"partition" -> topicAndPartition.partition.toString)
接下来看看Log.loadSegments的函数如何加载segment的流程:
private def loadSegments() {
首先检查,如果partition的目录还没有创建,先创建这个目录.
// create the log directory if it doesn't exist