Redis高级应用:分布式锁的实现与优化
在分布式系统中,资源竞争是一个永恒的话题。当多个服务实例并行运行时,如何确保它们对共享资源的访问是安全且高效的?Redis分布式锁作为一种轻量级解决方案,已经成为了很多企业的标配。然而,这把"看似简单"的锁,却暗藏无数技术陷阱…
🧩 题目描述
某电商平台的秒杀系统,使用了Spring Boot + Redis构建。在峰值时每秒处理订单请求高达5000+,系统采用集群部署(20个服务节点)。为保证库存数据一致性,需要实现一个高性能、高可靠的分布式锁方案。
现有代码:
public boolean secKill(String productId, String userId) {
String lockKey = "lock:" + productId;
try {
// 获取锁
Boolean isLock = stringRedisTemplate.opsForValue()
.setIfAbsent(lockKey, userId, 30, TimeUnit.SECONDS);
if (Boolean.TRUE.equals(isLock)) {
// 业务逻辑:检查并扣减库存
int stock = getProductStock(productId);
if (stock > 0) {
boolean result = reduceStock(productId);
if (result) {
// 创建订单等逻辑
return true;
}
}
return false;
} else {
// 获取锁失败,稍后重试
Thread.sleep(100);
return secKill(productId, userId);
}
} catch (Exception e) {
log.error("秒杀异常", e);
} finally {
// 释放锁
stringRedisTemplate.delete(lockKey);
}
return false;
}
🚨 找出问题
上述代码在实际生产环境中出现了以下问题:
- 高并发场景下,系统CPU使用率异常飙升
- 同一商品有时会超卖
- 偶尔出现死锁,导致秒杀活动无法进行
- Redis主从切换时,出现数据不一致问题
💎 要求
- 详细分析上述代码存在的缺陷
- 实现一个健壮的Redis分布式锁,需满足:
- 高性能(支撑5000+ TPS)
- 不会出现死锁
- 保证正确释放(锁只能被持有者释放)
- 可重入性设计
- 容错性(Redis节点故障时的处理策略)
- 对于Redis分布式锁,探讨Redlock算法在此场景的适用性
技术深度剖析
这绝不是一道简单的题目,它触及了分布式系统最核心的一些挑战。Redis分布式锁看似简单,却暗藏玄机。从最初的SETNX命令,到后来的SET EX NX命令的组合,再到Redlock算法,每一步演进都是对分布式一致性理论的深刻理解。
代码缺陷分析
让我们以架构师视角,逐一剖析当前代码存在的问题:
解决方案
1. 优化后的分布式锁实现
public class RedisDistributedLock {
private StringRedisTemplate redisTemplate;
private ThreadLocal<String> lockFlag = new ThreadLocal<>();
private static final String LOCK_SUCCESS = "OK";
private static final String SET_IF_NOT_EXIST = "NX";
private static final String SET_WITH_EXPIRE_TIME = "EX";
private static final Long RELEASE_SUCCESS = 1L;
// Redisson的看门狗默认续期时间
private static final long WATCHDOG_TIMEOUT = 30 * 1000;
public RedisDistributedLock(StringRedisTemplate redisTemplate) {
this.redisTemplate = redisTemplate;
}
/**
* 尝试获取分布式锁
* @param lockKey 锁键
* @param requestId 请求标识
* @param expireTime 超期时间
* @return 是否获取成功
*/
public boolean tryLock(String lockKey, String requestId, int expireTime) {
// 如果当前线程已经持有锁,实现可重入
String currentId = lockFlag.get();
if (requestId.equals(currentId)) {
return true;
}
// 使用Lua脚本确保操作原子性
String script = "if redis.call('set', KEYS[1], ARGV[1], 'NX', 'EX', ARGV[2]) then return 1 else return 0 end";
Long result = redisTemplate.execute(
new DefaultRedisScript<>(script, Long.class),
Collections.singletonList(lockKey),
requestId, String.valueOf(expireTime));
if (RELEASE_SUCCESS.equals(result)) {
lockFlag.set(requestId);
// 启动看门狗机制,自动续期
startWatchDog(lockKey, requestId, expireTime);
return true;
}
return false;
}
/**
* 释放分布式锁
* @param lockKey 锁键
* @param requestId 请求标识
* @return 是否释放成功
*/
public boolean releaseLock(String lockKey, String requestId) {
// 使用Lua脚本确保释放锁的原子性,同时验证身份
String script = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";
Long result = redisTemplate.execute(
new DefaultRedisScript<>(script, Long.class),
Collections.singletonList(lockKey),
requestId);
if (RELEASE_SUCCESS.equals(result)) {
lockFlag.remove();
return true;
}
return false;
}
/**
* 看门狗机制,自动续期锁
*/
private void startWatchDog(String lockKey, String requestId, int expireTime) {
Timer timer = new Timer(true);
timer.schedule(new TimerTask() {
@Override
public void run() {
String script = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('expire', KEYS[1], ARGV[2]) else return 0 end";
Long result = redisTemplate.execute(
new DefaultRedisScript<>(script, Long.class),
Collections.singletonList(lockKey),
requestId, String.valueOf(expireTime));
if (RELEASE_SUCCESS.equals(result)) {
// 继续延期
startWatchDog(lockKey, requestId, expireTime);
} else {
// 锁已经不存在,取消定时器
timer.cancel();
}
}
}, expireTime * 1000 / 3); // 在过期时间的1/3时间点续期
}
}
2. 使用Redisson实现更健壮的分布式锁
@Service
public class SecKillServiceWithRedisson {
@Autowired
private RedissonClient redissonClient;
public boolean secKill(String productId, String userId) {
String lockKey = "lock:" + productId;
RLock lock = redissonClient.getLock(lockKey);
try {
// 获取锁,最多等待500ms,锁过期时间为30s
boolean isLocked = lock.tryLock(500, 30000, TimeUnit.MILLISECONDS);
if (isLocked) {
// 业务逻辑:检查并扣减库存
int stock = getProductStock(productId);
if (stock > 0) {
boolean result = reduceStock(productId);
if (result) {
// 创建订单等逻辑
return true;
}
}
return false;
} else {
return false; // 获取锁失败,可以进行其他处理
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
return false;
} finally {
// 只有持有锁的线程才能解锁
if (lock.isHeldByCurrentThread()) {
lock.unlock();
}
}
}
}
3. Redlock算法在高可用场景的实现
@Service
public class SecKillServiceWithRedlock {
@Autowired
private RedissonClient redissonClient;
public boolean secKill(String productId, String userId) {
String lockKey = "lock:" + productId;
// 创建多个Redis实例的RLock
RLock lock1 = redissonClient.getLock("redis://redis1:6379/" + lockKey);
RLock lock2 = redissonClient.getLock("redis://redis2:6379/" + lockKey);
RLock lock3 = redissonClient.getLock("redis://redis3:6379/" + lockKey);
// 组合多个Redis实例的锁为一个Redlock
RedissonRedLock redLock = new RedissonRedLock(lock1, lock2, lock3);
try {
// 尝试获取锁,最多等待500ms,锁过期时间为30s
boolean isLocked = redLock.tryLock(500, 30000, TimeUnit.MILLISECONDS);
if (isLocked) {
// 业务逻辑:检查并扣减库存
int stock = getProductStock(productId);
if (stock > 0) {
boolean result = reduceStock(productId);
if (result) {
// 创建订单等逻辑
return true;
}
}
return false;
} else {
return false; // 获取锁失败
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
return false;
} finally {
// 释放锁
redLock.unlock();
}
}
}
分布式锁实现的CAP权衡
在分布式系统设计中,没有一种锁实现可以同时满足CAP三个特性。Redis分布式锁方案主要在AP和CP之间做出选择:
- 基础Redis锁:倾向于可用性(A)和分区容错性§,但在网络分区或主从切换时可能丧失一致性
- Redlock算法:通过多节点共识,更倾向于一致性©和分区容错性§,但可能影响可用性
- Redis+Zookeeper混合方案:结合两种锁的优势,但增加了系统复杂度
深度思考
你的方案是否真正解决了分布式锁的核心问题?当我们追求绝对的一致性时,可能需要考虑更重量级的解决方案,如Zookeeper或etcd。但在实际业务场景中,我们通常需要在性能和一致性之间做出权衡。
思考:在高并发秒杀场景下,是否可以考虑乐观锁方案结合Redis缓存来提升系统性能?
不知不觉,我们已经深入探讨了Redis分布式锁的核心技术细节。当你在项目中遇到类似问题时,希望这篇文章能给你带来启发。技术无止境,让我们一起在分布式系统的海洋中,寻找更优雅的解决之道。
本文由 绘问 技术团队出品,私信我或评论区留言,获取更多Java与分布式系统架构干货!
魔乐社区(Modelers.cn) 是一个中立、公益的人工智能社区,提供人工智能工具、模型、数据的托管、展示与应用协同服务,为人工智能开发及爱好者搭建开放的学习交流平台。社区通过理事会方式运作,由全产业链共同建设、共同运营、共同享有,推动国产AI生态繁荣发展。
更多推荐


所有评论(0)