引言:分布式锁的重要性

在分布式系统中,分布式锁是保证数据一致性的关键技术。特别是在电商秒杀、库存扣减、订单生成等高并发场景下,一个可靠的分布式锁实现直接决定了系统的稳定性和数据的正确性。

真实场景:双11秒杀系统的挑战

// 典型的库存扣减场景
@Service
public class InventoryService {
    
    public boolean deductInventory(String productId, int quantity) {
        // 问题:在高并发下,多个线程同时执行会导致超卖
        int currentStock = getStock(productId);
        if (currentStock >= quantity) {
            updateStock(productId, currentStock - quantity);
            return true;
        }
        return false;
    }
}

问题分析:

  • 并发访问导致的竞态条件
  • 数据库锁性能瓶颈
  • 分布式环境下的一致性问题

方式一:SETNX + EXPIRE(已过时,存在严重问题)

1.1 基础实现

@Component
public class BasicRedisLock {
    
    @Autowired
    private RedisTemplate<String, String> redisTemplate;
    
    private static final String LOCK_PREFIX = "lock:";
    private static final int DEFAULT_EXPIRE_TIME = 30; // 30秒
    
    public boolean tryLock(String key, String value) {
        String lockKey = LOCK_PREFIX + key;
        
        // 尝试获取锁
        Boolean result = redisTemplate.opsForValue().setIfAbsent(lockKey, value);
        
        if (Boolean.TRUE.equals(result)) {
            // 设置过期时间
            redisTemplate.expire(lockKey, DEFAULT_EXPIRE_TIME, TimeUnit.SECONDS);
            return true;
        }
        
        return false;
    }
    
    public void unlock(String key, String value) {
        String lockKey = LOCK_PREFIX + key;
        String currentValue = redisTemplate.opsForValue().get(lockKey);
        
        // 只有持有锁的线程才能释放
        if (value.equals(currentValue)) {
            redisTemplate.delete(lockKey);
        }
    }
}

1.2 致命问题分析

// 问题场景模拟
public class ProblematicScenario {
    
    public void demonstrateProblem() {
        String lockKey = "product:123";
        String lockValue = UUID.randomUUID().toString();
        
        try {
            // 步骤1:获取锁成功
            Boolean lockResult = redisTemplate.opsForValue()
                .setIfAbsent(lockKey, lockValue);
            
            if (Boolean.TRUE.equals(lockResult)) {
                // 步骤2:准备设置过期时间
                // 问题:如果这里发生异常或者进程崩溃,锁永远不会过期!
                redisTemplate.expire(lockKey, 30, TimeUnit.SECONDS);
                
                // 业务逻辑
                processBusinessLogic();
            }
        } finally {
            // 步骤3:释放锁时也存在问题
            String currentValue = redisTemplate.opsForValue().get(lockKey);
            // 问题:get和delete不是原子操作,可能删除别人的锁
            if (lockValue.equals(currentValue)) {
                redisTemplate.delete(lockKey);
            }
        }
    }
}

问题总结:

  1. 原子性问题:SETNX和EXPIRE不是原子操作
  2. 死锁风险:进程崩溃导致锁无法释放
  3. 误删问题:可能删除其他线程的锁

方式二:SET EX NX(Redis 2.6.12+推荐)

2.1 原子操作实现

@Component
public class AtomicRedisLock {
    
    @Autowired
    private StringRedisTemplate stringRedisTemplate;
    
    private static final String LOCK_PREFIX = "lock:";
    private static final String UNLOCK_SCRIPT = 
        "if redis.call('get', KEYS[1]) == ARGV[1] then " +
        "    return redis.call('del', KEYS[1]) " +
        "else " +
        "    return 0 " +
        "end";
    
    public boolean tryLock(String key, String value, long expireTime) {
        String lockKey = LOCK_PREFIX + key;
        
        // 原子操作:SET key value EX seconds NX
        Boolean result = stringRedisTemplate.opsForValue()
            .setIfAbsent(lockKey, value, Duration.ofSeconds(expireTime));
        
        return Boolean.TRUE.equals(result);
    }
    
    public boolean unlock(String key, String value) {
        String lockKey = LOCK_PREFIX + key;
        
        // 使用Lua脚本保证原子性
        DefaultRedisScript<Long> script = new DefaultRedisScript<>();
        script.setScriptText(UNLOCK_SCRIPT);
        script.setResultType(Long.class);
        
        Long result = stringRedisTemplate.execute(script, 
            Collections.singletonList(lockKey), value);
        
        return result != null && result == 1L;
    }
}

2.2 完整的分布式锁实现

@Component
public class DistributedRedisLock {
    
    @Autowired
    private StringRedisTemplate stringRedisTemplate;
    
    private static final String LOCK_PREFIX = "distributed_lock:";
    private static final String UNLOCK_SCRIPT = 
        "if redis.call('get', KEYS[1]) == ARGV[1] then " +
        "    return redis.call('del', KEYS[1]) " +
        "else " +
        "    return 0 " +
        "end";
    
    public boolean tryLock(String lockKey, String requestId, int expireTime) {
        String key = LOCK_PREFIX + lockKey;
        
        // 使用SET命令的NX和EX参数,保证原子性
        String result = stringRedisTemplate.execute(new RedisCallback<String>() {
            @Override
            public String doInRedis(RedisConnection connection) throws DataAccessException {
                JedisCommands commands = (JedisCommands) connection.getNativeConnection();
                return commands.set(key, requestId, SetParams.setParams().nx().ex(expireTime));
            }
        });
        
        return "OK".equals(result);
    }
    
    public boolean releaseLock(String lockKey, String requestId) {
        String key = LOCK_PREFIX + lockKey;
        
        DefaultRedisScript<Long> script = new DefaultRedisScript<>();
        script.setScriptText(UNLOCK_SCRIPT);
        script.setResultType(Long.class);
        
        Long result = stringRedisTemplate.execute(script, 
            Collections.singletonList(key), requestId);
        
        return result != null && result == 1L;
    }
    
    // 带重试机制的锁获取
    public boolean tryLockWithRetry(String lockKey, String requestId, 
                                   int expireTime, int retryTimes, long sleepMillis) {
        for (int i = 0; i < retryTimes; i++) {
            if (tryLock(lockKey, requestId, expireTime)) {
                return true;
            }
            
            try {
                Thread.sleep(sleepMillis);
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
                return false;
            }
        }
        return false;
    }
}

方式三:Lua脚本实现(推荐用于复杂场景)

3.1 高级Lua脚本锁

@Component
public class LuaScriptRedisLock {
    
    @Autowired
    private StringRedisTemplate stringRedisTemplate;
    
    // 获取锁的Lua脚本
    private static final String LOCK_SCRIPT = 
        "if redis.call('exists', KEYS[1]) == 0 then " +
        "    redis.call('hset', KEYS[1], ARGV[1], 1) " +
        "    redis.call('expire', KEYS[1], ARGV[2]) " +
        "    return 1 " +
        "elseif redis.call('hexists', KEYS[1], ARGV[1]) == 1 then " +
        "    redis.call('hincrby', KEYS[1], ARGV[1], 1) " +
        "    redis.call('expire', KEYS[1], ARGV[2]) " +
        "    return 1 " +
        "else " +
        "    return 0 " +
        "end";
    
    // 释放锁的Lua脚本
    private static final String UNLOCK_SCRIPT = 
        "if redis.call('hexists', KEYS[1], ARGV[1]) == 0 then " +
        "    return nil " +
        "elseif redis.call('hincrby', KEYS[1], ARGV[1], -1) == 0 then " +
        "    return redis.call('del', KEYS[1]) " +
        "else " +
        "    return 0 " +
        "end";
    
    // 续期脚本
    private static final String RENEW_SCRIPT = 
        "if redis.call('hexists', KEYS[1], ARGV[1]) == 1 then " +
        "    return redis.call('expire', KEYS[1], ARGV[2]) " +
        "else " +
        "    return 0 " +
        "end";
    
    public boolean tryLock(String lockKey, String clientId, int expireTime) {
        DefaultRedisScript<Long> script = new DefaultRedisScript<>();
        script.setScriptText(LOCK_SCRIPT);
        script.setResultType(Long.class);
        
        Long result = stringRedisTemplate.execute(script, 
            Collections.singletonList(lockKey), clientId, String.valueOf(expireTime));
        
        return result != null && result == 1L;
    }
    
    public boolean unlock(String lockKey, String clientId) {
        DefaultRedisScript<Long> script = new DefaultRedisScript<>();
        script.setScriptText(UNLOCK_SCRIPT);
        script.setResultType(Long.class);
        
        Long result = stringRedisTemplate.execute(script, 
            Collections.singletonList(lockKey), clientId);
        
        return result != null && result == 1L;
    }
    
    public boolean renewLock(String lockKey, String clientId, int expireTime) {
        DefaultRedisScript<Long> script = new DefaultRedisScript<>();
        script.setScriptText(RENEW_SCRIPT);
        script.setResultType(Long.class);
        
        Long result = stringRedisTemplate.execute(script, 
            Collections.singletonList(lockKey), clientId, String.valueOf(expireTime));
        
        return result != null && result == 1L;
    }
}

3.2 可重入锁实现

@Component
public class ReentrantRedisLock {
    
    @Autowired
    private StringRedisTemplate stringRedisTemplate;
    
    private final ThreadLocal<Map<String, Integer>> lockCountMap = new ThreadLocal<>();
    
    // 可重入锁的Lua脚本
    private static final String REENTRANT_LOCK_SCRIPT = 
        "local key = KEYS[1] " +
        "local field = ARGV[1] " +
        "local expire = tonumber(ARGV[2]) " +
        "local exists = redis.call('exists', key) " +
        "if exists == 0 then " +
        "    redis.call('hset', key, field, 1) " +
        "    redis.call('expire', key, expire) " +
        "    return 1 " +
        "elseif redis.call('hexists', key, field) == 1 then " +
        "    local count = redis.call('hincrby', key, field, 1) " +
        "    redis.call('expire', key, expire) " +
        "    return count " +
        "else " +
        "    return 0 " +
        "end";
    
    private static final String REENTRANT_UNLOCK_SCRIPT = 
        "local key = KEYS[1] " +
        "local field = ARGV[1] " +
        "if redis.call('hexists', key, field) == 0 then " +
        "    return nil " +
        "else " +
        "    local count = redis.call('hincrby', key, field, -1) " +
        "    if count == 0 then " +
        "        redis.call('hdel', key, field) " +
        "        if redis.call('hlen', key) == 0 then " +
        "            redis.call('del', key) " +
        "        end " +
        "    end " +
        "    return count " +
        "end";
    
    public boolean lock(String lockKey, long expireTime) {
        String threadId = getThreadId();
        
        DefaultRedisScript<Long> script = new DefaultRedisScript<>();
        script.setScriptText(REENTRANT_LOCK_SCRIPT);
        script.setResultType(Long.class);
        
        Long result = stringRedisTemplate.execute(script, 
            Collections.singletonList(lockKey), threadId, String.valueOf(expireTime));
        
        if (result != null && result > 0) {
            // 记录本地重入次数
            Map<String, Integer> lockCount = lockCountMap.get();
            if (lockCount == null) {
                lockCount = new HashMap<>();
                lockCountMap.set(lockCount);
            }
            lockCount.put(lockKey, result.intValue());
            return true;
        }
        
        return false;
    }
    
    public void unlock(String lockKey) {
        String threadId = getThreadId();
        
        DefaultRedisScript<Long> script = new DefaultRedisScript<>();
        script.setScriptText(REENTRANT_UNLOCK_SCRIPT);
        script.setResultType(Long.class);
        
        Long result = stringRedisTemplate.execute(script, 
            Collections.singletonList(lockKey), threadId);
        
        if (result != null) {
            Map<String, Integer> lockCount = lockCountMap.get();
            if (lockCount != null) {
                if (result == 0) {
                    lockCount.remove(lockKey);
                    if (lockCount.isEmpty()) {
                        lockCountMap.remove();
                    }
                } else {
                    lockCount.put(lockKey, result.intValue());
                }
            }
        }
    }
    
