Spring Boot 中的 Redis 分布式锁

 更新时间:2023年10月02日 15:56:37   投稿:wdc  
这篇文章主要介绍了Spring Boot 中的 Redis 分布式锁及,Redis分布式锁的优化需要的朋友可以参考下

Spring Boot 中的 Redis 分布式锁

在分布式系统中,多个进程同时访问共享资源时,很容易出现并发问题。为了避免这些问题,我们可以使用分布式锁来保证共享资源的独占性。Redis 是一款非常流行的分布式缓存,它也提供了分布式锁的功能。在 Spring Boot 中,我们可以很容易地使用 Redis 分布式锁来管理并发访问。

本文将介绍 Redis 分布式锁的概念和原理,并说明如何在 Spring Boot 中使用它们。

Redis 分布式锁的概念和原理

Redis 分布式锁是一种基于 Redis 的分布式锁解决方案。它的原理是利用 Redis 的原子性操作实现锁的获取和释放,从而保证共享资源的独占性。

在 Redis 中,我们可以使用 setnx 命令来实现分布式锁。setnx 命令可以将一个键值对设置到 Redis 中,但只有在该键不存在的情况下才会设置成功。因此,我们可以将锁的获取和释放分别实现为 setnx 和 del 命令。

以下是一个基本的 Redis 分布式锁示例:

public class RedisDistributedLock {
    private static final String LOCK_KEY_PREFIX = "lock:";
    private static final long LOCK_EXPIRE_TIME = 30000L;
    private RedisTemplate<String, Object> redisTemplate;
    private String lockKey;
    private String lockValue;
    private boolean locked = false;
    public RedisDistributedLock(RedisTemplate<String, Object> redisTemplate, String lockKey, String lockValue) {
        this.redisTemplate = redisTemplate;
        this.lockKey = LOCK_KEY_PREFIX + lockKey;
        this.lockValue = lockValue;
    }
    public boolean lock() {
        if (redisTemplate.opsForValue().setIfAbsent(lockKey, lockValue)) {
            redisTemplate.expire(lockKey, LOCK_EXPIRE_TIME, TimeUnit.MILLISECONDS);
            locked = true;
            return true;
        } else {
            return false;
        }
    }
    public void unlock() {
        if (locked) {
            redisTemplate.delete(lockKey);
        }
    }
}

在这个示例中,我们定义了一个 RedisDistributedLock 类,它包含了 lock 和 unlock 两个方法。lock 方法用于获取锁,unlock 方法用于释放锁。

lock 方法的实现逻辑如下:

  • 使用 setIfAbsent 方法尝试将锁的键值对设置到 Redis 中。
  • 如果设置成功,则调用 expire 方法设置锁的过期时间,并标记 locked 为 true。
  • 如果设置失败,则说明锁已经被其他进程占用,返回 false。

unlock 方法的实现逻辑如下:

  • 如果 locked 为 true,则使用 delete 方法删除锁的键值对。

Spring Boot 中的 Redis 分布式锁实现

在 Spring Boot 中,我们可以使用 RedisTemplate 来访问 Redis,并利用其提供的 setIfAbsent 和 delete 方法实现 Redis 分布式锁。

以下是一个基本的 Spring Boot + Redis 分布式锁示例:

@RestController
public class UserController {
    private RedisTemplate<String, Object> redisTemplate;
    @Autowired
    public UserController(RedisTemplate<String, Object> redisTemplate) {
        this.redisTemplate = redisTemplate;
    }
    @GetMapping("/user/{id}")
    public User getUser(@PathVariable("id") String id) throws InterruptedException {
        RedisDistributedLock lock = new RedisDistributedLock(redisTemplate, "user:" + id, UUID.randomUUID().toString());
        try {
            while (!lock.lock()) {
                Thread.sleep(100);
            }
            // 处理业务逻辑
            return new User();
        } finally {
            lock.unlock();
        }
    }
}

