Java中用redisTemplate实现redis的scan命令

业务场景

碰到一个业务场景,一个预约系统,前面大的访问量都被大佬的组件拦住,然后暂存到redis中,然后我再找个时间段去将redis中的数据取出,持久化到数据库中

思路分析

对以上问题进行初步简化,即从redis中获取大批量数据,引申出来的问题就是,如何保障大批量数据稳定取出并保存,如果一次性取出,有可能内存溢出,用时太长时遇到网络抖动会丢失数据等等。

首先想到的当然是分治,就是取一批数据异步存入数据库的同时,再去执行下次相同操作,即使某批数据出错,影响也可在可控范围内。

其实需求一有,我大佬就告诉我用redis中的scan命令来实现,该命令借用runoob菜鸟教程中的说法就是:

SCAN 命令是一个基于游标的迭代器,每次被调用之后, 都会向用户返回一个新的游标, 用户在下次迭代时需要使用这个新游标作为 SCAN 命令的游标参数, 以此来延续之前的迭代过程。

SCAN 返回一个包含两个元素的数组, 第一个元素是用于进行下一次迭代的新游标, 而第二个元素则是一个数组, 这个数组中包含了所有被迭代的元素。如果新游标返回 0 表示迭代已结束。

简而言之,scan命令就一个游标迭代器,下次迭代会根据上次得到的游标值继续进行。 借助sacn该特性,就可以做到分批在redis中拉取数据了。

代码实现

    public void getDataFromRedisAndSaveToDb()
    {
    	// 根据机器能力开启线程池
        ThreadFactory springThreadFactory = new CustomizableThreadFactory("redis pull n save pool-Thread-");
        ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(CPU_NUM, CPU_NUM * 2, 10, TimeUnit.SECONDS, new ArrayBlockingQueue<>(CPU_NUM * 3), springThreadFactory);
        List execList;
        String count = "1000";
        String cursorId = "0";
        // redis命令
        RedisScript<List> redisScript = RedisScript.of("return redis.call('scan',KEYS[1],'count',ARGV[1])", List.class);
        RedisSerializer serializer = redisTemplate.getStringSerializer();
        List<Object> valueList;
        do {
     		//执行
            execList = redisTemplate.execute(redisScript, serializer, serializer, Collections.singletonList(cursorId), count);
            // 返回值的1)表示下次要开始的游标位置,该游标仅在redis内部有参考价值,返回值2)表示满足正则表达式的key值集合
            assert execList != null;
            cursorId = String.valueOf(execList.get(0));
            //id的集合
            List<String> keyList = (List<String>) execList.get(1);
            // 游标值返回0,表示 整个数据集(collection)已经被完整遍历过了,称这个过程为一次完整遍历(full iteration)
            //scan 命令有重复风险,借助set去重,获取到key后再批量获取value
            valueList = redisTemplate.opsForValue().multiGet(new HashSet<>(keyList));
            //todo 此处开线程,多次将valueList存入数据库
        }
        //该处执行次数与分块大小有关
        while (!"0".equals(cursorId));
    }

注意事项

  1. scan命令返回的游标值cursorId,并不像在有序数组中的某个有意义地址,它只在redis内部有参考价值
  2. 每次遍历回来的key值数据,有可能存在重复

思路拓展

未完待续

参考资料

scan命令的深入了解:scan-redis命令参考
更多样的Java对scan命令的实现:在RedisTemplate中使用scan代替keys指令

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值