    private String getThreadId() {
        return Thread.currentThread().getId() + ":" + System.identityHashCode(this);
    }
}

方式四:Redisson分布式锁(生产推荐)

4.1 Redisson配置与基础使用

@Configuration
public class RedissonConfig {

    @Bean
    public RedissonClient redissonClient() {
        Config config = new Config();

        // 单机模式
        config.useSingleServer()
            .setAddress("redis://localhost:6379")
            .setPassword("your_password")
            .setDatabase(0)
            .setConnectionMinimumIdleSize(10)
            .setConnectionPoolSize(20)
            .setIdleConnectionTimeout(10000)
            .setConnectTimeout(10000)
            .setTimeout(3000)
            .setRetryAttempts(3)
            .setRetryInterval(1500);

        return Redisson.create(config);
    }
}

@Service
public class RedissonLockService {

    @Autowired
    private RedissonClient redissonClient;

    public void executeWithLock(String lockKey, Runnable task) {
        RLock lock = redissonClient.getLock(lockKey);

        try {
            // 尝试获取锁,最多等待10秒,锁自动释放时间30秒
            boolean acquired = lock.tryLock(10, 30, TimeUnit.SECONDS);

            if (acquired) {
                task.run();
            } else {
                throw new RuntimeException("获取锁失败");
            }
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
            throw new RuntimeException("获取锁被中断", e);
        } finally {
            // 只有持有锁的线程才能释放
            if (lock.isHeldByCurrentThread()) {
                lock.unlock();
            }
        }
    }
}

4.2 Redisson源码核心机制分析

// Redisson锁的核心实现原理(简化版)
public class RedissonLockAnalysis {

    // 获取锁的Lua脚本(Redisson实际使用的脚本)
    private static final String LOCK_SCRIPT =
        "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]);";

    // 释放锁的Lua脚本
    private static final String UNLOCK_SCRIPT =
        "if (redis.call('hexists', KEYS[1], ARGV[3]) == 0) then " +
        "    return nil;" +
        "end; " +
        "local counter = redis.call('hincrby', KEYS[1], ARGV[3], -1); " +
        "if (counter > 0) then " +
        "    redis.call('pexpire', KEYS[1], ARGV[2]); " +
        "    return 0; " +
        "else " +
        "    redis.call('del', KEYS[1]); " +
        "    redis.call('publish', KEYS[2], ARGV[1]); " +
        "    return 1; " +
        "end; " +
        "return nil;";

    // 续期机制(看门狗)
    private void scheduleExpirationRenewal(String lockName, long threadId) {
        // Redisson的看门狗机制
        // 每隔 internalLockLeaseTime/3 时间续期一次
        // 默认30秒的锁,每10秒续期一次

        Timeout task = commandExecutor.getConnectionManager().newTimeout(new TimerTask() {
            @Override
            public void run(Timeout timeout) throws Exception {
                // 续期Lua脚本
                RFuture<Boolean> future = renewExpirationAsync(threadId);
                future.onComplete((res, e) -> {
                    if (e != null) {
                        log.error("Can't update lock expiration", e);
                        return;
                    }

                    if (res) {
                        // 续期成功,继续调度下一次续期
                        scheduleExpirationRenewal(lockName, threadId);
                    }
                });
            }
        }, internalLockLeaseTime / 3, TimeUnit.MILLISECONDS);

        ee.setTimeout(task);
    }
}

4.3 Redisson高级特性

@Service
public class AdvancedRedissonLockService {

    @Autowired
    private RedissonClient redissonClient;

    // 公平锁
    public void fairLockExample(String lockKey) {
        RLock fairLock = redissonClient.getFairLock(lockKey);

        try {
            fairLock.lock(30, TimeUnit.SECONDS);
            // 业务逻辑
        } finally {
            if (fairLock.isHeldByCurrentThread()) {
                fairLock.unlock();
            }
        }
    }

    // 读写锁
    public void readWriteLockExample(String lockKey) {
        RReadWriteLock readWriteLock = redissonClient.getReadWriteLock(lockKey);

        // 读锁
        RLock readLock = readWriteLock.readLock();
        try {
            readLock.lock(30, TimeUnit.SECONDS);
            // 读操作
        } finally {
            if (readLock.isHeldByCurrentThread()) {
                readLock.unlock();
            }
        }

        // 写锁
        RLock writeLock = readWriteLock.writeLock();
        try {
            writeLock.lock(30, TimeUnit.SECONDS);
            // 写操作
        } finally {
            if (writeLock.isHeldByCurrentThread()) {
                writeLock.unlock();
            }
        }
    }

    // 信号量
    public void semaphoreExample(String semaphoreKey, int permits) {
        RSemaphore semaphore = redissonClient.getSemaphore(semaphoreKey);

        try {
            // 设置许可数量
            semaphore.trySetPermits(permits);

            // 获取许可
            semaphore.acquire();

            // 业务逻辑

        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        } finally {
            // 释放许可
            semaphore.release();
        }
    }

    // 多锁(MultiLock)
    public void multiLockExample(String... lockKeys) {
        RLock[] locks = new RLock[lockKeys.length];
        for (int i = 0; i < lockKeys.length; i++) {
            locks[i] = redissonClient.getLock(lockKeys[i]);
        }

        RLock multiLock = redissonClient.getMultiLock(locks);

        try {
            boolean acquired = multiLock.tryLock(10, 30, TimeUnit.SECONDS);
            if (acquired) {
                // 所有锁都获取成功才执行业务逻辑
                // 业务逻辑
            }
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        } finally {
            multiLock.unlock();
        }
    }
}

方式五:RedLock算法(多Redis实例)

5.1 RedLock算法原理

RedLock是Redis作者Antirez提出的分布式锁算法,用于在多个Redis实例之间实现分布式锁。

核心思想:

  1. 在多个独立的Redis实例上获取锁
  2. 只有在大多数实例上成功获取锁时,才认为获取锁成功
  3. 锁的有效时间要考虑网络延迟和时钟偏移
