# Redis分布式锁怎么应用 ## 一、分布式锁的核心需求 在分布式系统中,当多个进程/服务需要互斥地访问共享资源时,分布式锁成为关键技术。合格的分布式锁需要满足: 1. **互斥性**:同一时刻只有一个客户端能持有锁 2. **防死锁**:即使客户端崩溃,锁也能自动释放 3. **容错性**:只要大部分Redis节点存活,锁仍可用 4. **可重入性**(可选):同一客户端可多次获取同一把锁 ## 二、Redis实现分布式锁的基础方案 ### 2.1 SETNX + DEL 基础命令 ```bash # 获取锁(设置键值+过期时间) SET lock_key unique_value NX PX 30000 # 释放锁(Lua脚本保证原子性) if redis.call("get",KEYS[1]) == ARGV[1] then return redis.call("del",KEYS[1]) else return 0 end
参数说明: - NX
:仅当key不存在时设置 - PX 30000
:30秒自动过期 - unique_value
:客户端唯一标识(如UUID)
释放锁时必须验证value值,避免误删其他客户端的锁。非原子操作可能导致:
# 错误示例时序: 1. ClientA获取锁(value=123) 2. ClientA因GC停顿导致锁过期 3. ClientB获取到锁(value=456) 4. ClientA恢复执行DEL命令 5. Result:ClientB的锁被意外删除!
当使用Redis集群时,官方推荐Redlock算法:
# Python伪代码示例 def acquire_lock(servers, resource, ttl): start_time = time.time() successes = 0 for server in servers: if server.set(resource, random_value, nx=True, px=ttl): successes += 1 elapsed = time.time() - start_time if successes >= len(servers)/2 + 1 and elapsed < ttl: return True else: # 失败时需要释放已获得的锁 release_partial_locks() return False
对于长时间任务,需要自动延长锁有效期:
// Java示例(Redisson实现) RLock lock = redisson.getLock("orderLock"); try { // 默认30秒过期,看门狗每10秒续期 lock.lock(); // 业务处理 processOrder(); } finally { lock.unlock(); }
续约原理: 1. 加锁成功后启动后台线程 2. 每(过期时间/3)检查客户端是否仍持有锁 3. 若持有则延长过期时间
def deduct_stock(): lock_key = "product_123" request_id = str(uuid.uuid4()) # 尝试获取锁 locked = redis.set(lock_key, request_id, nx=True, px=5000) if not locked: return "系统繁忙,请重试" try: # 查询库存 stock = int(redis.get("stock")) if stock > 0: # 扣减库存 redis.decr("stock") return "秒杀成功" else: return "库存不足" finally: # 释放锁 script = """ if redis.call("get",KEYS[1]) == ARGV[1] then return redis.call("del",KEYS[1]) end""" redis.eval(script, 1, lock_key, request_id)
防止多个节点同时执行定时任务:
// Spring Scheduler示例 @Scheduled(cron = "0 0/5 * * * ?") public void syncDataJob() { String lockKey = "sync:data:job"; String clientId = UUID.randomUUID().toString(); try { Boolean locked = redisTemplate.opsForValue() .setIfAbsent(lockKey, clientId, 10, TimeUnit.MINUTES); if (locked != null && locked) { syncDataService.executeSync(); } } finally { String script = "if redis.call('get', KEYS[1]) == ARGV[1] then " + "return redis.call('del', KEYS[1]) " + "else return 0 end"; redisTemplate.execute( new DefaultRedisScript<>(script, Long.class), Collections.singletonList(lockKey), clientId); } }
现象: - 节点间系统时间不一致 - 导致锁提前过期或延迟释放
解决方案: 1. 使用NTP服务同步时间 2. 避免完全依赖时间判断,增加随机缓冲时间
案例: - 线程A获取锁后,因Full GC停顿超过锁有效期 - 线程B获取到锁开始操作共享资源 - 线程A恢复后继续操作,导致数据不一致
解决方案: 1. 优化JVM参数减少GC停顿 2. 使用可重入锁减少临界区代码 3. 添加版本号校验(类似乐观锁)
当Redis集群发生网络分区时可能出现多个主节点,导致锁状态不一致。
缓解方案: 1. 设置min-slaves-to-write
要求写入至少同步到指定数量从节点 2. 使用Redlock等多节点方案
锁粒度控制:
global_lock
(简单但性能差)user_123_order_lock
(推荐)超时时间设置:
非阻塞模式:
// 尝试获取锁,失败立即返回 boolean locked = lock.tryLock(0, 10, TimeUnit.SECONDS);
监控指标:
方案 | 优点 | 缺点 |
---|---|---|
Redis | 性能高,实现简单 | 强依赖Redis可用性 |
Zookeeper | 可靠性高,Watcher机制 | 性能较低,部署复杂 |
etcd | 高可用,强一致性 | 需要维护额外组件 |
数据库乐观锁 | 无需额外组件 | 性能差,表锁影响并发 |
# 查看锁剩余时间 TTL lock_key # 获取锁当前持有者 GET lock_key # 强制释放锁(仅限紧急情况) DEL lock_key
注:本文示例基于Redis 5.0+版本,实际实现时请根据所用语言和Redis版本调整具体API。 “`
(全文约3050字,可根据实际需要调整具体章节的详细程度)
免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:is@yisu.com进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。