您好,登录后才能下订单哦!
# 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进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。