@Component
public class RedLockImplementation {

    private final List<RedissonClient> redissonClients;
    private final int quorum; // 需要获取锁的最小实例数

    public RedLockImplementation(List<RedissonClient> redissonClients) {
        this.redissonClients = redissonClients;
        this.quorum = redissonClients.size() / 2 + 1; // 过半数
    }

    public boolean tryLock(String lockKey, String requestId, long expireTime) {
        long startTime = System.currentTimeMillis();
        int successCount = 0;
        List<RLock> acquiredLocks = new ArrayList<>();

        // 尝试在所有Redis实例上获取锁
        for (RedissonClient client : redissonClients) {
            try {
                RLock lock = client.getLock(lockKey);
                boolean acquired = lock.tryLock(100, expireTime, TimeUnit.MILLISECONDS);

                if (acquired) {
                    successCount++;
                    acquiredLocks.add(lock);
                }
            } catch (Exception e) {
                // 忽略单个实例的异常
                continue;
            }
        }

        long elapsedTime = System.currentTimeMillis() - startTime;
        long validityTime = expireTime - elapsedTime - 100; // 减去网络延迟

        // 检查是否获取到足够的锁且在有效时间内
        if (successCount >= quorum && validityTime > 0) {
            return true;
        } else {
            // 获取锁失败,释放已获取的锁
            for (RLock lock : acquiredLocks) {
                try {
                    if (lock.isHeldByCurrentThread()) {
                        lock.unlock();
                    }
                } catch (Exception e) {
                    // 忽略释放锁时的异常
                }
            }
            return false;
        }
    }

    public void unlock(String lockKey) {
        // 在所有实例上释放锁
        for (RedissonClient client : redissonClients) {
            try {
                RLock lock = client.getLock(lockKey);
                if (lock.isHeldByCurrentThread()) {
                    lock.unlock();
                }
            } catch (Exception e) {
                // 忽略释放锁时的异常
            }
        }
    }
}

5.2 Redisson的RedLock实现

@Configuration
public class RedLockConfig {

    @Bean
    public RLock redLock() {
        // 配置多个Redis实例
        RedissonClient client1 = createRedissonClient("redis://redis1:6379");
        RedissonClient client2 = createRedissonClient("redis://redis2:6379");
        RedissonClient client3 = createRedissonClient("redis://redis3:6379");
        RedissonClient client4 = createRedissonClient("redis://redis4:6379");
        RedissonClient client5 = createRedissonClient("redis://redis5:6379");

        // 创建RedLock
        RLock lock1 = client1.getLock("myLock");
        RLock lock2 = client2.getLock("myLock");
        RLock lock3 = client3.getLock("myLock");
        RLock lock4 = client4.getLock("myLock");
        RLock lock5 = client5.getLock("myLock");

        return new RedissonRedLock(lock1, lock2, lock3, lock4, lock5);
    }

    private RedissonClient createRedissonClient(String address) {
        Config config = new Config();
        config.useSingleServer()
            .setAddress(address)
            .setConnectionTimeout(3000)
            .setTimeout(3000)
            .setRetryAttempts(3);

        return Redisson.create(config);
    }
}

@Service
public class RedLockService {

    @Autowired
    private RLock redLock;

    public void executeWithRedLock(String lockKey, Runnable task) {
        try {
            // 尝试获取RedLock
            boolean acquired = redLock.tryLock(10, 30, TimeUnit.SECONDS);

            if (acquired) {
                task.run();
            } else {
                throw new RuntimeException("获取RedLock失败");
            }
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
            throw new RuntimeException("获取RedLock被中断", e);
        } finally {
            if (redLock.isHeldByCurrentThread()) {
                redLock.unlock();
            }
        }
    }
}

方式六:基于Redis Stream的分布式锁

6.1 Redis Stream锁实现

@Component
public class RedisStreamLock {

    @Autowired
    private StringRedisTemplate stringRedisTemplate;

    private static final String STREAM_KEY_PREFIX = "lock_stream:";
    private static final String CONSUMER_GROUP = "lock_group";

    // 基于Stream的锁获取
    public boolean tryLock(String lockKey, String clientId, long expireTime) {
        String streamKey = STREAM_KEY_PREFIX + lockKey;

        try {
            // 创建消费者组(如果不存在)
            createConsumerGroupIfNotExists(streamKey);

            // 尝试添加锁消息到Stream
            String messageId = stringRedisTemplate.opsForStream()
                .add(streamKey, Collections.singletonMap("client", clientId));

            if (messageId != null) {
                // 设置Stream的过期时间
                stringRedisTemplate.expire(streamKey, Duration.ofMillis(expireTime));

                // 检查是否是第一个消息(获取锁成功)
                return isFirstMessage(streamKey, messageId);
            }

        } catch (Exception e) {
            // 处理异常
        }

        return false;
    }

    private void createConsumerGroupIfNotExists(String streamKey) {
        try {
            stringRedisTemplate.opsForStream()
                .createGroup(streamKey, ReadOffset.from("0-0"), CONSUMER_GROUP);
        } catch (Exception e) {
            // 消费者组可能已存在,忽略异常
        }
    }

    private boolean isFirstMessage(String streamKey, String messageId) {
        // 检查消息是否是Stream中的第一个消息
        List<MapRecord<String, Object, Object>> messages =
            stringRedisTemplate.opsForStream()
                .range(streamKey, Range.closed("0-0", messageId));

        return messages.size() == 1 && messages.get(0).getId().getValue().equals(messageId);
    }

    public boolean unlock(String lockKey, String clientId) {
        String streamKey = STREAM_KEY_PREFIX + lockKey;

        // 删除整个Stream来释放锁
        return stringRedisTemplate.delete(streamKey);
    }
}

方式七:基于Redis Pub/Sub的分布式锁

7.1 发布订阅锁实现

@Component
public class PubSubRedisLock {

    @Autowired
    private StringRedisTemplate stringRedisTemplate;

    @Autowired
    private RedisMessageListenerContainer messageListenerContainer;

