redis与springboot整合使用

一、项目配置与使用

1、引入的依赖

<parent>
     <groupId>org.springframework.boot</groupId>
     <artifactId>spring-boot-starter-parent</artifactId>
     <version>2.5.5</version>
 </parent>
 <dependencies>
     <dependency>
         <groupId>org.springframework.boot</groupId>
         <artifactId>spring-boot-starter-data-redis</artifactId>
     </dependency>
     <!-- 连接池依赖 -->
     <dependency>
         <groupId>org.apache.commons</groupId>
         <artifactId>commons-pool2</artifactId>
     </dependency>
     <!-- jackson 用于redis配置序列化使用,因为没引入mvc模块,所以手动引入了下 -->
     <dependency>
         <groupId>com.fasterxml.jackson.core</groupId>
         <artifactId>jackson-databind</artifactId>
     </dependency>
     <dependency>
         <groupId>org.springframework.boot</groupId>
         <artifactId>spring-boot-starter-test</artifactId>
         <scope>test</scope>
     </dependency>
 </dependencies>

2、配置文件

spring:
    redis:
        host: 127.0.0.1
        port: 6379
        database: 0
        #password: 123456
        lettuce:
            pool:
                max-idle: 8
                min-idle: 8
                max-active: 8
                max-wait: 100

3、编写配置类与实体类,实体类必须实现序列化接口并有get set方法,否则会序列化出错,序列化器有俩选择,它们的区别在于存储时的值

GenericJackson2JsonRedisSerializer :存储的是{“@class”:“com.xp.User”,“name”:“徐鹏”,“age”:24},该对象可以反序列化为我们的实体类对象,缺点就是体积大了一些。

Jackson2JsonRedisSerializer :存储的是{“name”:“徐鹏”,“age”:24},推荐用这种,使用时转为JSON对象使用

