code自媒体

52 阅读3分钟

image.png

image.png

## 延迟任务概述

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);
        }
    }