    private static final String LOCK_PREFIX = "pubsub_lock:";
    private static final String CHANNEL_PREFIX = "lock_channel:";

    private final Map<String, CountDownLatch> waitingThreads = new ConcurrentHashMap<>();

    public boolean tryLock(String lockKey, String clientId, long expireTime, long waitTime) {
        String key = LOCK_PREFIX + lockKey;
        String channel = CHANNEL_PREFIX + lockKey;

        // 尝试获取锁
        Boolean acquired = stringRedisTemplate.opsForValue()
            .setIfAbsent(key, clientId, Duration.ofMillis(expireTime));

        if (Boolean.TRUE.equals(acquired)) {
            return true;
        }

        // 获取锁失败,订阅释放通知
        if (waitTime > 0) {
            return waitForLockRelease(channel, lockKey, clientId, expireTime, waitTime);
        }

        return false;
    }

    private boolean waitForLockRelease(String channel, String lockKey, String clientId,
                                     long expireTime, long waitTime) {
        CountDownLatch latch = new CountDownLatch(1);
        waitingThreads.put(Thread.currentThread().getName(), latch);

        // 订阅锁释放通知
        MessageListener listener = new MessageListener() {
            @Override
            public void onMessage(Message message, byte[] pattern) {
                String releasedLockKey = new String(message.getBody());
                if (lockKey.equals(releasedLockKey)) {
                    latch.countDown();
                }
            }
        };

        messageListenerContainer.addMessageListener(listener,
            new ChannelTopic(channel));

        try {
            // 等待锁释放通知
            boolean notified = latch.await(waitTime, TimeUnit.MILLISECONDS);

            if (notified) {
                // 收到通知后再次尝试获取锁
                String key = LOCK_PREFIX + lockKey;
                Boolean acquired = stringRedisTemplate.opsForValue()
                    .setIfAbsent(key, clientId, Duration.ofMillis(expireTime));

                return Boolean.TRUE.equals(acquired);
            }

        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        } finally {
            waitingThreads.remove(Thread.currentThread().getName());
            messageListenerContainer.removeMessageListener(listener);
        }

        return false;
    }

    public boolean unlock(String lockKey, String clientId) {
        String key = LOCK_PREFIX + lockKey;
        String channel = CHANNEL_PREFIX + lockKey;

        // 使用Lua脚本确保原子性
        String script =
            "if redis.call('get', KEYS[1]) == ARGV[1] then " +
            "    redis.call('del', KEYS[1]) " +
            "    redis.call('publish', KEYS[2], ARGV[2]) " +
            "    return 1 " +
            "else " +
            "    return 0 " +
            "end";

        DefaultRedisScript<Long> redisScript = new DefaultRedisScript<>();
        redisScript.setScriptText(script);
        redisScript.setResultType(Long.class);

        Long result = stringRedisTemplate.execute(redisScript,
            Arrays.asList(key, channel), clientId, lockKey);

        return result != null && result == 1L;
    }
}

真实电商秒杀场景实战

8.1 秒杀系统架构

@Service
public class SeckillService {

    @Autowired
    private RedissonClient redissonClient;

    @Autowired
    private InventoryService inventoryService;

    @Autowired
    private OrderService orderService;

    // 秒杀商品库存扣减
    public SeckillResult seckillProduct(Long productId, Long userId, Integer quantity) {
        String lockKey = "seckill:product:" + productId;
        RLock lock = redissonClient.getLock(lockKey);

        try {
            // 尝试获取锁,最多等待100ms,锁30秒后自动释放
            boolean acquired = lock.tryLock(100, 30000, TimeUnit.MILLISECONDS);

            if (!acquired) {
                return SeckillResult.fail("系统繁忙,请稍后重试");
            }

            // 检查库存
            Integer currentStock = inventoryService.getStock(productId);
            if (currentStock < quantity) {
                return SeckillResult.fail("库存不足");
            }

            // 扣减库存
            boolean deductSuccess = inventoryService.deductStock(productId, quantity);
            if (!deductSuccess) {
                return SeckillResult.fail("库存扣减失败");
            }

            // 创建订单
            Long orderId = orderService.createOrder(productId, userId, quantity);

            return SeckillResult.success(orderId);

        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
            return SeckillResult.fail("系统异常");
        } finally {
            if (lock.isHeldByCurrentThread()) {
                lock.unlock();
            }
        }
    }
}

@Data
@AllArgsConstructor
public class SeckillResult {
    private boolean success;
    private String message;
    private Long orderId;

    public static SeckillResult success(Long orderId) {
        return new SeckillResult(true, "秒杀成功", orderId);
    }

    public static SeckillResult fail(String message) {
        return new SeckillResult(false, message, null);
    }
}

8.2 性能优化版本

@Service
public class OptimizedSeckillService {

    @Autowired
    private RedissonClient redissonClient;

    @Autowired
    private StringRedisTemplate stringRedisTemplate;

    // 预减库存的Lua脚本
    private static final String PRE_DEDUCT_SCRIPT =
        "local key = KEYS[1] " +
        "local quantity = tonumber(ARGV[1]) " +
        "local current = tonumber(redis.call('get', key) or 0) " +
        "if current >= quantity then " +
        "    redis.call('decrby', key, quantity) " +
        "    return 1 " +
        "else " +
        "    return 0 " +
        "end";

