Redis分布式锁深度解析:从SETNX到Redisson,彻底搞懂分布式锁!

难度:⭐⭐⭐⭐⭐ | 适合人群:想掌握分布式锁的开发者


💥 开场:一次"恐怖"的超卖事故

时间: 双11凌晨
地点: 公司作战室
事件: 秒杀活动

产品经理: "iPhone限量100台,0点开抢!"

我: "代码都准备好了,没问题!" 😎


0点整,秒杀开始...

00:00:01 - 订单数:50
00:00:02 - 订单数:100
00:00:03 - 订单数:150 ← 等等?怎么超了?
00:00:05 - 订单数:237 😱

产品经理(咆哮): "怎么回事??库存只有100台,怎么卖出去237台???"

我: "不可能啊,我加了库存判断..." 😰


紧急查看代码:

java 复制代码
@Service
public class OrderService {
    
    @Autowired
    private RedisTemplate<String, String> redisTemplate;
    
    public boolean createOrder(Long productId, Long userId) {
        String stockKey = "stock:product:" + productId;
        
        // 1. 查询库存
        String stockStr = redisTemplate.opsForValue().get(stockKey);
        int stock = Integer.parseInt(stockStr);
        
        // 2. 判断库存
        if (stock <= 0) {
            return false;  // 没库存了
        }
        
        // 3. 扣减库存
        stock--;
        redisTemplate.opsForValue().set(stockKey, String.valueOf(stock));
        
        // 4. 创建订单
        orderDao.save(new Order(productId, userId));
        
        return true;
    }
}

哈吉米(紧急赶来): "你这代码有并发问题!"

我: "哪里有问题?" 🤔

南北绿豆画了个图:

sequenceDiagram participant 线程1 participant 线程2 participant Redis 线程1->>Redis: 查询库存(100) 线程2->>Redis: 查询库存(100) 线程1->>线程1: 判断库存>0,通过 线程2->>线程2: 判断库存>0,通过 线程1->>Redis: 设置库存(99) 线程2->>Redis: 设置库存(99) Note over 线程1,线程2: 两个线程都扣减成功!
但库存只减了1!

阿西噶阿西: "这就是经典的并发问题,需要加锁!"

我: "单机可以用synchronized,但现在是分布式部署,怎么办?" 😓

哈吉米: "这就需要分布式锁了!"


🎯 第一问:什么是分布式锁?

单机锁 vs 分布式锁

单机环境:

java 复制代码
// 单机可以用synchronized
public synchronized void deductStock() {
    int stock = getStock();
    if (stock > 0) {
        setStock(stock - 1);  // 只有一个线程能执行
    }
}

工作原理:

markdown 复制代码
JVM内存中的锁对象
    ↓
线程1获取锁 → 执行代码 → 释放锁
线程2等待 → 获取锁 → 执行代码 → 释放锁

分布式环境:

arduino 复制代码
服务器1(JVM1)
├─ 线程1:synchronized锁住了JVM1的对象
└─ 线程2:等待JVM1的锁

服务器2(JVM2)
├─ 线程3:synchronized锁住了JVM2的对象 ← 不同的锁!
└─ 线程4:等待JVM2的锁

线程1和线程3同时执行! ❌
synchronized失效!

南北绿豆: "分布式环境需要一个所有服务器都能访问的'外部锁'!"


分布式锁的特点

markdown 复制代码
分布式锁需要满足:

1. 互斥性
   - 同一时刻只有一个客户端能持有锁

2. 不会死锁
   - 即使持有锁的客户端崩溃,也能释放锁

3. 加锁和解锁必须是同一个客户端
   - 不能解别人的锁

4. 容错性
   - 只要大部分Redis节点正常,就能加锁和解锁

分布式锁的实现方式

方式 优点 缺点 推荐度
Redis 性能高、简单 可能丢失锁 ⭐⭐⭐⭐⭐
Zookeeper 可靠性高 性能低、复杂 ⭐⭐⭐⭐
数据库 简单、易理解 性能差 ⭐⭐
Etcd 可靠性高 运维复杂 ⭐⭐⭐

