强曰为道
与天地相似,故不违。知周乎万物,而道济天下,故不过。旁行而不流,乐天知命,故不忧.
文档目录

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 参数