【案例实战】SpringBoot整合Redisson实现RedLock分布式锁同步

思考:生产环境下Redis集群环境,怎么保证锁的同步?

我们先来回顾一下分布式锁的作用:就是保证同一时间只有一个客户端可以对共享资源进行操作。

当我们集群环境部署的时候,假如节点一在主节点获取分布式锁成功。Redis主节点再同步数据到从节点时宕机,数据没同步成功;高可用机制 则Redis从节点升级为主节点 (但是没有锁信息)。节点二 在新的Redis上也成功获取分布式锁,导致一个锁资源同时被两个节点获取,这个就出现了问题。

在这里插入图片描述

那么我们应该怎么去解决这个问题?

采用 RedLock 算法,Redis从3.0版本开始支持 Redlock 算法,通过在多个 Redis 节点上创建同一个锁来防止主从节点之间出现的数据不一致的问题。在 Redlock 算法中,需要从多个Redis节点获取锁,并对取锁结果进行校验,从而避免数据不一致性带来的问题。锁变量由多个实例维护,即使有实例发生了故障,锁变量仍然是存在的,客户端还是可以完成锁操作。需要注意的是, redlock算法会引入一定的错误率,需要根据业务场景进行权衡和控制。

RedLock加锁流程

在这里插入图片描述

  • 客户端 获取当前毫秒级时间戳,并设置超时时间 TTL
  • 依次向 N 个 Redis 服务发出请求,用能够保证全局唯一的 value 申请锁 key
  • 如果从 N/2+1 个 redis 服务中都获取锁成功,那本次分布式锁的获取被视为成功,否则获取锁失败
  • 如果获取锁失败,或执行达到 TTL,则向所有 Redis 服务都发出解锁请求

如果想搭建一个能够允许 N 台机器 down 掉的集群,那么就要部署一个由 2*N+1 台服务器构成的 集群。高可用部署最少的节点数计算公式 :N( 总节点数) = 2 * X(宕机数) +1,X > 0,最少需要 3 台,半数以上节点选举,不包含半数。

  • 宕机一台还可以高可用:3 = 2 * 1 + 1
  • 宕机二台还可以高可用:5= 2 * 2 + 1

这种架构上redis全部节点都是主节点,没有从节点,抛弃了主从的异步复制。各个节点之间没关系,不是集群也不是主从,互相独立 。由于使用比较复杂且概率性较低,但多数公司还是采用了主从架构,在一些特定的场景且要求高的情况才会采用,且节点数会根据情况增加。

ok,下面我们先来进行环境的搭建。采用Docker的方式搭建三个Redis节点。

docker run -itd --name redlock-1 -p 6380:6379 redis:7.0.8 --requirepass 123456
docker run -itd --name redlock-2 -p 6381:6379 redis:7.0.8 --requirepass 123456
docker run -itd --name redlock-3 -p 6382:6379 redis:7.0.8 --requirepass 123456

查看Redis节点部署情况。

在这里插入图片描述

OK,接下来我们正式进入编码环节。我们采用SpringBoot+Redisson+Redis来实现RedLock

首先创建SpringBoot项目添加依赖。

<dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.redisson</groupId>
            <artifactId>redisson</artifactId>
            <version>3.20.1</version>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
            <version>3.0.6</version>
        </dependency>
    </dependencies>

随后,我们编写程序主类,Redis的配置类。

/**
 * @author lixiang
 * @date 2023/6/25 11:33
 */
@SpringBootApplication
public class RedLockApplication {
    public static void main(String[] args) {
        SpringApplication.run(RedLockApplication.class, args);
    }
}
/**
 * @author lixiang
 * @date 2023/6/25 10:59
 */
@Configuration
public class RedisConfig {

    @Bean
    public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
        RedisTemplate<Object, Object> redisTemplate = new RedisTemplate<>();
        redisTemplate.setConnectionFactory(redisConnectionFactory);
        // 使用Jackson2JsonRedisSerialize 替换默认序列化
        Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);
        ObjectMapper objectMapper = new ObjectMapper();
        objectMapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
        jackson2JsonRedisSerializer.setObjectMapper(objectMapper);
        // 设置key和value的序列化规则
        redisTemplate.setKeySerializer(new StringRedisSerializer());
        redisTemplate.setValueSerializer(jackson2JsonRedisSerializer);
        // 设置hashKey和hashValue的序列化规则
        redisTemplate.setHashKeySerializer(new StringRedisSerializer());
        redisTemplate.setHashValueSerializer(jackson2JsonRedisSerializer);
        // 设置支持事物
        //redisTemplate.setEnableTransactionSupport(true);
        redisTemplate.afterPropertiesSet();
        return redisTemplate;
    }

    @Bean
    public Redisson redissonClient1(){
        Config config = new Config();
        config.useSingleServer().setAddress("redis://ip:6380").setDatabase(0).setPassword("123456");
        return (Redisson) Redisson.create(config);
    }

    @Bean
    public Redisson redissonClient2(){
        Config config = new Config();
        config.useSingleServer().setAddress("redis://ip:6381").setDatabase(0).setPassword("123456");
        return (Redisson) Redisson.create(config);
    }

    @Bean
    public Redisson redissonClient3(){
        Config config = new Config();
        config.useSingleServer().setAddress("redis://ip:6382").setDatabase(0).setPassword("123456");
        return (Redisson) Redisson.create(config);
    }
}

