延迟队列的实现:Redis SortedSet + MySQL(附带案例)

=====================本文章的案列实现为定时发布文章====================

定时扫描:

开启自动定时扫描:SpringbootApplication上添加注解@EnableScheduling,

定义的扫描方法上添加@Scheduled(fixedRate=1000) 设置定时扫描的时间,时间单位为毫秒

 

实现思路:

1f7964ed217b48958e65d9fec6c5e30f.png

解读:文章审核通过后先判断发布的时间,小于或等于当前时间则立即发布,大于当前时间则定时发布,。

        //判断用户选择的发布时间大于当前时间,进行定时发布
        if(wmNews.getPublishTime() != null && wmNews.getPublishTime().after(new Date())){
            //修改文章状态为8
            wmNews.setStatus(WmNews.Status.SUCCESS.getCode());
            wmNews.setReason("文章审核通过,进入定时发布队列");
            wmNewsMapper.updateById(wmNews);

            //=========把当前自媒体文章发布任务添加到延迟队列中==========
            Long taskId = wmNewsTaskService.addWmNewsTask(wmNews);
            return; //必须退出
        }
        //如果发布时间小于或等于当前时间,进行立即发布文章(调用Feign接口保存App文章,更新自媒体表状态等信息)
        //调用feign接口保存文章
        publishApArticle(wmNews);

解读:定义添加任务的方法,Task对象用于存储每个任务的数据,可以在微服务之间传输任务数据时使用

package com.heima.wemedia.service.impl;

import com.heima.common.constants.ScheduleConstants;
import com.heima.model.schedule.dtos.Task;
import com.heima.model.wemedia.pojos.WmNews;
import com.heima.schedule.feign.TaskFeign;
import com.heima.utils.common.JsonUtils;
import com.heima.wemedia.service.WmNewsTaskService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

@Service
public class WmNewsTaskServiceImpl implements WmNewsTaskService {

    @Autowired
    private TaskFeign taskFeign;


    /**
     * 添加自媒体定时发布延迟任务
     *
     * @param wmNews
     */
    @Override
    public Long addWmNewsTask(WmNews wmNews) {
        Task task = new Task();
        task.setTaskTopic(ScheduleConstants.TASK_TOPIC_NEWS_PUBLISH);
        task.setExecuteTime(wmNews.getPublishTime().getTime()); //文章发布的时间,可能是客户自己选择的
        
        //为了统一格式JSON,重新创建一个对象,只传一个id过去
        WmNews news = new WmNews();
        news.setId(wmNews.getId());

        task.setParameters(JsonUtils.toString(news));

        Long taskId = taskFeign.addTask(task);

        return taskId;
    }

解读:

1.将文章任务以及任务日志表添加到数据库

2.将未来5分钟内要发布的文章,添加到redis中

   /**
     * 添加任务
     *
     * @param task
     */
    @Override
    @Transactional
    public long addTask(Task task) {
        //把任务添加到DB
        addTaskToDb(task);

        //把任务添加Redis
        addTaskToCache(task);

        return task.getTaskId();
    }

 

 /**
     * 把任务添加到DB
     * @param task
     */
    private void addTaskToDb(Task task) {
        log.info("开始将任务添加到DB.................");
        //添加任务列表
        Taskinfo taskinfo = BeanHelper.copyProperties(task, Taskinfo.class);
        taskinfo.setExecuteTime(new Date(task.getExecuteTime())); //因为两个实体类的时间类型不用,一个Long一个date,需要自己重新设置
        taskinfoMapper.insert(taskinfo);

        //把新产生的任务ID赋值给Task对象
        task.setTaskId(taskinfo.getTaskId());

        //添加任务日志表
        TaskinfoLogs taskinfoLogs = BeanHelper.copyProperties(taskinfo, TaskinfoLogs.class);
        taskinfoLogs.setVersion(1); //设置初始值,后续依靠MybatisPlus乐观锁拦截器实现更新
        taskinfoLogs.setStatus(ScheduleConstants.SCHEDULED);
        taskinfoLogsMapper.insert(taskinfoLogs);
        log.info("成功将任务添加到DB.................");
    }



