开发者

Redis分布式锁的几种实现方法

目录
  • 一、基础方案:SETNX命令实现
  • 二、改进方案:原子SET命令
  • 三、高可用方案:RedLock算法
  • 四、生产级方案:Redisson实现

Redis基本命令:

// 设置键myKey的值为myValue,并且该键在10秒后过期
SET myKey myValue EX 10
// 设置键myKey的值为myValue,并且该键在1000毫秒(1秒)后过期
SET myKey myValue PX 1000
// 指定key过期时间,单位是秒,过期后自动删除
EXPIRE key_name second_num
// 指定key过期时间,单位是毫秒,过期后自动删除
PEXPIRE key_name millisecond_num

// 返回key过期时间
TTL key_name 

SET key value	 //设置键key的值为value
SETNX key value	 //只有在键key不存www.devze.com在的情况下,将key的值设置为value
SETEX key seconds value	 //将键key的值设置为value,并且超时时间为seconds秒
PSETEX key milliseconds value  //将键key的值设置为value,并且超时时间为milliseconds毫秒

一、基础方案:SETNX命令实现

public class SimpleRedisLock {
    private Jedis jedis;
    private String lockKey;

    public SimpleRedisLock(Jedis jedis, String lockKey) {
        this.jedis = jedis;
        this.lockKey = lockKey;
    }
    public boolean tryLock() {
        Long result = jedis.setnx(lockKey, "locked");
        if (result == 1) {
            jedis.expire(lockKey, 30); // 设置过期时间
            return true;
        }
        return false;
    }
    public void unlock() {
        jedis.del(lockKey);
    }
}

// 使用示例
Jedis jedis = new Jedis("localhost");
SimpleRedisLock lock = new SimpleRedisLock(jedis, "order_lock");
try{
    if(lock.tryLock()){
    // 业务逻辑
    }
} finally {
    lock.unlock();
}

问题分析:

  • 非原子操作:setnx和expire非原子操作,可能产生死锁
  • 锁误删:任何客户端都可以删除锁
  • 不可重入:同一线程重复获取会失败

二、改进方案:原子SET命令

public class AtomicRedisLock {
    private Jedis jedis;
    private String lockKey;
    private String clientId;

    public SimpleRedisLock(Jedis jedis, String lockKey) {
        this.jedis = jedis;
        this.lockKey = lockKey;
        this.clientId = UUID.randomUUID().toString();
    }
    public boolean tryLock(int expireSeconds) {
        String result = jedis.set(lockKey, clientId, SetParams.setParams().nx().ex(expireSeconds));
        return "OK".equals(result);
    }
    public boolean unlock() {
        String script = "if redis.call('get', KEYS[1]) == ARGV[1] then " +
                "return redis.call('del', KEYS[1]) " +
                "else return 0 end";
        Object result = jedis.eval(script, Collections.singletonList(lockKey), Collections.singletonList(clientId));
        return result.equals(1L);
    }
}

// 使用示例
Jedis jedis = new Jedis("localhost");
AtomicRedisLock lock = new AtomicRedisLock(jedis, "payment_lock");
try{
    if(lock.tryLock(30)){
    // 业务逻辑
    }
} finally {
    lock.unlock();
}

核心改进:

  • 使用原子SET命令:SET key value NX EX
  • Lua脚本保证删除原子性
  • 客户端唯一标识防止误删

仍然存在的问题:

  • 锁续期困难
  • 单点故障风险
  • 业务超时可能导致锁失效

三、高可用方案:RedLock算法

public class RedLock {
    pprivate List<Jedis> jedisList;
    private String lockKey;
    private String clientId;
    private int quorum;

    public RedLock(List<Jedis> jedisList, String lockKey) {
        this.jedisList = jedisList;
        this.lockKey = lockKey;
        this.clientId = UUID.randomUUID().toString();
        this.quorum = jedisList.size() / 2 + 1;
    }
    public boolean tryLock(int expireMillis) {
        long startTime = System.currentTimeMillis();
        // 第一阶段:尝试获取多数节点锁
        int successCount = 0;
        for (Jedis jedis : jedisList) {
            if (tryAcquire(jedis, expireMillis)) {
                successCount++;
            }
            if ((System.currentTimeMillis() - startTime) > expireMillis) {
                break;
            }
        }
        // 第二阶段:验证锁有效性
        if (successCount >= quorum) {
            long validityTime = expireMillis - (System.currentTimeMillis() - startTime);
            return validityTime > 0;
        }
        // 第三阶段:释放已获得的锁
        for (Jedis jedis : jedisList) {
            release(jedis);
        }
        return false;
    }
    private boolean tryAcquire(Jedis jedis, long expireMillis) {
        try编程客栈 {
            String result = jedis.set(lockKey, clientId, SetParams.setParams().nx().px(expireMillis));
            return "OK".equals(result);
        } catch (Exception e) {
            return false;
        }
    }
    private void release(Jedis jedis) {
        String script = "if redis.call('get', KEYS[1]) == ARGV[1] then " +
                "return redis.call('del', KEYS[1]) else return 0 end";
        jedis.eval(script, Collect编程客栈ions.singletonList(lockKey), Collections.singletonList(clientId));
    }
}

部署要求:

  • 至少5个独立Redis实例
  • 节点间时钟同步
  • 需要配置合理的超时时间

适用场景:

  • 金融交易等对可靠性要求极高的场景
  • 需要跨机房部署的分布式系统

四、生产级方案:Redisson实现

// 配置Redisson客户端
Config config = new Config();
config.useSingleServer().setAddress("redis://127.0.0.1:6379");
RedissonClient redisson = Redisson.create(config);
// 获取锁对象
RLock lock = redisson.getLock("orderLock");
try {
    // 尝试加锁,最多等待100秒,锁定后30秒自动解锁
    boolean isLock = lock.tryLock(100, 30, TimeUnit.SECONDS);
    if (isLock) {
        // 处理业务
    }
} finally {
    lock.unlock();
}

// 关闭客户端
redissonPtOhrcOg.shutdown();
// 自动续期机制(Watchdog),Watchdog实现原理(简化版)
private void renewExpiration() {
    Timeout task = commandExecutor.schedule(() -> {
        if (redisClienandroidt.eval(...)){ // 检查是否仍持有锁
            expireAsync(); // 续期
            renewExpiration(); // 递归调用
        }
    }, internalLockLeaseTime / 3, TimeUnit.MILLISECONDS);
}

核心特性:

  • 支持可重入锁
  • 提供公平锁、联锁(MultiLock)、红锁(RedLock)实现
  • 完善的故障处理机制

到此这篇关于Redis分布式锁的几种实现方法的文章就介绍到这了,更多相关Redis分布式锁内容请搜索编程客栈(www.devze.com)以前的文章或继续浏览下面的相关文章希望大家以后多多支持编程客栈(www.devze.com)! 

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新数据库

数据库排行榜