🔍 Redis实现分布式锁的细节和问题:魔鬼在细节中!

31 阅读15分钟

📖 开场:上厕所的各种意外

想象你去公共厕所 🚽:

场景1:忘记锁门

你进去 → 忘记反锁 🚪
别人推门进来 → 尴尬!😱

对应问题:没有加锁

场景2:门锁坏了

你进去反锁 → 门锁故障 🔒
出不去了!💀

对应问题:死锁

场景3:别人把你的锁打开了

你进去反锁 → 上厕所中...
清洁工误以为没人 → 用万能钥匙打开 🔑

对应问题:误删别人的锁

场景4:时间太长被强制开门

你进去反锁 → 上厕所中...(时间太长)
10分钟后 → 门锁自动打开(防止有人晕倒)
别人进来 → 尴尬!😱

对应问题:锁过期

这就是分布式锁的细节问题!


🎯 Redis分布式锁的8大问题

问题1:原子性问题 ⚛️

错误实现

/**
 * ❌ 错误:SETNX和EXPIRE不是原子操作
 */
public boolean tryLock(String lockKey, String holder) {
    // 步骤1:SETNX设置锁
    Boolean success = redisTemplate.opsForValue().setIfAbsent(lockKey, holder);
    
    if (Boolean.TRUE.equals(success)) {
        // 步骤2:设置过期时间
        redisTemplate.expire(lockKey, 10, TimeUnit.SECONDS);
        return true;
    }
    
    return false;
}

问题

步骤1:SETNX成功 ✅
    ↓
【此时服务器宕机】💀
    ↓
步骤2:EXPIRE没有执行 ❌
    ↓
锁永远不会过期 → 死锁!💀

正确实现

/**
 * ✅ 正确:SETNX和EXPIRE原子操作
 */
public boolean tryLock(String lockKey, String holder, int expireSeconds) {
    // ⭐ Redis 2.6.12+支持:SET key value EX seconds NX
    // 一条命令完成SETNX + EXPIRE,保证原子性
    Boolean success = redisTemplate.opsForValue()
        .setIfAbsent(lockKey, holder, expireSeconds, TimeUnit.SECONDS);
    
    return Boolean.TRUE.equals(success);
}

原理

SET lock_key uuid EX 10 NX

参数说明:
- NX:Not eXists,key不存在时才设置(SETNX)
- EX 10:10秒后过期(EXPIRE)
- 一条命令,原子操作 ✅

问题2:误删别人的锁 🔐

错误实现

/**
 * ❌ 错误:直接删除锁,可能删除别人的锁
 */
public void unlock(String lockKey) {
    redisTemplate.delete(lockKey);  // ❌ 直接删除
}

问题场景

时间线:
10:00:00  线程A获取锁(10秒过期)
10:00:05  线程A处理业务(慢)
10:00:10  线程A的锁过期,自动删除
10:00:11  线程B获取锁 ✅
10:00:12  线程A处理完成,调用unlock()
          → 删除了线程B的锁!❌

结果:
- 线程B以为自己持有锁,但锁已被删除
- 线程C也能获取锁
- 线程B和C同时执行业务 → 并发问题!😱

正确实现(Lua脚本)

/**
 * ✅ 正确:检查是否是自己的锁,再删除
 */
public void unlock(String lockKey, String holder) {
    // ⭐ Lua脚本:原子操作
    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),
        holder
    );
}

原理

Lua脚本在Redis中是原子执行的

步骤:
1. GET lock_key → 获取当前holder
2. 比较是否等于传入的holder
3. 相等才执行DEL lock_key
4. 整个过程原子执行 ✅

为什么不用下面的方式?

/**
 * ❌ 错误:GET和DEL分两步,不是原子操作
 */
