1. 背景
一个功能需要一次性解析大量数据放到 Redis 中缓存,并且每个 key 都需要设置过期时间,但是 Redis 本身命令不支持批量设置过期时间,RedisTemplate 中也没有相关的方法。
2. 实现方式
1. RedisTemplate 使用 redisTemplate.opsForValue().multiSet() 来实现批量插入数据
2. RedisTemplate使用PipeLine管道命令来批量修改过期时间
3. 代码
package com.insupro.flexibleServerJ.service.impl;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisCallback;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.util.StopWatch;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeUnit;
/**
* @author luoqifeng
*/
@Slf4j
@RestController
@RequestMapping("/api")
public class RedisDemo002 {
@Autowired
private RedisTemplate<String, String> redisTemplate;
@RequestMapping("testRedisMultiSet")
public Object testRedisMultiSet() {
Map<String, String> keysAndValues = new HashMap<>();
// 模拟数据
for (int i=0; i < 100; i++){
String redisKey = "testKey_"+i;
String value = "testValue_"+i;
keysAndValues.put(redisKey, value);
}
if (!keysAndValues.isEmpty()){
StopWatch stopWatch = new StopWatch();
stopWatch.start("正常遍历上传计时");
for (String key: keysAndValues.keySet()){
redisTemplate.opsForValue().set(key, keysAndValues.get(key), 20, TimeUnit.SECONDS);
}
stopWatch.stop();
// 以下是批量上传方式
stopWatch.start("批量上传redis计时");
redisTemplate.opsForValue().multiSet(keysAndValues);
// 设置过期值
stopWatch.stop();
stopWatch.start("批量设置过期时间");
redisTemplate.executePipelined((RedisCallback<Object>) connection -> {
keysAndValues.forEach((key, vaul) -> {
connection.expire(key.getBytes(), 20);
});
return null;
});
stopWatch.stop();
log.info("执行耗时:{}", stopWatch.prettyPrint());
}
return null;
}
}
执行代码结果:
可以看到,普通遍历上传因为每次都需要发起一次请求,造成多次请求消耗,从而占用大量时间,而使用批量上传可以通过PipeLine管道命令来设置过期时间,只会有两次请求,时间上大幅度提升。
疑问:为什么不直接使用管道上传并设置过期时间呢?
回答: 当然可以,我只是做个演示,具体实现需要根据业务需求做相应的操作。
例如:通过管道实现简单限流思想逻辑
//限流器
public void currentLimiter(){
List<Object> objects = redisTemplate.executePipelined(new RedisCallback<Long>() {
@Override
public Long doInRedis(RedisConnection connection) throws DataAccessException {
connection.openPipeline();
//key
String key = "userId:mucontroller:save";
//当前时间
Long time = System.currentTimeMillis();
//限定1min中内的请求
Long period = 1L;
//请求记录
connection.zSetCommands().zAdd(key.getBytes(), time, key.getBytes());
//删除1min之外的请求
connection.zSetCommands().zRemRangeByScore(key.getBytes(), 0.0, Double.valueOf(time - period * 1000));
//统计1min内的记录次数
Long size = connection.zSetCommands().zCard(key.getBytes());
//设置过期时间
connection.expire(key.getBytes(), period);
if(size > 3){
return 0L;
} else {
return 1L;
}
}
});
}
end
以上都是实例,需要根据自行业务逻辑进行修改封装,以确保程序的健壮性