导读:本期聚焦于小伙伴创作的《Redis分布式锁实现方法详解:基础SET命令与Redisson实战对比》,敬请观看详情,探索知识的价值。以下视频、文章将为您系统阐述其核心内容与价值。如果您觉得《Redis分布式锁实现方法详解:基础SET命令与Redisson实战对比》有用,将其分享出去将是对创作者最好的鼓励。

Redis实现分布式锁的方法示例

在分布式系统中,多个服务实例同时操作共享资源时,容易出现数据不一致的问题,分布式锁就是解决这类并发问题的常用方案。Redis凭借高性能、支持原子操作的特性,成为实现分布式锁的常用工具。本文将介绍两种常见的Redis分布式锁实现方式,并给出对应的代码示例。

一、基于SETNX命令的基础实现

Redis的SETNX命令(SET if Not eXists)是早期实现分布式锁的常用命令,它的作用是当指定的key不存在时,才设置key的值,若key已存在则不做任何操作。利用这个特性,我们可以让第一个成功设置key的客户端获得锁,其他客户端则获取锁失败。

1. 基础加锁逻辑

加锁时需要设置锁的过期时间,避免客户端获取到锁之后崩溃,导致锁永远无法释放。注意和设置过期时间需要保证原子性,否则可能出现设置key之后、设置过期时间之前客户端崩溃,锁依然无法释放的问题。Redis 2.6.12之后,SET命令支持同时设置过期时间和判断key是否存在,可替代SETNX实现原子操作。

以下是使用Jedis客户端的基础加锁示例:

import redis.clients.jedis.Jedis;

public class RedisDistributedLock {
    private static final String LOCK_KEY = "distributed_lockorder";
    // 锁的过期时间,单位毫秒
    private static final int LOCK_EXPIRE_TIME = 10000;

    /**
     * 尝试获取分布式锁
     * @param jedis Redis客户端实例
     * @param requestId 客户端唯一标识,用于后续释放锁时校验身份
     * @return 是否获取锁成功
     */
    public static boolean tryLock(Jedis jedis, String requestId) {
        // 使用SET命令的NX(不存在才设置)、PX(设置过期时间,单位毫秒)参数,保证原子操作
        String result = jedis.set(LOCK_KEY, requestId, "NX", "PX", LOCK_EXPIRE_TIME);
        // 返回OK表示设置成功,即获取锁成功
        return "OK".equals(result);
    }
}

2. 基础释放锁逻辑

释放锁时不能直接使用DEL命令删除key,否则可能出现以下问题:客户端A获取锁之后,锁过期自动释放,此时客户端B获取到锁,客户端A此时执行完业务逻辑后调用释放锁的方法,就会误删客户端B的锁。因此释放锁时需要先校验锁的持有者身份,也就是判断当前锁的value是否和自己的requestId一致,一致才删除。

这个校验和删除的操作也需要保证原子性,否则可能出现校验通过之后、删除之前锁过期,其他客户端获取锁,依然会误删的问题。我们可以使用Lua脚本实现原子操作:

-- 释放锁的Lua脚本
-- KEYS[1]:锁的key
-- ARGV[1]:客户端的requestId
if redis.call("get", KEYS[1]) == ARGV[1] then
    return redis.call("del", KEYS[1])
else
    return 0
end

对应的Java释放锁实现:

import redis.clients.jedis.Jedis;

public class RedisDistributedLock {
    private static final String LOCK_KEY = "distributed_lock:order";
    // 释放锁的Lua脚本
    private static final String RELEASE_LOCK_SCRIPT = 
        "if redis.call('get', KEYS[1]) == ARGV[1] then " +
        "return redis.call('del', KEYS[1]) " +
        "else " +
        "return 0 " +
        "end";

    /**
     * 释放分布式锁
     * @param jedis Redis客户端实例
     * @param requestId 客户端唯一标识
     * @return 是否释放成功
     */
    public static boolean releaseLock(Jedis jedis, String requestId) {
        // 执行Lua脚本,保证原子性
        Object result = jedis.eval(RELEASE_LOCK_SCRIPT, 1, LOCK_KEY, requestId);
        // 返回1表示删除成功,即释放锁成功
        return Long.valueOf(1).equals(result);
    }
}

二、基于Redisson的可重入分布式锁实现

基础的分布式锁实现存在功能上的不足,比如不支持可重入、没有自动续期机制,如果业务执行时间超过过期时间,锁会自动释放,依然可能出现并发问题。Redisson是Redis官方推荐的Java驻内存数据网格,它封装了多种分布式锁的实现,包括可重入锁、公平锁、读写锁等,使用起来更加方便,也解决了基础实现的诸多问题。

1. Redisson可重入锁的使用示例

首先需要引入Redisson的依赖,以Maven为例:

<dependency>
    <groupId>org.redisson</groupId>
    <artifactId>redisson</artifactId>
    <version>3.23.3</version>
</dependency>

初始化Redisson客户端,连接Redis服务:

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