public void unlock(String lockKey, String holder) {
    String currentHolder = redisTemplate.opsForValue().get(lockKey);
    
    if (holder.equals(currentHolder)) {
        // ⚠️ 问题:这里可能锁已经过期,被别人获取了
        redisTemplate.delete(lockKey);  // 可能删除的是别人的锁
    }
}

问题

时间线:
10:00:00  线程A的锁(10秒过期)
10:00:10  GET lock_key → 得到线程A的holder ✅
10:00:10.001  锁过期,自动删除
10:00:10.002  线程B获取锁 ✅
10:00:10.003  线程A执行DEL lock_key
              → 删除了线程B的锁!❌

结论:GET和DEL之间有时间差,不是原子操作 ❌

问题3:锁过期问题 ⏰

场景

获取锁(10秒过期)
    ↓
业务处理中...(5秒)
    ↓
业务处理中...(5秒)
    ↓
【锁过期,自动删除】❌
    ↓
其他线程获取锁 ✅
    ↓
原线程继续处理...
    ↓
两个线程同时执行业务 → 并发问题!😱

解决方案1:设置更长的过期时间 ❌

// ❌ 不推荐:设置很长的过期时间(如1小时)
redisTemplate.opsForValue()
    .setIfAbsent(lockKey, holder, 3600, TimeUnit.SECONDS);

问题

  • 业务异常时,锁要等1小时才释放 → 系统停摆!
  • 治标不治本

解决方案2:Redisson的Watch Dog ✅

@Service
@Slf4j
public class OrderService {
    
    @Autowired
    private RedissonClient redissonClient;
    
    public void processOrder(Long orderId) {
        RLock lock = redissonClient.getLock("order:lock:" + orderId);
        
        try {
            // ⭐ 获取锁(不指定过期时间,启动Watch Dog)
            lock.lock();
            
            log.info("获取锁成功,开始处理订单");
            
            // ⭐ 业务处理(可能需要很长时间)
            doSomethingTakesLongTime();
            
            log.info("订单处理完成");
            
        } finally {
            // ⭐ 释放锁(停止Watch Dog)
            lock.unlock();
        }
    }
    
    private void doSomethingTakesLongTime() {
        // 耗时操作...
    }
}

Watch Dog原理

获取锁(默认30秒过期)
    ↓
启动Watch Dog后台线程 🐕
    ↓
每10秒(30秒的1/3)检查一次
    ↓
如果锁还在持有 → 重置过期时间为30秒 🔄
    ↓
业务处理完成 → 释放锁 → 停止Watch Dog ✅

Watch Dog源码分析

// Redisson源码(简化版)
public class RedissonLock {
    
    private void scheduleExpirationRenewal(long threadId) {
        // 创建定时任务
        Timeout task = commandExecutor.getConnectionManager()
            .newTimeout(new TimerTask() {
                @Override
                public void run(Timeout timeout) {
                    // ⭐ 续期逻辑
                    renewExpiration();
                }
            }, internalLockLeaseTime / 3, TimeUnit.MILLISECONDS);
            //                            ↑
            //                  每10秒执行一次(30秒的1/3)
    }
    
    private void renewExpiration() {
        // ⭐ 重置过期时间为30秒
        commandExecutor.evalWriteAsync(getName(), LongCodec.INSTANCE,
            "if redis.call('hexists', KEYS[1], ARGV[2]) == 1 then " +
            "    redis.call('pexpire', KEYS[1], ARGV[1]); " +
            "    return 1; " +
            "else " +
            "    return 0; " +
            "end",
            Collections.singletonList(getName()),
            internalLockLeaseTime, getLockName(threadId));
    }
}

解决方案3:手动续期(不推荐)❌

public void processOrder(Long orderId) {
    String lockKey = "order:lock:" + orderId;
    String holder = UUID.randomUUID().toString();
    
    // 启动续期线程
    ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1);
    ScheduledFuture<?> future = scheduler.scheduleAtFixedRate(() -> {
        // 每5秒续期一次
        redisTemplate.expire(lockKey, 10, TimeUnit.SECONDS);
    }, 5, 5, TimeUnit.SECONDS);
    
    try {
        // 业务处理
        doSomething();
    } finally {
        // 停止续期
        future.cancel(true);
        scheduler.shutdown();
        
        // 释放锁
        unlock(lockKey, holder);
    }
}