编写Controller逻辑。

/**
 * @author lixiang
 * @date 2023/6/25 11:04
 */
@RestController
@RequestMapping("/")
public class RedLockController {

    /**
     * 定义锁的名字
     */
    public static final String CACHE_KEY_REDLOCK = "redlock";

    @Autowired
    private RedissonClient redissonClient1;

    @Autowired
    private RedissonClient redissonClient2;

    @Autowired
    private RedissonClient redissonClient3;

    @RequestMapping("getRedLock")
    public String getRedLock(){
        //每个客户端分别获取锁资源
        RLock lock1 = redissonClient1.getLock(CACHE_KEY_REDLOCK);
        RLock lock2 = redissonClient2.getLock(CACHE_KEY_REDLOCK);
        RLock lock3 = redissonClient3.getLock(CACHE_KEY_REDLOCK);
        //创建红锁 客户端
        RedissonRedLock redLock = new RedissonRedLock(lock1, lock2, lock3);
        //定义获取锁标志位,默认是获取失败
        boolean isLockBoolean = false;
        try {
            /**
             * waitTime:等待获取锁的最长时间。如果在等待时间内无法获取锁,并且没有其他锁释放,则返回 false。如果 waitTime < 0,则无限期等待,直到获得锁定。
             * leaseTime:就是redis key的过期时间,锁的持有时间,可以使用 ttl  查看过期时间。
             * 如果在持有时间结束前锁未被释放,则锁将自动过期,没有进行key续期,并且其他线程可以获得此锁。如果 leaseTime = 0,则锁将永久存在,直到被显式释放。
             */
            isLockBoolean = redLock.tryLock(1, 20, TimeUnit.SECONDS);

            System.out.printf("线程:"+Thread.currentThread().getId()+",是否拿到锁:" +isLockBoolean +"\n");
            if (isLockBoolean) {
                System.out.println("线程:"+Thread.currentThread().getId() + ",加锁成功,进入业务操作");
                try {
                    //业务逻辑,40s模拟,超过了key的过期时间
                    TimeUnit.SECONDS.sleep(40);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        } catch (Exception e) {
            System.err.printf("线程:"+Thread.currentThread().getId()+"发生异常,加锁失败");
            e.printStackTrace();
        } finally {
            // 无论如何,最后都要解锁
            redLock.unlock();
        }
        return isLockBoolean?"success":"fail";

    }

}

测试启动,接口调用。

在这里插入图片描述

模拟加锁故障

  • 3个节点停止1个节点,加锁成功
    在这里插入图片描述在这里插入图片描述

  • 3个节点停止2个节点,加锁失败

在这里插入图片描述

注意,RedLock也是非绝对安全的

RedLock解决了单Redis节点的分布式锁在failover的时候锁失效的问题,但节点如果出现奔溃重启,对锁的安全性依旧存在问题。

  • 假如一共有5个Redis节点:A, B, C, D, E
    • 客户端1成功锁住了A, B, C,获取锁成功(但D和E没有锁住)。
    • 节点C崩溃重启,但客户端1在C上加的锁没有持久化下来,aof机制导致。
    • 节点C重启后,客户端2锁住了C, D, E,获取锁成功。
  • Redis 的 AOF 持久化方式是每秒执行fsync写一次磁盘,最坏情况下可能丢失1秒的数据。
  • 尽可能不丢数据,Redis允许设置成每次修改数据都进行fsync,但会降低性能。
  • 即使执行了fsync也仍然有可能丢失数据,因为也取决操作系统的刷盘策略,文件系统写到了buffer里面。
  • 建议 延迟重启,一个节点崩溃后,先不立即重启它,而是等待一段时间再重启,这段时间应该大于锁的有效时间。
  • 那这个节点在重启前所参与的锁都会过期,它在重启后就不会对现有的锁造成影响。

OK,RedLock相关的知识点以及案例实战,我们就介绍到这里了哦,记得三连下哦!

在这里插入图片描述

  • 25
    点赞
  • 27
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 21
    评论
### 回答1: 在Spring Boot项目中使用Redisson实现分布式锁,需要按照以下步骤进行: 1. 在项目中引入Redisson依赖,可以在pom.xml文件中添加以下代码: ``` <dependency> <groupId>org.redisson</groupId> <artifactId>redisson</artifactId> <version>3.13.3</version> </dependency> ``` 2. 配置Redisson连接,可以在application.yml文件中添加以下代码: ``` redisson: address: redis://127.0.0.1:6379 database: 0 connection-pool-size: 100 password: 123456 ``` 3. 创建RedissonClient对象,可以在Spring Boot项目的启动类中添加以下代码: ``` @Configuration public class RedissonConfig { @Value("${redisson.address}") private String address; @Value("${redisson.password}") private String password; @Value("${redisson.connection-pool-size}") private int connectionPoolSize; @Value("${redisson.database}") private int database; @Bean public RedissonClient redissonClient() { Config config = new Config(); config.useSingleServer().setAddress(address) .setPassword(password) .setConnectionPoolSize(connectionPoolSize) .setDatabase(database); return Redisson.create(config); } } ``` 4. 使用Redisson实现分布式锁,可以在需要加锁的代码中添加以下代码: ``` @Autowired private RedissonClient redissonClient; public void lockMethod() { RLock lock = redissonClient.getLock("lockKey"); try { lock.lock(); // 被锁定的代码 } finally { lock.unlock(); } } ``` 以上代码就是使用Redisson实现分布式锁的基本过程,实际项目中可能还需要根据实际情况进行修改和优化。 ### 回答2: Spring Boot是一款用于快速构建Spring应用程序的开发框架,而Redisson则是一个基于RedisJava驻留内存数据网格(In-Memory Data Grid)和分布式锁框架。 在Spring Boot中使用Redisson实现分布式锁,需要进行以下几个步骤: 1. 引入Redisson依赖:在pom.xml文件中添加Redisson的依赖。例如: ```xml <dependency> <groupId>org.redisson</groupId> <artifactId>redisson-spring-boot-starter</artifactId> <version>3.13.3</version> </dependency> ``` 2. 配置Redisson:在application.properties或application.yml文件中配置Redisson连接信息。例如: ```yaml spring: redis: host: 127.0.0.1 port: 6379 password: password ``` 3. 创建RedissonClient Bean:在应用程序的配置类中创建RedissonClient的Bean实例。例如: ```java @Configuration public class RedissonConfig { @Value("${spring.redis.host}") private String host; @Value("${spring.redis.port}") private String port; @Bean public RedissonClient redisson() { Config config = new Config(); config.useSingleServer() .setAddress("redis://" + host + ":" + port); return Redisson.create(config); } } ``` 4. 使用分布式锁:在需要进行分布式锁控制的代码块中,通过RedissonClient来获取分布式锁对象,并使用分布式锁对象来实现具体的业务逻辑。例如: ```java @Service public class MyService { @Autowired private RedissonClient redisson; public void doSomething() { RLock lock = redisson.getLock("myLock"); try { lock.lock(); // 执行业务逻辑 } finally { lock.unlock(); } } } ``` 上述代码中,通过调用`redisson.getLock("myLock")`来获取名为"myLock"的分布式锁对象(RLock),然后通过`lock.lock()`来获取锁,执行业务逻辑,最后通过`lock.unlock()`来释放锁。 这样,Spring Boot就可以通过Redisson实现分布式锁的功能了。分布式锁的主要作用是在分布式系统中保证同一时刻只有一个线程能够访问共享资源,避免数据的冲突和不一致。通过使用Redisson,我们可以方便地在Spring Boot应用中实现分布式锁的控制,保证数据的一致性和可靠性。 ### 回答3: Spring Boot是一个快速开发框架,可以简化Java应用程序的开发过程。而Redisson是一个使用Java实现Redis客户端,它提供了一种简单易用且高效的分布式锁解决方案。 要使用Redisson实现分布式锁,我们需要完成以下几个步骤: 1. 添加Redisson依赖:首先,在Spring Boot项目的pom.xml文件中添加Redisson的依赖。可以通过在<dependencies>标签内添加如下代码来引入Redisson: ```xml <dependency> <groupId>org.redisson</groupId> <artifactId>redisson-spring-boot-starter</artifactId> <version>3.11.3</version> </dependency> ``` 2. 添加Redis配置信息:在Spring Boot项目的配置文件(如application.properties)中添加Redis的相关配置信息,包括主机名、端口号、密码等。示例配置如下: ```properties spring.redis.host=127.0.0.1 spring.redis.port=6379 spring.redis.password= ``` 3. 创建RedissonClient Bean:在Spring Boot的配置类中创建一个RedissonClient的Bean,并设置好相应的Redis配置信息。示例代码如下: ```java @Configuration public class RedissonConfig { @Value("${spring.redis.host}") private String redisHost; @Value("${spring.redis.port}") private String redisPort; @Bean public RedissonClient redissonClient() { Config config = new Config(); config.useSingleServer().setAddress("redis://" + redisHost + ":" + redisPort); return Redisson.create(config); } } ``` 4. 使用Redisson获取锁:在需要加锁的业务方法中,通过RedissonClient的getFairLock方法获取一个公平锁对象,然后使用lock方法获取锁。示例代码如下: ```java @Service public class MyService { @Autowired private RedissonClient redissonClient; public void doSomething() { RLock lock = redissonClient.getFairLock("myLock"); try { lock.lock(); // 执行需要加锁的业务逻辑 } finally { lock.unlock(); } } } ``` 以上就是使用Spring BootRedisson实现分布式锁的基本步骤。通过Redisson提供的锁对象,我们可以在需要时通过lock方法获取锁,然后执行需要加锁的业务逻辑,最后通过unlock方法释放锁。Redisson会自动处理锁的有效期和宕机等异常情况,保证高可用和数据一致性。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 21
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

互联网小阿祥

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值