掘金 后端 ( ) • 2024-04-08 14:24

最近工作中一直在使用redis 进行项目代码上不断使用,有分布式加锁也有用做缓存数据处理,故此总结了一下项目使用的一些技巧。

概括:

Redisson 是一个在 Redis 的基础上实现的 Java 分布式锁和同步器。它提供了一系列的 Java 并发工具,比如 LocksSemaphoresCountDownLatch 等。Redisson 的分布式锁是基于 Redis 的特性来实现的。

加锁原理:

Redisson 的锁实现通常使用 Redis 的 SETNX 命令或 SET 命令的变体(具有 NX 标志),这些命令只有在键不存在时才会设置键值对。加锁时,Redisson 将生成一个随机的 UUID(作为锁的值),然后尝试使用 SETNXSET 命令将其存储在 Redis 中。如果命令执行成功,那么这个线程就成功地获取了锁。如果键已经存在,表示锁已经被其他线程持有,当前线程将进入等待状态,直到锁被释放或超时。

为了防止死锁,Redisson 锁还会设置一个过期时间,这个时间被称为锁的“租约时间”。如果持有锁的线程在这段时间内没有释放锁(可能是因为崩溃或其他原因),锁将自动被释放。

解锁原理:

解锁时,Redisson 会发送一个 Lua 脚本到 Redis,这个脚本会检查当前锁是否由当前线程持有(通过比较 UUID),如果是,则删除锁(删除键)。这个操作是原子的,以确保锁的安全释放。

优点:

  1. 易用性:Redisson 提供了丰富的接口和简单的使用方式,使得在分布式环境中实现锁和同步变得容易。
  2. 性能:Redis 本身具有高性能,Redisson 的实现确保了在分布式锁的使用中也能保持高性能。
  3. 高可用性和可伸缩性:Redisson 可以通过 Redis 集群来提供高可用性和可伸缩性。
  4. 多种锁的实现:Redisson 不仅仅支持可重入锁,还支持公平锁、联锁、红锁等多种锁的实现。

缺点:

  1. 依赖于 Redis:如果 Redis 服务不可用,那么基于 Redisson 的锁也会不可用。
  2. 网络延迟:锁操作依赖于网络交互,如果 Redis 服务器与应用服务器之间的网络延迟较大,可能会影响锁的获取和释放。
  3. 资源消耗:如果大量使用分布式锁,可能会增加 Redis 的负载和网络流量。
  4. 时钟同步问题:分布式系统中的时钟同步问题可能会影响锁的正确性,尤其是在锁的续租和过期检测方面 创建一个工具类来管理 Redisson 的加锁和解锁操作是一个很好的实践,这样可以在整个应用程序中重用代码并保持代码的整洁。

实战:

下面是一个简单的 RedissonLockUtil 类的示例,它提供了静态方法来获取和释放锁。

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

import java.util.concurrent.TimeUnit;

public class RedissonLockUtil {

    private static RedissonClient redissonClient;

    static {
        // 初始化 Redisson 客户端
        Config config = new Config();
        config.useSingleServer().setAddress("redis://127.0.0.1:6379");
        // 根据 Config 创建 RedissonClient 实例
        redissonClient = Redisson.create(config);
    }

    // 获取锁
    public static boolean lock(String lockKey, long leaseTime, TimeUnit timeUnit) {
        RLock lock = redissonClient.getLock(lockKey);
        try {
            return lock.tryLock(0, leaseTime, timeUnit);
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
            return false;
        }
    }

    // 释放锁
    public static void unlock(String lockKey) {
        RLock lock = redissonClient.getLock(lockKey);
        if (lock.isHeldByCurrentThread()) {
            lock.unlock();
        }
    }

    // 关闭 Redisson 客户端
    public static void shutdown() {
        if (redissonClient != null) {
            redissonClient.shutdown();
        }
    }
}

使用这个工具类的示例:


public class BusinessService {

    // 业务逻辑方法
    public void doSomething() {
        String lockKey = "myLockKey";
        boolean isLocked = RedissonLockUtil.lock(lockKey, 5, TimeUnit.SECONDS);
        if (isLocked) {
            try {
                // 执行业务逻辑
                System.out.println("执行业务逻辑...");
            } finally {
                // 释放锁
                RedissonLockUtil.unlock(lockKey);
            }
        } else {
            System.out.println("获取锁失败,业务逻辑未执行");
        }
    }
}

在这个例子中,RedissonLockUtil 类提供了 lockunlock 方法,分别用于获取和释放锁。lock 方法尝试立即获取锁,并设置锁的持有时间。如果获取锁成功,则返回 true;如果获取失败,则返回 falseunlock 方法检查当前线程是否持有锁,如果是,则释放锁。

在业务服务中,我们调用 RedissonLockUtil.lock 来尝试获取锁,并在成功获取锁之后执行业务逻辑。不管业务逻辑是否成功,最后都会在 finally 块中调用 RedissonLockUtil.unlock 来释放锁。

请注意,这个工具类假设你使用的是单个 Redis 服务器。如果你的环境中使用的是 Redis 集群或哨兵模式,你需要根据你的配置调整 Config 对象的初始化代码。此外,不要忘记在应用程序关闭时调用 RedissonLockUtil.shutdown 方法来优雅地关闭 Redisson 客户端。

总结:

总的来说,Redisson 提供了一个高性能、可伸缩的分布式锁实现,但是它也带来了对 Redis 的依赖以及可能的网络延迟问题。在选择使用 Redisson 之前,需要权衡这些优缺点,并考虑它们对你的应用程序的影响。