问题

  • 代码复杂
  • 需要管理线程池
  • 不如Redisson的Watch Dog优雅

问题4:可重入性问题 🔄

什么是可重入锁?

同一个线程可以多次获取同一个锁

场景:
methodA() {
    获取锁A
        ↓
    调用methodB()
        ↓
    methodB() {
        再次获取锁A  ← ⭐ 可重入
            ↓
        业务逻辑
            ↓
        释放锁A
    }
        ↓
    释放锁A
}

简单实现(不可重入)❌

/**
 * ❌ 不支持可重入
 */
public boolean tryLock(String lockKey, String holder) {
    return redisTemplate.opsForValue()
        .setIfAbsent(lockKey, holder, 10, TimeUnit.SECONDS);
}

public void methodA() {
    tryLock("lock", "thread-1");  // 成功 ✅
    
    methodB();  // 调用methodB
    
    unlock("lock", "thread-1");
}

public void methodB() {
    tryLock("lock", "thread-1");  // 失败!❌(锁已存在)
    
    // 业务逻辑无法执行
    
    unlock("lock", "thread-1");
}

Redisson实现(可重入)✅

@Service
public class OrderService {
    
    @Autowired
    private RedissonClient redissonClient;
    
    public void methodA(Long orderId) {
        RLock lock = redissonClient.getLock("order:lock:" + orderId);
        
        try {
            lock.lock();  // ⭐ 第一次获取锁
            log.info("methodA获取锁");
            
            methodB(orderId);  // 调用methodB
            
        } finally {
            lock.unlock();
        }
    }
    
    public void methodB(Long orderId) {
        RLock lock = redissonClient.getLock("order:lock:" + orderId);
        
        try {
            lock.lock();  // ⭐ 再次获取锁(可重入)✅
            log.info("methodB获取锁");
            
            // 业务逻辑
            
        } finally {
            lock.unlock();
        }
    }
}

Redisson可重入锁原理

Redis数据结构:
lock_key = {
    "thread-1": 1,  // 计数器
    "expire": 30000
}

获取锁:
1. 如果key不存在,创建并设置计数器为1
2. 如果key存在且holder是自己,计数器+1(可重入)
3. 如果key存在且holder不是自己,获取失败

释放锁:
1. 计数器-1
2. 如果计数器==0,删除key
3. 如果计数器>0,保留key

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 1;
elseif redis.call('hexists', KEYS[1], ARGV[2]) == 1 then
    -- 锁存在且是自己持有,计数器+1
    redis.call('hincrby', KEYS[1], ARGV[2], 1);
    redis.call('pexpire', KEYS[1], ARGV[1]);
    return 1;
else
    -- 锁被别人持有
    return 0;
end

-- 释放锁
if redis.call('hexists', KEYS[1], ARGV[2]) == 0 then
    -- 锁不是自己的
    return nil;
end

-- 计数器-1
local counter = redis.call('hincrby', KEYS[1], ARGV[2], -1);
if counter > 0 then
    -- 还有重入,只重置过期时间
    redis.call('pexpire', KEYS[1], ARGV[1]);
    return 0;
else
    -- 计数器为0,删除锁
    redis.call('del', KEYS[1]);
    return 1;
end

问题5:Redis主从切换问题 🔄

场景

Redis主从架构:
Master(主)──→ Slave(从)
              (异步复制)

问题:
10:00:00  Client A 获取锁(写入Master)✅
10:00:01  【Master宕机】💀(锁还没同步到Slave)
10:00:02  【Slave升级为Master】
10:00:03  Client B 获取锁(新Master上没有锁)✅