在这个示例中,我们定义了一个 UserController 类,其中包含了一个 getUser 方法,用于获取用户信息。在方法中,我们首先创建了一个 RedisDistributedLock 对象,然后在 while 循环中调用 lock 方法获取锁,直到获取成功为止。在获取锁后,我们可以执行相应的业务逻辑。最后,在 finally 块中调用 unlock 方法释放锁。

需要注意的是,由于 Redis 分布式锁是基于时间的,因此必须设置合适的过期时间。在示例中,我们将锁的过期时间设置为 30 秒。

Redis 分布式锁的优化

在实际应用中,Redis 分布式锁的性能和可靠性都非常重要。以下是几个优化 Redis 分布式锁的方法:

1. 使用 Lua 脚本

在上面的示例中,我们使用了两个 Redis 命令(setIfAbsent 和 expire)来实现分布式锁,这将导致两次网络通信。在高并发情况下,这会增加 Redis 的负载,影响性能。为了避免这个问题,我们可以使用 Lua 脚本来将这两个命令合并为一个原子操作。

以下是一个使用 Lua 脚本实现 Redis 分布式锁的示例:

public class RedisDistributedLock {
    private static final String LOCK_SCRIPT = "if redis.call('setnx', KEYS[1], ARGV[1]) == 1 then redis.call('pexpire', KEYS[1], ARGV[2]) return true else return false end";
    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";
    private static final String LOCK_KEY_PREFIX = "lock:";
    private static final long LOCK_EXPIRE_TIME = 30000L;
    private RedisTemplate<String, Object> redisTemplate;
    private String lockKey;
    private String lockValue;
    private boolean locked = false;
    private String lockScript;
    private String unlockScript;
    public RedisDistributedLock(RedisTemplate<String, Object> redisTemplate, String lockKey, String lockValue) {
        this.redisTemplate = redisTemplate;
        this.lockKey = LOCK_KEY_PREFIX + lockKey;
        this.lockValue = lockValue;
        this.lockScript = new DefaultRedisScript<>(LOCK_SCRIPT, Boolean.class).getScriptAsString();
        this.unlockScript = new DefaultRedisScript<>(UNLOCK_SCRIPT, Long.class).getScriptAsString();
    }
    public boolean lock() {
        Object result = redisTemplate.execute(new DefaultRedisScript<>(lockScript, Boolean.class), Collections.singletonList(lockKey), lockValue, LOCK_EXPIRE_TIME);
        locked = (Boolean) result;
        return locked;
    }
    public void unlock() {
        if (locked) {
            redisTemplate.execute(new DefaultRedisScript<>(unlockScript, Long.class), Collections.singletonList(lockKey), lockValue);
        }
    }
}

在这个示例中,我们将 setIfAbsent 和 expire 命令合并为一个 Lua 脚本,并使用 RedisTemplate 的 execute 方法来执行脚本。在 lock 方法中,我们执行 LOCK_SCRIPT 脚本,如果返回 true,则表示获取锁成功。在 unlock 方法中,我们执行 UNLOCK_SCRIPT 脚本来释放锁。

使用 Lua 脚本可以减少 Redis 的网络通信次数,从而提高性能和可靠性。

2. 重试机制

由于分布式系统中存在网络抖动等问题,Redis 分布式锁的获取和释放可能会失败。为了提高可靠性,我们可以使用重试机制来重复执行获取锁和释放锁的操作。

以下是一个基本的重试机制实现示例:

public boolean lockWithRetry(int retryCount, long retryInterval) throws InterruptedException {
    int count = 0;
    while (count < retryCount) {
        if (lock()) {
            return true;
        }
        count++;
        Thread.sleep(retryInterval);
    }
    return false;
}
public void unlockWithRetry(int retryCount, long retryInterval) throws InterruptedException {
    int count = 0;
    while (count < retryCount) {
        try {
            unlock();
            break;
        } catch (Exception e) {
            count++;
            Thread.sleep(retryInterval);
        }
    }
}