哈吉米: "99%的场景用Redis分布式锁就够了!"


🔧 第二问:Redis分布式锁的演进

版本1:SETNX(最初版本)

java 复制代码
public boolean lock(String lockKey) {
    // SETNX:key不存在才设置
    Boolean result = redisTemplate.opsForValue().setIfAbsent(lockKey, "locked");
    return Boolean.TRUE.equals(result);
}

public void unlock(String lockKey) {
    redisTemplate.delete(lockKey);
}

// 使用
if (lock("lock:product:123")) {
    try {
        // 业务逻辑
    } finally {
        unlock("lock:product:123");
    }
}

问题1:死锁!

sequenceDiagram participant 客户端 participant Redis 客户端->>Redis: SETNX lock:order:1 "locked" Redis-->>客户端: OK(获取锁成功) Note over 客户端: 执行业务逻辑... Note over 客户端: 突然崩溃!💥 Note over Redis: 锁永远不会被释放
其他客户端永远获取不到锁
死锁!

版本2:SETNX + EXPIRE(有缺陷)

java 复制代码
public boolean lock(String lockKey) {
    Boolean result = redisTemplate.opsForValue().setIfAbsent(lockKey, "locked");
    if (Boolean.TRUE.equals(result)) {
        // 设置过期时间
        redisTemplate.expire(lockKey, 10, TimeUnit.SECONDS);
        return true;
    }
    return false;
}

问题2:非原子操作!

sequenceDiagram participant 客户端 participant Redis 客户端->>Redis: SETNX lock:order:1 "locked" Redis-->>客户端: OK Note over 客户端: 准备设置过期时间 Note over 客户端: 突然崩溃!💥 Note over Redis: 还没设置过期时间
又死锁了!

版本3:SET EX NX(原子操作)

java 复制代码
public boolean lock(String lockKey, String lockValue, long expireTime) {
    // SET key value EX seconds NX
    // 一条命令完成:设置值 + 过期时间 + 不存在才设置
    Boolean result = redisTemplate.opsForValue().setIfAbsent(
        lockKey, 
        lockValue, 
        expireTime, 
        TimeUnit.SECONDS
    );
    return Boolean.TRUE.equals(result);
}

public void unlock(String lockKey, String lockValue) {
    String currentValue = redisTemplate.opsForValue().get(lockKey);
    if (lockValue.equals(currentValue)) {
        redisTemplate.delete(lockKey);
    }
}

// 使用
String lockValue = UUID.randomUUID().toString();
if (lock("lock:product:123", lockValue, 10)) {
    try {
        // 业务逻辑
    } finally {
        unlock("lock:product:123", lockValue);
    }
}

改进:

  • ✅ 原子操作
  • ✅ 自动过期
  • ✅ 防止误删(检查lockValue)

问题3:误删别人的锁!

sequenceDiagram participant 客户端1 participant 客户端2 participant Redis 客户端1->>Redis: SET lock "uuid1" EX 10 NX Redis-->>客户端1: OK(获取锁) Note over 客户端1: 业务执行超过10秒 Note over Redis: 锁自动过期,被删除 客户端2->>Redis: SET lock "uuid2" EX 10 NX Redis-->>客户端2: OK(获取锁) Note over 客户端1: 业务执行完成 客户端1->>Redis: DEL lock Note over Redis: 删除的是客户端2的锁!💥 Note over 客户端2: 锁丢了,并发问题!

版本4:Lua脚本保证原子性

java 复制代码
public void unlock(String lockKey, String lockValue) {
    // 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),
        lockValue
    );
}

Lua脚本的优势:

  • ✅ 在Redis中原子执行
  • ✅ 判断和删除一气呵成
  • ✅ 不会误删

问题4:锁续期!

diff 复制代码
业务执行时间不确定:
- 锁过期时间:10秒
- 业务执行时间:15秒

10秒后锁过期 → 其他客户端获取锁 → 并发问题