结果:Client A 和 Client B 同时持有锁!❌

解决方案:Redlock算法 🔴

原理:在多个独立的Redis实例上获取锁

5个独立的Redis实例(不是主从)
┌─────┐  ┌─────┐  ┌─────┐  ┌─────┐  ┌─────┐
│Redis│  │Redis│  │Redis│  │Redis│  │Redis│
│  1  │  │  2  │  │  3  │  │  4  │  │  5  │
└─────┘  └─────┘  └─────┘  └─────┘  └─────┘

获取锁的步骤:
1. 获取当前时间t1
2. 依次在5个Redis上获取锁(超时时间5ms)
3. 获取当前时间t2
4. 判断是否成功:
   - 在大多数实例(3个)上获取到锁 ✅
   - 获取锁的耗时(t2-t1) < 锁的有效时间
5. 如果成功,锁的有效时间 = 原有效时间 - 获取锁的耗时
6. 如果失败,释放所有已获取的锁

Redisson实现

@Configuration
public class RedissonConfig {
    
    @Bean
    public RedissonClient redissonClient() {
        Config config = new Config();
        
        // ⭐ 配置5个独立的Redis实例
        config.useReplicatedServers()
            .addNodeAddress(
                "redis://127.0.0.1:6379",
                "redis://127.0.0.1:6380",
                "redis://127.0.0.1:6381",
                "redis://127.0.0.1:6382",
                "redis://127.0.0.1:6383"
            );
        
        return Redisson.create(config);
    }
}

@Service
public class OrderService {
    
    @Autowired
    private RedissonClient redissonClient;
    
    public void processOrder(Long orderId) {
        // ⭐ 获取5个独立的锁
        RLock lock1 = redissonClient.getLock("lock:" + orderId);
        RLock lock2 = redissonClient.getLock("lock:" + orderId);
        RLock lock3 = redissonClient.getLock("lock:" + orderId);
        RLock lock4 = redissonClient.getLock("lock:" + orderId);
        RLock lock5 = redissonClient.getLock("lock:" + orderId);
        
        // ⭐ RedissonRedLock(Redlock算法)
        RLock redLock = redissonClient.getRedLock(lock1, lock2, lock3, lock4, lock5);
        
        try {
            redLock.lock();
            
            // 业务逻辑
            
        } finally {
            redLock.unlock();
        }
    }
}

优缺点

  • ✅ 高可用(不受单个Redis故障影响)
  • ✅ 不会丢锁(大多数实例都有锁)
  • ❌ 成本高(需要5个独立的Redis实例)
  • ❌ 性能较差(需要在多个实例上操作)

问题6:锁粒度问题 🎯

粒度过大(性能差)❌

/**
 * ❌ 错误:所有订单共用一个锁
 */
public void processOrder(Long orderId) {
    RLock lock = redissonClient.getLock("order:lock");  // 全局锁
    
    try {
        lock.lock();
        
        // 处理订单
        
    } finally {
        lock.unlock();
    }
}

结果:
- 订单1处理中...(其他订单都要等待)
- 并发性极差 ❌

粒度合适(推荐)✅

/**
 * ✅ 正确:每个订单一个锁
 */
public void processOrder(Long orderId) {
    RLock lock = redissonClient.getLock("order:lock:" + orderId);  // ⭐ 按订单ID加锁
    
    try {
        lock.lock();
        
        // 处理订单
        
    } finally {
        lock.unlock();
    }
}

结果:
- 订单1处理中...(其他订单不受影响)✅
- 并发性好 ✅

粒度过小(内存浪费)⚠️

/**
 * ⚠️ 注意:锁太多,Redis内存消耗大
 */
public void processOrderItem(Long orderId, Long itemId) {
    // 每个商品明细一个锁
    RLock lock = redissonClient.getLock("order:lock:" + orderId + ":item:" + itemId);
    
    try {
        lock.lock();
        
        // 处理商品明细
        
    } finally {
        lock.unlock();
    }
}