  /**
     * 把任务添加Redis
     * @param task
     */
    private void addTaskToCache(Task task) {
        //判断任务的执行时间是否在未来5分钟以内
        //获取当前时间的未来5分钟的时间
        long futureTime = DateTime.now().plusMinutes(5).getMillis();
        if(task.getExecuteTime() <= futureTime) {
            log.info("开始将任务添加到redis.................");
            String key = RedisContants.TASK_TOPIC_PREFIX + task.getTaskTopic();
            redisTemplate.opsForZSet().add(key, JsonUtils.toString(task),task.getExecuteTime());
            log.info("成功将任务添加到redis.................");
        }
    }

解读:这个时候就会有数据一致的问题,需要数据库和redis的数据进行同步,定时去扫描数据库是否有符合条件的文章,导入到redis中

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;

import java.util.Date;
import java.util.List;

/**
 * 每间隔10秒就会去扫描数据库,同步数据到redis
 */
@Component
@Slf4j
public class SyncDbToCacheJob {

    @Autowired
    private TaskinfoMapper taskinfoMapper;
    @Autowired
    private StringRedisTemplate redisTemplate;

    @Scheduled(fixedRate = 10000) //固定速率(毫秒)
    public void syncData(){
        log.info("同步MySQL任务数据到Redis缓存");
        //查询符合条件的DB数据(只导入未来5分钟执行的任务)
        QueryWrapper<Taskinfo> queryWrapper = new QueryWrapper<>();
        Date futureTime = DateTime.now().plusMinutes(5).toDate();
        queryWrapper.le("execute_time", futureTime);
        List<Taskinfo> taskinfos = taskinfoMapper.selectList(queryWrapper);

        //导入Redis缓存
        if(CollectionUtils.isNotEmpty(taskinfos)){
            //log.info("开始将DB的数据导入到redis中................");
            for (Taskinfo taskinfo : taskinfos) {
                Task task = BeanHelper.copyProperties(taskinfo, Task.class);
                task.setExecuteTime(taskinfo.getExecuteTime().getTime()); //时间的类型不同,需要重新设置,date类型转为long
                String key = RedisContants.TASK_TOPIC_PREFIX + task.getTaskTopic();
                redisTemplate.opsForZSet().add(key, JsonUtils.toString(task),task.getExecuteTime());
            }
            //log.info("同步完成.................");
        }


    }

}

解读:设置每隔1秒就扫描消费定时任务一次,看redis是否有到时间发布的文章,有的话就调用发布文章的方法去发布文章。

/**
 * 定时扫描到期的文章,消费任务进行发布
 */
@Component
@Slf4j
public class WmNewsTaskJob {

    @Autowired
    private TaskFeign taskFeign;

    @Autowired
    private WmNewsService wmNewsService;

    @Autowired
    private WmNewsAutoScanService wmNewsAutoScanService;

