最近有个做电商的朋友小王遇到了头疼事——他们平台搞秒杀活动时,经常出现超卖问题,明明库存只剩最后10件商品,却卖出了15单,技术团队排查后发现,当多个用户同时点击"立即购买"时,多个服务器节点同时判断库存充足,结果大家都成功下单了。
"这不就是典型的并发竞争问题嘛!"我听完后脱口而出,在分布式系统中,这种多个节点同时操作共享资源的情况太常见了,要解决这个问题,分布式锁就是你的好帮手,而Redis的setnx命令在这方面可是个狠角色。
想象一下,你们公司卫生间只有三个隔间,但几十号人共用,如果没有锁,可能会出现多人同时挤进一个隔间的尴尬场面,分布式锁就是这个道理——在分布式系统中协调多个节点对共享资源的访问。
分布式锁需要满足几个基本要求:
在单机Redis中,setnx(SET if Not eXists)是最简单的锁实现方式:
SETNX lock_key unique_value
这个命令的神奇之处在于它的原子性——只有当lock_key不存在时才会设置成功,设置成功后返回1,表示获取锁成功;返回0则表示锁已被其他客户端持有。
不过光用setnx还不够完善,我们还需要:
完整的命令应该是这样的:
SET lock_key unique_value NX EX 30
这个命令在设置值的同时指定了过期时间(30秒),所有操作在一个原子命令中完成。
当我们的系统规模扩大,单机Redis变成Redis集群后,情况就变得复杂了,Redis集群采用分片机制,数据分散在不同节点上,这时直接使用setnx会遇到两个主要问题:
Redis官方推荐的Redlock算法是解决集群环境下分布式锁的经典方案,它的核心思想是"多数派":
import time import redis def acquire_lock(redis_nodes, resource, ttl): lock_value = str(time.time()) locked_nodes = 0 start_time = time.time() for node in redis_nodes: try: if node.set(resource, lock_value, nx=True, ex=ttl): locked_nodes += 1 except: continue elapsed_time = (time.time() - start_time) * 1000 validity_time = ttl * 1000 - elapsed_time if locked_nodes >= len(redis_nodes)/2 + 1 and validity_time > 0: return lock_value else: release_lock(redis_nodes, resource, lock_value) return False def release_lock(redis_nodes, resource, lock_value): for node in redis_nodes: try: current_value = node.get(resource) if current_value == lock_value: node.delete(resource) except: continue
Redis 6.0引入了WAIT命令,可以增强数据同步的可靠性:
# 客户端1在主节点上获取锁 SET lock_key unique_value NX EX 30 # 等待同步到至少1个从节点 WAIT 1 1000 # 检查是否同步成功 if 同步成功: 执行业务逻辑 else: 释放锁并重试
如果对性能要求不是极端高,可以指定集群中某个节点专门处理锁请求:
# 使用hash tag确保所有锁键落到同一节点 SET {lock}.order_123 unique_value NX EX 30
在实际项目中,我总结了这些经验:
锁粒度要适中:太粗影响并发度,太细增加管理成本
order_lock:123
global_lock
(太粗)或 order_item_lock:123:456:789
(太细)设置合理的超时时间:根据业务操作的最长时间设置,通常10-30秒
实现锁续约机制:对于长时间操作,可以周期性地延长锁时间
def renew_lock(redis_conn, lock_key, lock_value, ttl): if redis_conn.get(lock_key) == lock_value: redis_conn.expire(lock_key, ttl) return True return False
添加重试机制:获取锁失败后随机等待后重试,避免多个客户端同时重试
import random def acquire_lock_with_retry(redis_conn, lock_key, retry_count=3): for i in range(retry_count): if acquire_lock(redis_conn, lock_key): return True time.sleep(random.uniform(0.1, 0.5)) return False
监控锁竞争情况:记录锁等待时间和获取失败次数,及时发现瓶颈
误解原子性:认为多个命令组合是原子的
SETNX lock_key value EXPIRE lock_key 30
锁值过于简单:容易被其他客户端误删
忽略时钟漂移:不同服务器时间不同步导致锁提前释放
解决方案:使用Redis服务器时间而非客户端时间
未处理网络分区:脑裂情况下可能出现多个客户端持有锁
解决方案:实现fencing token机制或使用Redlock
使用Lua脚本:将多个操作打包成原子操作
if redis.call("get",KEYS[1]) == ARGV[1] then return redis.call("del",KEYS[1]) else return 0 end
选择合适的数据结构:复杂场景可使用Hash而非String
HSET locks order_123 client_id 12345 EX 30
本地缓存加速:对于非严格要求的场景,可以在本地缓存锁状态
回到小王的电商秒杀问题,我们最终采用Redis集群+Redlock的方案,配合适当的锁粒度和超时设置,成功解决了超卖问题,现在他们的秒杀活动可以平稳运行,再也不用担心库存混乱了。
分布式锁看似简单,但在生产环境中要考虑的细节很多,选择方案时要权衡一致性、可用性和性能,没有放之四海皆准的完美方案,只有最适合当前业务场景的解决方案。
本文由 所冷萱 于2025-08-01发表在【云服务器提供商】,文中图片由(所冷萱)上传,本平台仅提供信息存储服务;作者观点、意见不代表本站立场,如有侵权,请联系我们删除;若有图片侵权,请您准备原始证明材料和公证书后联系我方删除!
本文链接:https://vps.7tqx.com/wenda/509513.html
发表评论