结果:
- 锁太多,Redis内存消耗大
- 一般按订单ID加锁就够了

问题7:锁等待问题 ⏰

自旋等待(忙等待)❌

/**
 * ❌ 不推荐:自旋等待,浪费CPU
 */
public boolean tryLockWithSpin(String lockKey, String holder, int retryTimes) {
    for (int i = 0; i < retryTimes; i++) {
        if (tryLock(lockKey, holder)) {
            return true;
        }
        // ❌ 立即重试,CPU空转
    }
    return false;
}

间隔重试(推荐)✅

/**
 * ✅ 推荐:间隔重试,避免CPU空转
 */
public boolean tryLockWithRetry(String lockKey, String holder, 
                                 int retryTimes, long retryInterval) {
    for (int i = 0; i < retryTimes; i++) {
        if (tryLock(lockKey, holder)) {
            return true;
        }
        
        // ⭐ 重试前等待
        if (i < retryTimes - 1) {
            try {
                Thread.sleep(retryInterval);  // 休眠一段时间
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
                return false;
            }
        }
    }
    return false;
}

Redisson的tryLock(推荐)✅

/**
 * ✅ 推荐:Redisson的tryLock
 */
public void processOrder(Long orderId) {
    RLock lock = redissonClient.getLock("order:lock:" + orderId);
    
    try {
        // ⭐ tryLock(等待时间, 过期时间, 时间单位)
        // 等待时间内会自动重试,使用Redis的发布订阅机制,不浪费CPU
        boolean acquired = lock.tryLock(3, 10, TimeUnit.SECONDS);
        
        if (!acquired) {
            log.warn("获取锁失败");
            return;
        }
        
        // 业务逻辑
        
    } catch (InterruptedException e) {
        log.error("获取锁被中断", e);
    } finally {
        if (lock.isHeldByCurrentThread()) {
            lock.unlock();
        }
    }
}

Redisson的优化

1. 使用Redis的发布订阅(Pub/Sub)
2. 锁释放时,发布消息通知等待的客户端
3. 等待的客户端收到通知后,立即尝试获取锁
4. 避免了轮询,节省CPU ✅

问题8:锁超时问题 ⏱️

场景

设置锁的过期时间:10秒
实际业务处理时间:15秒

问题:
0秒:获取锁 ✅
5秒:业务处理中...
10秒:锁过期,自动释放 ❌
11秒:其他线程获取锁 ✅
15秒:原线程处理完成,尝试释放锁
      → 释放的是别人的锁(如果没有holder校验)❌

解决方案总结

方案优点缺点推荐
设置很长的过期时间简单异常时锁长时间不释放
Redisson Watch Dog自动续期,优雅需要使用Redisson
手动续期灵活代码复杂,容易出错
评估业务时间简单难以准确评估⚠️

🎯 Redis分布式锁的完整实现

自己实现(不推荐)

@Component
@Slf4j
public class RedisDistributedLock {
    
    @Autowired
    private StringRedisTemplate redisTemplate;
    
    /**
     * 获取锁
     */
    public boolean tryLock(String lockKey, String holder, int expireSeconds) {
        // ⭐ SETNX + 过期时间(原子操作)
        Boolean success = redisTemplate.opsForValue()
            .setIfAbsent(lockKey, holder, expireSeconds, TimeUnit.SECONDS);
        return Boolean.TRUE.equals(success);
    }
    
    /**
     * 释放锁
     */
    public void unlock(String lockKey, String holder) {
        // ⭐ Lua脚本:检查holder后删除(原子操作)
        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),
            holder
        );
    }
}

问题

  • 不支持可重入 ❌
  • 不支持自动续期 ❌
  • 不支持锁等待优化 ❌

使用Redisson(强烈推荐)⭐⭐⭐

