SpringBoot集成Redisson分布式锁(Redis集群方式)

在大多数项目中,都会使用缓存中间件,例如Redis、Memcached等,一般会选择项目适配的中间件,能够提高数据的访问速度,减轻后端服务器和数据库的压力,但在使用缓存时,又不得不考虑其线程安全问题,特别是现在大多数项目都使用了分布式架构,因此就需要对中间件进行优化和加强。
Redisson是一个开源框架,它提供了一系列分布式数据结构和服务,如分布式锁、分布式集合、分布式对象等,使得在分布式环境中使用Redis变得更加简单和高效(后续会出一篇关于Redisson的原理和特性),下面就来说一说在系统中如何去使用Redisson分布式锁。

1.搭建一个Springboot项目,引入Redisson依赖

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xmlns="http://maven.apache.org/POM/4.0.0"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <parent>
        <artifactId>shmily-demo-root</artifactId>
        <groupId>alp.starcode</groupId>
        <version>1.0.0</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>demo-redis</artifactId>

    <properties>
        <maven.compiler.source>8</maven.compiler.source>
        <maven.compiler.target>8</maven.compiler.target>
    </properties>

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

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
        </dependency>
        <!--使用lettuce时,需要额外引入commons-pool2依赖包-->
        <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-pool2</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>

        <!--redisson分布式锁-->
        <dependency>
            <groupId>org.redisson</groupId>
            <artifactId>redisson</artifactId>
        </dependency>

        <!--Lombok依赖-->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
        </dependency>
    </dependencies>

</project>

2.创建application.yml 、 application-multipart.yml、redisson.yml配置文件(application-multipart.yml配置文件创建是为了区分Redis集群和单节点的方式,Redisson的配置文件单独放在这个redisson中了)

spring:
  profiles:
    active: multipart #redis集群
    #active: single #单redis节点
spring:
  application:
    name: demo-redis
  redis:
    pool:
      max-idle: 100
      min-idle: 1
      max-active: 1000
      max-wait: -1
    timeout: 100000
    cluster:
      nodes:
       #地址要和redis配置中bind地址一致
        - 192.168.9.128:6379
        - 192.168.9.128:6380
        - 192.168.9.128:6381
        - 192.168.9.128:6382
        - 192.168.9.128:6383
        - 192.168.9.128:6384
    #jedis:
    #springboot2.0以上的版本默认使用的是lettuce redis客户端,如果想使用jedis客户端,则需把lettuce依赖进行排除以及手动引入jedis依赖
    lettuce:
      pool:
        max-active: 8 #连接池最大连接数(使用负值表示没有限制)
        max-wait: -1 # 连接池中的最大空闲连接
        max-idle: 500  # 连接池最大阻塞等待时间(使用负值表示没有限制)
        min-idle: 0  # 连接池中的最小空闲连接
    database: 0
    port: 6379
#集群配置
clusterServersConfig:
  # 连接空闲超时 如果当前连接池里的连接数量超过了最小空闲连接数,而同时有连接空闲时间超过了该数值,那么这些连接将会自动被关闭,并从连接池里去掉。时间单位是毫秒。
  idleConnectionTimeout: 10000
  pingTimeout: 1000
  # 连接超时
  connectTimeout: 10000
  # 命令等待超时
  timeout: 3000
  # 命令失败重试次数
  retryAttempts: 3
  # 命令重试发送时间间隔
  retryInterval: 1500
  # 重新连接时间间隔
  reconnectionTimeout: 3000
  # failedAttempts
  failedAttempts: 3
  # 密码
  #password: redis
  password:
  # 单个连接最大订阅数量
  subscriptionsPerConnection: 5
  # 客户端名称
  clientName: null
  #负载均衡算法类的选择  默认轮询调度算法RoundRobinLoadBalancer
  loadBalancer: !<org.redisson.connection.balancer.RoundRobinLoadBalancer> {}
  slaveSubscriptionConnectionMinimumIdleSize: 1
  slaveSubscriptionConnectionPoolSize: 50
  # 从节点最小空闲连接数
  slaveConnectionMinimumIdleSize: 32
  # 从节点连接池大小
  slaveConnectionPoolSize: 64
  # 主节点最小空闲连接数
  masterConnectionMinimumIdleSize: 32
  # 主节点连接池大小
  masterConnectionPoolSize: 64
  # 只在从服务节点里读取
  readMode: "SLAVE"
  # 主节点信息
  nodeAddresses:
    - "redis://192.168.9.128:6380"
    - "redis://192.168.9.128:6381"
    - "redis://192.168.9.128:6382"
    - "redis://192.168.9.128:6383"
    - "redis://192.168.9.128:6384"
    - "redis://192.168.9.128:6379"
  #集群扫描间隔时间 单位毫秒
  scanInterval: 1000
