Memcached incr 与 decr 命令(手把手讲解)
💡一则或许对你有用的小广告
欢迎加入小哈的星球 ,你将获得:专属的项目实战 / 1v1 提问 / Java 学习路线 / 学习打卡 / 每月赠书 / 社群讨论
- 新项目:《从零手撸:仿小红书(微服务架构)》 正在持续爆肝中,基于
Spring Cloud Alibaba + Spring Boot 3.x + JDK 17...
,点击查看项目介绍 ;演示链接: http://116.62.199.48:7070 ;- 《从零手撸:前后端分离博客项目(全栈开发)》 2 期已完结,演示链接: http://116.62.199.48/ ;
截止目前, 星球 内专栏累计输出 90w+ 字,讲解图 3441+ 张,还在持续爆肝中.. 后续还会上新更多项目,目标是将 Java 领域典型的项目都整一波,如秒杀系统, 在线商城, IM 即时通讯,权限管理,Spring Cloud Alibaba 微服务等等,已有 3100+ 小伙伴加入学习 ,欢迎点击围观
前言
在分布式系统与高性能应用开发中,Memcached incr 与 decr 命令是处理数值型数据增减的核心工具。它们以极低的延迟和高并发性能,解决了传统数据库在计数场景下的锁竞争问题。无论是统计网页点击量、管理购物车库存,还是实现分布式锁,这两个命令都能提供简洁高效的解决方案。本文将从基础概念、工作原理到实战案例,逐步解析其应用场景与技术细节。
一、基础概念:什么是 incr 和 decr?
Memcached incr 命令用于将存储的数值键值递增一个指定步长,默认步长为 1;decr 命令则相反,将数值键值递减一个步长。其核心特性是原子性,即操作在单个服务器内是不可分割的,避免了多线程/多进程下的数据竞争问题。
形象比喻
可以把 incr/decr 想象为“智能计数器”:
- 每次调用 incr,就像按下电梯楼层按钮,系统内部直接更新当前楼层,无需先读取再写入;
- 而传统读-改-写流程,则像多人同时抢按电梯按钮,可能导致楼层跳变或重复。
语法与参数
incr key [step]
decr key [step]
key
:要操作的键名step
(可选):递增/递减的步长,默认为 1
注意:这两个命令仅对数值型存储有效。若键不存在或值非数字,操作会失败并返回 0。
二、工作原理:原子操作与内存机制
1. 内存存储结构
Memcached 将键值对存储为内存中的slab 对象。当执行 incr/decr 时:
- 直接定位到键对应的内存地址;
- 通过CAS(Compare and Swap)指令快速验证并更新值;
- 无需序列化/反序列化,操作时间复杂度为 O(1)。
2. 原子性保障
原子性通过 CPU 的锁总线机制实现:
- 操作期间,其他线程无法修改同一内存区域;
- 即使百万级并发请求,也能保证计数结果的准确性。
对比传统数据库
场景 | Memcached incr/decr | 数据库 UPDATE 操作 |
---|---|---|
延迟(典型值) | <1ms | 几十到几百毫秒 |
并发能力(QPS) | 10万+ | 几千至万级 |
数据一致性 | 强保证 | 需显式加锁 |
三、核心使用场景与案例
场景 1:实时计数(如网页 PV 统计)
需求:记录某文章的浏览量,要求每秒支持 1 万次请求。
import memcache
mc = memcache.Client(['127.0.0.1:11211'])
key = "article:123:view_count"
mc.add(key, 0, time=3600)
current = mc.incr(key)
print(f"当前浏览量:{current}")
优势:无需数据库交互,避免了频繁写入导致的锁表问题。
场景 2:库存扣减(电商秒杀场景)
挑战:商品库存为 100 件,需保证并发扣减时不超卖。
mc.add("product:456:stock", 100)
while True:
stock = mc.decr("product:456:stock", 1)
if stock >= 0:
# 扣减成功,执行下单操作
break
else:
# 库存不足,回滚事务
mc.incr("product:456:stock", 1)
print("库存不足")
break
注意:需结合业务逻辑处理负值情况,此处仅为简单示例。
场景 3:分布式限流器
需求:限制某个接口每秒最多 100 次请求。
def check_rate_limit(client_ip):
key = f"rate_limit:{client_ip}"
now = int(time.time())
# 每秒重置计数器
if not mc.add(key, 1, time=1):
current = mc.incr(key)
if current > 100:
return False # 超限
return True
通过
time
参数自动过期,实现基于滑动时间窗口的限流。
四、关键注意事项与错误处理
1. 数据类型约束
incr/decr 仅支持数值型存储。若键值非数字(如字符串),操作会失败并返回 0:
set key "hello"
incr key → 返回 0,且键值不变
2. 键不存在的处理
若键未初始化,incr/decr 默认返回 0。建议先用add
命令设置初始值:
add counter 0 3600 1
1
END
incr counter → 成功返回 2
3. 负数溢出问题
当执行decr
导致值小于 0 时:
- Memcached 1.4.14+ 版本允许负数;
- 旧版本会停止减法并返回 0。
mc.set("balance", 50)
mc.decr("balance", 100) → 返回 -50(新版本)或 0(旧版本)
4. 多服务器集群场景
若使用多节点集群:
- 键的分布遵循一致性哈希,同一键始终落在固定节点;
- 需确保所有操作访问同一节点,避免跨节点竞争。
五、进阶技巧与最佳实践
技巧 1:组合使用 add 和 incr
if not mc.add(key, initial_value):
mc.incr(key)
技巧 2:批量操作优化
Memcached 不支持批量 incr/decr,但可通过客户端库合并请求:
pipeline = mc.pipeline()
pipeline.incr("counter1")
pipeline.decr("counter2", 5)
results = pipeline.execute()
技巧 3:与 get 命令配合
current = mc.gets(key)
mc.cas(key, current + 1) # 需配合 gets/cas 实现
结论
Memcached incr 与 decr 命令凭借其原子性和高性能,在计数场景中展现了独特优势。通过本文的案例解析与代码示例,开发者可以掌握其核心用法,并结合业务需求设计出高并发、低延迟的解决方案。无论是实时统计、库存管理还是分布式限流,合理运用这两个命令都能显著提升系统性能与可靠性。
提示:在生产环境中,建议搭配监控工具(如 Nagios 或 Prometheus)跟踪 incr/decr 的 QPS 和错误率,及时发现潜在问题。