方案思路
之前重置玩家每日积分3次是采用redis来实现的,每位玩家在进行重置积分操作时,就会进行存储reids的键值对,key时拼接玩家uid的字符串,value则是每日次数,在set时是设置24小时也就是1天过期。
基于此方案上,我想了想那么就使用定时任务+批量删除redis前缀key来实现比较合适。
其中的批量删除redis的前缀key有两种方案实现:
直接使用keys命令来进行匹配所有的key,然后进行批量delete删除(不推荐)。【问题:数据量达到几百万,keys这个指令就会导致 Redis 服务卡顿,因为 Redis 是单线程程序,顺序执行所有指令,其它指令必须等到当前的 keys 指令执行完了才可以继续 。】
使用scan渐进式遍历获取到所有的key,实现keys的效果,需要进行多次scan**(推荐)**。【好处:采用渐进式遍历的方式来解决keys命令可能带来的阻塞问题,每次scan命令的时间复杂度是 O(1) ,但是要真正实现keys的功能,需要执行多次scan。】
redis+lua脚本(暂未使用)。
当前实现方案中就是使用的scan渐进式遍历来进行获取。
实现方案
批量删除redis的key
关于SpringBoot如何集成Redis见:SpringBoot整合篇 04、Springboot整合Redis
在下面代码中我们使用到了MultiKeyCommands,这个类是jedis里的,那么我们就需要将redis启动器中的lettuce替换为jedis,如下来进行排除依赖并添加jedis依赖:
<!--redis配置--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> <exclusions> <exclusion> <groupId>io.lettuce</groupId> <artifactId>lettuce-core</artifactId> </exclusion> </exclusions> </dependency> <!-- 排除lettuce,使用jedis --> <dependency> <groupId>redis.clients</groupId> <artifactId>jedis</artifactId> <scope>compile</scope> </dependency>
下面scan方法已经封装好了,使用方式:调用方法,直接传入要匹配的前缀key即可使用scan返回所有匹配到的key。
一轮是获取1000个的。 /** * redis工具类 */ @SuppressWarnings(value = { "unchecked", "rawtypes" }) @Component public class RedisCache { /** * * @param key 要匹配的key前缀 * @return 匹配到的批量key值 */ public Set<String> scan(String key) { return (Set<String>) redisTemplate.execute((RedisCallback<Set<String>>) connection -> { HashSet<String> keys = new HashSet<>(); JedisCommands commands = (JedisCommands) connection.getNativeConnection(); MultiKeyCommands multiKeyCommands = (MultiKeyCommands) commands; //组装scan请求参数(匹配内容+请求数量) ScanParams scanParams = new ScanParams(); scanParams.match("*" + key + "*"); scanParams.count(1000); //执行scan命令(批量去获取) ScanResult<String> scan = multiKeyCommands.scan("0", scanParams); while (scan.getCursor() != null) { keys.addAll(scan.getResult()); if ("0".equals(scan.getCursor())) { break; } scan = multiKeyCommands.scan(scan.getCursor(), scanParams); } return keys; }); } }
定时任务
定时任务使用Spring给我们提供的,按照如下进行配置即可:
1、在启动器上开启定时任务注解
@EnableScheduling //开启定时任务
2、注册Bean对象,在指定的方法上添加定时任务注解
对于定时任务的cron表示式可用在线工具生成:cron表达式生成工具
生成的是7位,而@Scheduled中的cron要求是6位,我们只需要把最后一位表示年的去年即可。
package com.chuangmeng.horserace.tasks;
import com.chuangmeng.horserace.constant.RedisKeyConstant; import com.chuangmeng.horserace.utils.RedisCache; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.scheduling.annotation.Scheduled; import org.springframework.stereotype.Component; import org.springframework.util.ObjectUtils; import java.util.Set; @Component @Slf4j public class PlayerTask { @Autowired private RedisCache redisCache; /** * 每天晚上12点重置玩家积分 */ @Scheduled(cron = "0 0 0 * * ?") public void resetPlayerIntegral() { String key = "xxx"; Set<String> keys = redisCache.scan(key); log.info("相关重置积分的key为:" + keys); if (!ObjectUtils.isEmpty(keys)) { redisCache.deleteObject(keys); log.info("重置玩家积分完成!"); } } }
完成重置操作也很简单,就是调用下scan()方法,然后拿到所有的key,执行删除命令就好。
测试
使用redis手动来去进行匹配前缀key:
# 方式一:使用keys来进行匹配 keys "chuangmeng:horserace:resetimtegral:*" # 方式二:使用scan来进行渐进式获取,第一次设置游标为0,会响应过来两个数据,一个是下一次你要进行遍历开始的游标,以及一组匹配的数据。若是下一组游标为0,那么就无需进行scan获取了,已经获取了全部 scan 0 match "chuangmeng:horserace:resetimtegral:*" scan 17 match "chuangmeng:horserace:resetimtegral:*" # 删除key del chuangmeng:horserace:resetimtegral:3334322232 chuangmeng:horserace:resetimtegral:12364322232
对于程序的话我们测试可以设置个1分钟定时任务或者直接在test中调用方法测试即可!