    @Scheduled(fixedRate = 1000) //每隔1秒扫描一次到期发布的文章
    public void pollWmNewsTask(){

        //查询到期的文章
        List<Task> taskList = taskFeign.pollTask(ScheduleConstants.TASK_TOPIC_NEWS_PUBLISH);

        //发布到期的文章
        if(CollectionUtils.isNotEmpty(taskList)){
            for (Task task : taskList) {
                //获取参数
                String json = task.getParameters();
                //转为Java对象
                WmNews news = JsonUtils.toBean(json, WmNews.class);
                //通过Id查文章
                WmNews wmNews = wmNewsService.getById(news.getId());
                //发布文章
                wmNewsAutoScanService.publishApArticle(wmNews);

                log.info("文章已经成功发布到App端,ID:{}",wmNews.getId());

            }
        }


    }
}
    /**
     * 从延迟队列消费任务
     * 重点:从延迟队列取出符合条件(根据score查询,score小于或等于当前时间毫秒值)
     *
     * @param taskTopic
     */
    @Override
    @Transactional
    public List<Task> pollTask(Integer taskTopic) {
        String key = RedisContants.TASK_TOPIC_PREFIX + taskTopic;
        //查询redis中符合执行条件的任务
        Set<String> taskSet = redisTemplate.opsForZSet().rangeByScore(key, 0, System.currentTimeMillis());

        ArrayList<Task> taskList = new ArrayList<>();
        if(CollectionUtils.isNotEmpty(taskSet)) {
            log.info("redis中有符合执行条件的任务...............");
            for (String taskJson : taskSet) {
                Task task = JsonUtils.toBean(taskJson, Task.class);
                log.info("开始更新DB数据和删除redis中的数据..............");
                //更新DB数据
                updateTaskToDb(task);
                //删除redis数据
                redisTemplate.opsForZSet().remove(key,taskJson);
                taskList.add(task);
            }
        }

        return taskList;
    }
 /**
     * 调用feign接口从自媒体文章发布到App端文章
     * @param wmNews
     */
    public void publishApArticle(WmNews wmNews) {
        ApArticleDto dto = BeanHelper.copyProperties(wmNews, ApArticleDto.class);
        //重要代码:========重新绑定ap_article表的id=====
        dto.setId(wmNews.getArticleId()); //刚开始为空的

        //设置文章作者信息
        WmUser wmUser = wmUserMapper.selectById(wmNews.getArticleId());
        if(wmUser != null) {
            dto.setAuthorId(wmUser.getId());
            dto.setAuthorName(wmUser.getNickname());
        }

        //设置文章频道信息
        WmChannel channel = wmChannelMapper.selectById(wmNews.getChannelId());
        if(channel != null) {
            dto.setChannelId(channel.getId());
            dto.setChannelName(channel.getName());
        }

        //设置封面类型
        dto.setLayout((int)wmNews.getType());

        //设置文章类型
        dto.setFlag(0);

        //设置文章参数
        dto.setLikes(0);
        dto.setViews(0);
        dto.setComment(0);
        dto.setCollection(0);

        ResponseResult<Long> responseResult = apArticleFeign.save(dto);

        if(responseResult.getCode().equals(200)){
            //获取app的文章ID
            Long articleId = responseResult.getData();

            //设置自媒体文章的article_id
            wmNews.setArticleId(articleId);
            //修改文章状态为9(已发布)
            wmNews.setStatus(WmNews.Status.PUBLISHED.getCode());
            wmNews.setReason("文章已发布成功");
            wmNewsMapper.updateById(wmNews);
        }
    }

 

 

有缺点:没取消发布的逻辑

 

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
Redis单机和MySQL单机组成的架构中,读写过程通常包括以下步骤: 1. 客户端连接Redis服务器。客户端通过TCP/IP协议连接Redis服务器,并进行身份验证。 2. 客户端发送Redis命令。客户端向Redis服务器发送命令,包括读取和写入操作。 3. Redis服务器执行命令。Redis服务器根据接收到的命令执行相应的操作,包括读取和写入数据。 4. Redis服务器将写入操作同步到MySQL服务器。Redis服务器将写入操作同步到MySQL服务器上,保证数据的持久化。 5. 客户端连接MySQL服务器。客户端通过TCP/IP协议连接MySQL服务器,并进行身份验证。 6. 客户端发送SQL命令。客户端向MySQL服务器发送SQL命令,包括查询、插入、更新、删除等操作。 7. MySQL服务器解析SQL命令。MySQL服务器对接收到的SQL命令进行解析,包括语法分析、词法分析等操作。 8. MySQL服务器执行SQL命令。MySQL服务器根据解析后的结果执行SQL命令,并返回执行结果。 9. MySQL服务器将结果返回给客户端。MySQL服务器将执行结果返回给客户端,客户端进行相应的处理。 10. 客户端连接Redis服务器。客户端通过TCP/IP协议连接Redis服务器,并进行身份验证。 11. 客户端发送读取命令。客户端向Redis服务器发送读取命令。 12. Redis服务器执行读取命令。Redis服务器根据接收到的读取命令执行相应的操作,包括读取数据并返回结果。 13. Redis服务器将结果返回给客户端。Redis服务器将执行结果返回给客户端,客户端进行相应的处理。 在Redis单机和MySQL单机组成的架构中,Redis通常用作缓存层,MySQL用作持久化存储层。Redis可以提高读取性能,同时MySQL可以保证数据的持久化。当数据在Redis中过期或失效时,Redis会从MySQL中重新读取数据进行更新。这样可以充分利用Redis的高速读取性能,同时又不会影响数据的完整性和可靠性。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值