Memcached最佳实践 #
一、部署最佳实践 #
1.1 硬件规划 #
text
硬件规划建议:
1. 内存
- 根据数据量规划
- 预留20%余量
- 考虑内存碎片
2. CPU
- 多核CPU
- 线程数与核心数匹配
3. 网络
- 千兆网卡
- 低延迟网络
4. 存储
- 不需要持久化存储
- 日志存储足够
示例配置:
小型应用:
- 内存:4GB
- CPU:2核
- 节点数:1
中型应用:
- 内存:16GB
- CPU:4核
- 节点数:3
大型应用:
- 内存:64GB
- CPU:8核
- 节点数:10+
1.2 网络规划 #
text
网络规划建议:
1. 内网部署
- 只监听内网IP
- 不暴露到公网
2. 专用网络
- 使用独立网络
- 减少网络干扰
3. 网络拓扑
- 应用服务器靠近缓存服务器
- 减少网络延迟
示例:
┌─────────────────────────────────────────┐
│ 应用服务器集群 │
│ ┌─────────┐ ┌─────────┐ ┌─────────┐ │
│ │ App1 │ │ App2 │ │ App3 │ │
│ └─────────┘ └─────────┘ └─────────┘ │
└─────────────────────────────────────────┘
│
┌───────┴───────┐
│ 专用缓存网络 │
└───────┬───────┘
│
┌─────────────────────────────────────────┐
│ Memcached服务器集群 │
│ ┌─────────┐ ┌─────────┐ ┌─────────┐ │
│ │ MC1 │ │ MC2 │ │ MC3 │ │
│ └─────────┘ └─────────┘ └─────────┘ │
└─────────────────────────────────────────┘
1.3 配置建议 #
bash
# 生产环境推荐配置
memcached -d -m 2048 -p 11211 -u memcache -c 4096 -t 8 \
-f 1.15 -L -I 1m \
-o lru_crawler,slab_reassign,slab_automove=1 \
-l 192.168.1.100 \
-v >> /var/log/memcached.log 2>&1
# 参数说明:
# -d 后台运行
# -m 2048 2GB内存
# -p 11211 端口
# -u memcache 运行用户
# -c 4096 最大连接数
# -t 8 工作线程数
# -f 1.15 增长因子
# -L 大内存页
# -I 1m 最大Item大小
# -o ... 高级选项
# -l 192.168.1.100 监听内网IP
# -v 详细日志
二、键设计最佳实践 #
2.1 键命名规范 #
text
键命名规范:
1. 使用命名空间
格式:<namespace>:<type>:<id>
2. 使用层级结构
格式:<namespace>:<type>:<id>:<field>
3. 保持简洁
- 避免过长的键名
- 但要保持可读性
示例:
✓ user:info:1001
✓ user:profile:1001
✓ product:detail:2001
✓ order:items:3001
✓ cache:db:users:1001
✗ user_information_cache_user_id_1001
✗ 1001
✗ user-data-1001
2.2 键长度建议 #
text
键长度建议:
1. 最大长度:250字节
2. 推荐长度:< 100字节
3. 平衡可读性和性能
示例:
# 不好的做法
key = "user_information_cache_user_id_1001_profile_data"
# 长度:48字节
# 好的做法
key = "u:1001:p"
# 长度:8字节
# 平衡做法
key = "user:1001:profile"
# 长度:17字节
2.3 键命名示例 #
python
# 用户相关
user:info:1001 # 用户基本信息
user:profile:1001 # 用户资料
user:settings:1001 # 用户设置
user:friends:1001 # 用户好友列表
# 商品相关
product:info:2001 # 商品信息
product:stock:2001 # 商品库存
product:reviews:2001 # 商品评论
# 订单相关
order:info:3001 # 订单信息
order:items:3001 # 订单商品
order:status:3001 # 订单状态
# 缓存相关
cache:db:users:1001 # 数据库缓存
cache:api:weather:bj # API缓存
cache:page:home # 页面缓存
# Session相关
session:web:abc123 # Web Session
session:api:xyz789 # API Session
# 计数器相关
counter:page:views:home # 页面访问计数
counter:api:calls:weather # API调用计数
counter:user:likes:1001 # 用户点赞计数
三、数据存储最佳实践 #
3.1 数据大小优化 #
python
import json
# 不好的做法
data = {
"user_id": 1001,
"user_name": "John Doe",
"user_email": "john@example.com",
"user_description": "A very long description..." * 100
}
# 好的做法
data = {
"id": 1001,
"name": "John Doe",
"email": "john@example.com"
}
# 长文本单独存储或压缩
# 压缩大数据
import gzip
def compress_data(data):
json_str = json.dumps(data)
return gzip.compress(json_str.encode())
def decompress_data(compressed):
json_str = gzip.decompress(compressed).decode()
return json.loads(json_str)
# 使用
large_data = {"description": "..." * 10000}
compressed = compress_data(large_data)
client.set("large:data", compressed, flags=3)
3.2 序列化选择 #
text
序列化格式选择:
1. JSON
优点:可读性好,跨语言
缺点:体积较大
适用:配置数据,需要可读性
2. MessagePack
优点:体积小,速度快
缺点:需要额外库
适用:性能要求高
3. Protobuf
优点:体积最小
缺点:需要定义schema
适用:固定结构数据
4. Pickle(Python)
优点:支持复杂对象
缺点:仅Python,安全风险
适用:Python内部使用
示例:
# JSON
data = {"id": 1001, "name": "John"}
json_str = json.dumps(data) # 体积:32字节
# MessagePack
import msgpack
data = {"id": 1001, "name": "John"}
msgpack_data = msgpack.packb(data) # 体积:18字节
3.3 过期时间设置 #
python
import random
# 根据数据特性设置过期时间
# 1. 静态数据:较长过期时间
client.set("config:app", config_data, expire=86400) # 1天
# 2. 动态数据:较短过期时间
client.set("api:weather", weather_data, expire=1800) # 30分钟
# 3. 临时数据:很短的过期时间
client.set("code:verify", code, expire=300) # 5分钟
# 4. 避免缓存雪崩:添加随机值
def set_with_jitter(key, value, base_expire=3600, jitter=300):
expire = base_expire + random.randint(0, jitter)
client.set(key, value, expire=expire)
# 5. 热点数据:永不过期
client.set("hot:product:1001", product_data, expire=0)
四、缓存策略最佳实践 #
4.1 缓存模式选择 #
text
缓存模式选择:
1. Cache-Aside(旁路缓存)
- 读:先查缓存,未命中查数据库,然后写入缓存
- 写:先更新数据库,然后删除缓存
- 适用:读多写少
2. Write-Through(写穿透)
- 读:先查缓存,未命中查数据库,然后写入缓存
- 写:先写缓存,然后写数据库
- 适用:数据一致性要求高
3. Write-Behind(写回)
- 读:先查缓存
- 写:只写缓存,异步写数据库
- 适用:写性能要求高
4. Write-Around(写绕过)
- 读:先查缓存,未命中查数据库,然后写入缓存
- 写:只写数据库,不写缓存
- 适用:写后很少读
4.2 缓存更新策略 #
python
# Cache-Aside模式
def get_user(user_id):
key = f"user:{user_id}"
# 1. 查缓存
user = client.get(key)
if user is not None:
return json.loads(user)
# 2. 查数据库
user = db.query_user(user_id)
# 3. 写缓存
if user is not None:
client.set(key, json.dumps(user), expire=3600)
return user
def update_user(user_id, user_data):
# 1. 更新数据库
db.update_user(user_id, user_data)
# 2. 删除缓存
key = f"user:{user_id}"
client.delete(key)
# Write-Through模式
def update_user_write_through(user_id, user_data):
key = f"user:{user_id}"
# 1. 更新数据库
db.update_user(user_id, user_data)
# 2. 更新缓存
client.set(key, json.dumps(user_data), expire=3600)
4.3 缓存穿透防护 #
python
def get_user_with_cache_protection(user_id):
key = f"user:{user_id}"
# 1. 查缓存
user = client.get(key)
if user is not None:
if user == b"NULL":
# 缓存的空值
return None
return json.loads(user)
# 2. 查数据库
user = db.query_user(user_id)
# 3. 写缓存
if user is not None:
client.set(key, json.dumps(user), expire=3600)
else:
# 缓存空值,防止缓存穿透
client.set(key, "NULL", expire=300)
return user
4.4 缓存雪崩防护 #
python
import random
def set_with_random_expire(key, value, base_expire=3600, jitter=300):
# 添加随机过期时间,避免同时过期
expire = base_expire + random.randint(0, jitter)
client.set(key, value, expire=expire)
# 批量设置时使用
for user in users:
key = f"user:{user['id']}"
set_with_random_expire(key, json.dumps(user))
4.5 缓存击穿防护 #
python
import threading
import time
def get_hot_user(user_id):
key = f"user:{user_id}"
lock_key = f"lock:{key}"
# 1. 查缓存
user = client.get(key)
if user is not None:
return json.loads(user)
# 2. 获取锁
if client.add(lock_key, "1", expire=10):
try:
# 3. 查数据库
user = db.query_user(user_id)
# 4. 写缓存
if user is not None:
client.set(key, json.dumps(user), expire=3600)
return user
finally:
# 5. 释放锁
client.delete(lock_key)
else:
# 等待并重试
time.sleep(0.1)
return get_hot_user(user_id)
五、监控最佳实践 #
5.1 关键监控指标 #
text
关键监控指标:
1. 性能指标
- 命中率:> 90%
- QPS:监控峰值
- 延迟:< 1ms(本地)
2. 资源指标
- 内存使用率:< 80%
- 连接数:监控峰值
- CPU使用率:监控峰值
3. 业务指标
- 数据量:监控增长
- 淘汰次数:监控增长
- 错误率:监控异常
4. 集群指标
- 节点状态:监控存活
- 数据分布:监控均衡
- 网络延迟:监控延迟
5.2 告警规则 #
text
告警规则:
1. 命中率告警
- 命中率 < 80%:警告
- 命中率 < 60%:严重
2. 内存使用告警
- 内存使用率 > 80%:警告
- 内存使用率 > 90%:严重
3. 连接数告警
- 连接数使用率 > 80%:警告
- 连接数使用率 > 90%:严重
4. 淘汰告警
- 淘汰次数快速增长:警告
5. 节点故障告警
- 节点不可用:严重
5.3 监控脚本 #
bash
#!/bin/bash
# memcached_monitor.sh
HOST="127.0.0.1"
PORT="11211"
# 获取统计信息
stats=$(echo "stats" | nc $HOST $PORT)
# 解析数据
get_hits=$(echo "$stats" | grep "STAT get_hits" | awk '{print $3}')
get_misses=$(echo "$stats" | grep "STAT get_misses" | awk '{print $3}')
bytes=$(echo "$stats" | grep "STAT bytes " | awk '{print $3}')
limit_maxbytes=$(echo "$stats" | grep "STAT limit_maxbytes" | awk '{print $3}')
curr_connections=$(echo "$stats" | grep "STAT curr_connections" | awk '{print $3}')
max_connections=$(echo "$stats" | grep "STAT max_connections" | awk '{print $3}')
evictions=$(echo "$stats" | grep "STAT evictions" | awk '{print $3}')
# 计算指标
if [ $((get_hits + get_misses)) -gt 0 ]; then
hit_rate=$(echo "scale=2; $get_hits * 100 / ($get_hits + $get_misses)" | bc)
else
hit_rate=0
fi
mem_usage=$(echo "scale=2; $bytes * 100 / $limit_maxbytes" | bc)
conn_usage=$(echo "scale=2; $curr_connections * 100 / $max_connections" | bc)
# 检查告警
ALERTS=()
if [ $(echo "$hit_rate < 80" | bc) -eq 1 ]; then
ALERTS+=("命中率低于80%: ${hit_rate}%")
fi
if [ $(echo "$mem_usage > 80" | bc) -eq 1 ]; then
ALERTS+=("内存使用率超过80%: ${mem_usage}%")
fi
if [ $(echo "$conn_usage > 80" | bc) -eq 1 ]; then
ALERTS+=("连接数使用率超过80%: ${conn_usage}%")
fi
# 输出结果
echo "Memcached监控报告"
echo "=================="
echo "命中率: ${hit_rate}%"
echo "内存使用率: ${mem_usage}%"
echo "连接数使用率: ${conn_usage}%"
echo "淘汰次数: $evictions"
if [ ${#ALERTS[@]} -gt 0 ]; then
echo ""
echo "告警信息:"
for alert in "${ALERTS[@]}"; do
echo " - $alert"
done
fi
六、安全最佳实践 #
6.1 网络安全 #
bash
# 1. 只监听内网IP
memcached -l 192.168.1.100
# 2. 使用防火墙限制访问
iptables -A INPUT -p tcp -s 192.168.1.0/24 --dport 11211 -j ACCEPT
iptables -A INPUT -p tcp --dport 11211 -j DROP
# 3. 不要暴露到公网
# 错误示例:
# memcached -l 0.0.0.0 # 危险!
6.2 访问控制 #
bash
# 1. 使用SASL认证
memcached -S -d -m 64 -p 11211
# 2. 创建用户
echo "mech_list: plain" > /etc/sasl2/memcached.conf
saslpasswd2 -a memcached -c user1
# 3. 客户端认证
# Python示例
from pymemcache.client.base import Client
client = Client(
('localhost', 11211),
username='user1',
password='password'
)
6.3 数据安全 #
python
# 1. 敏感数据加密
from cryptography.fernet import Fernet
key = Fernet.generate_key()
cipher = Fernet(key)
def encrypt_data(data):
return cipher.encrypt(data.encode())
def decrypt_data(encrypted):
return cipher.decrypt(encrypted).decode()
# 使用
sensitive_data = "password123"
encrypted = encrypt_data(sensitive_data)
client.set("sensitive:key", encrypted)
# 2. 不存储敏感信息
# 不好的做法
user = {
"id": 1001,
"password": "hashed_password" # 不要存储
}
# 好的做法
user = {
"id": 1001,
"name": "John"
}
七、故障处理最佳实践 #
7.1 故障预防 #
text
故障预防措施:
1. 监控预警
- 实时监控关键指标
- 设置合理的告警阈值
2. 容量规划
- 预估数据增长
- 提前扩容
3. 高可用架构
- 多节点部署
- 故障自动转移
4. 定期备份
- Memcached不支持持久化
- 数据源需要备份
7.2 故障恢复 #
python
# 故障恢复策略
class ResilientClient:
def __init__(self, servers, max_retries=3, timeout=1):
self.servers = servers
self.max_retries = max_retries
self.timeout = timeout
self.client = self._create_client()
def _create_client(self):
from pymemcache.client.hash import HashClient
return HashClient(
self.servers,
use_pooling=True,
timeout=self.timeout,
ignore_exc=True
)
def get(self, key, default=None):
for i in range(self.max_retries):
try:
value = self.client.get(key)
if value is not None:
return value
return default
except Exception as e:
if i == self.max_retries - 1:
print(f"Failed to get {key}: {e}")
return default
time.sleep(0.1)
def set(self, key, value, expire=3600):
for i in range(self.max_retries):
try:
self.client.set(key, value, expire=expire)
return True
except Exception as e:
if i == self.max_retries - 1:
print(f"Failed to set {key}: {e}")
return False
time.sleep(0.1)
八、总结 #
最佳实践要点:
| 方面 | 建议 |
|---|---|
| 部署 | 内网部署、多节点、合理配置 |
| 键设计 | 命名规范、简洁明了 |
| 数据存储 | 优化大小、合理序列化 |
| 缓存策略 | 选择合适模式、防护穿透/雪崩/击穿 |
| 监控 | 关键指标、告警规则 |
| 安全 | 网络安全、访问控制、数据加密 |
| 故障处理 | 预防为主、快速恢复 |
恭喜你完成了Memcached的学习!
最后更新:2026-03-27