需要自动续期(Watch Dog)!


版本5:Redisson(推荐,生产级)

阿西噶阿西: "Redisson已经帮我们实现了所有细节!"

添加依赖:

xml 复制代码
<dependency>
    <groupId>org.redisson</groupId>
    <artifactId>redisson-spring-boot-starter</artifactId>
    <version>3.20.0</version>
</dependency>

配置:

yaml 复制代码
spring:
  redis:
    host: localhost
    port: 6379

使用:

java 复制代码
@Service
public class OrderService {
    
    @Autowired
    private RedissonClient redissonClient;
    
    public boolean createOrder(Long productId, Long userId) {
        
        // 获取锁对象
        RLock lock = redissonClient.getLock("lock:product:" + productId);
        
        try {
            // 尝试加锁
            // 参数:等待时间、锁自动释放时间、时间单位
            boolean locked = lock.tryLock(5, 10, TimeUnit.SECONDS);
            
            if (locked) {
                System.out.println("获取锁成功");
                
                // 业务逻辑
                int stock = getStock(productId);
                if (stock > 0) {
                    deductStock(productId);
                    saveOrder(productId, userId);
                    return true;
                }
                
                return false;
            } else {
                System.out.println("获取锁失败");
                return false;
            }
            
        } catch (InterruptedException e) {
            e.printStackTrace();
            return false;
        } finally {
            // 释放锁
            if (lock.isHeldByCurrentThread()) {
                lock.unlock();
                System.out.println("释放锁成功");
            }
        }
    }
}

Redisson的优势:

复制代码
✅ 自动续期(Watch Dog机制)
✅ 可重入锁
✅ 公平锁/非公平锁
✅ 读写锁
✅ 联锁(MultiLock)
✅ 红锁(RedLock)
✅ 自动释放(防死锁)

🐶 第三问:Watch Dog自动续期机制

什么是Watch Dog?

哈吉米: "Redisson的看门狗机制,自动给锁续命!"

工作原理:

sequenceDiagram participant 客户端 participant Redis participant WatchDog 客户端->>Redis: 加锁(过期时间30秒) Redis-->>客户端: 加锁成功 activate WatchDog Note over WatchDog: 启动Watch Dog定时任务
每10秒检查一次 loop 每10秒 WatchDog->>WatchDog: 检查锁是否还持有 WatchDog->>Redis: 续期(重置为30秒) end Note over 客户端: 业务执行完成 客户端->>Redis: 释放锁 deactivate WatchDog Note over WatchDog: 停止Watch Dog

源码分析(简化版):

java 复制代码
// Redisson的Watch Dog机制
private void renewExpiration() {
    
    // 定时任务:每 internalLockLeaseTime/3 执行一次
    // 默认30秒,所以每10秒执行
    Timeout task = commandExecutor.getConnectionManager().newTimeout(timeout -> {
        
        // 续期锁
        renewExpirationAsync(threadId);
        
    }, internalLockLeaseTime / 3, TimeUnit.MILLISECONDS);
}

private void renewExpirationAsync(long threadId) {
    // 执行Lua脚本,重置过期时间为30秒
    String script = 
        "if redis.call('hexists', KEYS[1], ARGV[2]) == 1 then " +
        "    redis.call('pexpire', KEYS[1], ARGV[1]); " +
        "    return 1; " +
        "else " +
        "    return 0; " +
        "end";
    
    // ...
}

效果:

makefile 复制代码
加锁时间:00:00:00,过期时间30秒
    ↓
00:00:10 - Watch Dog续期 → 过期时间变成00:00:40
    ↓
00:00:20 - Watch Dog续期 → 过期时间变成00:00:50
    ↓
00:00:25 - 业务执行完成,主动释放锁
    ↓
Watch Dog停止

再也不用担心业务执行时间长了!


🔄 第四问:可重入锁

什么是可重入?

南北绿豆: "可重入就是同一个线程可以多次获取同一把锁。"

场景:

