spring RedisTemplate set
场景:
数据库中有百万条酒店记录,我需要从上游全量同步一次结果,需要找出以下三种数据
- 新增:上游有,本地没有
- 修改:上游和本地都有
- 删除:本地有,上游没有
思路:
1.把数据库中百万条酒店的iD,同步到Redis的set 中
2.批量处理上游数据,比如一次处理100条上游记录,然后判断出这100个酒店ID是否在Redis 中从而区分出 哪些新增,哪些变更
3.把第二步中的100个酒店ID 从set 中移除出去
4.处理完上游,Redis的set 中剩余的酒店ID,就是需要删除的记录,处理set中剩余酒店数据,处理完后删除 set
问题:
1 如何批量的判断100条记录是否在Redis的set 中
2 针对最后Redis 中需要删除的数据,如何快速的遍历
方案:
常规方案
- 针对问题1:
一条条遍历 redisTemplate.opsForSet().isMember
问题:如果有100万条记录,会很耗时,如果1次查询1ms,那么100万次耗时:1 000 s - 针对问题2:
一条条移除:redisTemplate.opsForSet().pop()
好处:遍历一次,并且实现了删除
坏处:耗时很长,而且需要频繁的删除
我的解决方案
问题一:
批量操作,如果我一次能批量操作100个记录,一次耗时3ms,那么100万次耗时:30s ,效率提高了将近几十倍,
而Redis 中没有提供批量判断哪些元素在set 中
怎么办?
借助交集实现
1.把这100个记录放在一个新的set 中,确保key 不会和数据库中重复,把100个记录放入set中
2.计算 这两个set的交集:变相的得到了批量判断元素是否在该集合中
3.删除第一步的set
问题二:
方案1:想到批量能加快速度,那么能一次从set 中获得多个数据吗?
redisTemplate.opsForSet().members 能实现 但是,如果数据量很大会有问题,如果超过了本地 新生代Survivor的容量一半,会提前让大对象进入老年代,增加full gc的概率,此方法不可取
方案2:还有哪个方法可以批量返回数据?
看看接口redisTemplate.opsForSet().randomMembers,眼前一亮,
想到了一个骚操作:借助次方法批量返回,处理完以后在删除这些经过,一次返回200个随机数,经实测效率比一次次spop效率高了很多
问题:极端情况下只有一个元素,生成200个随机数,会有很大的重复,影响性能
方案三:redisTemplate.opsForSet().scan
采用游标,一次次处理,遍历完成以后,在删除del
public<T> void sscan(String key,int count, Consumer<T> consumer){
try {
Cursor<T> cursor = (Cursor<T>) redisTemplate.opsForSet().scan(key, ScanOptions.scanOptions().match("*").count(count).build());
while (cursor.hasNext()){
consumer.accept(cursor.next());
}
cursor.close();
}catch (Exception e){
log.info("scan key:{},异常",key,e);
throw new BaseException("遍历Redis key:{}异常,",key,e);
}
}
经测试针对10万条记录,取出来耗时在2s左右