在分布式系统中,锁机制是保证数据一致性的重要手段。但原生Redis的SETNX命令在实际业务场景中存在诸多问题,这正是Redisson分布式锁要解决的核心痛点。
当同一个线程多次获取同一把锁时,会出现自我阻塞的情况。例如:
java复制public void methodA() {
lock(); // 第一次获取成功
methodB(); // 内部再次尝试获取同一把锁
unlock();
}
public void methodB() {
lock(); // 第二次获取失败,线程自我阻塞
unlock();
}
这种嵌套调用场景在实际开发中非常常见,比如订单服务调用库存服务时都需要加锁。
原生SETNX是"一锤子买卖"——获取失败就直接返回。但在高并发场景下,短暂竞争后锁很可能很快释放。没有重试机制会导致大量本可以成功的请求被丢弃。
为避免死锁设置的过期时间,可能造成:
Redis主从异步复制特性导致:
采用Hash结构存储锁信息:
加锁Lua脚本逻辑:
lua复制if redis.call('exists', KEYS[1]) == 0 then
redis.call('hset', KEYS[1], ARGV[2], 1)
redis.call('pexpire', KEYS[1], ARGV[1])
return nil
end
if redis.call('hexists', KEYS[1], ARGV[2]) == 1 then
redis.call('hincrby', KEYS[1], ARGV[2], 1)
redis.call('pexpire', KEYS[1], ARGV[1])
return nil
end
return redis.call('pttl', KEYS[1])
解决主从同步问题:
java复制RLock lock1 = redissonClient1.getLock("lock");
RLock lock2 = redissonClient2.getLock("lock");
RLock lock3 = redissonClient3.getLock("lock");
RLock multiLock = redissonClient.getMultiLock(lock1, lock2, lock3);
multiLock.lock();
需要所有节点都加锁成功才算成功,避免单点故障。
Spring Boot集成示例:
java复制@Configuration
public class RedissonConfig {
@Bean
public RedissonClient redissonClient() {
Config config = new Config();
config.useSingleServer()
.setAddress("redis://127.0.0.1:6379")
.setPassword("yourpassword")
.setConnectionPoolSize(10)
.setConnectionMinimumIdleSize(5);
return Redisson.create(config);
}
}
java复制@Resource
private RedissonClient redissonClient;
public void doBusiness() {
RLock lock = redissonClient.getLock("orderLock");
try {
// 尝试获取锁,等待时间10秒,锁过期时间30秒
if (lock.tryLock(10, 30, TimeUnit.SECONDS)) {
// 业务逻辑
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
} finally {
if (lock.isLocked() && lock.isHeldByCurrentThread()) {
lock.unlock();
}
}
}
秒杀开始前将库存加载到Redis:
java复制// 初始化库存
stringRedisTemplate.opsForValue().set("seckill:stock:"+voucherId, "100");
lua复制local stockKey = 'seckill:stock:'..ARGV[1]
local orderKey = 'seckill:order:'..ARGV[1]
if (tonumber(redis.call('get', stockKey)) <= 0) then
return 1
end
if (redis.call('sismember', orderKey, ARGV[2]) == 1) then
return 2
end
redis.call('incrby', stockKey, -1)
redis.call('sadd', orderKey, ARGV[2])
redis.call('xadd', 'stream.orders', '*', 'userId', ARGV[2], 'voucherId', ARGV[1])
return 0
java复制private BlockingQueue<VoucherOrder> orderTasks = new ArrayBlockingQueue<>(1024);
private static final ExecutorService SECKILL_ORDER_EXECUTOR = Executors.newSingleThreadExecutor();
@PostConstruct
private void init() {
SECKILL_ORDER_EXECUTOR.submit(() -> {
while (true) {
try {
VoucherOrder order = orderTasks.take();
handleOrder(order);
} catch (Exception e) {
log.error("处理订单异常", e);
}
}
});
}
private void handleOrder(VoucherOrder order) {
// 分布式锁保证幂等性
RLock lock = redissonClient.getLock("lock:order:" + order.getUserId());
try {
if (lock.tryLock(1, 10, TimeUnit.SECONDS)) {
// 数据库操作
createVoucherOrder(order);
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
} finally {
lock.unlock();
}
}
| 方案 | QPS | 平均响应时间 | 数据库压力 |
|---|---|---|---|
| 传统方案 | ~500 | 200ms+ | 高 |
| Redis优化 | 5000+ | <50ms | 极低 |
线程组设置:
HTTP请求配置:
properties复制Protocol: http
Server Name: localhost
Port: 8080
Path: /voucher-order/seckill/10
Method: POST
请求头管理:
properties复制Name: authorization
Value: ${token} (从登录接口获取)
Redis连接池耗尽:
数据库写入瓶颈:
网络延迟:
锁粒度控制:
命名规范:
避免死锁:
多级降级策略:
熔断机制:
数据一致性保障:
在实际项目中,我曾遇到一个典型的锁误用案例:开发人员在循环内获取锁但未设置超时,导致Redis连接被耗尽。通过引入Redisson的tryLock并合理设置超时参数,不仅解决了问题,还将系统吞吐量提升了3倍。这提醒我们,分布式锁虽小,但使用不当可能成为系统瓶颈。