在分布式系统中,资源竞争是一个永恒的话题。当多个服务实例并行运行时,如何确保它们对共享资源的访问是安全且高效的?Redis分布式锁作为一种轻量级解决方案,已经成为了很多企业的标配。然而,这把"看似简单"的锁,却暗藏无数技术陷阱…

🧩 题目描述

某电商平台的秒杀系统,使用了Spring Boot + Redis构建。在峰值时每秒处理订单请求高达5000+,系统采用集群部署(20个服务节点)。为保证库存数据一致性,需要实现一个高性能、高可靠的分布式锁方案。

现有代码:

public boolean secKill(String productId, String userId) {
    String lockKey = "lock:" + productId;
    try {
        // 获取锁
        Boolean isLock = stringRedisTemplate.opsForValue()
                .setIfAbsent(lockKey, userId, 30, TimeUnit.SECONDS);
        
        if (Boolean.TRUE.equals(isLock)) {
            // 业务逻辑:检查并扣减库存
            int stock = getProductStock(productId);
            if (stock > 0) {
                boolean result = reduceStock(productId);
                if (result) {
                    // 创建订单等逻辑
                    return true;
                }
            }
            return false;
        } else {
            // 获取锁失败,稍后重试
            Thread.sleep(100);
            return secKill(productId, userId);
        }
    } catch (Exception e) {
        log.error("秒杀异常", e);
    } finally {
        // 释放锁
        stringRedisTemplate.delete(lockKey);
    }
    return false;
}

🚨 找出问题

上述代码在实际生产环境中出现了以下问题:

  1. 高并发场景下,系统CPU使用率异常飙升
  2. 同一商品有时会超卖
  3. 偶尔出现死锁,导致秒杀活动无法进行
  4. Redis主从切换时,出现数据不一致问题

💎 要求

  1. 详细分析上述代码存在的缺陷
  2. 实现一个健壮的Redis分布式锁,需满足:
    • 高性能(支撑5000+ TPS)
    • 不会出现死锁
    • 保证正确释放(锁只能被持有者释放)
    • 可重入性设计
    • 容错性(Redis节点故障时的处理策略)
  3. 对于Redis分布式锁,探讨Redlock算法在此场景的适用性

技术深度剖析

这绝不是一道简单的题目,它触及了分布式系统最核心的一些挑战。Redis分布式锁看似简单,却暗藏玄机。从最初的SETNX命令,到后来的SET EX NX命令的组合,再到Redlock算法,每一步演进都是对分布式一致性理论的深刻理解。

代码缺陷分析

让我们以架构师视角,逐一剖析当前代码存在的问题:

现有代码缺陷
锁超时问题
锁误释放问题
重入性问题
递归重试导致栈溢出
主从架构下的数据一致性
业务执行时间超过锁过期时间
没有锁续期机制
任何人都可以释放锁
非锁持有者可能释放他人的锁
同一线程无法重复获取锁
递归调用导致OOM
主从复制是异步的
主节点崩溃时锁信息丢失

解决方案

1. 优化后的分布式锁实现

public class RedisDistributedLock {
    private StringRedisTemplate redisTemplate;
    private ThreadLocal<String> lockFlag = new ThreadLocal<>();
    private static final String LOCK_SUCCESS = "OK";
    private static final String SET_IF_NOT_EXIST = "NX";
    private static final String SET_WITH_EXPIRE_TIME = "EX";
    private static final Long RELEASE_SUCCESS = 1L;
    
    // Redisson的看门狗默认续期时间
    private static final long WATCHDOG_TIMEOUT = 30 * 1000;
    
    public RedisDistributedLock(StringRedisTemplate redisTemplate) {
        this.redisTemplate = redisTemplate;
    }
    
    /**
     * 尝试获取分布式锁
     * @param lockKey 锁键
     * @param requestId 请求标识
     * @param expireTime 超期时间
     * @return 是否获取成功
     */
    public boolean tryLock(String lockKey, String requestId, int expireTime) {
        // 如果当前线程已经持有锁,实现可重入
        String currentId = lockFlag.get();
        if (requestId.equals(currentId)) {
            return true;
        }
        
        // 使用Lua脚本确保操作原子性
        String script = "if redis.call('set', KEYS[1], ARGV[1], 'NX', 'EX', ARGV[2]) then return 1 else return 0 end";
        Long result = redisTemplate.execute(
            new DefaultRedisScript<>(script, Long.class),
            Collections.singletonList(lockKey),
            requestId, String.valueOf(expireTime));
        
        if (RELEASE_SUCCESS.equals(result)) {
            lockFlag.set(requestId);
            // 启动看门狗机制,自动续期
            startWatchDog(lockKey, requestId, expireTime);
            return true;
        }
        return false;
    }
    
    /**
     * 释放分布式锁
     * @param lockKey 锁键
     * @param requestId 请求标识
     * @return 是否释放成功
     */
    public boolean releaseLock(String lockKey, String requestId) {
        // 使用Lua脚本确保释放锁的原子性,同时验证身份
        String script = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";
        Long result = redisTemplate.execute(
            new DefaultRedisScript<>(script, Long.class),
            Collections.singletonList(lockKey),
            requestId);
        
        if (RELEASE_SUCCESS.equals(result)) {
            lockFlag.remove();
            return true;
        }
        return false;
    }
    
