Memcached 完全指南 / 第 13 章:性能优化
第 13 章:性能优化
13.1 性能优化概览
优化层次
应用层优化
├── 批量操作(减少网络往返)
├── 连接池(复用连接)
├── 序列化优化(减少 CPU 开销)
├── 合理的 TTL(减少无用缓存)
└── Key 设计(均匀分布)
客户端优化
├── 一致性哈希(减少失效)
├── 故障转移(高可用)
├── 压缩(大 Value)
└── noreply(批量写入)
服务端优化
├── 线程数(-t)
├── 连接数(-c)
├── Slab 配置(-f, -n)
├── LRU 配置(lru_maintainer)
└── 系统参数(ulimit, TCP)
系统级优化
├── 内核参数(net.core.somaxconn, tcp_tw_reuse)
├── 文件描述符(ulimit -n)
├── 内存锁定(mlockall)
└── CPU 亲和性(taskset)
13.2 批量操作
批量 GET(最重要)
# ❌ 错误做法:逐个获取
for user_id in user_ids:
data = mc.get(f"user:{user_id}") # N 次网络往返
users.append(data)
# ✅ 正确做法:批量获取
user_keys = [f"user:{uid}" for uid in user_ids]
results = mc.get_multi(user_keys) # 1 次网络往返
for uid in user_ids:
users.append(results.get(f"user:{uid}"))
各语言批量操作
Python:
# 批量获取
keys = ["user:1001", "user:1002", "user:1003"]
results = mc.get_multi(keys)
# results = {"user:1001": ..., "user:1003": ...} # 不在的 Key 不返回
# 批量设置
data = {
"user:1001": json.dumps({"name": "Alice"}),
"user:1002": json.dumps({"name": "Bob"}),
}
mc.set_multi(data, time=3600)
# 批量删除
mc.delete_multi(["user:1001", "user:1002"])
PHP:
<?php
// 批量获取
$keys = ["user:1001", "user:1002", "user:1003"];
$results = $mc->getMulti($keys);
// $results = ["user:1001" => ..., "user:1003" => ...]
// 批量设置
$data = [
"user:1001" => json_encode(["name" => "Alice"]),
"user:1002" => json_encode(["name" => "Bob"]),
];
$mc->setMulti($data, 3600);
// 批量删除
$mc->deleteMulti(["user:1001", "user:1002"]);
Go:
// gomemcache 不直接支持 get_multi,需自行并发
func batchGet(mc *memcache.Client, keys []string) map[string]string {
results := make(map[string]string)
var mu sync.Mutex
var wg sync.WaitGroup
for _, key := range keys {
wg.Add(1)
go func(k string) {
defer wg.Done()
item, err := mc.Get(k)
if err == nil {
mu.Lock()
results[k] = string(item.Value)
mu.Unlock()
}
}(key)
}
wg.Wait()
return results
}
Java:
// 批量获取
Collection<String> keys = Arrays.asList("user:1001", "user:1002", "user:1003");
Map<String, Object> results = mc.getBulk(keys);
// results = {"user:1001": ..., "user:1003": ...}
// 异步批量获取
Future<Map<String, Object>> future = mc.asyncGetBulk(keys);
Map<String, Object> results = future.get();
批量操作性能对比
测试条件:获取 100 个 Key,每个 Value 200B
逐个 get: ~100ms (100 次往返,每次约 1ms)
批量 get_multi: ~2ms (1 次往返)
性能提升: 50x
13.3 noreply 模式
# 批量写入时使用 noreply
for key, value in data.items():
mc.set(key, value, time=3600, noreply=True) # 不等待 STORED 响应
# Python memcached 不直接支持 noreply
# 需要使用 pymemcached
from pymemcache.client.base import Client
mc = Client(('localhost', 11211))
mc.set('key1', 'value1', noreply=True)
mc.set('key2', 'value2', noreply=True)
13.4 连接池优化
连接池配置
# pymemcached 连接池
from pymemcache.client.hash import HashClient
from pymemcache.client.base import Client
# 带连接池的客户端
mc = HashClient(
[
('mc1', 11211),
('mc2', 11211),
('mc3', 11211),
],
timeout=1.0, # 操作超时
connect_timeout=1.0, # 连接超时
retry_attempts=2, # 重试次数
retry_timeout=1, # 重试超时
dead_timeout=5, # 标记死亡超时
)
// Java 连接池配置
ConnectionFactoryBuilder builder = new ConnectionFactoryBuilder()
.setProtocol(Protocol.TEXT)
.setOpTimeout(1000)
.setShouldOptimize(true)
.setFailureMode(FailureMode.Redistribute)
.setLocatorType(LocatorType.CONSISTENT)
.setHashAlg(DefaultHashAlgorithm.KETAMA_HASH)
.setReadBufferSize(16384);
MemcachedClient mc = new MemcachedClient(
builder.build(),
AddrUtil.getAddresses("mc1:11211 mc2:11211 mc3:11211")
);
13.5 序列化优化
# JSON(通用但较慢)
import json
data = json.dumps(obj).encode()
# MessagePack(快 2-5x,体积小 30-50%)
import msgpack
data = msgpack.packb(obj, use_bin_type=True)
# orjson(最快的 JSON 库)
import orjson
data = orjson.dumps(obj) # 比标准 json 快 3-10x
# 压缩(大 Value 场景)
import zlib
compressed = zlib.compress(data, level=6) # 压缩约 50-70%
序列化性能基准
测试数据:10000 个 User 对象
json.dumps: 85ms 128B/对象
orjson.dumps: 12ms 128B/对象 (7x faster)
msgpack.packb: 18ms 72B/对象 (5x faster, 44% smaller)
json.loads: 72ms
orjson.loads: 10ms (7x faster)
msgpack.unpackb: 15ms (5x faster)
13.6 Key 设计优化
# ❌ 不好的 Key 设计
key = "user" # 太通用,冲突概率高
key = "a" * 250 # 接近 Key 长度上限
key = "user_data_for_1001_2024" # 太长且无结构
# ✅ 推荐的 Key 设计
key = f"user:{user_id}" # 简洁、明确
key = f"cache:v2:product:{product_id}" # 带版本号
key = f"rate:{service}:{user_id}:{minute}" # 限流键
Key 长度 vs 性能
Key 长度影响:
- 每个 Key 都存储在 Item 中,占用 chunk 空间
- Key 越长 → 落入更大的 Slab Class → 浪费更多内存
- Key 越长 → 网络传输数据越多 → 延迟增加
推荐:Key 长度 10-60 字节
13.7 TTL 策略
# ❌ 不好的 TTL 策略
mc.set("config", data, time=0) # 永不过期 → 配置变更时旧数据永不更新
mc.set("session", data, time=86400*365) # 1年 → 浪费内存
# ✅ 推荐的 TTL 策略
mc.set("config", data, time=300) # 5 分钟(配置变更 5 分钟后生效)
mc.set("session", data, time=1800) # 30 分钟
mc.set("product", data, time=3600) # 1 小时
mc.set("hot_rank", data, time=60) # 1 分钟(热点数据频繁更新)
13.8 系统级优化
内核参数调优
# /etc/sysctl.conf
# 增大监听队列
net.core.somaxconn = 65535
# 增大 TCP 连接队列
net.ipv4.tcp_max_syn_backlog = 65535
# 启用 TCP TIME_WAIT 重用
net.ipv4.tcp_tw_reuse = 1
# 减小 TIME_WAIT 超时
net.ipv4.tcp_fin_timeout = 30
# 增大本地端口范围
net.ipv4.ip_local_port_range = 1024 65535
# 增大 socket 缓冲区
net.core.rmem_max = 16777216
net.core.wmem_max = 16777216
# 应用参数
sudo sysctl -p
文件描述符
# /etc/security/limits.conf
memcache soft nofile 65535
memcache hard nofile 65535
# systemd 服务文件中
[Service]
LimitNOFILE=65535
# 验证
cat /proc/$(pgrep memcached)/limits | grep "Max open files"
CPU 亲和性
# 绑定 Memcached 到特定 CPU 核心
taskset -cp 0,1,2,3 $(pgrep memcached)
# 或在 systemd 中配置
[Service]
CPUAffinity=0 1 2 3
NUMA 优化
# 在 NUMA 架构上,绑定到单个 NUMA 节点
numactl --interleave=all memcached -m 4096 -t 8
# 或绑定到特定 NUMA 节点
numactl --cpunodebind=0 --membind=0 memcached -m 4096 -t 8
13.9 生产级配置模板
#!/bin/bash
# 生产环境 Memcached 启动脚本
MEMCACHED=/usr/local/memcached/bin/memcached
$MEMCACHED \
-d \
-m 4096 \
-p 11211 \
-l 10.0.1.10 \
-c 65535 \
-t 8 \
-f 1.25 \
-n 72 \
-k \
-u memcache \
-P /var/run/memcached/memcached.pid \
-o lru_maintainer,slab_automove,slab_reassign,maxconns_fast,hash_algorithm=murmur3,lru_crawler \
-R 20 \
-b 4096 \
-U 0
13.10 性能问题排查清单
#!/bin/bash
# 性能问题排查脚本
echo "=== Memcached 性能排查 ==="
# 1. 命中率
echo "--- 命中率 ---"
STATS=$(echo "stats" | nc localhost 11211)
HITS=$(echo "$STATS" | grep "get_hits" | awk '{print $3}')
MISSES=$(echo "$STATS" | grep "get_misses" | awk '{print $3}')
echo "命中率: $(echo "scale=2; $HITS*100/($HITS+$MISSES)" | bc)%"
# 2. 内存使用
echo "--- 内存 ---"
echo "$STATS" | grep -E "bytes |limit_maxbytes|evictions"
# 3. 连接
echo "--- 连接 ---"
echo "$STATS" | grep -E "curr_connections|max_connections|rejected"
# 4. Slab 碎片
echo "--- Slab 利用率 ---"
echo "stats slabs" | nc localhost 11211 | grep -E "mem_requested|chunk_size|total_pages"
# 5. CPU/内存
echo "--- 系统资源 ---"
PID=$(echo "$STATS" | grep "pid" | awk '{print $3}')
ps -p $PID -o %cpu,%mem,rss,vsz
echo "=== 排查完成 ==="
扩展阅读
小结
| 要点 | 内容 |
|---|---|
| 最大优化 | 批量 get_multi,减少 50x 网络往返 |
| 连接池 | 复用连接,避免每次请求创建/销毁连接 |
| 序列化 | 优先 orjson / MessagePack |
| Key 设计 | 简洁、有结构、10-60 字节 |
| 系统调优 | somaxconn、文件描述符、TCP 参数 |