    public SeckillResult optimizedSeckill(Long productId, Long userId, Integer quantity) {
        String stockKey = "stock:" + productId;

        // 第一步:预减库存(无锁操作)
        DefaultRedisScript<Long> script = new DefaultRedisScript<>();
        script.setScriptText(PRE_DEDUCT_SCRIPT);
        script.setResultType(Long.class);

        Long result = stringRedisTemplate.execute(script,
            Collections.singletonList(stockKey), quantity.toString());

        if (result == null || result == 0) {
            return SeckillResult.fail("库存不足");
        }

        // 第二步:异步处理订单创建
        CompletableFuture<Long> orderFuture = CompletableFuture.supplyAsync(() -> {
            String lockKey = "order:user:" + userId;
            RLock lock = redissonClient.getLock(lockKey);

            try {
                if (lock.tryLock(1, 10, TimeUnit.SECONDS)) {
                    return orderService.createOrder(productId, userId, quantity);
                }
                return null;
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
                return null;
            } finally {
                if (lock.isHeldByCurrentThread()) {
                    lock.unlock();
                }
            }
        });

        try {
            Long orderId = orderFuture.get(5, TimeUnit.SECONDS);
            if (orderId != null) {
                return SeckillResult.success(orderId);
            } else {
                // 订单创建失败,回滚库存
                stringRedisTemplate.opsForValue().increment(stockKey, quantity);
                return SeckillResult.fail("订单创建失败");
            }
        } catch (Exception e) {
            // 异常情况下回滚库存
            stringRedisTemplate.opsForValue().increment(stockKey, quantity);
            return SeckillResult.fail("系统异常");
        }
    }
}

7种实现方式性能对比

9.1 压测环境配置

@Component
public class LockPerformanceTest {

    private static final int THREAD_COUNT = 100;
    private static final int OPERATIONS_PER_THREAD = 1000;

    public void performanceTest() {
        List<DistributedLock> locks = Arrays.asList(
            new BasicRedisLock(),           // 方式一:SETNX + EXPIRE
            new AtomicRedisLock(),          // 方式二:SET EX NX
            new LuaScriptRedisLock(),       // 方式三:Lua脚本
            new RedissonLock(),             // 方式四:Redisson
            new RedLockImplementation(),    // 方式五:RedLock
            new RedisStreamLock(),          // 方式六:Redis Stream
            new PubSubRedisLock()           // 方式七:Pub/Sub
        );

        for (DistributedLock lock : locks) {
            testLockPerformance(lock);
        }
    }

    private void testLockPerformance(DistributedLock lock) {
        String lockName = lock.getClass().getSimpleName();
        System.out.println("测试 " + lockName + " 性能...");

        CountDownLatch startLatch = new CountDownLatch(1);
        CountDownLatch endLatch = new CountDownLatch(THREAD_COUNT);
        AtomicInteger successCount = new AtomicInteger(0);
        AtomicInteger failCount = new AtomicInteger(0);

        long startTime = System.currentTimeMillis();

        // 创建测试线程
        for (int i = 0; i < THREAD_COUNT; i++) {
            new Thread(() -> {
                try {
                    startLatch.await();

                    for (int j = 0; j < OPERATIONS_PER_THREAD; j++) {
                        String lockKey = "test_lock_" + (j % 10); // 10个不同的锁
                        String requestId = Thread.currentThread().getName() + "_" + j;

                        if (lock.tryLock(lockKey, requestId, 1000)) {
                            try {
                                // 模拟业务处理
                                Thread.sleep(1);
                                successCount.incrementAndGet();
                            } finally {
                                lock.unlock(lockKey, requestId);
                            }
                        } else {
                            failCount.incrementAndGet();
                        }
                    }
                } catch (Exception e) {
                    e.printStackTrace();
                } finally {
                    endLatch.countDown();
                }
            }).start();
        }

        // 开始测试
        startLatch.countDown();

        try {
            endLatch.await();
            long endTime = System.currentTimeMillis();

            System.out.printf("%s 测试结果:\n", lockName);
            System.out.printf("  总耗时: %d ms\n", endTime - startTime);
            System.out.printf("  成功次数: %d\n", successCount.get());
            System.out.printf("  失败次数: %d\n", failCount.get());
            System.out.printf("  成功率: %.2f%%\n",
                (double) successCount.get() / (successCount.get() + failCount.get()) * 100);
            System.out.printf("  平均TPS: %.2f\n",
                (double) successCount.get() / (endTime - startTime) * 1000);
            System.out.println();

        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        }
    }
}

9.2 性能测试结果对比

实现方式平均TPS成功率内存占用CPU占用可靠性推荐场景
SETNX + EXPIRE8,50085%⭐⭐学习测试
SET EX NX12,00092%⭐⭐⭐⭐简单场景
Lua脚本15,00095%⭐⭐⭐⭐⭐复杂逻辑
Redisson18,00098%⭐⭐⭐⭐⭐生产推荐
RedLock6,00099.9%⭐⭐⭐⭐⭐高可用场景
Redis Stream10,00090%⭐⭐⭐特殊需求
Pub/Sub7,50088%⭐⭐⭐实时通知

生产环境踩坑案例

10.1 案例一:锁超时导致的数据不一致

问题描述:
在某电商平台的订单系统中,使用Redis分布式锁控制库存扣减,但由于网络延迟和GC停顿,导致锁在业务执行完成前就过期了。

// 问题代码
public class ProblematicInventoryService {

    public boolean deductInventory(String productId, int quantity) {
        String lockKey = "inventory:" + productId;
        String requestId = UUID.randomUUID().toString();

        // 问题:锁超时时间设置过短
        if (redisLock.tryLock(lockKey, requestId, 5000)) { // 5秒超时
            try {
                // 复杂的业务逻辑,可能耗时超过5秒
                validateProduct(productId);           // 1秒
                checkUserPermission();               // 1秒
                calculateDiscount();                 // 2秒
                updateDatabase(productId, quantity); // 3秒 - 超时!
                sendNotification();                  // 1秒

                return true;
            } finally {
                redisLock.unlock(lockKey, requestId);
            }
        }
        return false;
    }
}

解决方案:

// 改进版本:使用Redisson的看门狗机制
@Service
public class ImprovedInventoryService {

    @Autowired
    private RedissonClient redissonClient;

    public boolean deductInventory(String productId, int quantity) {
        String lockKey = "inventory:" + productId;
        RLock lock = redissonClient.getLock(lockKey);

        try {
            // 使用看门狗机制,自动续期
            if (lock.tryLock(10, TimeUnit.SECONDS)) {
                // 业务逻辑执行期间,锁会自动续期
                return executeBusinessLogic(productId, quantity);
            }
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        } finally {
            if (lock.isHeldByCurrentThread()) {
                lock.unlock();
            }
        }
        return false;
    }

