现在市面上大家使用的消息队列无非就是mq,什么rabbitmq,rocketmq,还有百万级别的kafka,无非就是生产者,消费者,中间一个交换机,一个队列啥的,今天我要替的是一个不怎么常见的工具,redis。
redis有5种数据结构,字符串,哈希,集合,zset,其中list和zset是今天的主角,list可作为及时消费队列,zeset可作为延迟消费队列,zset从数据库拿数据,list从zset拿数据,同样可实现消息的延迟消费,这里我以最近公司用到的场景为例进行说明
自媒体文章发布后会将自媒体文章信息映射为一个任务存入redis和数据表中
wmNewsTaskService.addNewsToTask(wmNews.getId(),wmNews.getPublishTime());
@Override
@Async
public void addNewsToTask(Integer id, Date publishTime) {
log.info("添加任务到延迟服务中----begin");
Task task = new Task();
task.setExecuteTime(publishTime.getTime());
task.setTaskType(TaskTypeEnum.NEWS_SCAN_TIME.getTaskType());
task.setPriority(TaskTypeEnum.NEWS_SCAN_TIME.getPriority());
WmNews wmNews = new WmNews();
wmNews.setId(id);
task.setParameters(ProtostuffUtil.serialize(wmNews));
scheduleClient.addTask(task);
log.info("添加任务到延迟服务中----end");
}
这里调用计划模块的功能,通过feign调用,关于feign如何使用,我在往期内容里有房,感兴趣的小伙伴可以去查看,
将任务加入redis和数据库表
@Override
public long addTask(Task task) {
//1.添加任务到数据库中
boolean success=addTaskToDb(task);
if (success){
//2.添加任务到redis
addTaskToCache(task);
}
return task.getTaskId();
}
================================================
//上述代码用到的方法
//方法1
/**
* 添加任务到数据库中
* @param task
* @return
*/
private boolean addTaskToDb(Task task) {
boolean flag=false;
try {
//保存任务表
Taskinfo taskinfo = new Taskinfo();
BeanUtils.copyProperties(task,taskinfo);
taskinfo.setExecuteTime(new Date(task.getExecuteTime()));
taskinfoMapper.insert(taskinfo);
//设置taskID
task.setTaskId(taskinfo.getTaskId());
//保存任务日志数据
TaskinfoLogs taskinfoLogs = new TaskinfoLogs();
BeanUtils.copyProperties(taskinfo,taskinfoLogs);
taskinfoLogs.setVersion(1);
taskinfoLogs.setStatus(ScheduleConstants.SCHEDULED);
taskinfoLogsMapper.insert(taskinfoLogs);
flag=true;
} catch (Exception e) {
e.printStackTrace();
}
return flag;
}
//方法2
//此处即使redis的两个队列使用,会设置一个未来时间作为延迟,list中的内容会按照消费频率进行消费,但不一定是马上,zset要等待指定时间
/**
* 把任务添加到redis中
* @param task
*/
private void addTaskToCache(Task task) {
String key = task.getTaskType()+"-"+task.getPriority();
//获取5分钟之后的时间 毫秒值
Calendar calendar = Calendar.getInstance();
calendar.add(Calendar.MINUTE,5);
long nextScheduleTime = calendar.getTimeInMillis();
//2.1 如果任务的执行时间小于等于当前时间,存入list
if (task.getExecuteTime()<=System.currentTimeMillis()){
cacheService.lLeftPush(ScheduleConstants.TOPIC+key, JSON.toJSONString(task));
}else if (task.getExecuteTime()<=nextScheduleTime){
//2.2 如果任务的执行时间大于当前时间 && 小于等于预设时间(未来5分钟) 存入zset中
cacheService.zAdd(ScheduleConstants.FUTURE+key,JSON.toJSONString(task),task.getExecuteTime());
}
}
消费任务
同样调用计划模块中的内容,消费速率这里设置的5秒
@Override
@Scheduled(fixedRate = 5000)
@SneakyThrows
public void scanNewsByTask() {
log.info("文章审核---消费任务执行---begin---");
ResponseResult responseResult = scheduleClient.poll(TaskTypeEnum.NEWS_SCAN_TIME.getTaskType(), TaskTypeEnum.NEWS_SCAN_TIME.getPriority());
if (responseResult.getCode().equals(200)&&responseResult.getData()!=null){
String json_str = JSON.toJSONString(responseResult.getData());
Task task = JSON.parseObject(json_str, Task.class);
byte[] parameters = task.getParameters();
WmNews wmNews = ProtostuffUtil.deserialize(parameters, WmNews.class);
System.out.println(wmNews.getId()+"-----------");
wmNewsAutoScanService.autoScanWmNews(wmNews.getId());
}
log.info("文章审核---消费任务执行---end---");
}
消费的具体方法
这里通过删除redis中的内容并更改其在数据库中的状态完成
/**
* 按照类型和优先级拉取任务
* @param type
* @param priority
* @return
*/
@Override
public Task poll(int type, int priority) {
Task task=null;
try {
String key = type+"-"+priority;
String task_json = cacheService.lRightPop(ScheduleConstants.TOPIC+key);
if (StringUtils.isNotBlank(task_json)){
task = JSON.parseObject(task_json,Task.class);
//更新数据库信息
updateDb(task.getTaskId(),ScheduleConstants.EXECUTED);
}
} catch (Exception e) {
e.printStackTrace();
log.error("poll task exception");
}
return task;
}
还有这里也是用到了分布式锁
/**
* 未来数据定时刷新
*/
@Scheduled(cron = "0 */1 * * * ?")
public void refresh(){
System.out.println(System.currentTimeMillis()/1000+"执行了定时任务");
String token = cacheService.tryLock("FUTURE_TASK_SYNC", 1000 * 30);
if (StringUtils.isNotBlank(token)){
log.info("未来数据定时刷新---定时任务");
// 获取所有未来数据集合的key值
Set<String> futureKeys = cacheService.scan(ScheduleConstants.FUTURE + "*");// future_*
for (String futureKey : futureKeys) { // future_250_250
//获取当前数据的key topic
String topicKey = ScheduleConstants.TOPIC+futureKey.split(ScheduleConstants.FUTURE)[1];
//获取该组key下当前需要消费的任务数据
Set<String> tasks = cacheService.zRangeByScore(futureKey, 0, System.currentTimeMillis());
//同步数据
if (!tasks.isEmpty()){
//将这些任务数据添加到消费者队列中
cacheService.refreshWithPipeline(futureKey,topicKey,tasks);
System.out.println("成功的将"+futureKey+"刷新到了"+topicKey);
}
}
}
}
数据库数据同步到缓存
public void reloadData(){
clearCache();
log.info("数据库数据同步到缓存");
Calendar calendar = Calendar.getInstance();
calendar.add(Calendar.MINUTE,5);
//查看小于未来5分钟的所有任务
List<Taskinfo> allTasks = taskinfoMapper.selectList(Wrappers.<Taskinfo>lambdaQuery().lt(Taskinfo::getExecuteTime, calendar.getTime()));
if (allTasks!=null&&allTasks.size()>0){
for (Taskinfo taskinfo : allTasks) {
Task task = new Task();
BeanUtils.copyProperties(taskinfo,task);
task.setExecuteTime(taskinfo.getExecuteTime().getTime());
addTaskToCache(task);
}
}
}
==============================================
private void clearCache(){
// 删除缓存中未来数据集合和当前消费者队列的所有key
Set<String> futurekeys = cacheService.scan(ScheduleConstants.FUTURE + "*");// future_
Set<String> topickeys = cacheService.scan(ScheduleConstants.TOPIC + "*");// topic_
cacheService.delete(futurekeys);
cacheService.delete(topickeys);
}
===============================================
/**
* 把任务添加到redis中
* @param task
*/
private void addTaskToCache(Task task) {
String key = task.getTaskType()+"-"+task.getPriority();
//获取5分钟之后的时间 毫秒值
Calendar calendar = Calendar.getInstance();
calendar.add(Calendar.MINUTE,5);
long nextScheduleTime = calendar.getTimeInMillis();
//2.1 如果任务的执行时间小于等于当前时间,存入list
if (task.getExecuteTime()<=System.currentTimeMillis()){
cacheService.lLeftPush(ScheduleConstants.TOPIC+key, JSON.toJSONString(task));
}else if (task.getExecuteTime()<=nextScheduleTime){
//2.2 如果任务的执行时间大于当前时间 && 小于等于预设时间(未来5分钟) 存入zset中
cacheService.zAdd(ScheduleConstants.FUTURE+key,JSON.toJSONString(task),task.getExecuteTime());
}
}
记得一键三连,持续更新