Add km module kafka

This commit is contained in:
leewei
2023-02-14 14:57:39 +08:00
parent 229140f067
commit 469baad65b
4310 changed files with 736354 additions and 46204 deletions

View File

@@ -0,0 +1,112 @@
结论:按照时间的过期策略,时间是按照时间索引文件的最后一条数据里面记录的时间作为是否过期的判断标准。那么另外一个问题,这个时间是怎么写入的,继续看下面
```java
/**
* If topic deletion is enabled, delete any log segments that have either expired due to time based retention
* or because the log size is > retentionSize.
* Whether or not deletion is enabled, delete any log segments that are before the log start offset
*/
// 清理策略
def deleteOldSegments(): Int = {
if (config.delete) {
// 清理策略保存时间、保存大小、开始offset
deleteRetentionMsBreachedSegments() + deleteRetentionSizeBreachedSegments() + deleteLogStartOffsetBreachedSegments()
} else {
deleteLogStartOffsetBreachedSegments()
}
}
// 调用按照时间的清理策略
private def deleteRetentionMsBreachedSegments(): Int = {
if (config.retentionMs < 0) return 0
val startMs = time.milliseconds
// segment按照 startMs - segment.largestTimestamp > config.retentionMs 的策略进行清理.
// 那么我们再看一下 segment.largestTimestamp 的时间是怎么获取的
deleteOldSegments((segment, _) => startMs - segment.largestTimestamp > config.retentionMs,
reason = s"retention time ${config.retentionMs}ms breach")
}
/**
* The largest timestamp this segment contains.
*/
// LogSegment的代码可以发现如果maxTimestampSoFar>0时就是maxTimestampSoFar否则是最近一次修改时间
// 那么maxTimestampSoFar是怎么获取的呢
def largestTimestamp = if (maxTimestampSoFar >= 0) maxTimestampSoFar else lastModified
// maxTimestampSoFar相当于是时间索引的最后一个entry的时间那么我们继续看一下timeIndex.lastEntry是什么时间
def maxTimestampSoFar: Long = {
if (_maxTimestampSoFar.isEmpty)
_maxTimestampSoFar = Some(timeIndex.lastEntry.timestamp)
_maxTimestampSoFar.get
}
// 获取时间索引的最后一个entry里面的时间&offset
// 在同一个时间索引文件里面,时间字段是单调递增的,因此这里获取到的是时间索引里面最大的那个时间。
// 那么这个时间是怎么写入的呢?我们继续往下看,看时间索引的写入这块
private def lastEntryFromIndexFile: TimestampOffset = {
inLock(lock) {
_entries match {
case 0 => TimestampOffset(RecordBatch.NO_TIMESTAMP, baseOffset)
case s => parseEntry(mmap, s - 1)
}
}
}
```
时间索引文件这个时间是如何写入的?
结论如果配置了LOG_APPEND_TIME那么就是写入服务器的时间。如果是配置CREATE_TIME那么就是record时间里面的最大的那一个。
```java
// 从TimeIndex类的maybeAppend方法一步一步的向上查找查看里面的时间数据的写入我们可以发现
// 这个时间是在 LogValidator.validateMessagesAndAssignOffsets 这个方法里面生成的
// 遍历records
for (batch <- records.batches.asScala) {
validateBatch(topicPartition, firstBatch, batch, origin, toMagicValue, brokerTopicStats)
val recordErrors = new ArrayBuffer[ApiRecordError](0)
for ((record, batchIndex) <- batch.asScala.view.zipWithIndex) {
validateRecord(batch, topicPartition, record, batchIndex, now, timestampType,
timestampDiffMaxMs, compactedTopic, brokerTopicStats).foreach(recordError => recordErrors += recordError)
// we fail the batch if any record fails, so we stop appending if any record fails
if (recordErrors.isEmpty)
// 拼接offset这里还会计算那个时间戳
builder.appendWithOffset(offsetCounter.getAndIncrement(), record)
}
processRecordErrors(recordErrors)
}
//
private long appendLegacyRecord(long offset, long timestamp, ByteBuffer key, ByteBuffer value, byte magic) throws IOException {
ensureOpenForRecordAppend();
if (compressionType == CompressionType.NONE && timestampType == TimestampType.LOG_APPEND_TIME)
// 定义了LOG_APPEND_TIME则使用logAppendTime
timestamp = logAppendTime;
int size = LegacyRecord.recordSize(magic, key, value);
AbstractLegacyRecordBatch.writeHeader(appendStream, toInnerOffset(offset), size);
if (timestampType == TimestampType.LOG_APPEND_TIME)
timestamp = logAppendTime;
long crc = LegacyRecord.write(appendStream, magic, timestamp, key, value, CompressionType.NONE, timestampType);
// 时间计算
recordWritten(offset, timestamp, size + Records.LOG_OVERHEAD);
return crc;
}
// 最值的计算
private void recordWritten(long offset, long timestamp, int size) {
if (numRecords == Integer.MAX_VALUE)
throw new IllegalArgumentException("Maximum number of records per batch exceeded, max records: " + Integer.MAX_VALUE);
if (offset - baseOffset > Integer.MAX_VALUE)
throw new IllegalArgumentException("Maximum offset delta exceeded, base offset: " + baseOffset +
", last offset: " + offset);
numRecords += 1;
uncompressedRecordsSizeInBytes += size;
lastOffset = offset;
if (magic > RecordBatch.MAGIC_VALUE_V0 && timestamp > maxTimestamp) {
// 时间更新最后时间索引记录的是maxTimestamp这个字段
maxTimestamp = timestamp;
offsetOfMaxTimestamp = offset;
}
}
```

