keys模糊查询遇到性能问题redis cup 99%以及解决方案
之前写过一篇文章 《java redis通过key模糊删除,批量删除,批量查询相关数据》,在项目中我也是这样使用的。刚开始还没有什么问题,后来数据多了,服务器redis cup 就飙升到98%-99%,才发现redis通过key模糊批量查询非常耗性能。 《java redis通过key模糊删除,批量删除,批量查询相关数据》访问量很大,为了后面的同学不再掉入坑中就写了这篇文章作为补充
替代方案:
1.multiGet 数据结构为string类型的,使用RedisTemplate的multiGet方法;
2.pipeline 数据结构为hash,使用Pipeline(管道),组合命令,批量操作redis
测试代码
/**
* @author 猿份哥
* @description
* @createTime 2019/8/01 20:35
*/
@SpringBootTest
@RunWith(SpringRunner.class)
public class RedisPipelineServiceTest {
@Autowired
private RedisPipelineService pipelineService;
@Autowired
private StringRedisTemplate stringRedisTemplate;
private String pattern="yuanfenge:test:";
@Test
public void batchGet() throws Exception {
batchInsert();
redisPipelineGet();
//generalBatchGet();
redisStringBatchGet();
}
public void batchInsert() throws Exception {
long stime=System.currentTimeMillis();
List<Map<String, String>> saveList=new ArrayList<Map<String, String>>();
for (int i = 0; i < 10000; i++) {
Map<String,String> map=new HashMap<>();
map.put("key",pattern+i);
map.put("value","value值为"+i);
saveList.add(map);
}
pipelineService.batchInsert(saveList, TimeUnit.MINUTES,15);
long etime=System.currentTimeMillis();
System.out.println("插入10000条数据消耗时间为:"+(etime-stime));
}
private void redisPipelineGet() {
long stime=System.currentTimeMillis();
Set<String> keys = stringRedisTemplate.keys(pattern + "*");
List<String> keyList=new ArrayList<>();
keys.forEach(i->{
keyList.add(i);
});
List<String> strings = pipelineService.batchGet(keyList);
long etime=System.currentTimeMillis();
System.out.println("string="+strings);
System.out.println("使用Pipelined消耗时间为:"+(etime-stime));
}
public void redisStringBatchGet() {
long stime=System.currentTimeMillis();
Set<String> keys = stringRedisTemplate.keys(pattern + "*");
List<String> keyList=new ArrayList<>();
keys.forEach(i->{
keyList.add(i);
});
List<String> strings = stringRedisTemplate.opsForValue().multiGet(keyList);
long etime=System.currentTimeMillis();
System.out.println("string="+strings);
System.out.println("使用multiGet消耗时间为:"+(etime-stime));
}
public void generalBatchGet() {
long stime=System.currentTimeMillis();
Set<String> keys = stringRedisTemplate.keys(pattern + "*");
List<String> keyList=new ArrayList<>();
keys.forEach(i->{
keyList.add(i);
});
List<String> strings=new ArrayList<>();
for (String key : keyList) {
String value = stringRedisTemplate.opsForValue().get(key);
strings.add(value);
}
long etime=System.currentTimeMillis();
System.out.println("string="+strings);
System.out.println("消耗时间为:"+(etime-stime));
}
}
首先:调用batchInsert方法插入10000条测试数据,然后使用redisPipelineGet批量获取数据打印,再调用redisStringBatchGet方法批量获取数据打印(之前用的方法generalBatchGet我注释了太慢了运行太久有兴趣的可以打开注释试试)
贴出RedisPipeline代码
/**
* @author 猿份哥
* @description
* @createTime 2019/8/01 21:00
*/
@Service
public class RedisPipelineService {
@Autowired
StringRedisTemplate redisTemplate;
public void batchInsert(List<Map<String, String>> saveList, TimeUnit unit, int timeout) {
/* 插入多条数据 */
redisTemplate.executePipelined(new SessionCallback<Object>() {
@Override
public <K, V> Object execute(RedisOperations<K, V> redisOperations) throws DataAccessException {
for (Map<String, String> needSave : saveList) {
redisTemplate.opsForValue().set(needSave.get("key"), needSave.get("value"), timeout,unit);
}
return null;
}
});
}
public List<String> batchGet(List<String> keyList) {
/* 批量获取多条数据 */
List<Object> objects = redisTemplate.executePipelined(new RedisCallback<String>() {
@Override
public String doInRedis(RedisConnection redisConnection) throws DataAccessException {
StringRedisConnection stringRedisConnection = (StringRedisConnection) redisConnection;
for (String key : keyList) {
stringRedisConnection.get(key);
}
return null;
}
});
List<String> collect = objects.stream().map(val -> String.valueOf(val)).collect(Collectors.toList());
return collect;
}
}
效果
大家可以看出在极短的时间就得到并打印了10000条数据。
最后感谢这位好心分享者的文章https://my.oschina.net/u/3266761/blog/3023454,他写的很详细推荐大家阅读
其他关于redis一些小建议
个人观点:造成cpu过高一般原因是因为大量数据频繁的读写。积累了过多的垃圾数据。所以可以从这些方面优化
1.优化读取方法
如果有大量或模糊查询使用multiGet或pipeline替代
2.不要将数据一直持久化到redis,造成大量的数据累积。
用完就清除数据,最好设置一个过期时间
3.禁止大string
核心集群禁用1mb的string大key(虽然redis支持512MB大小的string),如果1mb的key每秒重复写入10次,就会导致写入网络IO达10MB;对于必须要存储的大文本数据一定要压缩后存储
最后不管对你有没有用,有心的我还是为你准备了一份代码,万一您想去看看呢!哈哈哈
https://github.com/tiankonglanlande/springboot/tree/master/springboot-redis-pipeline
本文章原文链接:https://www.lskyf.com/post/editing?id=68 猿份哥欢迎你(^_^)
注意:本文归作者所有,未经作者允许,不得转载