@Service
@Slf4j
public class OrderService {
    
    @Autowired
    private RedissonClient redissonClient;
    
    /**
     * 扣减库存(完整示例)
     */
    public boolean deductStock(Long productId, int quantity) {
        // ⭐ 获取锁对象
        RLock lock = redissonClient.getLock("stock:lock:" + productId);
        
        try {
            // ⭐ 尝试获取锁
            // tryLock(等待时间, 过期时间, 时间单位)
            boolean acquired = lock.tryLock(3, 10, TimeUnit.SECONDS);
            
            if (!acquired) {
                log.warn("获取锁失败,库存扣减失败");
                return false;
            }
            
            log.info("获取锁成功,开始扣减库存");
            
            // ⭐ 业务逻辑
            int stock = getStock(productId);
            if (stock < quantity) {
                log.warn("库存不足: stock={}, need={}", stock, quantity);
                return false;
            }
            
            setStock(productId, stock - quantity);
            log.info("库存扣减成功: productId={}, quantity={}, remaining={}", 
                productId, quantity, stock - quantity);
            
            return true;
            
        } catch (InterruptedException e) {
            log.error("获取锁被中断", e);
            return false;
        } finally {
            // ⭐ 释放锁(检查是否是当前线程持有的锁)
            if (lock.isHeldByCurrentThread()) {
                lock.unlock();
                log.info("释放锁成功");
            }
        }
    }
    
    private int getStock(Long productId) {
        // 从Redis或数据库读取库存
        String stock = redisTemplate.opsForValue().get("stock:" + productId);
        return stock != null ? Integer.parseInt(stock) : 0;
    }
    
    private void setStock(Long productId, int stock) {
        redisTemplate.opsForValue().set("stock:" + productId, String.valueOf(stock));
    }
}

Redisson的优势

  • ✅ 支持可重入锁
  • ✅ 支持Watch Dog自动续期
  • ✅ 支持公平锁、读写锁、信号量等多种锁
  • ✅ 使用Pub/Sub优化锁等待
  • ✅ 代码简洁,易用

🎓 面试题速答

Q1: Redis分布式锁有哪些问题?

A: 8大问题

  1. 原子性问题:SETNX和EXPIRE分两步 → 用SET NX EX一条命令
  2. 误删别人的锁:直接DELETE → 用Lua脚本校验holder
  3. 锁过期问题:业务处理时间长 → Redisson Watch Dog自动续期
  4. 可重入性问题:不支持重入 → Redisson可重入锁
  5. 主从切换问题:主从异步复制 → Redlock算法
  6. 锁粒度问题:粒度太大或太小 → 按业务对象ID加锁
  7. 锁等待问题:自旋浪费CPU → Redisson Pub/Sub优化
  8. 锁超时问题:过期时间难评估 → Watch Dog自动续期

Q2: 如何防止误删别人的锁?

A: Lua脚本校验holder

String script = 
    "if redis.call('get', KEYS[1]) == ARGV[1] then " +
    "    return redis.call('del', KEYS[1]) " +
    "else " +
    "    return 0 " +
    "end";

原理

  1. GET lock_key,获取当前holder
  2. 比较是否等于传入的holder
  3. 相等才执行DEL
  4. Lua脚本原子执行 ✅

Q3: Redisson的Watch Dog是什么?

A: Watch Dog = 自动续期机制 🐕

// 获取锁时不指定过期时间
lock.lock();  // 默认30秒,启动Watch Dog

// Watch Dog每10秒检查一次
// 如果锁还在持有,重置过期时间为30秒

作用:防止业务处理时间过长,锁自动过期

原理:后台线程定时检查,自动续期


Q4: Redis主从切换会丢锁吗?

A: 会!

场景

Client A获取锁(写入Master)✅
    ↓
Master宕机(锁还没同步到Slave)💀
    ↓
Slave升级为Master(没有锁记录)
    ↓