java 复制代码
public void method1() {
    RLock lock = redissonClient.getLock("lock:test");
    lock.lock();
    
    try {
        System.out.println("method1 获取锁");
        method2();  // 调用method2
    } finally {
        lock.unlock();
    }
}

public void method2() {
    RLock lock = redissonClient.getLock("lock:test");  // 同一把锁
    lock.lock();  // 再次获取锁
    
    try {
        System.out.println("method2 获取锁");
        // 业务逻辑
    } finally {
        lock.unlock();
    }
}

时序图:

sequenceDiagram participant 线程1 participant Redis 线程1->>Redis: 加锁 lock:test(计数=1) Redis-->>线程1: 成功 Note over 线程1: 执行method1 线程1->>Redis: 再次加锁 lock:test(计数=2) Redis-->>线程1: 成功(同一线程) Note over 线程1: 执行method2 线程1->>Redis: 释放锁(计数=1) 线程1->>Redis: 释放锁(计数=0,删除)

Redisson实现原理:

java 复制代码
// Redisson使用Hash结构存储锁
HSET lock:test threadId 1  // 加锁,计数1
HSET lock:test threadId 2  // 再次加锁,计数2
HINCRBY lock:test threadId -1  // 释放锁,计数1
HINCRBY lock:test threadId -1  // 释放锁,计数0,删除

💻 第五问:完整实战 - 秒杀场景

秒杀业务代码

java 复制代码
@Service
public class SeckillService {
    
    @Autowired
    private RedissonClient redissonClient;
    
    @Autowired
    private RedisTemplate<String, String> redisTemplate;
    
    @Autowired
    private OrderDao orderDao;
    
    /**
     * 秒杀下单
     */
    public boolean seckill(Long productId, Long userId) {
        
        String lockKey = "lock:seckill:product:" + productId;
        RLock lock = redissonClient.getLock(lockKey);
        
        try {
            // 尝试加锁(等待5秒,锁10秒后自动释放)
            boolean locked = lock.tryLock(5, 10, TimeUnit.SECONDS);
            
            if (!locked) {
                System.out.println("获取锁失败,请稍后重试");
                return false;
            }
            
            System.out.println("获取锁成功:" + Thread.currentThread().getName());
            
            // 1. 检查库存
            String stockKey = "stock:product:" + productId;
            String stockStr = redisTemplate.opsForValue().get(stockKey);
            
            if (stockStr == null) {
                return false;
            }
            
            int stock = Integer.parseInt(stockStr);
            if (stock <= 0) {
                System.out.println("库存不足");
                return false;
            }
            
            // 2. 检查是否已经购买过(一人一单)
            String userKey = "seckill:user:" + userId + ":product:" + productId;
            Boolean bought = redisTemplate.hasKey(userKey);
            if (Boolean.TRUE.equals(bought)) {
                System.out.println("已经购买过了");
                return false;
            }
            
            // 3. 扣减库存
            Long newStock = redisTemplate.opsForValue().decrement(stockKey);
            System.out.println("扣减库存成功,剩余:" + newStock);
            
            // 4. 创建订单
            Order order = new Order();
            order.setProductId(productId);
            order.setUserId(userId);
            order.setCreateTime(new Date());
            orderDao.save(order);
            
            // 5. 标记已购买
            redisTemplate.opsForValue().set(userKey, "1", 1, TimeUnit.DAYS);
            
            System.out.println("秒杀成功:用户" + userId + ",订单" + order.getId());
            
            return true;
            
        } catch (InterruptedException e) {
            e.printStackTrace();
            return false;
        } finally {
            // 释放锁(必须是自己持有的锁)
            if (lock.isHeldByCurrentThread()) {
                lock.unlock();
                System.out.println("释放锁成功");
            }
        }
    }
}

并发测试

