SpringBoot 精通系列-SpringBoot整合Redis的常用操作

 2023-09-15 阅读 21 评论 0

摘要:导语   在之前的博客中介绍过关于Memcache的使用,这篇文章中主要介绍关于Redis的有关概念及如何与SpringBoot整合使用。   首先Redis是目前使用最为广泛的缓存中间件,相比较Memcache来说,Redis支持更多的数据结构而且对于这些数据结构的操作也很多&

导语
  在之前的博客中介绍过关于Memcache的使用,这篇文章中主要介绍关于Redis的有关概念及如何与SpringBoot整合使用。
  首先Redis是目前使用最为广泛的缓存中间件,相比较Memcache来说,Redis支持更多的数据结构而且对于这些数据结构的操作也很多,当然Redis也支持非常丰富的高可用集群解决方案。下面就来看一下关于Redis相关操作。

文章目录

  • Redis介绍
    • 数据模型
    • 优势
    • spring-boot-starter-data-redis
  • 快速开发
    • 第一步、引入相关依赖
    • 第二步、application的配置
    • 第三步、缓存配置
    • 第四步、单元测试
  • Redis对于其他数据类型的操作
    • 1、实体类型
    • 2、超时失效
    • 3、删除数据
    • 4、Hash
    • 5、List
    • 6、Set
    • 7、对于Set的其他操作
      • 7.1、difference
      • 7.2、unions
    • 8、Zet
  • 封装缓存
  • 总结

Redis介绍

  Redis是一个高速非关系型数据库也就是常说的No-SQL数据库,它可以存储键值对,支持五种不同Value的存储,可以将存储在内存的键值对持久化到硬盘中,可以使用复制的特点扩展其可读性,还可以使用客户端进行分片来进行扩展。

  为了高性能,Redis也采用的是内存数据集,根据使用场景,可以通过每隔一段时间转存数据到磁盘,或者追加每条命令到日志来维持持久化操作。当然持久化也可以被禁用,如果只是需要一个功能则可以不用持久化。

数据模型

  Redis数据模型不仅仅与关系型数据库不同,也不同于其他的NoSQL键值对存储。Redis数据类型类似于编程中的基本数据类型,可以通过这个基本的数据类型做其他的高级操作。主要支持的数据类型包括如下一些

  • String
  • Hash
  • List
  • Set
  • ZSet

优势

  Redis是使用C语言来编程的,直接操作内存在速度上有了优势,其次它支持的基本数据类型丰富、支持原子性的操作、对于所有语言的通用性等等。

  • 高性能:每秒可以执行10w个set以及10w个get操作
  • 数据类型丰富:Redis对于多数开发人员已知的数据类型提供了原生的支持
  • 原子性:Redis操作都是原子性的,也就是多个客户端并发访问Redis服务器,获取到的值一定是最新的。
  • 其他特性:Redis所适用的场景有很多,包括缓存、消息队列(Redis原生支持)、短期的应用数据存储(例如Session、Web命中数据等)等。

spring-boot-starter-data-redis

  作为一个优秀的企业级解决方案。SpringBoot提供了与Redis集成的组件包,spring-boot-starter-date-redis, 它依赖与spring-data-redis 和 lettuce。在SpringBoot1.0默认使用的是Jedis客户端,2.0的时候被替换成了Lettuce,如果使用的是1.5.x的SpringBoot是感觉不到差异的,因为SpringBoot隔离了其中的一些差异性。

  • Lettuce:一个可伸缩并且线程安全的Redis客户端,多个线程同时共享了一个RedisConnection,利用了Netty NIO作为IO连接来高效的管理多个连接。
  • SpringData: Spring中的提供数据存储解决方案的框架,包括支持关系型数据库、非关系型数据库、Map-Reduce框架、云数据服务等。
  • SpringData Redis: 是SpringData提供的一个关于Redis的解决方案,实现了对于Redis客户端API的高度整合。

关系如下

Lettuce
SpringDataRedis
SpringData
Spring-Boot-Data-Redis

如上图所示,其实SpringDataRedis和Lettuce具备的功能,在SpringBoot Redis的启动器中都会存在。

快速开发

