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实现好友关注的示例代码,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2023-01-01
  • 详解redis数据结构之sds

    详解redis数据结构之sds

    sds是Simple Dynamic String的缩写,译为简单动态字符串,redis使用该结构保存字符串,不同于c中的字符串,redis使用该结构来更方便的进行字符串的处理,需要的朋友可以参考下
    2017-05-05
  • Redis实现高并发计数器

    Redis实现高并发计数器

    这篇文章主要为大家详细介绍了Redis实现高并发计数器,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2018-10-10
  • Redis Cluster 字段模糊匹配及删除

    Redis Cluster 字段模糊匹配及删除

    在数据库内我们可以通过like关键字、%、*或者REGEX关键字进行模糊匹配。而在Redis内我们如何进行模糊匹配呢?本文就来介绍一下
    2021-05-05
  • Redis的使用模式之计数器模式实例

    Redis的使用模式之计数器模式实例

    这篇文章主要介绍了Redis的使用模式之计数器模式实例,本文讲解了汇总计数器、按时间汇总的计数器、速度控制、使用 Hash 数据类型维护大量计数器等内容,需要的朋友可以参考下
    2015-03-03
  • IDEA初次连接Redis配置的实现

    IDEA初次连接Redis配置的实现

    本文主要介绍了IDEA初次连接Redis配置的实现,文中通过图文步骤介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2023-12-12
  • redis实现好友关注&消息推送的方法示例

    redis实现好友关注&消息推送的方法示例

    Redis作为一款开源的内存数据库,具有可靠性、速度快、易用性等优点,已经被广泛应用于开发实际项目中,本文主要介绍了redis实现好友关注&消息推送的方法示例,感兴趣的可以了解一下
    2023-10-10
  • Redis 中的布隆过滤器的实现

    Redis 中的布隆过滤器的实现

    这篇文章主要介绍了Redis 中的布隆过滤器的实现,详细的介绍了什么是布隆过滤器以及如何实现,非常具有实用价值,需要的朋友可以参考下
    2018-10-10
  • 利用Redis lua实现高效读写锁的代码实例

    利用Redis lua实现高效读写锁的代码实例

    这篇文章给大家介绍了如何利用Redis lua实现高效的读写锁,读写锁的好处就是能帮助客户读到的数据一定是最新的,写锁是排他锁,而读锁是一个共享锁,需要的朋友可以参考下
    2024-01-01
  • Redis Lua脚本实现ip限流示例

    Redis Lua脚本实现ip限流示例

    这篇文章主要介绍了Redis Lua脚本实现ip限流示例详解,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2022-07-07

最新评论