3. 压测工具选型与部署:benchmark 工具、ghz、自定义压测脚本的搭建与配置
在多集群 etcd 压测场景中,工具选型直接影响测试结果的可靠性与可复现性。本章围绕三类主流压测工具——etcd 官方 benchmark、通用 gRPC 压测工具 ghz、以及基于 clientv3 的自定义脚本——展开部署与配置说明,并给出选型对比与典型部署拓扑。
3.1 工具选型对比
| 维度 | benchmark (etcd 官方) | ghz (gRPC 通用) | 自定义脚本 (clientv3) |
|---|---|---|---|
| 协议层 | 直接调用 etcd v3 gRPC | 通用 gRPC 反射/Protobuf | etcd clientv3 封装 |
| 安装方式 | go install / 预编译二进制 | go install / 预编译二进制 | 自行编写 + go build |
| 并发模型 | 固定 goroutine 池 | worker 池 + 异步报告 | 完全可控 (协程/连接池) |
| 指标输出 | 延迟分位值、QPS、错误数 | 延迟分布、RPS、状态码 | 自定义 Prometheus/日志 |
| 多集群支持 | 需手动切换 endpoint | 多 target 轮询 | 原生多 client 管理 |
| 适用场景 | 单集群快速基准测试 | 跨集群 gRPC 接口压测 | 复杂业务模型、长稳测试 |
选型建议: 基准测试阶段优先使用 benchmark 工具;多集群横向对比时使用 ghz;需要模拟真实业务读写比例、事务、租约等复杂场景时,必须采用自定义脚本。
3.2 benchmark 工具部署与配置
etcd 官方 benchmark 工具位于 etcd/tools/benchmark 目录下,支持 put、range、txn、watch 等核心操作。部署方式如下:
# 方式一:从源码编译(推荐,版本对齐)
git clone https://github.com/etcd-io/etcd.git --branch v3.5.15
cd etcd/tools/benchmark
go build -o benchmark .
# 方式二:直接 go install
go install go.etcd.io/etcd/v3/tools/benchmark@latest
典型配置参数:
| 参数 | 示例值 | 说明 |
|---|---|---|
--endpoints |
10.0.1.10:2379,10.0.1.11:2379 |
etcd 集群端点列表 |
--conns |
10 |
gRPC 长连接数 |
--clients |
50 |
并发客户端数(goroutine) |
--total |
100000 |
总请求数 |
--key-size |
32 |
Key 字节长度 |
--val-size |
1024 |
Value 字节长度 |
--rate |
10000 |
限速(请求/秒,0 为不限) |
# 示例:100 并发写入 50 万 key,value 2KB
./benchmark put \
--endpoints=https://10.0.1.10:2379,https://10.0.1.11:2379 \
--conns=20 \
--clients=100 \
--total=500000 \
--key-size=64 \
--val-size=2048 \
--rate=0
注意: benchmark 工具默认使用 etcd 的根证书进行 TLS 认证。若集群启用了双向 TLS,需额外指定
--cacert、--cert、--key 参数。生产环境压测前务必确认证书链有效,否则会因 TLS 握手失败导致压测中断。
3.3 ghz 部署与配置
ghz 是通用的 gRPC 压测工具,支持反射协议和 Protobuf 文件两种模式。对于 etcd,推荐使用 Protobuf 文件模式以获得更精确的请求构造。
# 安装 ghz
go install github.com/bojand/ghz/cmd/ghz@latest
# 验证安装
ghz --version
etcd 压测配置示例:
# 使用 Protobuf 文件构造 Put 请求
ghz \
--proto /path/to/etcd/api/etcdserverpb/rpc.proto \
--call etcdserverpb.KV.Put \
--data '{"key":"dGVzdC1rZXk=","value":"dGVzdC12YWx1ZQ=="}' \
--insecure \
--concurrency=50 \
--total=100000 \
--rps=5000 \
10.0.1.10:2379
| 参数 | 说明 | etcd 场景建议 |
|---|---|---|
--proto |
指定 Protobuf 定义文件 | 使用 etcd 源码中的 rpc.proto |
--call |
完整 gRPC 方法名 | etcdserverpb.KV.Put / Range / Txn |
--data |
请求体 JSON(字段需 Base64 编码) | key/value 必须为 Base64 字符串 |
--concurrency |
并发 worker 数 | 通常 50~200 |
--rps |
每秒请求数限制 | 0 表示不限速 |
--insecure |
跳过 TLS 验证 | 测试环境使用;生产环境用 --cacert |
多集群压测技巧: ghz 支持
--multiple-targets 模式,可将多个 etcd 集群端点写入一个文件,实现一轮压测同时向多个集群发送请求,便于横向对比延迟与吞吐。
3.4 自定义压测脚本搭建
当官方工具无法满足复杂业务模型时(如混合读写、事务、租约续期、watch 流),需要基于 etcd clientv3 编写自定义压测脚本。以下是一个最小化框架:
package main
import (
"context"
"fmt"
"sync"
"time"
clientv3 "go.etcd.io/etcd/client/v3"
"go.uber.org/zap"
)
type LoadConfig struct {
Endpoints []string
Concurrency int
TotalOps int
KeyPrefix string
ValueSize int
ReadRatio float64 // 0.0 ~ 1.0
}
type Stats struct {
mu sync.Mutex
ops int
errors int
latencies []time.Duration
}
func runWorker(cli *clientv3.Client, cfg *LoadConfig, stats *Stats, wg *sync.WaitGroup) {
defer wg.Done()
for i := 0; i < cfg.TotalOps/cfg.Concurrency; i++ {
start := time.Now()
key := fmt.Sprintf("%s/key-%d", cfg.KeyPrefix, i)
val := make([]byte, cfg.ValueSize)
var err error
if float64(i)/float64(cfg.TotalOps/cfg.Concurrency) < cfg.ReadRatio {
_, err = cli.Get(context.Background(), key)
} else {
_, err = cli.Put(context.Background(), key, string(val))
}
stats.mu.Lock()
stats.ops++
if err != nil {
stats.errors++
}
stats.latencies = append(stats.latencies, time.Since(start))
stats.mu.Unlock()
}
}
func main() {
cfg := &LoadConfig{
Endpoints: []string{"10.0.1.10:2379", "10.0.1.11:2379"},
Concurrency: 50,
TotalOps: 100000,
KeyPrefix: "/bench/load",
ValueSize: 1024,
ReadRatio: 0.7, // 70% 读,30% 写
}
cli, err := clientv3.New(clientv3.Config{
Endpoints: cfg.Endpoints,
DialTimeout: 5 * time.Second,
Logger: zap.NewNop(),
})
if err != nil {
panic(err)
}
defer cli.Close()
stats := &Stats{}
var wg sync.WaitGroup
for i := 0; i < cfg.Concurrency; i++ {
wg.Add(1)
go runWorker(cli, cfg, stats, &wg)
}
wg.Wait()
fmt.Printf("Total Ops: %d, Errors: %d\n", stats.ops, stats.errors)
}
扩展建议: 生产级自定义脚本应加入 Prometheus 直方图采集、优雅退出信号处理、动态调整并发数、以及多集群 client 池管理。推荐使用
clientv3.NewCtx 结合 grpc.WithBalancer 实现客户端侧负载均衡。
3.5 压测部署拓扑
下图展示了一个典型的多集群 etcd 压测部署架构,压测节点与 etcd 集群之间通过独立网络平面隔离,避免压测流量干扰集群内部心跳与选举。
部署关键原则:
- 压测节点与 etcd 集群之间使用独立交换机或 VLAN,避免压测流量与集群内部心跳、选举、快照流量争抢带宽。
- 每个压测客户端与 etcd 集群之间保持 10~20 个 gRPC 长连接,连接数过多会导致 etcd 侧文件描述符耗尽。
- 多集群压测时,每个集群使用独立的 client 实例,避免连接复用导致请求路由错误。
- 监控数据采集(Prometheus)应走独立的管理网络平面,不占用压测网络带宽。
3.6 配置清单与验证步骤
| 步骤 | 操作 | 验证方法 |
|---|---|---|
| 1 | 安装 benchmark / ghz / 编译自定义脚本 | benchmark --version / ghz --version |
| 2 | 配置 etcd 端点与 TLS 证书 | etcdctl endpoint health 返回健康 |
| 3 | 执行单连接单并发探测(smoke test) | 延迟 < 5ms,无错误 |
| 4 | 逐步增加并发数至目标值 | 观察 QPS 曲线是否线性增长 |
| 5 | 开启限速(--rate)避免过载 | 错误率 < 0.1% |
| 6 | 多集群同时压测 | 各集群指标独立采集,无串扰 |
常见部署问题:
- 连接泄漏: 自定义脚本中未调用
client.Close(),导致 etcd 侧连接数持续增长。建议在 defer 中显式关闭,并监控 etcd 的etcd_network_client_active_connections指标。 - Protobuf 版本不匹配: ghz 使用的
rpc.proto必须与目标 etcd 版本完全一致,否则反序列化失败。建议直接从目标 etcd 二进制或源码中提取 proto 文件。 - 限速参数冲突: benchmark 的
--rate与--clients同时设置时,实际 QPS 取两者较小值。若需要精确控制吞吐,建议只设置--rate并将--clients设为一个较大的值。