第一步、引入相关依赖

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"><modelVersion>4.0.0</modelVersion><parent><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-parent</artifactId><version>2.1.6.RELEASE</version><relativePath/> <!-- lookup parent from repository --></parent><groupId>com.nh.redis</groupId><artifactId>redis</artifactId><version>0.0.1-SNAPSHOT</version><name>redis</name><description>Demo project for Spring Boot</description><properties><java.version>1.8</java.version></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><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></dependencies><build><plugins><plugin><groupId>org.springframework.boot</groupId><artifactId>spring-boot-maven-plugin</artifactId></plugin></plugins></build></project>

注意
  这里说明一下为什么要使用commons-pool2是因为Lettuce需要使用commons-pool2来创建连接池。

第二步、application的配置

# Redis 数据库索引
spring.redis.database=0
# Redis 服务器地址
spring.redis.host=localhost
# Redis 服务器连接端口
spring.redis.port=6379
# Redis 服务器连接密码(默认为空)
spring.redis.password=
# 连接池最大连接数据 默认为 8
spring.redis.lettuce.pool.max-active=8
# 连接池最大阻塞时间 默认为 -1
spring.redis.lettuce.pool.max-wait= -1
# 连接池中最大空闲连接
spring.redis.lettuce.pool.max-idle=8
# 连接池中最小空闲连接
spring.redis.lettuce.pool.min-idle=0

第三步、缓存配置

  这里是为了更好的整合Redis而设置一些全局的配置,例如key的生成策略,设置默认参数等等操作。

@Configuration
@EnableCaching
public class RedisConfig extends CachingConfigurerSupport {@Beanpublic KeyGenerator keyGenerator(){return new KeyGenerator() {@Overridepublic Object generate(Object target, Method method, Object... params) {StringBuilder sb = new StringBuilder();sb.append(target.getClass().getName());sb.append(method.getName());for (Object obj: params) {sb.append(obj.toString());}return sb.toString();   }       };}   
}

第四步、单元测试

  在单元测试中注入一个RedisTemplate,对于这个RedisTemplate与JDBCTemplate的作用是相同的。而String是以最长用的数据类型,普通的KV存储都可以使用String类型,当然这个是不一定的,也是使用其他的。

@RunWith(SpringRunner.class)
@SpringBootTest
public class RedisApplicationTests {@Autowiredprivate RedisTemplate redisTemplate;@Testpublic void contextLoads() {redisTemplate.opsForValue().set("nihui","hello");Assert.assertEquals("hello",redisTemplate.opsForValue().get("nihui"));}}

  从上面的流程来看只需要使用spring-boot-starter-data-redis只需要三步就可以与Redis进行集成。下面就来介绍一下Redis对于其他数据类型的操作。

Redis对于其他数据类型的操作

  Redis在SpringBoot中支持的数据类型有,实体、哈希、列表、集合、有序集合。这些类型在SpringBoot中怎么去使用,下面就来看看各个数据类型怎么使用。

1、实体类型

  首先来看看Redis对于Pojo的支持,首先来创建一个User对象,将其放入到缓存中,然后再取出来,测试操作如下

@Test
public void testObj(){User user = new User("nihui",23,"123@163.com");ValueOperations<String,User> operations = redisTemplate.opsForValue();operations.set("user",user);User u = operations.get("user");System.out.println("user : "+ u.toString());
}

2、超时失效

  Redis对于每个存储到其中的数据都可以设置一个超时时间,过了这个时间就会自动删除数据,这种特点就适合进行阶段性的数据缓存操作。

@Test
public void testExpire() throws InterruptedException {User user = new User("nihui",23,"123@163.com");ValueOperations<String,User> operations = redisTemplate.opsForValue();operations.set("nihui",user,100,TimeUnit.MILLISECONDS);Thread.sleep(1000);boolean exists = redisTemplate.hasKey("nihui");if (exists){System.out.println("exists is true");}else {System.out.println("exists is false");}
}

  从测试结果来看Redis已经不能存在User对象了,也就是说这个数据已经过期了,同时在则是过程中使用了hasKey来判断key是否存在。

3、删除数据

  在有些时候需要对过期的数据进行清理,下面就来看看如何清理这些数据。