Client B获取锁(成功)✅
    ↓
Client AB同时持有锁 ❌

解决方案:Redlock算法

  • 在多个独立的Redis实例上获取锁
  • 大多数实例获取成功才算成功
  • 不受单个Redis故障影响

Q5: 为什么要用Redisson而不是自己实现?

A: Redisson的优势

  1. 可重入锁:支持同一线程多次获取锁
  2. Watch Dog:自动续期,防止锁过期
  3. Pub/Sub优化:不浪费CPU轮询
  4. 多种锁类型:公平锁、读写锁、信号量等
  5. 久经考验:生产环境广泛使用,稳定可靠

自己实现的问题

  • 不支持可重入 ❌
  • 不支持自动续期 ❌
  • 容易出现细节问题 ❌

结论:推荐使用Redisson ⭐⭐⭐


Q6: 锁的过期时间如何设置?

A: 三种方案

  1. 评估业务时间 + 安全系数

    业务处理时间:2秒
    安全系数:3倍
    过期时间:6秒
    

    缺点:难以准确评估

  2. 使用Redisson(不指定过期时间)

    lock.lock();  // 启动Watch Dog,自动续期
    

    优点:不需要评估业务时间 ✅

  3. 使用tryLock指定等待时间和过期时间

    lock.tryLock(3, 10, TimeUnit.SECONDS);
    //          ↑   ↑
    //       等待3秒 过期10秒
    

    优点:灵活控制

推荐:使用Redisson的lock()或tryLock()


🎬 总结

      Redis分布式锁的8大问题和解决方案

┌────────────────────────────────────────────┐
│ 1. 原子性问题                              │
│    SET NX EX(一条命令)✅                 │
└────────────────────────────────────────────┘

┌────────────────────────────────────────────┐
│ 2. 误删别人的锁                            │
│    Lua脚本校验holder ✅                    │
└────────────────────────────────────────────┘

┌────────────────────────────────────────────┐
│ 3. 锁过期问题                              │
│    Redisson Watch Dog自动续期 ✅           │
└────────────────────────────────────────────┘

┌────────────────────────────────────────────┐
│ 4. 可重入性问题                            │
│    Redisson可重入锁 ✅                     │
└────────────────────────────────────────────┘

┌────────────────────────────────────────────┐
│ 5. 主从切换问题                            │
│    Redlock算法 ✅                          │
└────────────────────────────────────────────┘

        使用Redisson,解决大部分问题!⭐

🎉 恭喜你!

你已经完全掌握了Redis分布式锁的细节和问题!🎊

核心要点

  1. 原子性:SET NX EX一条命令
  2. 防误删:Lua脚本校验holder
  3. 防过期:Watch Dog自动续期
  4. 可重入:Redisson内置支持
  5. 高可用:Redlock算法

下次面试,这样回答

"Redis分布式锁主要有8大问题,其中最关键的是原子性、误删别人的锁和锁过期问题。

原子性问题通过SET NX EX一条命令解决,保证SETNX和设置过期时间是原子操作。

误删问题通过Lua脚本解决,释放锁时先检查holder是否匹配,只删除自己的锁。

锁过期问题通过Redisson的Watch Dog解决,后台线程每10秒检查一次,如果锁还在持有就自动续期到30秒。

我们项目使用Redisson实现分布式锁,它内置了可重入锁、Watch Dog自动续期、Pub/Sub优化等特性,代码简洁且久经考验。对于金融等高可用场景,我们会使用Redlock算法,在多个独立的Redis实例上获取锁。"

面试官:👍 "很好!你对Redis分布式锁的细节理解很透彻!"


本文完 🎬

上一篇: 195-分布式锁的实现方式.md
下一篇: 197-Zookeeper实现分布式锁的原理.md

作者注:写完这篇,我对Redis分布式锁的理解又深了一层!🔒
如果这篇文章对你有帮助,请给我一个Star⭐!