View File

@@ -0,0 +1,112 @@
结论:按照时间的过期策略,时间是按照时间索引文件的最后一条数据里面记录的时间作为是否过期的判断标准。那么另外一个问题,这个时间是怎么写入的,继续看下面
```java
/**
* If topic deletion is enabled, delete any log segments that have either expired due to time based retention
* or because the log size is > retentionSize.
* Whether or not deletion is enabled, delete any log segments that are before the log start offset
*/
// 清理策略
def deleteOldSegments(): Int = {
if (config.delete) {
// 清理策略保存时间、保存大小、开始offset
deleteRetentionMsBreachedSegments() + deleteRetentionSizeBreachedSegments() + deleteLogStartOffsetBreachedSegments()
} else {
deleteLogStartOffsetBreachedSegments()
}
}
// 调用按照时间的清理策略
private def deleteRetentionMsBreachedSegments(): Int = {
if (config.retentionMs < 0) return 0
val startMs = time.milliseconds
// segment按照 startMs - segment.largestTimestamp > config.retentionMs 的策略进行清理.
// 那么我们再看一下 segment.largestTimestamp 的时间是怎么获取的
deleteOldSegments((segment, _) => startMs - segment.largestTimestamp > config.retentionMs,
reason = s"retention time ${config.retentionMs}ms breach")
}
/**
* The largest timestamp this segment contains.
*/
// LogSegment的代码可以发现如果maxTimestampSoFar>0时就是maxTimestampSoFar否则是最近一次修改时间
// 那么maxTimestampSoFar是怎么获取的呢
def largestTimestamp = if (maxTimestampSoFar >= 0) maxTimestampSoFar else lastModified
// maxTimestampSoFar相当于是时间索引的最后一个entry的时间那么我们继续看一下timeIndex.lastEntry是什么时间
def maxTimestampSoFar: Long = {
if (_maxTimestampSoFar.isEmpty)
_maxTimestampSoFar = Some(timeIndex.lastEntry.timestamp)
_maxTimestampSoFar.get
}
// 获取时间索引的最后一个entry里面的时间&offset
// 在同一个时间索引文件里面,时间字段是单调递增的,因此这里获取到的是时间索引里面最大的那个时间。
// 那么这个时间是怎么写入的呢?我们继续往下看,看时间索引的写入这块
private def lastEntryFromIndexFile: TimestampOffset = {
inLock(lock) {
_entries match {
case 0 => TimestampOffset(RecordBatch.NO_TIMESTAMP, baseOffset)
case s => parseEntry(mmap, s - 1)
}
}
}
```
时间索引文件这个时间是如何写入的?
结论如果配置了LOG_APPEND_TIME那么就是写入服务器的时间。如果是配置CREATE_TIME那么就是record时间里面的最大的那一个。
```java
// 从TimeIndex类的maybeAppend方法一步一步的向上查找查看里面的时间数据的写入我们可以发现
// 这个时间是在 LogValidator.validateMessagesAndAssignOffsets 这个方法里面生成的
// 遍历records
for (batch <- records.batches.asScala) {
validateBatch(topicPartition, firstBatch, batch, origin, toMagicValue, brokerTopicStats)
val recordErrors = new ArrayBuffer[ApiRecordError](0)
for ((record, batchIndex) <- batch.asScala.view.zipWithIndex) {
validateRecord(batch, topicPartition, record, batchIndex, now, timestampType,
timestampDiffMaxMs, compactedTopic, brokerTopicStats).foreach(recordError => recordErrors += recordError)
// we fail the batch if any record fails, so we stop appending if any record fails
if (recordErrors.isEmpty)
// 拼接offset这里还会计算那个时间戳
builder.appendWithOffset(offsetCounter.getAndIncrement(), record)
}
processRecordErrors(recordErrors)
}
//
private long appendLegacyRecord(long offset, long timestamp, ByteBuffer key, ByteBuffer value, byte magic) throws IOException {
ensureOpenForRecordAppend();
if (compressionType == CompressionType.NONE && timestampType == TimestampType.LOG_APPEND_TIME)
// 定义了LOG_APPEND_TIME则使用logAppendTime
timestamp = logAppendTime;
int size = LegacyRecord.recordSize(magic, key, value);
AbstractLegacyRecordBatch.writeHeader(appendStream, toInnerOffset(offset), size);
if (timestampType == TimestampType.LOG_APPEND_TIME)
timestamp = logAppendTime;
long crc = LegacyRecord.write(appendStream, magic, timestamp, key, value, CompressionType.NONE, timestampType);
// 时间计算
recordWritten(offset, timestamp, size + Records.LOG_OVERHEAD);
return crc;
}
// 最值的计算
private void recordWritten(long offset, long timestamp, int size) {
if (numRecords == Integer.MAX_VALUE)
throw new IllegalArgumentException("Maximum number of records per batch exceeded, max records: " + Integer.MAX_VALUE);
if (offset - baseOffset > Integer.MAX_VALUE)
throw new IllegalArgumentException("Maximum offset delta exceeded, base offset: " + baseOffset +
", last offset: " + offset);
numRecords += 1;
uncompressedRecordsSizeInBytes += size;
lastOffset = offset;
if (magic > RecordBatch.MAGIC_VALUE_V0 && timestamp > maxTimestamp) {
// 时间更新最后时间索引记录的是maxTimestamp这个字段
maxTimestamp = timestamp;
offsetOfMaxTimestamp = offset;
}
}
```

