## 延迟任务概述
1 DelayQueue-程序挂掉之后,任务都是放在内存,需要考虑未处理消息的丢失带来的影响,如何保证数据不丢失,需要持久化(磁盘)
#### RabbitMQ实现延迟任务 Time To Live (消息存活时间)
2死信队列:Dead Letter Exchange(死信交换机),当消息成为Dead message后,可以重新发送另一个交换机(死信交换机)会阻塞
3基于RabbitMQ延迟队列插件(rabbitmq-delayed-message-exchange):发送消息时通过在请求头添加延时参数(headers.put("x-delay", 5000))即可达到延迟队列的效果 不支持消息量大
#### 4用这个redis使用Zset和队列实现
zset数据类型的去重有序(分数排序)特点进行延迟。例如:时间戳作为score进行排序 zset查和list 第三方工具
#### 5redis的key过期通知
使用redis的key的过期通知策略,设置一个key的过期时间为延迟时间,过期后通知客户端
### 安装redis
① 拉取镜像docker pull redis
② 创建容器
docker run -d --name redis --restart=always -p 6379:6379 redis --requirepass "leadnews"
③链接测试
打开资料中的Redis Desktop Manager,输入host、port、password链接测试
能链接成功,即可
自媒体服务引入redis配置 wemedia的application.yml,添加
spring: redis: host: 192.168.137.136 port: 6379
### 项目集成redis
#### 在heima-leadnews-common中创建delayTask包
在delayTask包创建RedisDelayedQueueUtil工具类
这个工具类包含了添加数据到延迟队列,获取延迟队列,删除延迟队列中数据功能
自媒体服务引入redis配置
自媒体服务创建redisson的配置类
package com.heima.wemedia.config;
import org.redisson.Redisson;
import org.redisson.api.RedissonClient;
import org.redisson.config.Config;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class RedissonConfig {
@Value("${spring.redis.host}")读取
private String host;
@Value("${spring.redis.port}")
private String port;
@Bean createapi
public RedissonClient redissonClient(){
Config config = new Config();
config.useSingleServer().setAddress("redis://"+host+":"+port);
return Redisson.create(config);
}
}
wemedia的application.yml,添加
spring:
redis:
host: 192.168.137.136
port: 6379
package com.heima.common.delayTask;
import lombok.extern.slf4j.Slf4j;
import org.redisson.api.RBlockingDeque;
import org.redisson.api.RDelayedQueue;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.util.concurrent.TimeUnit;
@Slf4j
@Component
public class RedisDelayedQueueUtil {
@Autowired
private RedissonClient redissonClient;
/**
* 添加延迟队列
* @param t
* @param delay
* @param timeUnit
* @param queueName
* @param <T>
*/
public <T> void addQueue(T t, long delay, TimeUnit timeUnit,String queueName){
RBlockingDeque<T> blockingDeque = redissonClient.getBlockingDeque(queueName);
RDelayedQueue<T> delayedQueue = redissonClient.getDelayedQueue(blockingDeque);
delayedQueue.offer(t,delay,timeUnit);
log.info("添加延时队列-队列名:{},值:{},延迟时间:{}",queueName,t,delay);
}
public <T> T getDelayQueue(String queueName) throws InterruptedException {
RBlockingDeque<T> blockingDeque = redissonClient.getBlockingDeque(queueName);
T take = blockingDeque.take();
return take;
}
public <T> void removeDelayQueue(String queueName,T t) {
RBlockingDeque<T> blockingDeque = redissonClient.getBlockingDeque(queueName);
RDelayedQueue<T> delayedQueue = redissonClient.getDelayedQueue(blockingDeque);
boolean remove = delayedQueue.remove(t);
log.info("删除t={},remove=={}",t,remove);
}
}
自媒体服务创建延迟消息的初始化方法
package com.heima.wemedia.task;
import com.heima.common.delayTask.RedisDelayedQueueUtil;
import com.heima.model.media.dtos.WmNewsResultDTO;
import com.heima.wemedia.service.WmNewsAuditService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import javax.annotation.PostConstruct;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
@Slf4j
@Component
public class DelayTask {
@Autowired
private RedisDelayedQueueUtil delayedQueueUtil;
@Autowired
private WmNewsAuditService auditService;
@PostConstruct这个注解有用初始化
public void doTask(){
// 创建可以周期执行的线程池
ScheduledExecutorService scheduledThreadPool = Executors.newScheduledThreadPool(1);
// 创建可以缓存的线程池
ExecutorService executorService = Executors.newCachedThreadPool();
// 周期执行,每秒中从redis延迟队列中获取元素
scheduledThreadPool.scheduleAtFixedRate(new Runnable() {
@Override
public void run() {
// 从redis延迟队列中获取元素
try {
Object obj = delayedQueueUtil.getDelayQueue("wm.news.pub");
if(obj == null){
return ;
}
Integer wmNewsId = Integer.valueOf(obj.toString());
log.info("延迟元素,wmNewsId={}",wmNewsId);
// 实现业务解耦,发布文章
因为没有返回参数所以用execute不用submit
executorService.execute(new Runnable() {
@Override
public void run() {
// 发布文章
WmNewsResultDTO dto = new WmNewsResultDTO();
dto.setId(wmNewsId);
auditService.publishWmNews(dto);
}
});
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},0,1, TimeUnit.SECONDS);
}
}
在审核文章时,如果没有到发布时间,需要放入延迟队列
service--void publishWmNews(WmNewsResultDTO newsResultDTO);
// 根据id 修改文章状态, 1-> 8
updateWmNewsStaus(wmNewsId,8,null,null);
// 如果有定时发布,并且没有到发布时间,不要发布,直接结束
if(wmNews.getPublishTime() != null &&
System.currentTimeMillis() < wmNews.getPublishTime().getTime()){
log.info("没有到发布时间,放入延迟任务");
// 计算延迟时间,用 发布时间 - 当前时间
long delay = wmNews.getPublishTime().getTime() - System.currentTimeMillis();
// 放入延迟任务四个参数
delayedQueueUtil.addQueue(wmNewsId,delay, TimeUnit.MILLISECONDS,"wm.news.pub");
return ;
}
// 对象转换
WmNewsResultDTO newsResultDTO = BeanHelper.copyProperties(wmNews, WmNewsResultDTO.class);
// 发布文章,远程调用article服务
publishWmNews(newsResultDTO);
/**
* 发布文章
* @param newsResultDTO
自媒体服务创建定时发布文章方法
其中saveArticle在之前的审核文章时已经完成
*/
@Override
public void publishWmNews(WmNewsResultDTO newsResultDTO) {
// 自媒体文章id
Integer wmNewsId = newsResultDTO.getId();
if(StringUtils.isBlank(newsResultDTO.getTitle())){
// 如果title是空,说明是 定时发布调用
// 根据id 查询文章内容
WmNews wmNews = wmNewsService.getById(wmNewsId);
newsResultDTO = BeanHelper.copyProperties(wmNews,WmNewsResultDTO.class);
}
try {
Long articleId = articleFeign.saveArticle(newsResultDTO);
// 修改文章状态,改成-9
updateWmNewsStaus(wmNewsId,9,null,articleId);
}catch (Exception e){
log.error("远程调用article保存文章,失败");
e.printStackTrace();
throw new LeadException(AppHttpCodeEnum.SERVER_ERROR);
}
}