Memcached incr 与 decr 命令详解(2025 版)
incr(increase)和 decr(decrease)是 Memcached 的 原子计数器操作,用于 高并发场景下的安全计数。
核心优势:
- 原子性:无需锁,避免竞态
- 高性能:服务端直接操作,微秒级
- 自动创建:
decr 时不存在可设初始值
1. 基本语法
incr — 原子加法
incr <key> <value> [noreply]\r\n
decr — 原子减法
decr <key> <value> [noreply]\r\n
| 参数 | 说明 |
|---|
<key> | 计数器键名 |
<value> | 增减步长(正整数) |
[noreply] | 异步操作 |
响应:
<new_value>\r\n → 当前值(成功)
NOT_FOUND\r\n → key 不存在(incr 失败,decr 可自动创建)
2. telnet 完整示例
telnet 127.0.0.1 11211
# 1. 初始化计数器
set counter:views 0 0 1
0
STORED
# 2. 原子 +1
incr counter:views 1
1
# 3. 连续 +1
incr counter:views 1
2
# 4. 原子 -1
decr counter:views 1
1
# 5. key 不存在时 incr → 失败
incr missing:counter 1
NOT_FOUND
# 6. key 不存在时 decr → 自动创建为 0
decr missing:counter 1
0
# 7. 异步操作
incr counter:views 5 noreply
# 无响应
3. 客户端使用
Python(pymemcache)
from pymemcache.client.base import Client
client = Client(('127.0.0.1', 11211))
# 初始化
client.set('likes:1001', 100)
# 原子 +1
new_val = client.incr('likes:1001', 1)
print(new_val) # 101
# 原子 -1(最小 0)
new_val = client.decr('likes:1001', 1)
print(new_val) # 100
# 异步
client.incr('clicks:home', 1, noreply=True)
PHP
$mc = new Memcached();
$mc->addServer('127.0.0.1', 11211);
$mc->set('visits', 1000);
// +5
$mc->increment('visits', 5);
// -3
$mc->decrement('visits', 3);
// 不存在时自动创建
$mc->increment('new:counter', 1); // → 1
$mc->decrement('new:counter', 1); // → 0
Java(XMemcached)
client.set("score", 0, "50");
long newVal = client.incr("score", 10);
long decVal = client.decr("score", 5);
4. incr vs decr 行为对比
| 操作 | key 不存在 | 最小值 |
|---|
incr | NOT_FOUND(失败) | 无限制 |
decr | 自动创建为 0 | 不能低于 0 |
# decr 不会负数
decr counter 100
0 ← 即使原值为 50,减 100 也只到 0
5. 经典应用场景
| 场景 | 推荐命令 |
|---|
| 页面浏览量 | incr pv:{page_id} 1 |
| 视频播放数 | incr play:{vid} 1 |
| 点赞/点踩 | incr like:{id} 1 |
| 库存扣减 | decr stock:{sku} 1 |
| 限流计数 | incr rate:{ip} 1 + 过期 |
库存扣减(防超卖)
def buy_sku(sku_id, qty=1):
key = f'stock:{sku_id}'
new_stock = client.decr(key, qty)
if new_stock is None:
# 首次购买,初始化库存
if client.set(key, 100 - qty): # 初始 100
return True
return False
if new_stock >= 0:
return True
else:
# 超卖,回滚
client.incr(key, qty)
return False
6. 与 CAS 对比
| 方式 | 适用场景 | 性能 |
|---|
incr/decr | 纯计数 | 最高 |
CAS | 复杂修改(如结构体) | 稍低 |
推荐:能用 incr 就用 incr
7. 高级技巧
7.1 批量计数(客户端循环)
# Memcached 无原生批量 incr
for uid in user_ids:
client.incr(f'online:{uid}', 1)
7.2 带初始值的 decr
# 模拟库存初始 100
client.delete('stock:123') # 确保不存在
client.decr('stock:123', 0) # → 创建为 0
client.incr('stock:123', 100) # → 设置为 100
7.3 限流器(每分钟 100 次)
key = f'rate:{ip}:2025'
client.incr(key, 1)
client.expire(key, 60) # 1 分钟过期
if client.get(key) > b'100':
return "限流"
8. 监控与统计
echo "stats" | nc 127.0.0.1 11211
| 指标 | 说明 |
|---|
cmd_incr | incr 次数 |
cmd_decr | decr 次数 |
incr_hits | 成功次数 |
decr_hits | 成功次数 |
# 实时监控
watch -n 1 "echo 'stats' | nc 127.0.0.1 11211 | grep -E 'incr|decr'"
9. 常见错误
| 错误 | 原因 | 解决 |
|---|
NOT_FOUND | incr 时 key 不存在 | 先 set |
| 值不变 | 步长为 0 | 检查参数 |
| 负数 | decr 超过当前值 | 正常(自动到 0) |
| 字符串错误 | value 不是数字 | 只能用于数字字符串 |
警告:incr 只能对 数字字符串 操作
set str 0 0 5
hello
STORED
incr str 1
CLIENT_ERROR cannot increment non-numeric value
10. 最佳实践
| 建议 | 说明 |
|---|
先 set 初始值 | 避免 NOT_FOUND |
用 decr 防超卖 | 自动到 0 |
异步 noreply | 高并发计数 |
| 定期归档 | get → 落库 → delete |
| key 命名 | pv:page:123, stock:sku:456 |
11. 小结:incr/decr 速查
incr key value [noreply] → 新值 或 NOT_FOUND
decr key value [noreply] → 新值(≥0)或 0
| 项目 | 推荐 |
|---|
| 用途 | 计数器、库存、限流 |
| 初始值 | set key 0 0 1 + "0" |
| 最小值 | decr 自动到 0 |
| 异步 | noreply=True |
练习建议
- 用 telnet 实现 页面 PV 计数器
- 用 Python 实现 商品库存扣减(支持并发)
- 实现 IP 限流(每分钟 10 次)
- 写一个 实时排行榜(
incr 点赞 → zadd 排序)
需要 touch 延长过期、flush_all 清理、stats 监控?继续问我!