Memcached CAS 命令详解(2025 版)
CAS(Check and Set)是 Memcached 的 乐观锁核心机制,用于 解决并发更新冲突,是高并发场景下的 原子更新神器。
场景:多个线程同时读取 → 修改 → 写回,
set会导致覆盖,CAS保证 “只有数据未被修改才更新”
1. 什么是 CAS?
1. get(带 cas_token)
2. 修改 value
3. cas(检查 cas_token 是否一致)
├─> STORED(未被他人修改)
└─> EXISTS(已被他人修改)
类似数据库的
UPDATE ... WHERE version = old_version
2. 核心命令:gets 和 cas
2.1 gets — 获取带 CAS token 的值
gets <key>\r\n
响应格式:
VALUE <key> <flags> <bytes> <cas_token>\r\n
<value>\r\n
END\r\n
| 字段 | 说明 |
|---|---|
<cas_token> | 64 位整数,每更新一次递增 |
2.2 cas — 带版本检查的写入
cas <key> <flags> <exptime> <bytes> <cas_token> [noreply]\r\n
<value>\r\n
| 参数 | 说明 |
|---|---|
<cas_token> | 从 gets 获取的版本号 |
| 响应 | STORED / EXISTS / NOT_FOUND |
STORED→ 版本一致,更新成功EXISTS→ 版本不一致,已被他人修改NOT_FOUND→ key 不存在
3. telnet 完整流程示例
telnet 127.0.0.1 11211
# 1. 首次写入
set counter:likes 0 0 1
0
STORED
# 2. 读取带 cas_token
gets counter:likes
VALUE counter:likes 0 1 1
0
END
# 3. 模拟两个客户端并发修改
# 客户端 A:
cas counter:likes 0 0 1 1
1
STORED
# 客户端 B(使用旧 cas_token):
cas counter:likes 0 0 1 1
2
EXISTS ← 失败!被 A 修改过
# 客户端 B 重新 gets
gets counter:likes
VALUE counter:likes 0 1 2 ← cas_token 已变为 2
1
END
# 客户端 B 再 cas
cas counter:likes 0 0 1 2
2
STORED
4. 客户端使用 CAS
Python(pymemcache)
from pymemcache.client.base import Client
from pymemcache import ClientError
client = Client(('127.0.0.1', 11211))
# 初始化
client.set('user:1001:score', 100)
# CAS 更新
def increment_score(uid, delta):
key = f'user:{uid}:score'
while True:
result = client.gets(key)
if result is None:
return False
value, cas_token = result
new_value = int(value) + delta
try:
if client.cas(key, str(new_value), cas_token):
print(f"更新成功: {value} → {new_value}")
return True
else:
print("版本冲突,重新尝试...")
except ClientError as e:
if "EXISTS" in str(e):
continue
raise
# 测试
increment_score(1001, 5) # 100 → 105
PHP
$mc = new Memcached();
$mc->addServer('127.0.0.1', 11211);
$mc->set('visits', 100);
// CAS 循环
do {
$data = $mc->get('visits', null, $cas);
$new = $data + 1;
} while ($mc->cas($cas, 'visits', $new) === false);
echo "当前访问: " . $mc->get('visits');
Java(XMemcached)
CASValue<Object> casValue = client.gets("counter");
long cas = casValue.getCas();
Object value = casValue.getValue();
CASResponse response = client.cas("counter", 0, (Integer)value + 1, cas);
if (response == CASResponse.OK) {
System.out.println("CAS 成功");
} else if (response == CASResponse.EXISTS) {
System.out.println("版本冲突");
}
5. 经典应用场景
| 场景 | 说明 |
|---|---|
| 计数器 | 点赞、浏览量、库存扣减 |
| 购物车 | 并发添加商品 |
| 秒杀库存 | cas 扣减,防止超卖 |
| 配置热更新 | 多节点同时修改配置 |
| 状态机 | 订单状态流转 |
秒杀库存扣减(Python)
def seckill(stock_key, user_id):
while True:
result = client.gets(stock_key)
if not result:
return False
stock, cas = result
stock = int(stock)
if stock <= 0:
return False
if client.cas(stock_key, str(stock - 1), cas):
# 扣减成功,记录用户
client.sadd('seckill:users', user_id)
return True
# 否则重试
6. CAS 工作原理(底层)
每更新一次 value → cas_token + 1
| 操作 | cas_token 变化 |
|---|---|
set / add / replace | +1 |
append / prepend | +1 |
incr / decr | +1 |
delete | 失效 |
gets返回的cas_token是 当前最新版本
7. 常见错误与响应
| 响应 | 含义 | 处理 |
|---|---|---|
STORED | 版本一致,更新成功 | 完成 |
EXISTS | 版本冲突 | 重新 gets → 修改 → cas |
NOT_FOUND | key 不存在 | 初始化或放弃 |
ERROR | 命令格式错 | 检查参数 |
8. 最佳实践
| 建议 | 说明 |
|---|---|
| 所有并发写用 CAS | 避免覆盖 |
| 循环重试 | EXISTS 时 gets 重来 |
| 设置最大重试次数 | 防死循环 |
| 结合本地缓存 | 减少 gets 开销 |
| 避免大 value | CAS 适合小数据(如计数器) |
带重试限制的 CAS
def cas_update(key, modify_fn, max_retries=5):
for i in range(max_retries):
result = client.gets(key)
if not result:
return False
value, cas = result
new_value = modify_fn(value)
if client.cas(key, new_value, cas):
return True
raise Exception("CAS 重试失败")
9. 性能对比
| 方式 | 并发安全 | 性能 |
|---|---|---|
set | 否 | 最高 |
add/replace | 部分 | 高 |
CAS | 是 | 中等(多一次 gets) |
推荐:读多写少用
set,写并发用CAS
10. 小结:CAS 流程图
START
↓
gets key → (value, cas_token)
↓
修改 value → new_value
↓
cas key ... cas_token new_value
↓
STORED? → YES → 结束
↓ NO
EXISTS? → 重新 gets(循环)
练习建议
- 用 telnet 模拟 两个客户端抢购 1 件商品
- 用 Python 实现 点赞计数器(支持 1000 并发)
- 实现 订单状态流转:
待支付 → 已支付(防并发)
需要 incr/decr 原子计数、delete 失效、多 key 事务替代方案?继续问我!