基本需求:前端客户指定任意时间执行动作,如:定时发布公告、定时发布产品、定时提醒
实现思路:利用 redis+mq
实现逻辑:利用redis 的key 失效事件触发任务
实现准备:
redis版本 2.8.0+
修改redis.conf中的notify-keyspace-events Ex,默认为notify-keyspace-events ""
也可以使用执行命令:CONFIG set notify-keyspace-events Ex (采用此种方法若重启redis需重新设置)
代码如下:
redis配置如下
@Bean
RedisMessageListenerContainer container(RedisConnectionFactory connectionFactory) {
RedisMessageListenerContainer container = new RedisMessageListenerContainer();
container.setConnectionFactory(connectionFactory);
// container.addMessageListener(new RedisExpiredListener(), new PatternTopic("__keyevent@0__:expired"));
return container;
}
key失效监听:
@Component
public class RedisKeyExpirationListener extends KeyExpirationEventMessageListener {
public RedisKeyExpirationListener(RedisMessageListenerContainer listenerContainer) {
super(listenerContainer);
}
@Autowired(required = false)
private DefaultMQProducer producer;
@Resource
private RedisUtil redisUtil;
@Value("${notice.redis.send.tempValueKeyPrefix:NOTICE:SEND:TEMP:VALUE:}")
private String tempValueKeyPrefix;
/**
* 针对redis数据失效事件,进行数据处理
*
* @param message
* @param pattern
*/
@Override
public void onMessage(Message message, byte[] pattern) {
// 用户做自己的业务处理即可,注意message.toString()可以获取失效的key
String expiredKey = message.toString();
if (expiredKey.startsWith("invalid:")) {
//如果是Order:开头的key,进行处理
System.out.println("----------------------------------------------------------------------");
System.out.println(expiredKey);
System.out.println("channel:" + new String(message.getChannel()));
org.apache.rocketmq.common.message.Message msg = null;
try {
///截取MQ_TOPIC,MQ_TAG,redisKey
String[] key = expiredKey.split(":");
String vk = String.valueOf(redisUtil.get(tempValueKeyPrefix + key[3]));
if (redisUtil.delete(tempValueKeyPrefix + key[3])){
msg = new org.apache.rocketmq.common.message.Message(key[1], key[2], vk.getBytes("utf-8"));
SendResult result = producer.send(msg);
if (result.getSendStatus() == SendStatus.SEND_OK) {
System.out.println("redis超时监听消息发送成功:" + expiredKey);
}
return;
}
} catch (Exception e) {
// 消息发送失败,需要进行重试处理,可重新发送这条消息或持久化这条数据进行补偿处理
System.out.println(e.getMessage());
}
System.out.println("----------------------------------------------------------------------");
}
}
}
接口:
/**
* @Title: execute
* @Description: TODO(根据传入时间判断是否立即执行)
* @author wolf
* @date 2020年11月18日10:54:56
* @param type 类型
* @param vk redisKey
* @param targetTime 执行时间
* @return
*/
@RequestMapping(value = "execute")
MsgModel<?> execute(@NotNull EnumTopic type,@NotNull String vk,@NotNull Long targetTime) throws Exception;
实现:
@RestController
@Service
public class SuperTimedTaskServiceImpl implements SuperTimedTaskService {
@Resource
private RedisUtil redisUtil;
@Autowired(required = false)
private DefaultMQProducer producer;
@Value("${notice.redis.send.tempValueKeyPrefix:NOTICE:SEND:TEMP:VALUE:}")
private String tempValueKeyPrefix;
@Value("${notice.redis.send.redisValueKeyPrefix}")
private String redisValueKeyPrefix;
@Override
public MsgModel<?> execute(EnumTopic type, String vk, Long targetTime) throws Exception {
//判断失效时间
///如果失效时间在正负10秒则直接进入队列
Long runTime = (targetTime - System.currentTimeMillis()) / 1000;
if(runTime<2){
org.apache.rocketmq.common.message.Message msg=null;
try {
msg = new org.apache.rocketmq.common.message.Message(type.getEnumTopic(), type.getEnumTopic(), vk.getBytes("utf-8"));
SendResult result = producer.send(msg);
return new MsgModel<>("直接进入队列成功", RespStatusEnum.SUCCESS);
} catch (Exception e) {
// 消息发送失败,需要进行重试处理,可重新发送这条消息或持久化这条数据进行补偿处理
throw e;
}
}
///否则生成失效key放入redis 同时把需要处理的数据单独存储redis中
SnowFlake idWorker = new SnowFlake(0, 0);
String key=String.valueOf(idWorker.nextId());
String redisKey= tempValueKeyPrefix + key;
String invalidRedisKye=new StringBuffer("invalid:").append(type.getEnumTopic()).append(":").append(type.getEnumTopic()).append(":").append(key).toString();
try {
redisUtil.set(redisKey,vk);
redisUtil.set(invalidRedisKye,"123",runTime);
} catch (Exception e) {
redisUtil.del(redisKey);
redisUtil.del(invalidRedisKye);
throw e;
}
return new MsgModel<>("成功", RespStatusEnum.SUCCESS);
}
}
枚举:
// 定义数据库类型枚举
public enum EnumTopic {
TEST("topicTest555", "tagTest666");
;
private String enumTopic;
private String enumTag;
private EnumTopic(String enumTopic, String enumTag) {
this.enumTopic = enumTopic;
this.enumTag = enumTag;
}
public String getEnumTopic() {
return this.enumTopic;
}
public String getEnumTag() {
return this.enumTag;
}
}