Redis 分布式锁实战:从手写实现到 Redisson 优化
引言
在分布式系统中,并发资源竞争是高频问题 —— 比如库存扣减、订单创建、缓存更新等场景,若缺乏有效的同步机制,可能导致数据不一致、重复操作等严重问题。Redis 凭借高性能、高可用的特性,成为实现分布式锁的主流方案之一。本文将从原生 Redis 分布式锁的手写实现入手,分析其潜在缺陷,再引入 Redisson 框架进行优化,最终提供可直接复用的生产级模板,帮助开发者快速解决分布式并发问题。
一、分布式锁的核心设计要求
一个可靠的分布式锁必须满足以下 5 个核心条件,否则可能引发业务异常:
二、原生 Redis 分布式锁手写实现(基础版)
2.1 核心原理
利用 Redis 的 SET key value NX EX timeout 命令实现锁的获取:
- NX:仅当 key 不存在时才设置成功(保证互斥性);
- EX timeout:自动设置过期时间(防死锁);
- 释放锁时通过 Lua 脚本原子化删除 key(避免误释放)。
2.2 基础实现代码(Java)
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import java.util.Collections;
import java.util.UUID;
import java.util.concurrent.TimeUnit;
/**
* 原生 Redis 分布式锁基础实现
*/
public class RedisLockBasic {
// Redis 客户端(Spring 注入)
private final StringRedisTemplate stringRedisTemplate;
// 锁的前缀(避免 key 冲突)
private static final String LOCK_PREFIX = "distributed:lock:";
// 解锁 Lua 脚本(原子操作,避免误释放)
private static final String UNLOCK_SCRIPT = """
if redis.call('get', KEYS[1]) == ARGV[1] then
return redis.call('del', KEYS[1])
else
return 0
end
""";
public RedisLockBasic(StringRedisTemplate stringRedisTemplate) {
this.stringRedisTemplate = stringRedisTemplate;
}
/**
* 获取锁
* @param lockKey 锁的业务标识(如:stock:1001)
* @param expireTime 过期时间(秒)
* @param retryTimes 重试次数(获取锁失败时重试)
* @param retryInterval 重试间隔(毫秒)
* @return 锁的唯一标识(解锁时需验证),获取失败返回 null
*/
public String tryLock(String lockKey, int expireTime, int retryTimes, long retryInterval) {
// 生成锁的唯一标识(避免误释放其他线程的锁)
String lockValue = UUID.randomUUID().toString();
String realLockKey = LOCK_PREFIX + lockKey;
// 循环重试获取锁
for (int i = 0; i <= retryTimes; i++) {
// 执行 SET NX EX 命令获取锁
Boolean success = stringRedisTemplate.opsForValue()
.setIfAbsent(realLockKey, lockValue, expireTime, TimeUnit.SECONDS);
if (Boolean.TRUE.equals(success)) {
return lockValue; // 获取锁成功,返回唯一标识
}
// 重试间隔
try {
Thread.sleep(retryInterval);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
return null;
}
}
return null; // 重试次数耗尽,获取锁失败
}
/**
* 释放锁(原子操作)
* @param lockKey 锁的业务标识
* @param lockValue 锁的唯一标识(与获取时返回值一致)
* @return 释放成功返回 true,失败返回 false
*/
public boolean unlock(String lockKey, String lockValue) {
String realLockKey = LOCK_PREFIX + lockKey;
// 执行 Lua 脚本解锁
DefaultRedisScript<Long> redisScript = new DefaultRedisScript<>(UNLOCK_SCRIPT, Long.class);
Long result = stringRedisTemplate.execute(
redisScript,
Collections.singletonList(realLockKey),
lockValue
);
return result != null && result > 0;
}
}
2.3 基础版的潜在缺陷
三、Redisson 优化实现(生产级方案)
Redisson 是 Redis 官方推荐的 Java 客户端,内置了分布式锁的完整实现,解决了原生方案的诸多缺陷,且支持多种锁类型(可重入锁、公平锁、红锁等)。
3.1 核心优化点
3.2 生产级集成模板(Spring Boot)
3.2.1 第一步:引入依赖
<!– Redisson 核心依赖 –>
<dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson-spring-boot-starter</artifactId>
<version>3.23.3</version> <!– 建议使用最新稳定版 –>
</dependency>
3.2.2 第二步:配置 Redisson
# application.yml
spring:
redis:
host: 127.0.0.1
port: 6379
password: your–redis–password # 无密码可省略
database: 0
redisson:
# 单机模式配置(集群/哨兵模式参考官方文档)
singleServerConfig:
address: redis://${spring.redis.host}:${spring.redis.port}
password: ${spring.redis.password}
database: ${spring.redis.database}
# 连接池配置(根据业务调整)
connectionPoolSize: 16
connectionMinimumIdleSize: 8
idleConnectionTimeout: 30000
connectTimeout: 10000
timeout: 3000
3.2.3 第三步:封装分布式锁工具类
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
import java.util.concurrent.TimeUnit;
/**
* Redisson 分布式锁工具类(生产级可直接复用)
*/
@Component
public class RedissonLockUtil {
@Resource
private RedissonClient redissonClient;
// 锁的前缀(统一命名规范,便于排查)
private static final String LOCK_PREFIX = "distributed:lock:";
/**
* 获取可重入锁(默认非公平锁)
* @param lockKey 锁的业务标识(如:order:create:1001)
* @param waitTime 最大等待时间(秒):获取锁失败时,最多等待多久
* @param leaseTime 锁的持有时间(秒):0 表示自动续命(默认 30 秒续期一次)
* @return 锁实例(需手动释放或使用 try-with-resources)
*/
public RLock getLock(String lockKey, long waitTime, long leaseTime) {
String realLockKey = LOCK_PREFIX + lockKey;
RLock lock = redissonClient.getLock(realLockKey);
// 尝试获取锁:waitTime 内获取不到则返回失败
try {
boolean success = lock.tryLock(waitTime, leaseTime, TimeUnit.SECONDS);
return success ? lock : null;
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
return null;
}
}
/**
* 获取公平锁(按请求顺序获取锁,避免饥饿)
* @param lockKey 锁的业务标识
* @param waitTime 最大等待时间(秒)
* @param leaseTime 锁的持有时间(秒)
* @return 公平锁实例
*/
public RLock getFairLock(String lockKey, long waitTime, long leaseTime) {
String realLockKey = LOCK_PREFIX + lockKey;
RLock fairLock = redissonClient.getFairLock(realLockKey);
try {
boolean success = fairLock.tryLock(waitTime, leaseTime, TimeUnit.SECONDS);
return success ? fairLock : null;
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
return null;
}
}
/**
* 释放锁(推荐使用 try-with-resources 自动释放)
* @param lock 锁实例
*/
public void unlock(RLock lock) {
if (lock != null && lock.isHeldByCurrentThread()) {
lock.unlock();
}
}
}
3.2.4 第四步:业务场景使用示例
import org.redisson.api.RLock;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
@Service
public class OrderService {
@Resource
private RedissonLockUtil redissonLockUtil;
@Resource
private StockMapper stockMapper; // 库存 DAO 层
/**
* 下单扣减库存(分布式锁保证原子性)
* @param productId 商品 ID
* @param quantity 购买数量
* @return 下单结果
*/
public String createOrder(Long productId, Integer quantity) {
// 锁的业务标识:按商品 ID 粒度加锁,避免大面积阻塞
String lockKey = "product:stock:" + productId;
RLock lock = null;
try {
// 获取锁:最大等待 3 秒,持有锁 10 秒(10 秒内未完成自动释放,避免死锁)
lock = redissonLockUtil.getLock(lockKey, 3, 10);
if (lock == null) {
return "下单失败,请稍后重试";
}
// 核心业务逻辑:查询库存 -> 扣减库存
Integer stock = stockMapper.selectStockByProductId(productId);
if (stock < quantity) {
return "库存不足";
}
stockMapper.decreaseStock(productId, quantity);
return "下单成功";
} finally {
// 释放锁(确保无论业务成功与否都释放锁)
redissonLockUtil.unlock(lock);
}
}
}
3.3 高级特性:红锁(RedLock)
若 Redis 集群存在主从切换风险(如主节点宕机后从节点未同步锁数据),可使用红锁算法,需部署多个独立的 Redis 节点(至少 3 个):
import org.redisson.api.RedissonClient;
import org.redisson.api.RedissonRedLock;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.TimeUnit;
@Component
public class RedLockUtil {
@Resource
private RedissonClient redissonClient1; // 第一个 Redis 节点客户端
@Resource
private RedissonClient redissonClient2; // 第二个 Redis 节点客户端
@Resource
private RedissonClient redissonClient3; // 第三个 Redis 节点客户端
/**
* 获取红锁(多节点锁,提升可用性)
* @param lockKey 锁的业务标识
* @param waitTime 最大等待时间(秒)
* @param leaseTime 持有时间(秒)
* @return 红锁实例
*/
public RedissonRedLock getRedLock(String lockKey, long waitTime, long leaseTime) {
String realLockKey = LOCK_PREFIX + lockKey;
// 获取多个节点的锁
List<RLock> locks = new ArrayList<>();
locks.add(redissonClient1.getLock(realLockKey));
locks.add(redissonClient2.getLock(realLockKey));
locks.add(redissonClient3.getLock(realLockKey));
// 构建红锁(需超过半数节点获取锁成功才算整体成功)
RedissonRedLock redLock = new RedissonRedLock(locks.toArray(new RLock[0]));
try {
boolean success = redLock.tryLock(waitTime, leaseTime, TimeUnit.SECONDS);
return success ? redLock : null;
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
return null;
}
}
}
四、使用注意事项
五、总结
本文从原生 Redis 分布式锁的手写实现出发,分析了基础版的缺陷,进而引入 Redisson 框架提供生产级解决方案。Redisson 不仅解决了锁超时、可重入、高可用等问题,还提供了丰富的锁类型和优雅的 API,极大降低了分布式锁的使用门槛。
文中提供的 Redisson 工具类和业务示例可直接复用,开发者可根据实际场景(单机 / 集群、普通锁 / 公平锁 / 红锁)灵活调整。在分布式系统中,合理使用 Redis 分布式锁能有效保障数据一致性,提升系统并发能力。




