Redis

关注公众号 jb51net

关闭
首页 > 数据库 > Redis > redis实现红锁

redis实现红锁的示例代码

作者:morris131

在分布式系统中,实现一个可靠的锁机制是非常重要的,本文主要介绍了redis实现红锁的示例代码,具有一定的参考价值,感兴趣的可以了解一下

举个真实的例子:你的团队刚上线了一个秒杀系统,用Redis锁来防止超卖。测试环境明明跑得好好的,但大促当晚却出现了100件库存卖出了120单的灵异事件。查看日志才发现:就在用户疯狂点击的瞬间,Redis主节点突然挂了,新的主节点还没拿到锁的信息,结果两个用户同时抢到了"同一把锁"。

这就是很多开发者踩过的坑——你以为用了Redis分布式锁就万事大吉,其实这些情况随时可能让锁失效:

为了解决这些头疼问题,Redis作者提出了**红锁(RedLock)**方案。简单来说就是"不要把鸡蛋放在一个篮子里":让多个独立的Redis节点投票决定锁的归属,只有半数以上同意才算真正拿到锁。

但这套方案也引发过激烈争论,有人甚至说它"数学上就不安全"。本文将用最直白的语言:

读完本文你会明白:没有完美的分布式锁,只有适合场景的选择。下次设计系统时,至少能清楚知道手里的锁到底有几成把握。

集群锁的缺陷与挑战

在Redis Cluster环境中,传统的SETNX分布式锁存在以下致命缺陷:主从切换导致锁失效。

问题步骤复现:

# 主节点写入锁
SET resource_1 8a3e72 NX PX 10000  
OK

# 主节点宕机,从节点晋升但未同步锁数据
# 新主节点处理客户端B的请求
SET resource_1 5b9fd2 NX PX 10000  
OK  # 锁被重复获取!

红锁(RedLock)的设计与实现

N个独立Redis节点(非Cluster模式)中,当客户端在半数以上节点成功获取锁,且总耗时小于锁有效期时,才认为锁获取成功。

实现步骤详解

假设部署5个Redis节点(N=5):

SET lock_key valueNX PX $ttl
if redis.call("get",KEYS[1]) == ARGV[1] then
   return redis.call("del",KEYS[1])
else
   return 0
end

NPC争议问题

红锁算法自诞生起就伴随着**N(网络延迟)、P(进程暂停)、C(时钟漂移)**三个核心争议,这些现实世界中的不确定因素,动摇了红锁在数学意义上的绝对安全性。

网络延迟(Network Delay)的致命时间差

问题场景

后果
其他客户端可能在此期间获取节点C的锁,导致锁状态分裂,多个客户端同时进入临界区。

进程暂停(Process Pause)的「薛定谔锁」

经典案例

// 伪代码:获取锁后执行业务逻辑
if (redLock.tryLock()) {
    // 触发Full GC暂停300ms
    System.gc(); 
    
    // 此时锁已过期,但客户端仍在写数据
    updateInventory(); 
}

关键时间线

数据灾难
其他客户端在T0+200ms到T0+400ms期间可能修改数据,导致最终结果错乱。

时钟漂移(Clock Drift)的时空扭曲

物理机时钟偏移实验数据

节点时钟误差范围常见诱因
节点A±200ms/分钟虚拟机时钟不同步
节点B±500ms/天NTP服务异常
节点C±10秒/小时宿主机硬件时钟故障

连锁反应

行业领袖的正面交锋

Martin Kleppmann(《数据密集型应用设计》作者)

“红锁依赖的假设——『客户端能准确感知锁存活时间』,在异步分布式系统中根本无法保证。即使没有节点故障,NPC问题也会导致锁状态的不确定性。”

Antirez(Redis作者)的反驳

"工程实践中可以通过以下手段控制风险:

使用带温度补偿的原子钟硬件

禁用NTP服务的时钟跳变调整

监控进程暂停(如GC日志分析)

为锁TTL设置冗余缓冲时间(如额外20%)"

红锁的Java实现示例

使用Jedis客户端实现红锁:

package com.morris.redis.demo.redlock;

import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;
import redis.clients.jedis.params.SetParams;

import java.util.Collections;
import java.util.List;
import java.util.UUID;
import java.util.concurrent.TimeUnit;

/**
 * 使用jedis手写RedLock
 */
public class JedisRedLock {

    public static final int EXPIRE_TIME = 30_000;

    private final List<JedisPool> jedisPoolList;

    private final String lockKey;

    private final String lockValue;

    public JedisRedLock(List<JedisPool> jedisPoolList, String lockKey) {
        this.jedisPoolList = jedisPoolList;
        this.lockKey = lockKey;
        this.lockValue = UUID.randomUUID().toString();
    }