View File

@@ -0,0 +1,42 @@
# Kafka服务端—权限控制
[TOC]
资源类型:
- UNKNOWN 未知
- ANY任意的资源
- TOPICTopic
- GROUP消费组
- CLUSTER整个集群
- TRANSACTIONAL_ID事物ID
- DELEGATION_TOKENToken
资源操作:
- UNKNOWN未知
- ANY任意的操作
- ALL所有的操作
- READ
- WRITE
- CREATE创建
- DELETE删除
- ALTER修改
- DESCRIBE描述查看
- CLUSTER_ACTION集群动作
- DESCRIBE_CONFIGS查看配置
- ALTER_CONFIGS修改配置
- IDEMPOTENT_WRITE幂等写
资源书写类型:
- UNKNOWN未知
- ANY任意
- MATCH满足LITERAL、PREFIXED或者*的任意中的一个即可
- LITERAL全匹配完全按照原文匹配
- PREFIXED前缀匹配
认证结果:
- ALLOWED允许
- DENIED拒绝

View File

@@ -0,0 +1,180 @@
# Kafka集群平稳滚动重启实践
[TOC]
## 0、前言
Kafka集群的滚动重启是一件非常危险的事情操作不当的情况下可能会导致Kafka集群不可服务。即便操作上准确无误也可能因为业务方服务非常敏感、服务健壮性不足、使用的客户端存在BUG等原因导致业务方业务受损。
基于以上的原因以及我们以往的经验我们梳理了一下在对Kafka集群滚动重启中需要做的事情以及注意的点。
## 1、用户告知
### 1.1、告知内容
提前告知用户:
- 我们要做什么;
- 为什么要做;
- 可能的影响及简单处理方式比如因为leader会切换node客户端可能会消费中断需要重启等
- 联系人;
- 操作时间;**在操作时间选择上,建议选择业务方在的工作时间,方便出问题后能及时协同处理**
告知内容例子:
```
标题:
[2021-11-11] XXX-Kafka 集群升级至 kafka_2.12-xxxx
变更原因:
1、性能优化
变更内容:
1、集群单机连接数限制调整到 1200
变更影响:
升级过程中会有leader切换理论上无影响有问题及时联系kafka服务号
联系人:
xxxxx@xxxx.com
计划时间:
2021-11-11T10:00:00+08:00 至 2021-11-11T16:30:00+08:00
```
### 1.2、相关建议
- 增加对自身服务监控比如监控服务对应的Topic的流量监控消费的Lag等指标以便出现问题时能被及时发现
-
---
## 2、滚动重启
**真正操作前,建议演练一下。**
---
### 2.1、整体操作流程
- 1、再次通知用户我们现在要开始进行重启操作有问题随时联系
- 2、重启**非Controller**的一台Broker
- 3、观察重启后指标等是否都正常如果出现异常则进行相应的处理
- 4、告知用户我们已重启一台xxx分钟后要操作剩余所有的机器让用户注意自身服务是否正常有问题随时反馈
- 5、xxx分钟后剩余机器逐台重启**Kafka-Controller放在最后重启**
- 6、操作完成后告知用户已操作完成让用户关注自身服务是否正常有问题随时反馈
---
### 2.2、单台操作流程
单台操作时,主要分两部分,第一部分时操作进行重启,第二部分是重启完成之后观察服务是否正常。
#### 2.2.1、重启
**第一步:停服务**
```bash
# 以kill的方式停Kafka服务。
# 强调不能以kill -9的方式停服
```
**第二步:修改配置**
```bash
# 对本次重启需要进行修改的配置进行修改;
# 强烈要求将本次修改的配置的具体操作步骤罗列出来;
```
**第三步Broker限流**
```bash
# 如果在停服务和启动服务之间的时间间隔非常的久导致启动后需要同步非常多的数据则在启动服务之前我们需要做好副本同步之间的限流否则可能会拉打满带宽挂其他Broker等。
# 这里的需要同步的数据量怎么样算多这个没有一个非常准确的值只要说可能将leader带宽打满拉挂其他Broker都算是数据量大。
```
**第四步:起服务**
```bash
# 启动Kafk服务然后观察服务是否正常
```
---
#### 2.2.2、观察
**第一步:观察启动日志**
```bash
# 查看server.log文件看到该日志后表示Kafka服务端已启动完成
[2021-11-17 14:07:22,459][INFO][main]: [KafkaServer id=2] started
# 查看server.log文件检查是否存在ERROR及FATAL日志如果出现这些日志需要暂停升级并分析出现这些日志的影响。
```
**第二步:观察服务监控**
如果可以做到下列指标的监控的话,建议都在监控系统中,配置上这些监控。
这一步正常来说只要配置上了,如果出现异常监控系统会主动通知,不需要我们细致的去看,所以虽然列的比较多,但是操作的时候不太需要主动去看所有的指标。
```bash
# 服务存活监控;
# 错误日志监控;
# GC监控
# 脏选举监控;
# ISR收缩速度监控
# leader=-1监控
# 网络处理线程负载监控;
# 请求处理线程负载监控;
# 副本未同步监控;
# 系统负载监控(CPU、磁盘IO、磁盘容量、网络带宽、网络丢包、TCP连接数、TCP连接增速、文件句柄数)
```
**第三步:检查变更是否生效**
这一步骤没有什么好说的,就是检查是否生效。
**第四步:观察流量是否正常**
```
观察一存在Broker组的概念则可以观察重启所在的Broker组的整个流量和重启之前是否基本一致。
观察二重点选取几个Broker上的Topic观察流量是否出现异常比如突然没有流入或流出流量了。
```
**第五步:等待副本同步完成**
```bash
# 查看整个集群的副本同步状态确保整个集群都是处于已同步的状态。该信息可以通过LogiKM查看。
# 实际上是不需要整个集群所有的Broker处于已同步的状态只需要是落在所重启的Broker上的所有的分区都处于同步状态即可但是这个不太好判断因此简单粗暴的就是看整个集群都处于同步状态。
```
### 2.3、其他重要说明
- 如果重启中需要同步非常大的数据量Broker本身负载也较高则建议重启操作要避开leader rebalance的时间
- 重启的过程中会进行leader的切换最后一台操作完成之后需要进行leader rebalance
---
## 3、信息记录
操作中,很难避免就不出现任何问题,出现问题时就需要我们做好相关的记录,比如记录:
- 1、重要的业务及其Topic
- 2、敏感的业务及其Topic
- 3、特殊客户端的业务及其Topic
- 4、不合理使用的业务及其Topic
后续我们可以将这些Topic进行重点保障以及再次进行操作的时候我们能够更准确的触达到用户。