threads: 0
nettyThreads: 0
codec: !<org.redisson.codec.JsonJacksonCodec> {}

3.添加配置类 RedisConfig 和工具类 RedisUtil、SpringContextUtils

import org.redisson.Redisson;
import org.redisson.api.RedissonClient;
import org.redisson.config.Config;
import org.springframework.cache.CacheManager;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.ClassPathResource;
import org.springframework.data.redis.cache.RedisCacheConfiguration;
import org.springframework.data.redis.cache.RedisCacheManager;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.RedisSerializationContext;
import org.springframework.data.redis.serializer.StringRedisSerializer;

import java.io.IOException;
import java.io.Serializable;

@Configuration
@EnableCaching
public class RedisConfig {

    /**
     * 这是redis分布式锁的配置 1.有两个配置文件一个redisson.yml 和一个redisson-single.yml 前者是集群环境,后者是单个redis的配置(也就是没有配置集群的情况下使用)
     * @return
     * @throws IOException
     */
    @Bean(value = "redisson",name = "redisson",destroyMethod="shutdown")
    RedissonClient redisson() throws IOException {
        //1、创建配置
//        Config config = new Config();
//        config.useSingleServer().setAddress("172.24.17.119:6371").setPassword(redisPassword);

        Config config = Config.fromYAML(new ClassPathResource("redisson.yml").getInputStream());
       //
        //
        // Config config = Config.fromYAML(new ClassPathResource(configurationFiles).getInputStream());
        return Redisson.create(config);
    }

    /**
     * 默认情况下的模板只能支持RedisTemplate<String, String>,也就是只能存入字符串,因此支持序列化
     */
    @Bean
    public RedisTemplate<String, Serializable> redisCacheTemplate(LettuceConnectionFactory redisConnectionFactory) {
        RedisTemplate<String, Serializable> template = new RedisTemplate<>();
        template.setKeySerializer(new StringRedisSerializer());
        template.setValueSerializer(new GenericJackson2JsonRedisSerializer());
        template.setConnectionFactory(redisConnectionFactory);
        return template;
    }

    @Bean
    public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) {
        RedisTemplate<String, Object> template = new RedisTemplate<>();
        //关联
        template.setConnectionFactory(factory);
        //设置key的序列化器
        template.setKeySerializer(new StringRedisSerializer());
        //设置value的序列器
        template.setValueSerializer(new Jackson2JsonRedisSerializer<Object>(Object.class));
        return template;
    }

    /**
     * 配置使用注解的时候缓存配置,默认是序列化反序列化的形式,加上此配置则为 json 形式
     */
    @Bean
    public CacheManager cacheManager(RedisConnectionFactory factory) {
        // 配置序列化
        RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig();
        RedisCacheConfiguration redisCacheConfiguration = config.serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(new StringRedisSerializer())).serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(new GenericJackson2JsonRedisSerializer()));
        return RedisCacheManager.builder(factory).cacheDefaults(redisCacheConfiguration).build();
    }


}

import lombok.extern.slf4j.Slf4j;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.util.CollectionUtils;

import java.util.concurrent.TimeUnit;

/**
 * @author shmily
 * @description
 * @project shmily-demo-root
 * @package alp.starcode.demo.tools.utils
 * @clazz RedisUtil
 * @since 2022/9/14 0:10
 **/
@Slf4j
public class RedisUtil {
    private static RedisTemplate<String, Object> redisTemplate = SpringContextUtils.getBean("redisTemplate", RedisTemplate.class);

    //=============================common============================