    public void lock() {
        while (!tryLock()) {
            try {
                TimeUnit.MILLISECONDS.sleep(100); // 失败后短暂等待
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        }
    }

    public boolean tryLock() {
        long startTime = System.currentTimeMillis();
        int successCount = 0;
        try {
            for (JedisPool jedisPool : jedisPoolList) {
                try (Jedis jedis = jedisPool.getResource();) {
                    // 原子化加锁:SET lockKey UUID NX PX expireTime
                    String result = jedis.set(lockKey, lockValue,
                            SetParams.setParams().nx().px(EXPIRE_TIME));
                    if ("OK".equals(result)) {
                        successCount++;
                    }
                }
            }
            // 计算获取锁耗时
            long elapsedTime = System.currentTimeMillis() - startTime;

            // 验证:多数节点成功 且 耗时小于TTL
            return successCount >= (jedisPoolList.size() / 2 + 1) && elapsedTime < EXPIRE_TIME;
        } finally {
            // 若加锁失败,立即释放已获得的锁
            if (successCount < (jedisPoolList.size() / 2 + 1)) {
                unlock();
            }
        }
    }

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

        for (JedisPool jedisPool : jedisPoolList) {
            try (Jedis jedis = jedisPool.getResource()) {
                jedis.eval(script, Collections.singletonList(lockKey), Collections.singletonList(lockValue));
            }
        }
    }

}

手写RedLock的使用:

package com.morris.redis.demo.redlock;

import redis.clients.jedis.JedisPool;
import redis.clients.jedis.JedisPoolConfig;

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;

/**
 * 手写RedLock的使用
 */
public class JedisRedLockDemo {

    private volatile static int count;

    public static void main(String[] args) throws InterruptedException {
        List<JedisPool> jedisPoolList = new ArrayList<>();
        jedisPoolList.add(new JedisPool(new JedisPoolConfig(), "127.0.0.1", 6379));
        jedisPoolList.add(new JedisPool(new JedisPoolConfig(), "127.0.0.1", 6380));
        jedisPoolList.add(new JedisPool(new JedisPoolConfig(), "127.0.0.1", 6381));
        jedisPoolList.add(new JedisPool(new JedisPoolConfig(), "127.0.0.1", 6382));
        jedisPoolList.add(new JedisPool(new JedisPoolConfig(), "127.0.0.1", 6383));

        int threadCount = 3;
        CountDownLatch countDownLatch = new CountDownLatch(threadCount);
        ExecutorService executorService = Executors.newFixedThreadPool(threadCount);

        for (int i = 0; i < threadCount; i++) {
            executorService.submit(() -> {
                JedisRedLock jedisRedLock = new JedisRedLock(jedisPoolList, "lock-key");
                jedisRedLock.lock();
                try {
                    System.out.println(Thread.currentThread().getName() + "获得锁,开始执行业务逻辑。。。");
                    try {
                        TimeUnit.SECONDS.sleep(3);
                    } catch (InterruptedException e) {
                        throw new RuntimeException(e);
                    }
                    System.out.println(Thread.currentThread().getName() + "获得锁,结束执行业务逻辑。。。");
                    count++;
                } finally {
                    jedisRedLock.unlock();
                }
                countDownLatch.countDown();
            });
        }

        countDownLatch.await();
        executorService.shutdown();
        System.out.println(count);
    }

}

Redisson中红锁的使用

Redisson已封装红锁实现,自动处理节点通信与锁续期:

package com.morris.redis.demo.redlock;

import org.redisson.Redisson;
import org.redisson.RedissonRedLock;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.redisson.config.Config;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;

/**
 * Redisson中红锁的使用
 */
public class RedissonRedLockDemo {

    private volatile static int count;

    public static void main(String[] args) throws InterruptedException {

        List<String> serverList = Arrays.asList("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");

        List<RedissonClient> redissonClientList = new ArrayList<>(serverList.size());
        for (String server : serverList) {
            Config config = new Config();
            config.useSingleServer()
                    .setAddress(server);

            redissonClientList.add(Redisson.create(config));
        }

        List<RLock> lockList = new ArrayList<>(redissonClientList.size());
        for (RedissonClient redissonClient : redissonClientList) {
            lockList.add(redissonClient.getLock("java-lock"));
        }
        
        int threadCount = 3;
        CountDownLatch countDownLatch = new CountDownLatch(threadCount);
        ExecutorService executorService = Executors.newFixedThreadPool(threadCount);

        for (int i = 0; i < threadCount; i++) {
            executorService.submit(() -> {
                RedissonRedLock redissonRedLock = new RedissonRedLock(lockList.toArray(new RLock[0]));
                redissonRedLock.lock();
                try {
                    System.out.println(Thread.currentThread().getName() + "获得锁,开始执行业务逻辑。。。");
                    try {
                        TimeUnit.SECONDS.sleep(3);
                    } catch (InterruptedException e) {
                        throw new RuntimeException(e);
                    }
                    System.out.println(Thread.currentThread().getName() + "获得锁,结束执行业务逻辑。。。");
                    count++;
                } finally {
                    redissonRedLock.unlock();
                }
                countDownLatch.countDown();
            });
        }

        countDownLatch.await();
        executorService.shutdown();
        System.out.println(count);

        for (RedissonClient redissonClient : redissonClientList) {
            redissonClient.shutdown();
        }
    }

}

Redisson优势

总结

红锁通过多节点投票机制,显著提升了分布式锁的可靠性,但需权衡其实现复杂度与运维成本。建议在以下场景选择红锁:

对于大多数场景,可优先使用Redisson等成熟框架,避免重复造轮子。若对一致性有极致要求,可考虑ZooKeeper/etcd等基于共识算法的方案。

到此这篇关于redis实现红锁的示例代码的文章就介绍到这了,更多相关redis实现红锁内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

您可能感兴趣的文章:
阅读全文