在这个示例中,我们定义了 lockWithRetry 和 unlockWithRetry 两个方法。lockWithRetry 方法会重复执行 lock 方法,如果获取锁成功,则返回 true;否则,等待一段时间后重试。unlockWithRetry 方法会重复执行 unlock 方法,如果释放锁成功,则结束重试;否则,等待一段时间后重试。

使用重试机制可以提高 Redis 分布式锁的可靠性,保证在网络抖动等情况下仍能正常工作。

3. 采用 Redlock 算法

Redis 分布式锁的另一个问题是单点故障。由于 Redis 是单点的,如果 Redis 实例宕机,那么所有的分布式锁都会失效。为了解决这个问题,我们可以采用 Redlock 算法。

Redlock 算法是 Redis 官方提出的一种分布式锁算法。它通过使用多个 Redis 实例来避免单点故障的问题。具体来说,Redlock 算法首先获取多个 Redis 实例上的锁,然后比较这些锁的时间戳,选择时间戳最小的锁为有效锁。

以下是一个使用 Redlock 算法实现 Redis 分布式锁的示例:

public class RedisDistributedLock {
    private static final String LOCK_KEY_PREFIX = "lock:";
    private static final long LOCK_EXPIRE_TIME = 30000L;
    private static final int RETRY_COUNT = 3;
    private static final long RETRY_INTERVAL = 100L;
    private RedisTemplate<String, Object> redisTemplate;
    private String lockKey;
    private String lockValue;
    private boolean locked = false;
    private List<RedisConnection> connections = new ArrayList<>();
    public RedisDistributedLock(RedisTemplate<String, Object> redisTemplate, String lockKey, String lockValue) {
        this.redisTemplate = redisTemplate;
        this.lockKey = LOCK_KEY_PREFIX + lockKey;
        this.lockValue = lockValue;
    }
    public boolean lock() {
        for (int i = 0; i < RETRY_COUNT; i++) {
            RedisConnection connection = redisTemplate.getConnectionFactory().getConnection();
            connections.add(connection);
            try {
                byte[] keyBytes = redisTemplate.getKeySerializer().serialize(lockKey);
                byte[] valueBytes = redisTemplate.getValueSerializer().serialize(lockValue);
                long expireTime = System.currentTimeMillis() + LOCK_EXPIRE_TIME + 1;
                for (int j = 0; j < connections.size(); j++) {
                    RedisConnection conn = connections.get(j);
                    if (j == connections.size() - 1) {
                        conn.set(keyBytes, valueBytes, Expiration.milliseconds(LOCK_EXPIRE_TIME), RedisStringCommands.SetOption.SET_IF_ABSENT);
                    } else {
                        conn.set(keyBytes, valueBytes, Expiration.milliseconds(LOCK_EXPIRE_TIME), RedisStringCommands.SetOption.SET_IF_ABSENT);
                        conn.pExpire(keyBytes, expireTime);
                    }
                }
                locked = true;
                return true;
            } catch (Exception e) {
                // ignore
            }
            try {
                Thread.sleep(RETRY_INTERVAL);
            } catch (InterruptedException e) {
                // ignore
            }
        }
        return false;
    }
    public void unlock() {
        for (RedisConnection connection : connections) {
            try {
                connection.del(redisTemplate.getKeySerializer().serialize(lockKey));
            } catch (Exception e) {
                // ignore
            } finally {
                connection.close();
            }
        }
        connections.clear();
        locked = false;
    }
}

在这个示例中,我们定义了一个 RedisDistributedLock 类,它使用多个 RedisConnection 来实现 Redlock 算法。在 lock 方法中,我们首先获取多个 RedisConnection,然后在这些连接上分别执行 set 和 pExpire 命令。在执行完这些命令后,我们比较这些锁的时间戳,选择时间戳最小的锁为有效锁。在 unlock 方法中,我们分别释放每个 RedisConnection 上的锁。