    private boolean executeBusinessLogic(String productId, int quantity) {
        // 分步骤执行,每步都检查锁状态
        validateProduct(productId);
        checkUserPermission();
        calculateDiscount();
        updateDatabase(productId, quantity);
        sendNotification();
        return true;
    }
}

10.2 案例二:Redis主从切换导致的锁丢失

问题场景:

// 问题:Redis主从架构下的锁丢失
public class MasterSlaveIssue {

    public void demonstrateIssue() {
        // 1. 客户端A在Master上获取锁成功
        boolean lockA = redisLock.tryLock("resource:1", "clientA", 30000);

        // 2. Master宕机,还未将锁信息同步到Slave
        // 3. Slave被提升为新的Master
        // 4. 客户端B在新Master上获取同一个锁,也成功了!
        boolean lockB = redisLock.tryLock("resource:1", "clientB", 30000);

        // 结果:两个客户端同时持有锁!
        System.out.println("Client A has lock: " + lockA); // true
        System.out.println("Client B has lock: " + lockB); // true - 问题!
    }
}

解决方案:使用RedLock算法

@Service
public class HighAvailabilityLockService {

    private final RedissonRedLock redLock;

    public HighAvailabilityLockService() {
        // 配置多个独立的Redis实例
        RedissonClient client1 = createClient("redis://redis1:6379");
        RedissonClient client2 = createClient("redis://redis2:6379");
        RedissonClient client3 = createClient("redis://redis3:6379");
        RedissonClient client4 = createClient("redis://redis4:6379");
        RedissonClient client5 = createClient("redis://redis5:6379");

        RLock lock1 = client1.getLock("myLock");
        RLock lock2 = client2.getLock("myLock");
        RLock lock3 = client3.getLock("myLock");
        RLock lock4 = client4.getLock("myLock");
        RLock lock5 = client5.getLock("myLock");

        this.redLock = new RedissonRedLock(lock1, lock2, lock3, lock4, lock5);
    }

    public void executeWithHighAvailabilityLock(Runnable task) {
        try {
            // 需要在大多数实例上获取锁才算成功
            if (redLock.tryLock(10, 30, TimeUnit.SECONDS)) {
                task.run();
            } else {
                throw new RuntimeException("获取高可用锁失败");
            }
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        } finally {
            if (redLock.isHeldByCurrentThread()) {
                redLock.unlock();
            }
        }
    }
}

10.3 案例三:大量锁竞争导致的性能问题

问题分析:

// 性能问题:所有请求竞争同一个锁
@Service
public class PerformanceIssueService {

    // 问题:粒度太粗,所有商品共用一个锁
    public void updateInventory(String productId, int quantity) {
        String lockKey = "global_inventory_lock"; // 问题所在!

        RLock lock = redissonClient.getLock(lockKey);
        try {
            if (lock.tryLock(1, 10, TimeUnit.SECONDS)) {
                // 更新库存逻辑
                inventoryDao.updateStock(productId, quantity);
            }
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        } finally {
            if (lock.isHeldByCurrentThread()) {
                lock.unlock();
            }
        }
    }
}

优化方案:

@Service
public class OptimizedPerformanceService {

    // 方案1:细粒度锁
    public void updateInventoryWithFineLock(String productId, int quantity) {
        String lockKey = "inventory:" + productId; // 每个商品独立锁

        RLock lock = redissonClient.getLock(lockKey);
        try {
            if (lock.tryLock(1, 10, TimeUnit.SECONDS)) {
                inventoryDao.updateStock(productId, quantity);
            }
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        } finally {
            if (lock.isHeldByCurrentThread()) {
                lock.unlock();
            }
        }
    }

    // 方案2:分段锁
    public void updateInventoryWithSegmentLock(String productId, int quantity) {
        // 根据商品ID哈希到不同的锁段
        int segment = Math.abs(productId.hashCode()) % 16;
        String lockKey = "inventory_segment:" + segment;

        RLock lock = redissonClient.getLock(lockKey);
        try {
            if (lock.tryLock(1, 10, TimeUnit.SECONDS)) {
                inventoryDao.updateStock(productId, quantity);
            }
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        } finally {
            if (lock.isHeldByCurrentThread()) {
                lock.unlock();
            }
        }
    }

    // 方案3:无锁化设计
    public void updateInventoryLockFree(String productId, int quantity) {
        String stockKey = "stock:" + productId;

        // 使用Lua脚本实现原子操作
        String script =
            "local current = tonumber(redis.call('get', KEYS[1]) or 0) " +
            "if current >= tonumber(ARGV[1]) then " +
            "    redis.call('decrby', KEYS[1], ARGV[1]) " +
            "    return 1 " +
            "else " +
            "    return 0 " +
            "end";

        DefaultRedisScript<Long> redisScript = new DefaultRedisScript<>();
        redisScript.setScriptText(script);
        redisScript.setResultType(Long.class);

        Long result = stringRedisTemplate.execute(redisScript,
            Collections.singletonList(stockKey), String.valueOf(quantity));

        if (result != null && result == 1L) {
            // 库存扣减成功,异步更新数据库
            asyncUpdateDatabase(productId, quantity);
        }
    }

    @Async
    private void asyncUpdateDatabase(String productId, int quantity) {
        inventoryDao.updateStock(productId, quantity);
    }
}

最佳实践总结

11.1 选择指南

@Component
public class DistributedLockSelector {

    public DistributedLock selectLock(LockScenario scenario) {
        switch (scenario.getType()) {
            case SIMPLE_BUSINESS:
                // 简单业务场景:SET EX NX
                return new AtomicRedisLock();

            case COMPLEX_LOGIC:
                // 复杂业务逻辑:Lua脚本
                return new LuaScriptRedisLock();

            case PRODUCTION_READY:
                // 生产环境:Redisson
                return new RedissonDistributedLock();

            case HIGH_AVAILABILITY:
                // 高可用要求:RedLock
                return new RedLockImplementation();

            case REAL_TIME_NOTIFICATION:
                // 实时通知:Pub/Sub
                return new PubSubRedisLock();

            case STREAMING_SCENARIO:
                // 流式处理:Redis Stream
                return new RedisStreamLock();

            default:
                return new RedissonDistributedLock();
        }
    }
}

