Memcached add 命令详解(2025 版)
add 是 Memcached 的 “仅当 key 不存在时写入” 命令,是 防止并发覆盖 的原子操作。
与
set区别:
set:存在则覆盖add:存在则失败(返回NOT_STORED)
1. 基本语法
add <key> <flags> <exptime> <bytes> [noreply]\r\n
<value>\r\n
| 参数 | 说明 |
|---|---|
<key> | 键名(≤ 250 字节) |
<flags> | 客户端自定义标志(0~65535) |
<exptime> | 过期时间(秒) |
<bytes> | Value 字节数 |
[noreply] | 可选,异步写入 |
<value> | 实际数据 |
响应:
STORED\r\n→ 成功(key 不存在)NOT_STORED\r\n→ 失败(key 已存在)
2. telnet 实际操作示例
telnet 127.0.0.1 11211
示例 1:第一次 add → 成功
add user:1001 0 0 8
zhangsan
STORED
示例 2:再次 add 同一 key → 失败
add user:1001 0 0 5
lisi
NOT_STORED
说明:
user:1001已存在,拒绝写入
3. 客户端使用 add
Python(pymemcache)
from pymemcache.client.base import Client
client = Client(('127.0.0.1', 11211))
# add 返回 True/False
result = client.add('config:version', 'v1.0.0', expire=3600)
print(result) # True(首次成功)
result = client.add('config:version', 'v2.0.0')
print(result) # False(已存在,失败)
PHP
$mc = new Memcached();
$mc->addServer('127.0.0.1', 11211);
if ($mc->add('lock:job123', 'running', 300)) {
echo "任务锁获取成功";
} else {
echo "任务正在运行中";
}
Java(XMemcached)
boolean success = client.add("counter:init", 3600, "0");
if (success) {
System.out.println("初始化成功");
}
4. add vs set vs replace
| 命令 | key 不存在 | key 存在 | 用途 |
|---|---|---|---|
add | STORED | NOT_STORED | 初始化、锁、防重 |
set | STORED | STORED(覆盖) | 通用写入 |
replace | NOT_STORED | STORED | 仅更新 |
# 演示对比
add newkey 0 0 5 → STORED
set newkey 0 0 3 → STORED(覆盖)
replace newkey 0 0 2 → STORED
add newkey 0 0 1 → NOT_STORED(已存在)
5. 经典应用场景
5.1 分布式锁(防并发)
def acquire_lock(lock_id, timeout=60):
return client.add(f"lock:{lock_id}", "1", expire=timeout)
def release_lock(lock_id):
client.delete(f"lock:{lock_id}")
多个进程竞争:只有第一个
add成功
5.2 初始化配置(防止覆盖)
add app:config:db_host 0 0 9
127.0.0.1
STORED
后续
add失败 → 保证配置只初始化一次
5.3 防止重复提交(防重令牌)
token = "submit_abc123"
if client.add(f"token:{token}", "1", expire=300):
# 允许提交
process_form()
else:
# 重复提交
return "请勿重复操作"
5.4 计数器初始化
add counter:page_views 0 0 1
0
STORED
后续用
incr累加
6. 高级技巧
6.1 add + get 实现“存在即返回”
value = client.get('cache:key')
if value is None:
if client.add('cache:key', compute_expensive(), expire=3600):
value = client.get('cache:key') # 再次读取
return value
防止缓存穿透时的并发计算
6.2 noreply 异步 add(高吞吐)
client.add('log:event', 'click', noreply=True)
# 不等待响应,继续执行
7. 过期时间 exptime 详解
| 值 | 含义 |
|---|---|
0 | 永不过期 |
60 | 60 秒后过期 |
1739431200 | 绝对时间戳(2025-02-13 00:00:00) |
8. 常见错误
| 错误 | 原因 | 解决 |
|---|---|---|
NOT_STORED | key 已存在 | 改用 set 或 replace |
CLIENT_ERROR | 参数格式错 | 检查空格、换行、bytes |
SERVER_ERROR out of memory | 内存不足 | 增加 -m 参数 |
9. 最佳实践
| 建议 | 说明 |
|---|---|
用 add 做 初始化 | 防止重复设置 |
用 add 做 分布式锁 | 简单高效 |
配合 expire 自动释放 | 防止死锁 |
| key 命名清晰 | init:config:xx、lock:job:xx |
不要用 add 做频繁更新 | 性能差 |
10. 调试与验证
# 查看 key 是否存在
get user:1001
# 查看 add 统计
echo "stats" | nc 127.0.0.1 11211 | grep cmd_add
# cmd_add: 10
# add_hits: 5
# add_misses: 5
小结:add 命令速查
add key flags exptime bytes [noreply]
└─> STORED(仅当 key 不存在)
└─> NOT_STORED(key 已存在)
| 项目 | 推荐值 |
|---|---|
flags | 0 或类型标记 |
exptime | 60 ~ 3600 |
bytes | < 100KB |
noreply | 高并发启用 |
练习建议
- 用 telnet 实现“只能 add 一次”的配置
- 用 Python 实现 分布式任务锁
- 模拟 100 个并发请求,只允许 1 个初始化
需要 replace 命令、CAS 乐观锁、incr/decr 计数器?继续问我!