    /**
     * 指定缓存失效时间
     *
     * @param key  键
     * @param time 时间(秒)
     * @return
     */
    public static boolean expire(String key, long time) {
        try {
            if (time > 0) {
                redisTemplate.expire(key, time, TimeUnit.SECONDS);
            }
            return true;
        } catch (Exception e) {
            log.error("设置redis指定key失效时间错误:", e);
            return false;
        }
    }

    /**
     * 根据key 获取过期时间
     *
     * @param key 键 不能为null
     * @return 时间(秒) 返回0代表为永久有效 失效时间为负数,说明该主键未设置失效时间(失效时间默认为-1)
     */
    public static long getExpire(String key) {
        return redisTemplate.getExpire(key, TimeUnit.SECONDS);
    }

    /**
     * 判断key是否存在
     *
     * @param key 键
     * @return true 存在 false 不存在
     */
    public static boolean hasKey(String key) {
        try {
            return redisTemplate.hasKey(key);
        } catch (Exception e) {
            log.error("redis判断key是否存在错误:", e);
            return false;
        }
    }

    /**
     * 删除缓存
     *
     * @param key 可以传一个值 或多个
     */
    @SuppressWarnings("unchecked")
    public static void del(String... key) {
        if (key != null && key.length > 0) {
            if (key.length == 1) {
                redisTemplate.delete(key[0]);
            } else {
                redisTemplate.delete(CollectionUtils.arrayToList(key));
            }
        }
    }

    //============================String=============================

    /**
     * 普通缓存获取
     *
     * @param key 键
     * @return 值
     */
    @SuppressWarnings("unchecked")
    public static <T> T get(String key) {
        return key == null ? null : (T) redisTemplate.opsForValue().get(key);
    }

    /**
     * 普通缓存放入
     *
     * @param key   键
     * @param value 值
     * @return true成功 false失败
     */
    public static boolean set(String key, Object value) {
        try {
            redisTemplate.opsForValue().set(key, value);
            return true;
        } catch (Exception e) {
            log.error("设置redis缓存错误:", e);
            return false;
        }

    }

    /**
     * 普通缓存放入并设置时间
     *
     * @param key   键
     * @param value 值
     * @param time  时间(秒) time要大于0 如果time小于等于0 将设置无限期
     * @return true成功 false 失败
     */
    public static boolean set(String key, Object value, long time) {
        try {
            if (time > 0) {
                redisTemplate.opsForValue().set(key, value, time, TimeUnit.SECONDS);
            } else {
                set(key, value);
            }
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }

    /**
     * 递增 此时value值必须为int类型 否则报错
     *
     * @param key   键
     * @param delta 要增加几(大于0)
     * @return
     */
    public static long incr(String key, long delta) {
        if (delta < 0) {
            throw new RuntimeException("递增因子必须大于0");
        }
        return redisTemplate.opsForValue().increment(key, delta);
    }

    /**
     * 递减
     *
     * @param key   键
     * @param delta 要减少几(小于0)
     * @return
     */
    public static long decr(String key, long delta) {
        if (delta < 0) {
            throw new RuntimeException("递减因子必须大于0");
        }
        return redisTemplate.opsForValue().increment(key, -delta);
    }
}

import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.stereotype.Component;

/**
 * @author shmily
 * @description Spring Context 工具类
 * @project shmily-demo-root
 * @package alp.starcode.demo.tools.utils
 * @clazz SpringContextUtils
 * @since 2022/9/14 0:03
 **/
@Component
public class SpringContextUtils implements ApplicationContextAware {
    public static ApplicationContext applicationContext;

    public static Object getBean(String name) {
        return applicationContext.getBean(name);
    }

    public static <T> T getBean(Class<T> requiredType) {
        return applicationContext.getBean(requiredType);
    }

    public static <T> T getBean(String name, Class<T> requiredType) {
        return applicationContext.getBean(name, requiredType);
    }

    public static boolean containsBean(String name) {
        return applicationContext.containsBean(name);
    }

    public static boolean isSingleton(String name) {
        return applicationContext.isSingleton(name);
    }

    public static Class<? extends Object> getType(String name) {
        return applicationContext.getType(name);
    }

