SpringBoot系列12-redis-pipeline keys模糊查询替代方案

猿份哥 24天前 ⋅ 189 阅读

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;
    }
}

效果

图片1.png

大家可以看出在极短的时间就得到并打印了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 猿份哥欢迎你(^_^)


全部评论: 0

    我有话说: