废弃原因:使用redis缓存失效监听会有一定的延时,dev环境下延时已经达到90s左右,线上可能更甚,所以必须更换方案。
(基本上,expired事件是在Redis服务器删除键的时候生成的,而不是在理论上生存时间达到零值时生成的。)
可参考文章:
https://blog.csdn.net/a13935302660/article/details/121285975
http://www.redis.cn/topics/notifications.html
技术选型1.定时任务。-》因为游戏的开始时间和结束时间不确定,所以定时任务不可以用。
2. 消息中间件。-》公司目前使用的消息中间件是rocketmq,在rocketmq官网找到rocketmq目前仅支持指定时间片轮转。所以也不能使用mq实现定时功能。
3. redis缓存失效监听。-》利用redis提供的特性,key失效之后可以通知客户端对应的失效key值,将对应的信息放入key,对key进行过滤,实现自动开始结束游戏。(已废弃)
流程图 代码redis监听过期key的配置如下:
@Configuration
public class RedisListenerConfig {
@Bean
RedisMessageListenerContainer container(@Qualifier("getJedisConnectionFactory") RedisConnectionFactory connectionFactory) {
RedisMessageListenerContainer container = new RedisMessageListenerContainer();
container.setConnectionFactory(connectionFactory);
return container;
}
} |
对应redis监听key过期的处理器逻辑如下:
@Slf4j
@Component
public class RedisKeyExpirationListener extends KeyExpirationEventMessageListener {
private final LockService lockService;
private final GmGameService gameService;
private final GameCommonService gameCommonService;
public static final String SEPARATOR_CHARS = ":";
public RedisKeyExpirationListener(RedisMessageListenerContainer listenerContainer, LockService lockService,
GmGameService gameService, GameCommonService gameCommonService) {
super(listenerContainer);
this.lockService = lockService;
this.gameService = gameService;
this.gameCommonService = gameCommonService;
}
@Override
public void onMessage(Message message, byte[] pattern) {
// 获取到失效的 key,进行取消订单业务处理
String expiredKey = message.toString();
if (expiredKey.startsWith(GAME_START_PREFIX)) {
//到达游戏开始时间了,更新一波游戏状态
String[] keyArr = StringUtils.split(expiredKey, SEPARATOR_CHARS);
long gameId = Long.parseLong(keyArr[keyArr.length-1]);
log.debug("game {} is starting.", gameId);
try (RLockSupport locker = RLockSupport.ofLocker(lockService)){
locker.acquire(String.format(GAME_START_LOCK, gameId), INT_5, TimeUnit.SECONDS);
GmGame game = gameService.getById(gameId);
game.setUpdateTime(LocalDateTime.now());
game.setUpdator(StringUtils.EMPTY);
game.setGameStatus(GameStatusEnum.STARTED.getStatus());
gameService.updateById(game);
locker.unlock();
}
log.debug("game {} is started.", gameId);
} else if (expiredKey.startsWith(GAME_FINISH_PREFIX)) {
//到达游戏开始时间了,更新一波游戏状态
String[] keyArr = StringUtils.split(expiredKey, SEPARATOR_CHARS);
long gameId = Long.parseLong(keyArr[keyArr.length-1]);
log.debug("game {} is finishing.", gameId);
try (RLockSupport locker = RLockSupport.ofLocker(lockService)){
locker.acquire(String.format(GAME_AUTO_FINISH_LOCK, gameId), INT_5, TimeUnit.SECONDS);
GmGame game = gameService.getById(gameId);
game.setUpdateTime(LocalDateTime.now());
game.setUpdator(StringUtils.EMPTY);
game.setGameStatus(GameStatusEnum.FINISHED.getStatus());
gameService.updateById(game);
//游戏结束之后,给外包发送一个结束消息
gameCommonService.sendFinishToClient(game);
locker.unlock();
}
log.debug("game {} is finished.", gameId);
}
}
} |
代码如下:
@Transactional(rollbackFor = Exception.class)
public Long publishGame(Long gameId) {
GmGame game = getById(gameId);
//只有未发布状态的游戏才可以发布
gameCommonService.checkGamePublishStatus(game);
//判断游戏下面是否有题目以及成员,如果没有题目和成员,也不可以发布
Integer userCount = gameUserMapper.selectCount(
new QueryWrapper |
简单逻辑是:
游戏发布的时候,会判断是否到达开始时间和结束时间了:
switch 开始时间:
case 未到达:设置ttl为当前时间到开始时间的缓存
case 到达:设置游戏状态为已开始,并判断是否到达结束时间
switch 结束时间:
case 未到达:设置ttl为当前时间到开始时间的缓存
case 到达:设置游戏状态为已结束
另外需要在:游戏取消、游戏结束(手动)、游戏下所有人员都已完成游戏 之后取消相对应的缓存