public class RedissonConfig {
    public static RedissonClient getRedissonClient() {
        Config config = new Config();
        // 单机模式配置,替换为实际的Redis地址,示例地址为https://www.ipipp.com
        config.useSingleServer().setAddress("redis://https://www.ipipp.com:6379");
        return Redisson.create(config);
    }
}

使用Redisson的可重入锁实现分布式锁的业务逻辑:

import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;

public class RedissonDistributedLockExample {
    private static final String LOCK_KEY = "distributed_lock:order";
    // 锁的过期时间,单位毫秒,Redisson会自动续期,无需担心业务执行时间超过过期时间
    private static final long LOCK_LEASE_TIME = 10000;

    public static void main(String[] args) {
        RedissonClient redissonClient = RedissonConfig.getRedissonClient();
        RLock lock = redissonClient.getLock(LOCK_KEY);
        
        try {
            // 尝试获取锁,最多等待5秒,锁的过期时间为10秒
            boolean isLocked = lock.tryLock(5, LOCK_LEASE_TIME / 1000, java.util.concurrent.TimeUnit.SECONDS);
            if (isLocked) {
                // 获取锁成功,执行业务逻辑
                System.out.println("获取分布式锁成功,执行业务逻辑");
                // 模拟业务执行耗时
                Thread.sleep(3000);
                // 可重入特性:同一个线程可以多次获取锁,不会死锁
                lock.lock();
                try {
                    System.out.println("重入锁获取成功,执行嵌套业务逻辑");
                } finally {
                    lock.unlock();
                }
            } else {
                System.out.println("获取分布式锁失败");
            }
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
            e.printStackTrace();
        } finally {
            // 判断当前线程是否持有锁,避免未获取锁却调用unlock导致异常
            if (lock.isHeldByCurrentThread()) {
                lock.unlock();
            }
            // 关闭Redisson客户端
            redissonClient.shutdown();
        }
    }
}

三、两种实现方式的对比

以下是基础实现和Redisson实现的特性对比:

对比维度基于SET命令的基础实现基于Redisson的实现
原子性保证需要自行使用Lua脚本保证释放锁的原子性内部封装了原子操作,无需手动处理
可重入性不支持可重入,同一线程重复获取锁会失败支持可重入,同一线程可多次获取锁
自动续期无自动续期,业务执行时间超过锁过期时间会导致锁释放有自动续期机制,Watch Dog会定期延长锁的过期时间
实现复杂度需要手动处理各类边界问题,实现复杂度高封装完善,开箱即用,实现复杂度低
适用场景简单的分布式锁场景,对功能要求不高复杂的分布式场景,需要可重入、自动续期等特性

四、使用Redis分布式锁的注意事项

  • 锁的过期时间设置需要合理,既要避免设置过短导致业务未完成锁就释放,也要避免设置过长导致锁长时间占用。

  • 释放锁时必须校验锁的持有者身份,避免误删其他客户端的锁。

  • 如果是Redis集群环境,需要考虑主从切换导致的锁丢失问题,此时可以使用RedLock算法实现更可靠的分布式锁,不过RedLock本身的性能和适用场景也需要结合实际业务评估。

  • 尽量不要在锁的持有期间执行耗时的IO操作,避免锁占用时间过长影响系统并发性能。

以上就是Redis实现分布式锁的两种常见方式的介绍,开发者可以根据自身的业务场景选择合适的实现方案。如果对功能完整性要求较高,优先选择Redisson这类成熟的工具库,避免重复造轮子带来的潜在问题。

分布式锁 RedisSET命令 Redisson 可重入锁 原子性

免责声明:已尽一切努力确保本网站所含信息的准确性。网站部分内容来源于网络或由用户自行发表,内容观点不代表本站立场。本站是个人网站免费分享,内容仅供个人学习、研究或参考使用,如内容中引用了第三方作品,其版权归原作者所有。若内容触犯了您的权益,请联系我们进行处理。
内容垂直聚焦
专注技术核心技术栏目,确保每篇文章深度聚焦于实用技能。从代码技巧到架构设计,为用户提供无干扰的纯技术知识沉淀,精准满足专业提升需求。
知识结构清晰
覆盖从开发到部署的全链路。前端、网络、数据库、服务器、建站、系统层层递进,构建清晰学习路径,帮助用户系统化掌握网站开发与运维所需的核心技术栈。
深度技术解析
拒绝泛泛而谈,深入技术细节与实践难点。无论是数据库优化还是服务器配置,均结合真实场景与代码示例进行剖析,致力于提供可直接应用于工作的解决方案。
专业领域覆盖
精准对应开发生命周期。从前端界面到后端逻辑,从数据库操作到服务器运维,形成完整闭环,一站式满足全栈工程师和运维人员的技术需求。
即学即用高效
内容强调实操性,步骤清晰、代码完整。用户可根据教程直接复现和应用于自身项目,显著缩短从学习到实践的距离,快速解决开发中的具体问题。
持续更新保障
专注既定技术方向进行长期、稳定的内容输出。确保各栏目技术文章持续更新迭代,紧跟主流技术发展趋势,为用户提供经久不衰的学习价值。