java 复制代码
@Test
public void testSeckill() throws InterruptedException {
    
    Long productId = 123L;
    
    // 初始化库存
    redisTemplate.opsForValue().set("stock:product:" + productId, "100");
    
    // 模拟1000个用户并发秒杀
    CountDownLatch latch = new CountDownLatch(1);
    AtomicInteger successCount = new AtomicInteger(0);
    
    ExecutorService executor = Executors.newFixedThreadPool(100);
    
    for (int i = 1; i <= 1000; i++) {
        final long userId = i;
        executor.submit(() -> {
            try {
                latch.await();  // 等待,模拟同时请求
                
                boolean success = seckillService.seckill(productId, userId);
                if (success) {
                    successCount.incrementAndGet();
                }
                
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        });
    }
    
    Thread.sleep(1000);
    System.out.println("准备开始秒杀...");
    
    latch.countDown();  // 开始!
    
    Thread.sleep(10000);  // 等待执行完成
    
    System.out.println("========== 秒杀结果 ==========");
    System.out.println("成功下单:" + successCount.get() + " 个");
    System.out.println("剩余库存:" + redisTemplate.opsForValue().get("stock:product:" + productId));
    System.out.println("数据库订单数:" + orderDao.count());
    
    executor.shutdown();
}

输出:

arduino 复制代码
准备开始秒杀...
获取锁成功:pool-1-thread-1
扣减库存成功,剩余:99
秒杀成功:用户1,订单1001
释放锁成功
获取锁成功:pool-1-thread-2
扣减库存成功,剩余:98
秒杀成功:用户2,订单1002
释放锁成功
...

========== 秒杀结果 ==========
成功下单:100 个
剩余库存:0
数据库订单数:100

完全准确!没有超卖! ✅

🎨 第六问:Redisson高级特性

公平锁

java 复制代码
// 公平锁:先来先得
RLock fairLock = redissonClient.getFairLock("lock:fair");

fairLock.lock();
try {
    // 业务逻辑
} finally {
    fairLock.unlock();
}

读写锁

java 复制代码
RReadWriteLock rwLock = redissonClient.getReadWriteLock("lock:rw");

// 读锁(共享锁,多个线程可以同时持有)
RLock readLock = rwLock.readLock();
readLock.lock();
try {
    // 读操作
} finally {
    readLock.unlock();
}

// 写锁(排他锁,只有一个线程可以持有)
RLock writeLock = rwLock.writeLock();
writeLock.lock();
try {
    // 写操作
} finally {
    writeLock.unlock();
}

时序图:

sequenceDiagram participant 线程1(读) participant 线程2(读) participant 线程3(写) participant Redis 线程1(读)->>Redis: 获取读锁 Redis-->>线程1(读): 成功(读锁计数=1) 线程2(读)->>Redis: 获取读锁 Redis-->>线程2(读): 成功(读锁计数=2) Note over 线程1(读),线程2(读): 两个读操作同时进行✅ 线程3(写)->>Redis: 获取写锁 Redis-->>线程3(写): 失败(有读锁) Note over 线程3(写): 等待... 线程1(读)->>Redis: 释放读锁(计数=1) 线程2(读)->>Redis: 释放读锁(计数=0) 线程3(写)->>Redis: 获取写锁 Redis-->>线程3(写): 成功

联锁(MultiLock)

java 复制代码
// 同时锁定多个资源
RLock lock1 = redissonClient.getLock("lock:1");
RLock lock2 = redissonClient.getLock("lock:2");
RLock lock3 = redissonClient.getLock("lock:3");

// 联锁:必须同时获取所有锁
RLock multiLock = redissonClient.getMultiLock(lock1, lock2, lock3);

multiLock.lock();
try {
    // 同时操作多个资源
} finally {
    multiLock.unlock();
}

使用场景: 转账(同时锁定两个账户)


📊 第七问:三种分布式锁对比

Redis vs Zookeeper vs 数据库

维度 Redis Zookeeper 数据库
性能 高 ⚡⚡ 中 ⚡ 低 🐢
可靠性 高 ✅
实现复杂度 简单 复杂 简单
是否阻塞 非阻塞 阻塞 阻塞
Watch Dog 有(Redisson) 不需要 不需要
推荐场景 高并发、对可靠性要求不极致 强一致性要求 并发低
推荐度 ⭐⭐⭐⭐⭐ ⭐⭐⭐⭐ ⭐⭐

Redis分布式锁的问题

阿西噶阿西: "Redis锁也不是完美的。"

问题:主从切换时可能丢锁

sequenceDiagram participant 客户端1 participant Redis主节点 participant Redis从节点 客户端1->>Redis主节点: 加锁 Redis主节点-->>客户端1: 成功 Note over Redis主节点: 主节点宕机!💥
(锁还没同步到从节点) Note over Redis从节点: 从节点升级为主节点 participant 客户端2 客户端2->>Redis从节点: 加锁(同一个key) Redis从节点-->>客户端2: 成功(因为没有这个锁) Note over 客户端1,客户端2: 两个客户端都持有锁!💥

解决方案:RedLock算法

java 复制代码
// 使用RedLock(需要多个Redis实例)
Config config1 = new Config();
config1.useSingleServer().setAddress("redis://redis1:6379");
RedissonClient client1 = Redisson.create(config1);

Config config2 = new Config();
config2.useSingleServer().setAddress("redis://redis2:6379");
RedissonClient client2 = Redisson.create(config2);

Config config3 = new Config();
config3.useSingleServer().setAddress("redis://redis3:6379");
RedissonClient client3 = Redisson.create(config3);

// RedLock:至少N/2+1个实例加锁成功才算成功
RLock lock1 = client1.getLock("lock:order");
RLock lock2 = client2.getLock("lock:order");
RLock lock3 = client3.getLock("lock:order");

RedissonRedLock redLock = new RedissonRedLock(lock1, lock2, lock3);

redLock.lock();
try {
    // 业务逻辑
} finally {
    redLock.unlock();
}

但: RedLock实现复杂,一般场景不需要


💡 最佳实践

1. 锁的粒度

java 复制代码
// ❌ 不推荐:锁粒度太大
RLock lock = redissonClient.getLock("lock:order");
lock.lock();
try {
    // 所有订单操作都用同一把锁,并发度低
} finally {
    lock.unlock();
}

// ✅ 推荐:锁粒度细化
RLock lock = redissonClient.getLock("lock:order:" + orderId);
lock.lock();
try {
    // 每个订单一把锁,并发度高
} finally {
    lock.unlock();
}

2. 锁的超时时间

java 复制代码
// ❌ 不推荐:时间太短
lock.tryLock(1, 2, TimeUnit.SECONDS);  // 业务可能执行不完

// ❌ 不推荐:时间太长
lock.tryLock(100, 300, TimeUnit.SECONDS);  // 锁持有时间过长

// ✅ 推荐:合理的时间 + Watch Dog
lock.tryLock(5, 30, TimeUnit.SECONDS);
// 等待5秒,锁30秒(Watch Dog会续期)

3. 异常处理

java 复制代码
RLock lock = redissonClient.getLock(lockKey);

try {
    if (lock.tryLock(5, 30, TimeUnit.SECONDS)) {
        try {
            // 业务逻辑
            
        } catch (BusinessException e) {
            // 业务异常处理
            throw e;
        } catch (Exception e) {
            // 系统异常处理
            log.error("系统异常", e);
            throw new SystemException("系统错误");
        } finally {
            // 确保释放锁
            if (lock.isHeldByCurrentThread()) {
                lock.unlock();
            }
        }
    }
} catch (InterruptedException e) {
    Thread.currentThread().interrupt();
}

4. 监控

java 复制代码
// 记录锁的持有时间
long start = System.currentTimeMillis();

lock.lock();
try {
    // 业务逻辑
} finally {
    long cost = System.currentTimeMillis() - start;
    
    // 如果持有锁超过5秒,记录警告
    if (cost > 5000) {
        log.warn("锁持有时间过长:{}ms,lockKey:{}", cost, lockKey);
    }
    
    lock.unlock();
}

💡 知识点总结

分布式锁核心要点

为什么需要分布式锁?

  • 分布式环境synchronized失效
  • 需要外部锁协调多个服务器

Redis锁的演进

  1. SETNX - 会死锁
  2. SETNX + EXPIRE - 非原子
  3. SET EX NX - 原子,但可能误删
  4. Lua脚本 - 原子删除
  5. Redisson - 生产级(推荐)

Redisson特性

  • Watch Dog自动续期
  • 可重入锁
  • 公平锁/非公平锁
  • 读写锁
  • 联锁、红锁

实战场景

  • 秒杀防超卖
  • 库存扣减
  • 防止重复提交
  • 定时任务防重复执行

注意事项

  • 锁粒度要细
  • 超时时间要合理
  • 必须在finally中释放
  • 只释放自己的锁

记忆口诀

复制代码
分布式锁很重要,
Redis实现最常用。
SETNX加EXPIRE,
原子操作要保证。
Lua脚本删锁好,
判断删除一起搞。
Redisson是神器,
自动续期看门狗。
秒杀防超卖,
库存扣减要加锁。

🤔 常见面试题

Q1: Redis分布式锁如何实现?

A:

vbnet 复制代码
基础实现:
SET lock_key unique_value EX 10 NX

- EX 10:10秒后自动过期
- NX:key不存在才设置
- unique_value:唯一标识(UUID)

释放锁(Lua脚本):
if redis.call('get', KEYS[1]) == ARGV[1] then
    return redis.call('del', KEYS[1])
else
    return 0
end

生产推荐:Redisson
- 自动续期
- 可重入
- 更多特性

Q2: Redis锁有什么问题?

A:

diff 复制代码
问题1:主从切换时可能丢锁
- 主节点加锁
- 锁还没同步到从节点
- 主节点宕机
- 从节点升级,没有锁
- 其他客户端能加锁成功

解决:RedLock(多个独立Redis实例)

问题2:业务执行时间长,锁过期
解决:Watch Dog自动续期

问题3:误删别人的锁
解决:Lua脚本原子删除

Q3: 什么场景用分布式锁?

A:

markdown 复制代码
典型场景:

1. 秒杀/抢购
   - 防止超卖
   
2. 库存扣减
   - 保证库存准确

3. 防止重复提交
   - 订单重复创建

4. 定时任务
   - 多实例只执行一次

5. 缓存重建
   - 防止缓存击穿

💬 写在最后

从SETNX到Redisson,我们深入学习了Redis分布式锁:

  • 🔐 理解了分布式锁的必要性
  • 🔄 掌握了锁的演进过程
  • 🐶 学会了Watch Dog机制
  • 💻 完成了秒杀实战案例

这篇文章,希望能让你在生产环境中正确使用分布式锁!

如果这篇文章对你有帮助,请:

  • 👍 点赞支持
  • ⭐ 收藏备用
  • 🔄 转发分享
  • 💬 评论交流

下一篇我们聊Redis主从复制与哨兵! 👋


相关推荐
绝无仅有6 小时前
百度面试题解析:微服务架构、Dubbo、Redis及其一致性问题(一)
后端·面试·github
绝无仅有6 小时前
百度面试题解析:Zookeeper、ArrayList、生产者消费者模型及多线程(二)
后端·面试·github
菜鸟谢6 小时前
二进制翻译技术
后端
自由的疯7 小时前
Java 如何学习Docker
java·后端·架构
程序员爱钓鱼7 小时前
Python编程实战 · 基础入门篇 | 变量与命名规范
后端·python
自由的疯7 小时前
Java Docker本地部署
java·后端·架构
William_cl7 小时前
ASP.NET MVC 前置基础:宿主环境 & HttpRuntime 管道,从部署到流程拆透(附避坑指南)
后端·asp.net·mvc
IT_陈寒7 小时前
Vue3性能优化实战:这7个技巧让我的应用加载速度提升50%!
前端·人工智能·后端
小宁爱Python7 小时前
Django Web 开发系列(一):视图基础与 URL 路由配置全解析
后端·python·django