    /**
     * 看门狗机制,自动续期锁
     */
    private void startWatchDog(String lockKey, String requestId, int expireTime) {
        Timer timer = new Timer(true);
        timer.schedule(new TimerTask() {
            @Override
            public void run() {
                String script = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('expire', KEYS[1], ARGV[2]) else return 0 end";
                Long result = redisTemplate.execute(
                    new DefaultRedisScript<>(script, Long.class),
                    Collections.singletonList(lockKey),
                    requestId, String.valueOf(expireTime));
                
                if (RELEASE_SUCCESS.equals(result)) {
                    // 继续延期
                    startWatchDog(lockKey, requestId, expireTime);
                } else {
                    // 锁已经不存在,取消定时器
                    timer.cancel();
                }
            }
        }, expireTime * 1000 / 3);  // 在过期时间的1/3时间点续期
    }
}

2. 使用Redisson实现更健壮的分布式锁

@Service
public class SecKillServiceWithRedisson {
    
    @Autowired
    private RedissonClient redissonClient;
    
    public boolean secKill(String productId, String userId) {
        String lockKey = "lock:" + productId;
        RLock lock = redissonClient.getLock(lockKey);
        
        try {
            // 获取锁,最多等待500ms,锁过期时间为30s
            boolean isLocked = lock.tryLock(500, 30000, TimeUnit.MILLISECONDS);
            if (isLocked) {
                // 业务逻辑:检查并扣减库存
                int stock = getProductStock(productId);
                if (stock > 0) {
                    boolean result = reduceStock(productId);
                    if (result) {
                        // 创建订单等逻辑
                        return true;
                    }
                }
                return false;
            } else {
                return false; // 获取锁失败,可以进行其他处理
            }
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
            return false;
        } finally {
            // 只有持有锁的线程才能解锁
            if (lock.isHeldByCurrentThread()) {
                lock.unlock();
            }
        }
    }
}

3. Redlock算法在高可用场景的实现

@Service
public class SecKillServiceWithRedlock {
    
    @Autowired
    private RedissonClient redissonClient;
    
    public boolean secKill(String productId, String userId) {
        String lockKey = "lock:" + productId;
        
        // 创建多个Redis实例的RLock
        RLock lock1 = redissonClient.getLock("redis://redis1:6379/" + lockKey);
        RLock lock2 = redissonClient.getLock("redis://redis2:6379/" + lockKey);
        RLock lock3 = redissonClient.getLock("redis://redis3:6379/" + lockKey);
        
        // 组合多个Redis实例的锁为一个Redlock
        RedissonRedLock redLock = new RedissonRedLock(lock1, lock2, lock3);
        
        try {
            // 尝试获取锁,最多等待500ms,锁过期时间为30s
            boolean isLocked = redLock.tryLock(500, 30000, TimeUnit.MILLISECONDS);
            if (isLocked) {
                // 业务逻辑:检查并扣减库存
                int stock = getProductStock(productId);
                if (stock > 0) {
                    boolean result = reduceStock(productId);
                    if (result) {
                        // 创建订单等逻辑
                        return true;
                    }
                }
                return false;
            } else {
                return false; // 获取锁失败
            }
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
            return false;
        } finally {
            // 释放锁
            redLock.unlock();
        }
    }
}

分布式锁实现的CAP权衡

在分布式系统设计中,没有一种锁实现可以同时满足CAP三个特性。Redis分布式锁方案主要在AP和CP之间做出选择:

  1. 基础Redis锁:倾向于可用性(A)和分区容错性§,但在网络分区或主从切换时可能丧失一致性
  2. Redlock算法:通过多节点共识,更倾向于一致性©和分区容错性§,但可能影响可用性
  3. Redis+Zookeeper混合方案:结合两种锁的优势,但增加了系统复杂度

深度思考

你的方案是否真正解决了分布式锁的核心问题?当我们追求绝对的一致性时,可能需要考虑更重量级的解决方案,如Zookeeper或etcd。但在实际业务场景中,我们通常需要在性能和一致性之间做出权衡。

思考:在高并发秒杀场景下,是否可以考虑乐观锁方案结合Redis缓存来提升系统性能?


不知不觉,我们已经深入探讨了Redis分布式锁的核心技术细节。当你在项目中遇到类似问题时,希望这篇文章能给你带来启发。技术无止境,让我们一起在分布式系统的海洋中,寻找更优雅的解决之道。

本文由 绘问 技术团队出品,私信我评论区留言,获取更多Java与分布式系统架构干货!

Logo

魔乐社区(Modelers.cn) 是一个中立、公益的人工智能社区,提供人工智能工具、模型、数据的托管、展示与应用协同服务,为人工智能开发及爱好者搭建开放的学习交流平台。社区通过理事会方式运作,由全产业链共同建设、共同运营、共同享有,推动国产AI生态繁荣发展。

更多推荐