Files
cs-note/hhs/MQ/10-监控与运维/40-MQ-性能调优.md
T
2026-05-24 20:51:06 +08:00

181 lines
6.8 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
---
tags:
- MQ
- 性能调优
- Kafka
- JVM
create time: 2026-05-24 19:52
---
# MQ 性能调优
## 概述
MQ 的性能调优是一个系统工程,涉及 Producer、Broker、Consumer、JVM、操作系统多个层面。本文以 Kafka 为主线,从性能指标出发,逐层拆解调优策略,帮助你建立完整的调优方法论。
## 正文
### 性能指标三角
调优之前先明确目标。MQ 的核心性能指标有三个:
- **吞吐量(Throughput)**:每秒处理的消息数(msgs/sec)或字节数(MB/sec)
- **延迟(Latency)**:消息从发送到被确认/消费的时间,关注 P50、P99、P999
- **可用性(Availability)**:系统可正常服务的时间比例
> [!question]
> 吞吐量和延迟往往是矛盾的——批处理提升吞吐但增加延迟。你的业务场景更看重哪个?为什么?
### Producer 调优
Producer 是性能瓶颈的第一个关口。核心参数和调优思路:
**batch.size**(默认 16KB):批量发送的消息大小上限。增大可以提升吞吐(减少网络往返),但增加内存占用和首次延迟。生产环境建议设为 64KB-1MB。
**linger.ms**(默认 0):等待凑满 batch 的时间。默认 0 表示来一条发一条,设为 5-100ms 可以显著提升吞吐。和 batch.size 配合使用——达到任一条件即发送。
**compression.type**(默认 none):压缩算法。`lz4` 性价比最高(压缩率和速度均衡),`zstd` 压缩率更好但 CPU 开销略高。压缩能显著减少网络带宽和磁盘占用。
**acks**:可靠性级别。
- `acks=0`:不等确认,最快但可能丢消息
- `acks=1`:Leader 确认,平衡选择
- `acks=all`:ISR 全部确认,最安全但最慢
**buffer.memory**(默认 32MB):Producer 端发送缓冲区大小。高吞吐场景下如果 Broker 响应慢,缓冲区可能打满导致阻塞,可适当增大到 64-128MB。
### Broker 调优
Broker 是整个集群的性能中枢。
**刷盘策略**:Kafka 依赖页缓存和顺序写,刷盘策略直接影响性能。
- `flush.messages` 和 `flush.ms` 控制刷盘频率
- 生产环境建议**关闭主动刷盘**(设为超大值),依赖操作系统的页缓存和后台刷盘,配合多副本保证数据安全
**页缓存**:Kafka 的性能秘诀在于大量利用 OS 页缓存。消息写入先进页缓存,异步刷盘。消费者如果跟得上生产者,直接从页缓存读取,接近内存速度。
**网络线程和 IO 线程**:
- `num.network.threads`:处理网络请求的线程数,建议设为 CPU 核数
- `num.io.threads`:处理磁盘 IO 的线程数,建议设为 CPU 核数的 2 倍
- `num.replica.fetchers`:副本同步线程数,高吞吐场景可增大到 2-4
### Consumer 调优
Consumer 的调优重点在于批量拉取和并发处理。
**fetch.min.bytes**(默认 1):一次 fetch 请求的最小数据量。增大可以减少网络往返次数,但增加延迟。建议设为 1KB-64KB。
**fetch.max.wait.ms**(默认 500ms):等待凑满 fetch.min.bytes 的最大时间。配合 fetch.min.bytes 使用,在吞吐和延迟之间找平衡。
**max.poll.records**(默认 500):单次 poll 返回的最大消息数。如果消费逻辑较重(如写数据库),适当减小避免处理超时。
**并发消费**:单个 Consumer 的吞吐有限,通常通过增加 Consumer 数量(不超过 Partition 数量)来提升并行度。
> [!question]
> Consumer 数量超过 Partition 数量会发生什么?如何在不增加 Partition 的情况下提升消费能力?
### JVM 调优
Kafka Broker 运行在 JVM 上,JVM 调优至关重要。
**堆大小**:不宜过大!Kafka Broker 推荐 4-6GB。堆越大 GC 停顿越长,而且 Kafka 的性能很大程度依赖页缓存——堆占了太多内存,页缓存就被挤压了。
**GC 选择**:
- **G1GC**:成熟稳定,适合 4-8GB 堆。关键参数 `-XX:MaxGCPauseMillis=20`
- **ZGC**:超低停顿(<10ms),适合大堆场景,JDK 15+ 生产可用
**直接内存**:Kafka 的网络传输使用堆外内存(DirectBuffer),需要通过 `-XX:MaxDirectMemorySize` 预留足够空间。
### 操作系统调优
操作系统层面的优化往往被忽视,但影响显著:
- **文件描述符**:Kafka 每个 Partition 对应多个日志段文件,需要大量 fd。建议设置 `ulimit -n 100000` 以上
- **TCP 参数**:增大缓冲区 `net.core.rmem_max=16777216`、`net.core.wmem_max=16777216`,启用 TCP 快速打开
- **vm.swappiness**:设为 1(非 0),避免系统过度 swap 导致 Broker 性能急剧下降
- **磁盘调度算法**:SSD 使用 `none`(noop)或 `mq-deadline`,避免 `cfq` 的高开销
### 基准测试工具
调优前后都要用数据说话。常用工具:
- **kafka-producer-perf-test**:Producer 吞吐和延迟测试
- **kafka-consumer-perf-test**:Consumer 吞吐测试
- **JMeter**:支持自定义场景的压测框架
### 性能瓶颈定位决策树
```mermaid
graph TD
Start["性能不达标"] --> CheckWhere{"瓶颈在哪?"}
CheckWhere -->|"Producer 端"| P["Producer 调优"]
CheckWhere -->|"Broker 端"| B["Broker 调优"]
CheckWhere -->|"Consumer 端"| C["Consumer 调优"]
P --> P1["增大 batch.size"]
P --> P2["增大 linger.ms"]
P --> P3["开启压缩"]
P --> P4["acks=1"]
B --> B1["关闭主动刷盘"]
B --> B2["增大网络/IO线程"]
B --> B3["JVM 调优"]
B --> B4["OS 参数调优"]
C --> C1["增大 fetch.min.bytes"]
C --> C2["增加并发消费者"]
C --> C3["增大 max.poll.records"]
```
### Go 性能测试示例
```go
package main
import (
"fmt"
"sync/atomic"
"time"
"github.com/IBM/sarama"
)
func main() {
config := sarama.NewConfig()
config.Producer.RequiredAcks = sarama.WaitForLocal
config.Producer.Compression = sarama.CompressionLZ4 // 开启压缩
config.Producer.Flush.Bytes = 64 * 1024 // batch.size 64KB
config.Producer.Flush.Frequency = 10 * time.Millisecond // linger.ms 10
producer, _ := sarama.NewAsyncProducer([]string{"localhost:9092"}, config)
defer producer.Close()
var count int64
msg := &sarama.ProducerMessage{
Topic: "benchmark",
Value: sarama.StringEncoder("test message payload"),
}
// 10 个 goroutine 并发发送
for i := 0; i < 10; i++ {
go func() {
for {
producer.Input() <- msg
atomic.AddInt64(&count, 1)
}
}()
}
// 每秒输出吞吐量
ticker := time.NewTicker(time.Second)
for range ticker.C {
n := atomic.SwapInt64(&count, 0)
fmt.Printf("Throughput: %d msgs/sec\n", n)
}
}
```
这段代码展示了 Sarama 异步 Producer 的基准测试:开启 LZ4 压缩、64KB 批量、10ms 等待时间,10 个 goroutine 并发发送,每秒统计吞吐量。通过调整参数组合对比不同配置下的性能表现。
## 关联笔记
- [[39-MQ-容器化与-K8s-部署]]
- [[41-MQ-测试策略]]
- [[42-MQ-认证与授权]]