@Test
public void testDelete(){redisTemplate.opsForValue().set("deleteKey","hello,nihui");redisTemplate.delete("deleteKey");boolean exists = redisTemplate.hasKey("deleteKey");if (exists){System.out.println("exists is true");}else {System.out.println("exists is false");}
}

4、Hash

  在正常情况下存储一个键自然会对应有一个值,实际上在很多场景中这种操作并不是最好的,Redis存储一个键只会占用很小的内存,但是不管你这个键值字节数有多少都会是一样的,这个时候就是用Hash来解决这个资源浪费的问题。

  Hash 就表示使用Hash这种数据结构进行key和value的存储。哈希表本身就是一个键值对的集合。这个集合可以根据kv的大小来动态开辟空间。对于一些有规律性的数据可以使用这种方式进行存储。

在这里插入图片描述
从上图可以看到IDEA给出的操作提示,第一个参数表示hash表的Key,第二个参数表示Hash表中的key,第三个参数表示Hash表中的Value。

@Test
public void testHash(){HashOperations<String,Object,Object> hashOperations = redisTemplate.opsForHash();hashOperations.put("nihui","hello","hello");String value = (String) hashOperations.get("nihui","hello");System.out.println("hash value "+value);
}

5、List

  在Redis中的List使用的场景也是比较多的,也作为Redis中最为重要的一个数据结构存在,使用List可以实现一个数据队列的存储,也就是说支持一类数据的存储集合,使用List最常用的一个场景就是消息队列。可以利用List进行Push操作,将任务存储到List中,然后工作线程从中执行先进先出的操作,从List中操作对应的任务。

@Test
public void testList(){ListOperations<String,String> listOperations = redisTemplate.opsForList();listOperations.leftPush("list","nihui");listOperations.leftPush("list","hello");listOperations.leftPush("list","know");String value = listOperations.leftPop("list");System.out.println("list value : "+value.toString());
}

  从上面的操作中可以看到从左面进从左面出,表示这个是一个栈,后进先出,所有最后取出的值是know,当然也可以从左边进从右边出构成一个队列。这可以后续自己设置符合自己程序的数据结构进行操作。

注意
  Redis List 是实现了一个双向链表,也就是支持反向的查找和遍历操作,但是使用双向链表所带来的结果就是一部分的内存开销,在Redis内部有很多的实现方式。具体的双向链表可以关注后续的数据结构系列。

6、Set

  Redis Set对外提供的是一个类似于List的功能,特殊的地方是在于Set是可以自动重排的,需要存储一个列表数据,又不希望出现重复数据的时候可以使用这种数据结构,当然这个也是集合这种数据结构的特点,集合中的元素是不重复的,当然也可以使用Set来判断是否存在某个元素。这个是List不能实现的功能。

@Test
public void testSet(){String key = "set";SetOperations<String,String> setOperations = redisTemplate.opsForSet();setOperations.add(key,"nihui");setOperations.add(key,"nihui");setOperations.add(key,"hello");setOperations.add(key,"world");Set<String> values = setOperations.members(key);for (String v:values) {System.out.println("set value : "+v);}
}

  当然这里往这个Set中存储了两个nihui,但是读取的时候只有一条数据也就是说Set对数据进行了重排。

7、对于Set的其他操作

&emps; 从数学的角度上讲,对于一个集合来说有交集、并集、差集等操作,Redis中对这些操作也提供了一些支持

7.1、difference

@Test
public void testSetDifference(){SetOperations<String,String> setOperations = redisTemplate.opsForSet();String key1 = "key1";String key2 = "key2";setOperations.add(key1,"nihui");setOperations.add(key1,"hello");setOperations.add(key1,"hello");setOperations.add(key1,"world");setOperations.add(key2,"xxx");setOperations.add(key2,"nihui");Set<String> diffs = setOperations.difference(key1,key2);for (String v :diffs){System.out.println("diffs set value :"+v);}     
}

  上面这个例子是吧key1中不同于key2 的数据对比出来。类似于在集合A中但是不在集合B中的操作。在数学上被称为是补集。

7.2、unions

