在Java中使用redisTemplate操作缓存
摘要: 背景 在最近的项目中,有一个需求是对一个很大的数据库进行查询,数据量大概在几千万条。但同时对查询速度的要求也比较高。 这个数据库之前在没有使用Presto的情况下,使用的是Hive,使用Hive进行一个简单的查询,速度可能在几分钟。
背景
在最近的项目中,有一个需求是对一个很大的数据库进行查询,数据量大概在几千万条。但同时对查询速度的要求也比较高。
这个数据库之前在没有使用Presto的情况下,使用的是Hive,使用Hive进行一个简单的查询,速度可能在几分钟。当然几分钟也并不完全是跑SQL的时间,这里面包含发请求,查询数据并且返回数据的时间的总和。但是即使这样,这样的速度明显不能满足交互式的查询需求。
我们的下一个解决方案就是Presto,在使用了Presto之后,查询速度降到了秒级。但是对于一个前端查询界面的交互式查询来说,十几秒仍然是一个不能接受的时间。
虽然Presto相比Hive已经快了很多(FaceBook官方宣称的是10倍),但是对分页的支持不是很友好。我在使用的时候是自己在后端实现的分页。
在这种情况下应用缓存实属无奈之举。讲道理,优化应从底层开始,自底而上。上层优化的方式和效率感觉都很有局限。
为什么要使用缓存
前端查询中,单次查询的匹配数据量有可能会达到上百甚至上千条,在前端中肯定是需要分页展示的。就算每次查询10条数据,整个查询也要耗时6-8s的时间。想象一下,每翻一页等10s的场景。
所以,此时使用redis缓存。减少请求数据库的次数。将匹配的数据一并存入数据库。这样只有在第一次查询时耗费长一点,一旦查询完成,用户点击下一页就是毫秒级别的操作了。
使用redisTemplate
Spring封装了一个比较强大的模板,也就是redisTemplate,方便在开发的时候操作Redis缓存。在Redis中可以存储String、List、Set、Hash、Zset。下面将针对List和Hash分别介绍。
List
Redis中的List为简单的字符串列表,常见的有下面几种操作。
hasKey
判断一个键是否存在,只需要调用hasKey就可以了。假设这个Key是test,具体用法如下。
[Java] 纯文本查看 复制代码
1
2
3
4
5
|
if (redisTemplate.hasKey( "test" )) {
System.out.println( "存在" );
} else {
System.out.println( "不存在" );
}
|
range
该函数用于从redis缓存中获取指定区间的数据。具体用法如下。
[Java] 纯文本查看 复制代码
01
02
03
04
05
06
07
08
09
10
11
|
if (redisTemplate.hasKey( "test" )) {
// 该键的值为 [4, 3, 2, 1]
System.out.println(redisTemplate.opsForList().range( "test" , 0 , 0 )); // [4]
System.out.println(redisTemplate.opsForList().range( "test" , 0 , 1 )); // [4, 3]
System.out.println(redisTemplate.opsForList().range( "test" , 0 , 2 )); // [4, 3, 2]
System.out.println(redisTemplate.opsForList().range( "test" , 0 , 3 )); // [4, 3, 2, 1]
System.out.println(redisTemplate.opsForList().range( "test" , 0 , 4 )); // [4, 3, 2, 1]
System.out.println(redisTemplate.opsForList().range( "test" , 0 , 5 )); // [4, 3, 2, 1]
System.out.println(redisTemplate.opsForList().range( "test" , 0 , - 1 )); // [4, 3, 2, 1] 如果结束位是-1, 则表示取所有的值
}
|
delete
删除某个键。
[Java] 纯文本查看 复制代码
01
02
03
04
05
06
07
08
09
10
|
List<String> test = new ArrayList<>();
test.add( "1" );
test.add( "2" );
test.add( "3" );
test.add( "4" );
redisTemplate.opsForList().rightPushAll( "test" , test);
System.out.println(redisTemplate.opsForList().range( "test" , 0 , - 1 )); // [1, 2, 3, 4]
redisTemplate.delete( "test" );
System.out.println(redisTemplate.opsForList().range( "test" , 0 , - 1 )); // []
|
size
获取该键的集合长度。
[Java] 纯文本查看 复制代码
1
2
3
4
5
6
7
8
|
List<String> test = new ArrayList<>();
test.add( "1" );
test.add( "2" );
test.add( "3" );
test.add( "4" );
redisTemplate.opsForList().rightPushAll( "test" , test);
System.out.println(redisTemplate.opsForList().size( "test" )); // 4
|
用法如下
[Java] 纯文本查看 复制代码
1
2
3
4
5
|
for ( int i = 0 ; i < 4 ; i++) {
Integer value = i + 1 ;
redisTemplate.opsForList().leftPush( "test" , value.toString());
System.out.println(redisTemplate.opsForList().range( "test" , 0 , - 1 ));
}
|
控制台输出的结果如下。
[Java] 纯文本查看 复制代码
1
2
3
4
|
[ 1 ]
[ 2 , 1 ]
[ 3 , 2 , 1 ]
[ 4 , 3 , 2 , 1 ]
|
rightPushAll
同rightPush,一次性将List存入。
[Java] 纯文本查看 复制代码
1
2
3
4
5
6
7
|
List<String> test = new ArrayList<>();
test.add( "1" );
test.add( "2" );
test.add( "3" );
test.add( "4" );
redisTemplate.opsForList().rightPushAll( "test" , test);
System.out.println(redisTemplate.opsForList().range( "test" , 0 , - 1 )); // [1, 2, 3, 4]
|
remove
用于移除键中指定的元素。接受3个参数,分别是缓存的键名,计数事件,要移除的值。计数事件可以传入的有三个值,分别是-1、0、1。
-1代表从存储容器的最右边开始,删除一个与要移除的值匹配的数据;0代表删除所有与传入值匹配的数据;1代表从存储容器的最左边开始,删除一个与要移除的值匹配的数据。
[Java] 纯文本查看 复制代码
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
|
List<String> test = new ArrayList<>();
test.add( "1" );
test.add( "2" );
test.add( "3" );
test.add( "4" );
test.add( "4" );
test.add( "3" );
test.add( "2" );
test.add( "1" );
redisTemplate.opsForList().rightPushAll( "test" , test); // [1, 2, 3, 4, 4, 3, 2, 1]
// 当计数事件是-1、传入值是1时
redisTemplate.opsForList().remove( "test" , - 1 , "1" ); // [1, 2, 3, 4, 4, 3, 2]
// 当计数事件是1,传入值是1时
redisTemplate.opsForList().remove( "test" , 1 , "1" ); // [2, 3, 4, 4, 3, 2]
// 当计数事件是0,传入值是4时
redisTemplate.opsForList().remove( "test" , 0 , "4" ); // [2, 3, 3, 2]
|
引入redisTemplate
如果大家看懂了怎么用,就可以将redisTemplate引入项目中了。
引入pom依赖
[XML] 纯文本查看 复制代码
1
2
3
4
5
|
< dependency >
< groupId >org.springframework.boot</ groupId >
< artifactId >spring-boot-starter-data-redis</ artifactId >
< version >2.0.5.RELEASE</ version >
</ dependency >
|
新建配置文件
然后需要新建一个RedisConfig配置文件。
[Java] 纯文本查看 复制代码
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
|
package com.detectivehlh;
import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
/**
* RedisConfig
*
* @author Lunhao Hu
* @date 2019-01-17 15:12
**/
@Configuration
public class RedisConfig {
@Bean
public RedisTemplate<String, String> redisTemplate(RedisConnectionFactory factory) {
ObjectMapper om = new ObjectMapper();
om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
//redis序列化
Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object. class );
jackson2JsonRedisSerializer.setObjectMapper(om);
StringRedisTemplate template = new StringRedisTemplate(factory);
template.setValueSerializer(jackson2JsonRedisSerializer);
template.setHashKeySerializer(jackson2JsonRedisSerializer);
template.setHashValueSerializer(jackson2JsonRedisSerializer);
template.setValueSerializer(jackson2JsonRedisSerializer);
template.afterPropertiesSet();
return template;
}
}
|
注入
将redisTemplate注入到需要使用的地方。
[Java] 纯文本查看 复制代码
1
2
|
@Autowired
private RedisTemplate redisTemplate;
|