使用 Redlock 算法可以提高 Redis 分布式锁的可靠性,避免单点故障的问题。

总结

在分布式系统中,使用分布式锁是保证共享资源独占性的重要方式。Redis 分布式锁是一种基于 Redis 的分布式锁解决方案,它通过利用 Redis 的原子性操作实现锁的获取和释放,从而保证共享资源的独占性。在 Spring Boot 中,我们可以很容易地使用 Redis 分布式锁,通过 RedisTemplate 来操作 Redis 实例。在使用 Redis 分布式锁时,需要注意锁的粒度、锁的超时时间、重试机制和 Redlock 算法,以保证分布式锁的可靠性和高可用性。

到此这篇关于Spring Boot 中的 Redis 分布式锁的文章就介绍到这了,更多相关Spring Boot 中的 Redis 分布式锁内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • 详解Redis数据类型实现原理

    详解Redis数据类型实现原理

    这篇文章主要介绍了Redis数据类型实现原理,在工作中或学习中有需要的小伙伴可以参考一下这篇文章
    2021-08-08
  • Redis sort 排序命令详解

    Redis sort 排序命令详解

    这篇文章主要介绍了Redis sort 排序命令详解,本文讲解了默认排序命令、排序方式命令、BY语法、GET用法示例等内容,需要的朋友可以参考下
    2015-07-07
  • Redis 分片集群的实现

    Redis 分片集群的实现

    本文主要介绍了Redis 分片集群的实现,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2023-01-01
  • Redis利用Pipeline加速查询速度的方法

    Redis利用Pipeline加速查询速度的方法

    这篇文章主要给大家介绍了关于Redis利用Pipeline加速查询速度的相关资料,文中通过示例代码介绍的非常详细,对大家学习或者使用Redis具有一定的参考学习价值,需要的朋友们下面来一起学习学习吧
    2019-07-07
  • Redis入门教程详解

    Redis入门教程详解

    本文详细介绍了Redis,文中主要讲解了其基本数据结构、高级数据结构、高级特性、使用场景等,需要了解的朋友可以参考一下
    2021-08-08
  • Linux中设置Redis开机启动的方法

    Linux中设置Redis开机启动的方法

    这篇文章主要给大家介绍了关于Linux中设置Redis开机启动的方法,主要包括在CentOS7.0系统和Debian 8.0系统下实现方法,文中介绍的非常详细,需要的朋友可以参考借鉴,下面来一起看看吧。
    2017-04-04
  • 详谈redis优化配置和redis.conf说明(推荐)

    详谈redis优化配置和redis.conf说明(推荐)

    下面小编就为大家带来一篇详谈redis优化配置和redis.conf说明(推荐)。小编觉得挺不错的,现在就分享给大家,也给大家做个参考。一起跟随小编过来看看吧
    2017-03-03
  • 基于Redis位图实现用户签到功能

    基于Redis位图实现用户签到功能

    这篇文章主要介绍了基于Redis位图实现用户签到功能,本文通过实例代码给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2021-05-05
  • Redis中Scan命令的基本使用教程

    Redis中Scan命令的基本使用教程

    这篇文章主要给大家介绍了关于Redis中Scan命令的基本使用教程,文中通过示例代码介绍的非常详细,对大家学习或者使用Redis具有一定的参考学习价值,需要的朋友们下面来一起学习学习吧
    2019-06-06
  • 一文搞懂Redis中的慢查询日志和监视器

    一文搞懂Redis中的慢查询日志和监视器

    我们都知道MySQL有慢查询日志,但Redis也有慢查询日志,可用于监视和优化查询,本文给大家详细介绍了Redis中的慢查询日志和监视器,文章通过代码示例讲解的非常详细,需要的朋友可以参考下
    2024-04-04

最新评论