@Test
public void testSetUnions(){SetOperations<String,String> setOperations = redisTemplate.opsForSet();String key1 = "key1";String key2 = "key2";setOperations.add(key1,"nihui");setOperations.add(key1,"hello");setOperations.add(key1,"xxx");setOperations.add(key2,"aa");setOperations.add(key2,"bb");setOperations.add(key2,"world");Set<String> unions = setOperations.union(key1,key2);for (String v :unions){System.out.println("unions set value :"+v);}}

  根据测试结果可以发现,unions会联合两个集合,类似于并集。

注意
  其实Set的内部实现是一个Value永远为null的HashMap,也就是说实际是通过计算Hash的方式来快速重排,这个也是为什么Set可以快速判断是否有数据在集合中的原因,如果有重复元素就会产生Hash冲突,如果没有Hash冲突则不会有重复集合。

8、Zet

  Redis Sorted Set 的使用场景与Set是类似的,区别是Set不会自动有序,而Sorted Set可以通过用户额外提供的一个Score来实现排序操作并且插入有序,也就是自动排序,
注意这里的排序和重排是两个不一样的概念
  在使用Zset的时候需要额外输入一个Score的参数,Zset会根据Score的值来对集合进行排序,可以利用这个特点来实现根据权重进行排序,例如最重要消息为Score为1,重要消息Score为2 然后通过权重来获取消息。

 @Test
public void testZset(){String key = "zset";redisTemplate.delete(key);ZSetOperations<String,String> zSetOperations = redisTemplate.opsForZSet();zSetOperations.add(key,"nihui",1);zSetOperations.add(key,"hello",6);zSetOperations.add(key,"world",4);zSetOperations.add(key,"test",3);Set<String> zsets = zSetOperations.range(key,0,3);for (String value:zsets) {System.out.println("zset value : "+value);}Set<String> zsett = zSetOperations.rangeByScore(key,0,3);for (String value:zsett) {System.out.println("zsett value : "+value);}
}

  通过上面的例子会发现插入的Zset中的数据会更具Score进行重新排序,根据这特性可以做优先级队列等操作,另外Zset还提供了rangeByScore的方法,获取Score范围内的数据排序数据。
注意
  Redis Sorted Set的内部使用HashMap和跳跃表(SkipList)来保证数据的存储和有序,HashMap里放的成员到Score的映射,而跳跃表中存放的是所有的数据成员,排序依据的是HashMap里的Score,使用跳跃表结构可以获得比较高的查找效率,并且在实现上也相对比较简单。

  到这里关于Redis所支持的数据类型的基本操作都已经说完了,其他的高级操作可以继续关注博客提供更优秀的解决方案。

封装缓存

  在实际工作中并不是使用RedisTemplate来进行操作的,不是为每个类都注入一个RedisTemplate,而是对不同的功能模块进行封装操作,然后暴露其中的一个服务接口来进行缓存操作。这也符合面向对象的三大特性。

@Service
public class RedisCacheService {@Autowiredprivate RedisTemplate redisTemplate;//增加缓存public boolean set(final String key,Object value){boolean result = false;try{ValueOperations<Serializable,Object> operations = redisTemplate.opsForValue();operations.set(key,value);result = true;}catch (Exception e){e.printStackTrace();}return result;}//删除某一类键public void removePattern(final String pattern){Set<Serializable> keys = redisTemplate.keys(pattern);if (keys.size()>0){redisTemplate.delete(keys);}}
}

  在其他地方使用的时候只需要注入这个服务即可

总结

  相比较于Memcache来说,Redis是比较优秀的一款高可用性的缓存中间件,在有些企业中更是使用Redis来作为数据库来使用,Spring官方也为Redis提供良好的支持,Redis支持丰富的数据类型以及对数据类型的操作,方便了各种场景下的使用。提供了很多内置的支持。当然这个只是Redis在SpringBoot中的使用方式,后续的博客中将对Redis做一个专题的学习。敬请期待

版权声明:本站所有资料均为网友推荐收集整理而来,仅供学习和研究交流使用。

原文链接:https://hbdhgg.com/3/60452.html

发表评论:

本站为非赢利网站,部分文章来源或改编自互联网及其他公众平台,主要目的在于分享信息,版权归原作者所有,内容仅供读者参考,如有侵权请联系我们删除!

Copyright © 2022 匯編語言學習筆記 Inc. 保留所有权利。

底部版权信息