@Configuration
public class RedisConfig {
    @Bean
    public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) {
        RedisTemplate<String, Object> template = new RedisTemplate<String, Object>();
        template.setConnectionFactory(factory);
        //设置序列化工具 - 有两种
        //GenericJackson2JsonRedisSerializer genericJackson2JsonRedisSerializer = new GenericJackson2JsonRedisSerializer();
        Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);
        ObjectMapper om = new ObjectMapper();
        om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
        jackson2JsonRedisSerializer.setObjectMapper(om);

        StringRedisSerializer stringRedisSerializer = new StringRedisSerializer();
        // key采用String的序列化方式
        template.setKeySerializer(stringRedisSerializer);
        // hash的key也采用String的序列化方式
        template.setHashKeySerializer(stringRedisSerializer);
        // value序列化方式采用jackson
        template.setValueSerializer(jackson2JsonRedisSerializer);
        // hash的value序列化方式采用jackson
        template.setHashValueSerializer(jackson2JsonRedisSerializer);
        template.afterPropertiesSet();
        return template;
    }
}
public class User implements Serializable {
    private String name;
    private int age;
    public User(String name, int age){
        this.name = name;
        this.age = age;
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public int getAge() {
        return age;
    }
    public void setAge(int age) {
        this.age = age;
    }
}

4、编写测试代码

@SpringBootTest
class RedisDemoApplicationTests {
    // 配置了redis的序列化器后使用
    @Autowired
    private RedisTemplate<String, Object> redisTemplate;
    // key和值都是字符串可以用这个
    @Autowired
    private StringRedisTemplate stringRedisTemplate;
    @Test
    void test1(){
        ValueOperations valueOperations = stringRedisTemplate.opsForValue();
        valueOperations.set("key1","v1");
        Object key1 = valueOperations.get("key1");
        System.out.println((String) key1);//v1
    }
    @Test
    void test2() throws Exception {
        User u = new User("徐鹏", 24);
        redisTemplate.opsForValue().set("key2",u);
        Object key2 = redisTemplate.opsForValue().get("key2");
        System.out.println(String.valueOf(key2));//{name=徐鹏, age=24}
    }
}

5、截图效果
在这里插入图片描述
在这里插入图片描述

二、监听过期key

1、监听过期key需要修改redis配置文件

notify-keyspace-events Ex

2、修改上图代码配置类,新增如下配置

@Bean
RedisMessageListenerContainer container(RedisConnectionFactory connectionFactory) {
    RedisMessageListenerContainer container = new RedisMessageListenerContainer();
    container.setConnectionFactory(connectionFactory);
    return container;
}

@Component
public class RedisListener extends KeyExpirationEventMessageListener {
   public RedisListener(RedisMessageListenerContainer listenerContainer) {
       super(listenerContainer);
   }
   @Override
   public void onMessage(Message message, byte[] pattern) {
       String expiredKey = message.toString();
       System.out.println("接收到了过期key" + expiredKey);//key2
       String topic = new String(pattern);
       System.out.println("渠道名称是" + topic);//keyevent@*__:expired
       if(expiredKey.startsWith("user:")){
            //业务逻辑
        }else if(expiredKey.startsWith("order:")){
            //业务逻辑
        }
   }
}

3、修改测试代码

@Test
voidtest2() throws Exception {
    User u = new User("徐鹏", 24);
    redisTemplate.opsForValue().set("key2",u);
	// 5秒后key过期
    redisTemplate.expire("key2",5, TimeUnit.SECONDS);
    // 休眠10秒,让key过期监听器能触发
    Thread.sleep(10000);
    System.out.println("====进程结束=====");
}

三、redis实现分布式主键

解释:雪花算法,下图生成的是一个long类型的正数。long类型占用62位,为了保证是正正数,所以第一位一定是0。

获取当前秒数(用31位就可以装下),然后向左位移32位,会形成一个首位是0,后面31位是原本秒值所占的位,后面都是0。redis通过指定日期自增,或取出一个当天的唯一数字。防止冲突。
实际上相当每秒有四十多亿个key组成,而且redis是命令执行时单线程的,不用担心同一秒获取到了一样的自增数字

这个是当前秒,用32位就可以装下
01100011000101100101100111111111
左位移32位后
01100011000101100101100111111111 00000000 00000000 00000000 00000000

假设redis取出了1,它的二进制为就是如下
00000000 00000000 00000000 00000001

此时让他与秒值左位移使用|运算符号得出了
01100011000101100101100111111111 00000000 00000000 00000000 00000001
@Test
//void test3(String keyPrefix) {
void test3() {
    String keyPrefix = "userid";
    // 1.获取当前秒数 转换为二进制(不用数了是31位),1100011000101100101100111111111
    LocalDateTime now = LocalDateTime.now();
    long nowSecond = now.toEpochSecond(ZoneOffset.UTC);
    System.out.println(Long.toBinaryString(nowSecond));

    // 2.生成序列号自增长,按天自增
    String yyyyMMdd = now.format(DateTimeFormatter.ofPattern("yyyyMMdd"));
    Long increment = redisTemplate.opsForValue().increment("icr:" + keyPrefix + ":" + yyyyMMdd, 0);
    System.out.println(increment);

    // 3.组装key,7139993210994032640
    System.out.println(nowSecond << 32 | increment);
}

四、发布和订阅

项目启动时监听指定通道信息,当有信息时会触发onMessage(),可以做一些自己的业务逻辑。

@Bean
RedisMessageListenerContainer container(RedisConnectionFactory connectionFactory) {
    RedisMessageListenerContainer container = new RedisMessageListenerContainer();
    container.setConnectionFactory(connectionFactory);
    // 订阅指定通道
    container.addMessageListener(new MessageListener(){
        @Override
        public void onMessage(Message message, byte[] pattern) {
            System.out.println(message.toString());
            // 自己的业务逻辑................
        }
    }, new PatternTopic("channel_01"));
    return container;
}
/**
*	测试类新增代码如下
*/
@Test
void test4() {
    redisTemplate.convertAndSend("channel_01","我是发送的消息");
    try {
        Thread.sleep(10000);
    }catch (Exception e){

    }
}

在这里插入图片描述

五、分布式锁Redisson

因为redis自身执行命令是单线程的,我们可以通过setnx命令来实现分布式锁,但是这把锁是不可重入的。也就是递归行为的方法,因为自己持有锁了再次尝试获取会失败,导致线程死锁。
Redisson是一个在Redis的基础上实现的Java驻内存数据网格(In-Memory Data Grid)。它不仅提供了一系列的分布式的Java常用对象,还提供了许多分布式服务,其中就包含了各种分布式锁的实现。
分布式锁的大致实现思路
1.把原本的string类型替换为hash类型,
2.hash类型里面存储两个字段,一个是持有锁的线程名称,一个是计数器,还要设置锁的过期时间
3.使用时尝试添加数据,如果已存在,获取出来判断是否是当前线程持有锁,是的话,计数器+1,重置锁的过期时间
4.每次业务方法执行完时,先判断锁的计数器,如果为1直接删除key即可,大于1表示是重复进入方法,只需要将计数器-1即可

<dependency>
    <groupId>org.redisson</groupId>
    <artifactId>redisson</artifactId>
    <version>3.13.6</version>
</dependency>
// 配置
@Configuration
public class RedissonConfig {
    @Bean
    public RedissonClient redissonClient(){
        Config config = new Config();
        // 配置的是单节点redis
        config.useSingleServer().setAddress("redis://127.0.0.1:6379").setPassword("123456");
        return Redisson.create(config);
    }
}
// 基本使用
@RestController
public class RedissonController {
    @Resource
    private RedissonClient redissonClient;
    @RequestMapping("/test")
    public void test() throws InterruptedException {
        // 获取锁(可重入),指定锁的名称
        RLock lock = redissonClient.getLock("test1");
        // 尝试获取锁,锁的最大等待时间(期间会重试),自动释放时间,时间单位
        boolean isLock = lock.tryLock(1, 10, TimeUnit.SECONDS);
        // 判断释放获取成功
        if(isLock){
            try {
                System.out.println("执行业务");
            }finally {
                lock.unlock();
            }
        }
    }
}

版权声明:本文为weixin_44080194原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。