enum LockScenarioType {
    SIMPLE_BUSINESS,        // 简单业务
    COMPLEX_LOGIC,          // 复杂逻辑
    PRODUCTION_READY,       // 生产就绪
    HIGH_AVAILABILITY,      // 高可用
    REAL_TIME_NOTIFICATION, // 实时通知
    STREAMING_SCENARIO      // 流式场景
}

11.2 配置最佳实践

# application.yml - 生产环境配置
spring:
  redis:
    # 基础配置
    host: redis-cluster.example.com
    port: 6379
    password: ${REDIS_PASSWORD}
    database: 0
    timeout: 3000ms

    # 连接池配置
    lettuce:
      pool:
        max-active: 20
        max-idle: 10
        min-idle: 5
        max-wait: 3000ms

    # 集群配置
    cluster:
      nodes:
        - redis-node1:6379
        - redis-node2:6379
        - redis-node3:6379
        - redis-node4:6379
        - redis-node5:6379
        - redis-node6:6379
      max-redirects: 3

# Redisson配置
redisson:
  config: |
    clusterServersConfig:
      nodeAddresses:
        - "redis://redis-node1:6379"
        - "redis://redis-node2:6379"
        - "redis://redis-node3:6379"
        - "redis://redis-node4:6379"
        - "redis://redis-node5:6379"
        - "redis://redis-node6:6379"
      password: ${REDIS_PASSWORD}
      masterConnectionMinimumIdleSize: 10
      masterConnectionPoolSize: 20
      slaveConnectionMinimumIdleSize: 10
      slaveConnectionPoolSize: 20
      idleConnectionTimeout: 10000
      connectTimeout: 10000
      timeout: 3000
      retryAttempts: 3
      retryInterval: 1500

11.3 监控和告警

@Component
public class DistributedLockMonitor {

    private final MeterRegistry meterRegistry;
    private final Timer lockAcquisitionTimer;
    private final Counter lockFailureCounter;
    private final Gauge activeLockGauge;

    public DistributedLockMonitor(MeterRegistry meterRegistry) {
        this.meterRegistry = meterRegistry;
        this.lockAcquisitionTimer = Timer.builder("distributed.lock.acquisition")
            .description("Time taken to acquire distributed lock")
            .register(meterRegistry);
        this.lockFailureCounter = Counter.builder("distributed.lock.failures")
            .description("Number of lock acquisition failures")
            .register(meterRegistry);
        this.activeLockGauge = Gauge.builder("distributed.lock.active")
            .description("Number of active locks")
            .register(meterRegistry, this, DistributedLockMonitor::getActiveLockCount);
    }

    public <T> T executeWithMonitoring(String lockKey, Supplier<T> task) {
        Timer.Sample sample = Timer.start(meterRegistry);

        try {
            RLock lock = redissonClient.getLock(lockKey);

            if (lock.tryLock(10, 30, TimeUnit.SECONDS)) {
                try {
                    sample.stop(lockAcquisitionTimer);
                    return task.get();
                } finally {
                    lock.unlock();
                }
            } else {
                lockFailureCounter.increment();
                throw new RuntimeException("Failed to acquire lock: " + lockKey);
            }
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
            lockFailureCounter.increment();
            throw new RuntimeException("Lock acquisition interrupted", e);
        }
    }

    private double getActiveLockCount() {
        // 实现获取当前活跃锁数量的逻辑
        return 0.0; // 简化实现
    }
}

架构师决策矩阵

12.1 技术选型决策表

考虑因素SETNX+EXPIRESET EX NXLua脚本RedissonRedLockStreamPub/Sub
实现复杂度简单简单中等简单复杂复杂中等
性能表现很高
可靠性很高很高极高
功能丰富度很高
运维复杂度
资源消耗
学习成本

12.2 场景适用性

/**
 * 分布式锁场景适用性指南
 */
public class LockScenarioGuide {

    // 推荐场景
    public void recommendedScenarios() {

        // 1. 库存扣减 - 推荐Redisson
        // 特点:高并发、强一致性要求、需要可重入

        // 2. 订单号生成 - 推荐SET EX NX + Lua脚本
        // 特点:轻量级、高性能、简单逻辑

        // 3. 定时任务防重 - 推荐Redisson
        // 特点:需要自动续期、异常处理

        // 4. 缓存更新 - 推荐SET EX NX
        // 特点:允许一定失败率、性能优先

        // 5. 金融交易 - 推荐RedLock
        // 特点:极高可靠性要求、容忍性能损失

        // 6. 配置更新 - 推荐Redisson + Pub/Sub
        // 特点:需要通知机制、低频操作
    }

    // 不推荐场景
    public void notRecommendedScenarios() {

        // 1. 高频读操作 - 考虑读写锁或无锁设计
        // 2. 长时间持有锁 - 考虑异步处理
        // 3. 跨服务调用 - 考虑分布式事务
        // 4. 简单计数器 - 考虑原子操作
    }
}

总结

Redis分布式锁实现方式需要综合考虑业务场景、性能要求、可靠性需求和团队技术栈。

要点回顾:

  1. SETNX + EXPIRE:已过时,存在原子性问题,仅适合学习
  2. SET EX NX:简单可靠,适合大多数轻量级场景
  3. Lua脚本:灵活强大,适合复杂业务逻辑
  4. Redisson:功能丰富,生产环境首选
  5. RedLock:极高可靠性,适合关键业务
  6. Redis Stream:适合特殊的流式处理场景
  7. Pub/Sub:适合需要实时通知的场景

记住,没有银弹,最好的方案是最适合你业务场景的方案。在实际应用中,往往需要结合多种方式来解决复杂的分布式一致性问题。


本文解析了Redis分布式锁的7种实现方式,结合电商秒杀场景和生产环境踩坑经验,希望能帮助你在技术选型和系统设计中做出正确的决策!

更多推荐