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指令

Java中使用RedisTemplate实现Redis加写锁的步骤如下: 1. 创建RedisTemplate对象 RedisTemplate是Spring Data Redis提供的一个基于Jedis的Redis客户端。首先需要创建一个RedisTemplate对象,用于连接Redis服务器。可以使用以下代码创建一个RedisTemplate对象: ``` @Autowired private RedisTemplate<String, Object> redisTemplate; ``` 2. 获取锁 获取锁的过程可以采用Redis的setnx命令,该命令可以保证在多线程情况下只有一个线程能够获取到锁。如果当前锁已经被其他线程占用,则当前线程会等待一段时间后再次尝试获取锁,直到成功为止。可以使用以下代码实现锁的获取: ``` public boolean lock(String key, String value, long expireTime) { boolean result = redisTemplate.opsForValue().setIfAbsent(key, value); if (result) { redisTemplate.expire(key, expireTime, TimeUnit.SECONDS); } return result; } ``` 其中,key表示锁的名称,value表示锁的值,expireTime表示锁的过期时间。 3. 释放锁 释放锁的过程可以采用Redis的del命令,该命令可以将锁从Redis中删除。可以使用以下代码实现锁的释放: ``` public void unlock(String key, String value) { Object currentValue = redisTemplate.opsForValue().get(key); if (currentValue != null && currentValue.equals(value)) { redisTemplate.delete(key); } } ``` 其中,key表示锁的名称,value表示锁的值。 完整代码如下: ``` @Component public class RedisLock { @Autowired private RedisTemplate<String, Object> redisTemplate; public boolean lock(String key, String value, long expireTime) { boolean result = redisTemplate.opsForValue().setIfAbsent(key, value); if (result) { redisTemplate.expire(key, expireTime, TimeUnit.SECONDS); } return result; } public void unlock(String key, String value) { Object currentValue = redisTemplate.opsForValue().get(key); if (currentValue != null && currentValue.equals(value)) { redisTemplate.delete(key); } } } ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值