    @Override
    public void setApplicationContext(ApplicationContext applicationContext)
            throws BeansException {
        SpringContextUtils.applicationContext = applicationContext;
    }

}

4.测试Redisson分布式锁

import alp.starcode.demo.redis.util.RedisUtil;
import org.redisson.Redisson;
import org.redisson.api.RLock;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import javax.annotation.Resource;
import java.util.UUID;

@RestController
public class RedissonController {

    public static final String REDIS_LOCK = "ticket_lock";

    @Resource
    Redisson redisson;

    /**
     * 测试分布式锁
     *
     * @throws InterruptedException
     */
    @RequestMapping("/testRedisson")
    public void testRedisson() throws InterruptedException{
        for (int i = 0; i <100; i++) {
            purchaseTicket();
        }
    }

    /**
     * 购买门票
     * @throws InterruptedException
     */
    @Async
    public void purchaseTicket() throws InterruptedException {
        RLock lock = redisson.getLock(REDIS_LOCK);
        lock.lock();
        // 每个人进来先要进行加锁,key值为"ticket_lock"
        String value = UUID.randomUUID().toString().replace("-","");
        try {
            // Redis中存有ticket:001号门票,数量为100
            String result = RedisUtil.get("ticket:001").toString();
            int total = result == null ? 0 : Integer.parseInt(result);
            if (total > 0) {
                // 剩余门票数大于0 ,则进行扣减
                int realTotal = total - 1;
                // 将门票数回写数据库
                RedisUtil.set("ticket:001", String.valueOf(realTotal));
                System.out.println("线程:" + Thread.currentThread().getName() + "获得了锁");
                System.out.println("购买门票成功,库存还剩:" + realTotal + "件, 服务端口为8080");
                //System.out.println("购买门票成功,库存还剩:"+realTotal +"件, 服务端口为8080");
            } else {
                System.out.println("购买门票失败,服务端口为8080");
            }
        }catch (Exception e){
            e.printStackTrace();
        }finally {

            if(lock.isLocked() && lock.isHeldByCurrentThread()){
                lock.unlock();
                System.out.println("线程:" + Thread.currentThread().getName() + "释放锁");
            }
        }
    }

}

5.如下图,测试分布式锁是否生效

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
至此,SpringBoot+Redisson整合完毕,后续会单独出一版Redis单节点分布式锁的实现。

  • 23
    点赞
  • 21
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
回答: 要在Spring Boot集成Redisson连接Redis集群,你需要进行以下步骤。首先,你需要在pom.xml文件中引入Redisson的依赖项,如下所示:\[2\] ```xml <dependency> <groupId>org.redisson</groupId> <artifactId>redisson</artifactId> <version>3.17.0</version> </dependency> ``` 接下来,你需要在配置文件中进行相应的配置。你可以使用Redisson提供的配置类来配置Redis连接,例如使用`RedissonClusterConfig`来配置Redis集群连接。你可以在配置文件中指定Redis集群的节点信息和其他相关配置。\[2\] 最后,你可以在Spring Boot应用程序中使用Redisson的API来连接和操作Redis集群。你可以使用`RedissonClient`接口来获取Redis连接,并使用相应的方法来执行操作,如存储、读取和删除数据等。\[2\] 如果你想使用命令行来搭建Redis集群,你可以使用以下命令:\[3\] ``` redis-cli --cluster create 192.168.2.66:7000 192.168.2.66:7001 192.168.2.66:7002 192.168.2.66:7003 192.168.2.66:7004 192.168.2.66:7005 --cluster-replicas 1 ``` 这个命令将创建一个包含6个节点的Redis集群,并设置每个主节点有一个从节点。你需要根据你自己的实际情况修改IP地址和端口号。\[3\] 希望这些信息对你有帮助! #### 引用[.reference_title] - *1* *3* [SpringBoot整合Redisson操作集群redis](https://blog.csdn.net/u013658328/article/details/112661206)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^control,239^v3^insert_chatgpt"}} ] [.reference_item] - *2* [SpringBoot集成redisson操作redis](https://blog.csdn.net/quanzhan_King/article/details/130961128)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^control,239^v3^insert_chatgpt"}} ] [.reference_item] [ .reference_list ]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值