6. SpringBoot整合Redis
Redis是NoSQL类型的数据库,我们也常称为内存型数据库类型.在SpringBoot中使用Redis非常简单.
6.1 添加包依赖
<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>
添加依赖后其实就直接可以使用redis了(SpringBoot提供了默认配置,也可以自己配置)
6.2 配置
spring.redis.host=localhost
spring.redis.port=6379
spring.redis.timeout=1000
spring.redis.lettuce.pool.max-active=10
spring.redis.lettuce.pool.max-idle=10
spring.redis.lettuce.pool.min-idle=5
spring.redis.lettuce.pool.max-wait=1000
6.3 编写Controller测试
@Controller
@RequestMapping("/redis")
public class RedisController {
@Autowired
private RedisTemplate redisTemplate;
@RequestMapping("/set")
@ResponseBody
public String redisStringSet(String key, String value) {
ValueOperations ops = redisTemplate.opsForValue();
ops.set(key, value);
return "OK";
}
}
当执行了上面的代码后,使用Redis客户端可查看到redis中多出了一个记录.但是该记录是编码后的,不方面我们查看.这就需要我们进行相应的配置了.让redis直接以字符串的方式显示我们插入的内容.
@Configuration
public class RedisTemplateConfig {
@Bean
public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
RedisTemplate<Object, Object> redisTemplate = new RedisTemplate<>();
redisTemplate.setConnectionFactory(redisConnectionFactory);
// 自定义的string序列化器和fastjson序列化器
StringRedisSerializer stringRedisSerializer = new StringRedisSerializer();
// jackson 序列化器
GenericJackson2JsonRedisSerializer jsonRedisSerializer = new GenericJackson2JsonRedisSerializer();
// fastjson 序列化器
// GenericFastJsonRedisSerializer jsonRedisSerializer = new GenericFastJsonRedisSerializer();
// kv 序列化
redisTemplate.setKeySerializer(stringRedisSerializer);
redisTemplate.setValueSerializer(jsonRedisSerializer);
// hash 序列化
redisTemplate.setHashKeySerializer(stringRedisSerializer);
redisTemplate.setHashValueSerializer(jsonRedisSerializer);
redisTemplate.afterPropertiesSet();
return redisTemplate;
}
}
RedisTemplate是我们操作数据库的类,Spring也提供了StringRedisTemplate和HashKeyRedisTemplate分别用于处理内容序列化显示的问题。添加完上面的配置代码后,重新执行redis插入,就可以看到redis显示了真实的字符串内容。
Redis提供7种基本数据类型,可通过RedisTemplate提供的方法执行。
/获取地理位置操作接口
redisTemplate.opsForGeo();
/获取散列操作接口
redisTemplate.opsForHash();
//获取基数操作接口
redisTemplate.opsForHyperLogLog();
/获取列表操作接口
redisTemplate.opsForlist();
/获取集合操作接口
redisTemplate.opsForset();
//获取字符串操作接口
redisTemplate.opsForvalue();
//获取有序集合操作接口
redisTemplate.psForzset();
一个Redis连接中只执行一个redis命令。Redis也提供了方法让我们可以在一个连接中执行多个命令。就是SessionCallback和RedisCallback接口。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-RllYlt4s-1578452712845)(C:\Users\mxlei\AppData\Roaming\Typora\typora-user-images\image-20200107151716183.png)]
Redis的各种数据类型的使用这里不做介绍。
6.4 Redis高级用法
6.4.1 redis事务
@RequestMapping("/transaction")
@ResponseBody
public List redisTransaction() {
//key1设置为字符串类型
redisTemplate.opsForValue().set("key1", "value1");
//Redis事务
List list = (List) redisTemplate.execute(new SessionCallback() {
@Override
public Object execute(RedisOperations operations) throws DataAccessException {
//设置要监控的KEY
operations.watch("key1");
//开启事务,在exec命令执行之前,全部都知识加入队列
operations.multi();
operations.opsForValue().set("key2", "value2");
// operations.opsForValue().increment("key1",1); //①
//取得的值为null,因为redis只是把命令加入队列
Object value2 = operations.opsForValue().get("key2");
System.out.println("取得的值为null,因为redis只是把命令加入队列 value2 = " + value2);
operations.opsForValue().set("key3", "value3");
Object value3 = operations.opsForValue().get("key3");
System.out.println("取得的值为null,因为redis只是把命令加入队列 value3 = " + value3);
//执行exec命令,将判断key1是否在监控后被修改过,如果是则不执行事务,否则就执行事务
return operations.exec(); //②
}
});
System.out.println(list);
return list;
}
为了揭示 Redis事务的特性,我们对这段代码做以下两种测试先在 Redis客户端清空key2和key3两个键的数据,然后在②处设置断点,在调试的环境下让请求达到断点,此时在 Redis上修改keyl的值,然后再跳过断点,在请求完成后在 Redis上查询key2和key3值,可以发现key2、key3返回的值都为空(ni),因为程序中先使得 Redis的 watch命令监控了keyl的值,而后的 multi让之后的命令进入队列,而在exec方法运行前我们修改了keyl,根据 Redis事务的规则,它在exec方法后会探测keyl是否被修改过,如果没有则会执行事务,否则就取消事务,所以key2和key3没有被保存到Redis服务器中迷续把key2和key3两个值清空,把①处的注释取消,让代码可以运行,因为keyl是一个字符串,所以这里的代码是对字符串加一,这显然是不能运算的。同样地,我们运行这段代码后,可以看到服务器抛出了异常,然后我们去 Redis服务器查询key2和key3,可以看到它们已经有了值。注意,这就是 Redis事务和数据库事务的不一样,对于 Redis事务是先让命令进入队列,所以一开始它并没有检测这个加一命令是否能够成功,只有在exec命令执行的时候,才能发现错误,对于出错的命令 Redis只是报出错误,而错误后面的命令依旧被执行,所以key2和key3都存在数据,这就是 Redis事务的特点,也是使用Reds事务需要特别注意的地方。为了克服这个问题,一般我们要在执行 Redis事务前,严格地检查数据,以避免这样的情况发生。
6.4.2 Redis流水线
在默认的情况下, Redis客户端是一条条命令发送给 Redis服务器的,这样显然性能不高。在关系数据库中我们可以使用批量,也就是只有需要执行SQL时,才一次性地发送所有的SQL去执行,这样性能就提高了许多。对于 Redis也是可以的,这便是流水线( pipline)技术,在很多情况下并不是 Redis性能不佳,而是网络传输的速度造成瓶颈,使用流水线后就可以大幅度地在需要执行很多命令时提升 Redis的性能。
下面使用Redis流水线技术测试10万次读写功能
@RequestMapping("/pipeline")
@ResponseBody
public Long pipeline(){
long start = System.currentTimeMillis();
List list = (List)redisTemplate.executePipelined(new SessionCallback() {
@Override
public Object execute(RedisOperations redisOperations) throws DataAccessException {
for(int i=0;i<=100000;i++){
redisOperations.opsForValue().set("pipeline_"+i,"value_"+i);
String value = (String) redisOperations.opsForValue().get("pipeline_"+i);
if(i == 100000){
System.out.println("命令只是进入队列,所有值为空 = "+value);
}
}
return null;
}
});
long end = System.currentTimeMillis();
long cost = end - start;
System.out.println("耗时:"+cost+" 毫秒");
return cost;
}
为了测试性能,这里记录了开始执行时间和结束执行时间,并且打出了耗时。在我的测试中,这10万次读写基本在300600ms,大约平均值在400500ms,也就是不到s就能执行10万次读和写命令,这个速度还是十分快的。在使用非流水线的情况下,我的测试大约每秒只能执行2万~3万条命令,可见使用流水线后可以提升大约10倍的速度,它十分适合大数据量的执行。
6.4.3 Redis发布订阅
发布订阅是消息的一种常用模式。例如,在企业分配任务之后,可以通过邮件、短信或者微信通知到相关的责任人,这就是一种典型的发布订阅模式。首先是 Redis提供一个渠道,让消息能够发送到这个渠道上,而多个系统可以监听这个渠道,如短信、微信和邮件系统都可以监听这个渠道,当一条消息发送到渠道,渠道就会通知它的监听者,这样短信、微信和邮件系统就能够得到这个渠道给它们的消息了,这些监听者会根据自己的需要去处理这个消息,于是我们就可以得到各种各样的通知了。
为了接收 Redis渠道发送过来的消息,我们先定义一个消息监听器( Messagelistener)
@Configuration
public class RedisMessageListenerConfig {
@Autowired
private RedisTemplate redisTemplate;
@Autowired
private RedisConnectionFactory connectionFactory;
@Autowired
private RedisMessageListener redisMessageListener;
private ThreadPoolTaskExecutor taskExecutor;
@Bean
public RedisMessageListenerContainer redisMessageListenerContainer(){
RedisMessageListenerContainer container = new RedisMessageListenerContainer();
container.setConnectionFactory(connectionFactory);
container.setTaskExecutor(threadPoolTaskExecutor());
Topic topic = new ChannelTopic("topic1");
container.addMessageListener(redisMessageListener, topic);
return container;
}
@Bean
public ThreadPoolTaskExecutor threadPoolTaskExecutor(){
if(taskExecutor != null){
return taskExecutor;
}
taskExecutor = new ThreadPoolTaskExecutor();
taskExecutor.setCorePoolSize(5);
return taskExecutor;
}
}
这里 RedisTemplate和 RedisConnection Factory对象都是 Spring Boot自动创建的,所以这里只是把它们注入进来,只需要使用@ Autowired注解即可。然后定义了一个任务池,并设置了任务池大小为20,这样它将可以运行线程,并进行阻塞,等待 Redis消息的传入。接着再定义了一个 Redis消息监听的容器 RedisMessageListenerContainer,并且往容器设置了 Redis连接工厂和指定运行消息的线程池,定义了接收“ topicI”渠道的消息,这样系统就可以监听 Redis关于“ topic1”渠道的消息了。启用 Spring Boot项目后,在 Redis的客户端输入命令:
publish topic msg
在 Spring中,我们也可以使用 Redis Template来发送消息,例如:
redisTemplate.convertAndSend(channel, message);
其中, channel代表渠道, message代表消息,这样就能够得到 Redis发送过来的消息了。
6.4.4 使用Lua脚本
Redis中有很多的命令,但是严格来说 Redis提供的计算能力还是比较有限的。为了增强 Redis的计算能力, Redis在2.6版本后提供了Lua脚本的支持,而且执行Lua脚本在 Redis中还具备原子性,所以在需要保证数据一致性的高并发环境中,我们也可以使用 Redis的Lua语言来保证数据的致性,且Lua脚本具备更加强大的运算功能,在高并发需要保证数据一致性时,Lua脚本方案比使用Redis自身提供的事务要更好一些。
在 Redis中有两种运行Lua的方法,一种是直接发送Lua到 Redis服务器去执行,另一种是先把Lua发送给 Redis, Redis会对Lua脚本进行缓存,然后返回一个SHA1的32位编码回来,之后只需要发送SHA1和相关参数给 Redis便可以执行了。这里需要解释的是为什么会存在通过32位编码执行的方法。如果Lua脚本很长,那么就需要通过网络传递脚本给 Redis去执行了,而现实的情况是网络的传递速度往往跟不上 Redis的执行速度,所以网络就会成为 Redis执行的瓶颈。如果只是传递32位编码和参数,那么需要传递的消息就少了许多,这样就可以极大地减少网络传输的内容,从而提供系统性能。
为了支持 Redis的Lua脚本, Spring提供了 Redis Script接口,与此同时也有一个 DefaultRedis Script
实现类。让我们先来看看 Redis script接口的源码
public interface RedisScript<T> {
//获取脚本的SHA1
String getSha1();
//获取脚本的返回值类型
@Nullable
Class<T> getResultType();
//获取脚本的字符串内容
String getScriptAsString();
}
这里 Spring会将Lua脚本发送到 Redis服务器进行缓存,而此时 Redis服务器会返回一个32位的SHA编码,这时候通过 geiSha方法就可以得到 Redis返回的这个编码了; getResult Type方法是获取Lua脚本返回的Java类型; getScriptAs String是返回脚本的字符串,以便我们观看脚本。下面我们采用 Redis Script接口执行一个十分简单的Lua脚本,这个脚本只是简单地返回一个字符串
这里的代码,首先Lua只是定义了一个简单的字符串,然后就返回了,而返回类型则定义为字符串。这里必须定义返回类型,否则对于 Spring不会把脚本执行的结果返回。接着获取了由RedisTemplate自动创建的字符串序列化器,而后使用 Redis Template的 execute方法执行了脚本。在Redis Template中, execute方法执行脚本的方法有两种,其定义如下:
6.5 使用Spring缓存注解操作Redis
略
来源:CSDN
作者:雷建方
链接:https://blog.csdn.net/u012184539/article/details/103887740