Redis分布式锁

8 阅读3分钟

1、分布式锁

实现原理:分布式锁的核心思想是通过 Redis 这种分布式缓存系统来控制多台机器之间的锁竞争。当一个客户端(如用户的请求)获得锁时,其他客户端不能同时执行相同的操作,从而避免超卖问题。

核心要求:互斥性、可重入性、防死锁、高性能、高可用。

使用Redis来实现分布式锁,尤其在处理并发预约号源,能有效避免并发请求导致的超卖问题。

2、Redis分布锁实现,解决并发预约号源超卖问题

2.1 Redis分布锁

利用 Redis 单线程执行命令 + 原子操作的特性,在多实例、多进程环境下,实现共享资源的互斥访问。

特点

  • 加锁原子性:只有第一个执行 SETNX(SET if Not eXists)的线程获得锁。
  • 可设置过期时间:防止死锁。
  • 高性能:Redis 内存存储,单线程,响应快。

2.2 Redis分布锁实现基本流程

  • 客户端请求获得分布式锁。
  • Redis 设置锁的键值对,若存在则说明锁被占用。
  • 客户端在执行操作(如预定号源)前,先检查是否获取到了锁。
  • 执行操作后,客户端释放锁。

2.3 SETNX + EXPIRE(传统方式)

原理:先使用SETNX设置锁,成功后设置过期时间

public boolean tryLock(Jedis jedis, String lockKey, String requestId, int expireTime) {
    // 1. 尝试获取锁
    Long result = jedis.setnx(lockKey, requestId);
    if (result == 1) {
        // 2. 设置过期时间(非原子操作!)
        jedis.expire(lockKey, expireTime);
        return true;
    }
    return false;
}

问题

  • 非原子性:SETNX成功但EXPIRE失败时,锁将永久存在,导致死锁
  • 锁误删:可能删除其他客户端的锁(未使用唯一标识)
  • 无重入性:同一线程无法重入

2.4 SET NX PX

原理:使用Redis 2.6.12+的SET命令扩展,支持原子性设置锁和过期时间

public boolean tryLockWithSet(Jedis jedis, String lockKey, String requestId, int expireMillis) {
    String result = jedis.set(lockKey, requestId, "NX", "PX", expireMillis);
    return "OK".equals(result);
}

// 释放锁(需保证原子性)
public boolean unlock(Jedis jedis, String lockKey, String requestId) {
    // 使用Lua脚本保证原子性
    String luaScript = "if redis.call('get', KEYS[1]) == ARGV[1] then " +
                       "return redis.call('del', KEYS[1]) " +
                       "else " +
                       "return 0 " +
                       "end";
    Object result = jedis.eval(luaScript, 
                               Collections.singletonList(lockKey), 
                               Collections.singletonList(requestId));
    return result.equals(1L);
}

优势

  • 原子性:单条命令完成锁设置和过期时间
  • 避免死锁:确保过期时间一定被设置
  • 简单可靠:生产环境常用方案

2.5 Redisson (企业级解决方案)

原理:基于Netty的Redis客户端,提供完整的分布式锁实现。

// Maven依赖
<dependency>
    <groupId>org.redisson</groupId>
    <artifactId>redisson</artifactId>
    <version>3.23.5</version>
</dependency>

// 使用示例
Config config = new Config();
config.useSingleServer().setAddress("redis://127.0.0.1:6379");

RedissonClient redisson = Redisson.create(config);
RLock lock = redisson.getLock("myLock");

try {
    // 尝试加锁,最多等待100秒,上锁后30秒自动解锁
    boolean isLocked = lock.tryLock(100, 30, TimeUnit.SECONDS);
    if (isLocked) {
        // 执行业务逻辑
    }
} finally {
    if (lock.isHeldByCurrentThread()) {
        lock.unlock();
    }
}

优点

  • 看门狗机制:自动续期锁,避免业务执行时间过长导致锁过期。
  • 支持可重入锁(同一个线程可多次获取锁)、读写锁(读读共享,读写互斥)、信号量(控制并发数),适合复杂分布式场景。
  • 封装原子性、超时处理、异常安全,极大降低死锁风险。