181 lines
6.8 KiB
Markdown
181 lines
6.8 KiB
Markdown
|
|
---
|
|||
|
|
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-认证与授权]]
|