使用redis实现消息延时推送

项目背景

我们在很多场景下都可能使用延时消息队列,比如我们在微信公众号进行自动回复操作的时候,你发送一条消息给微信公众号,微信公众号调用后端接口给予我们回应,当提问一段时间后,我们不再回应了,比如默认5分钟,这时候我们就想自主的推送一条消息给用户,比如说给这次回答进行打分,这时考虑简单操作以及可以接受误差的情况,我们可以使用redis实现延时消息队列,而不使用专业的消息队列,因为不用再部署的时候还得部署一套中间件的操作。

使用redis的数据结构

采用listlpush rpop可以实现消息队列的生产消费,但没有排序,不能实现延时任务
所以最终方案使用zset数据结构,而这个数据结构有三个重要参数,一个是key,一个是value,一个是分数,分数可以存放时间的值,这样通过定时任务扫描时间,当超过我们所设定的时间就将消息进行推送,这样就可以达到消息延迟推送的效果,最后用户提交了评价之后,我们就把这个队列的值(也就是id)删除

使用redis实现延时消息队列的优缺点

优点:zset 数据结构支持有序集合,可以根据 score 对元素进行排序,所以可以非常快速地查找出到期的消息,从而实现高效的延时消息队列。
redis支持持久化
缺点:由于zset是有序集合,每次添加、删除和查询操作都需要进行排序,因此性能不是很高

关键代码展示

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.wengzw.message.redis.model.MessNotice;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;

import java.text.SimpleDateFormat;
import java.util.Iterator;
import java.util.Set;


@Slf4j
@Component
@RequiredArgsConstructor
public class DelayQueue {
    // 相当于队列的键
    public static final String DELAY_MESSAGE_QUEUE = "delayQueue";

    private static SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");

    private final RedisTemplate<String, String> redisTemplate;

    /**
     * 消息入队
     * 只要用户继续发起请求,就会继续延长时间戳的时间 直到用户一直没有请求后,然后到达指定时间给消费者进行消费
     * 如果有其他用户进来,那么这个队列会根据时间戳进行排序 排在前面的并且达到指定时间就先进行消费
     *
     * @param id
     */
    public void pushQueue(String id) {
        MessNotice messNotice = new MessNotice();
        messNotice.setId(id);
        //序列化
        ObjectMapper objectMapper = new ObjectMapper();
        try {
            String str = objectMapper.writeValueAsString(messNotice);
            // 存的值是集合的主键 一般固定
            // 第二个是id 每个用户一个id
            // 第三个是根据什么进行排序,这里是根据时间戳,这里如果是同个key,那么会刷新同个key的时间戳并重新进行排序
            // 不同的key就是当前时间加10秒,也就是10秒后过期
            redisTemplate.opsForZSet().add(DELAY_MESSAGE_QUEUE, str, System.currentTimeMillis() + 10000);
        } catch (JsonProcessingException e) {
            e.printStackTrace();
        }
    }

    // 消息出队
    // 权重就是时间戳
    @Scheduled(cron = "0/5 * * * * ?")
    public void pullQueue() {
        System.out.println("------------等待消费--------------" + simpleDateFormat.format(System.currentTimeMillis()));
        // 获取0-当前时间戳的范围的所有值
        Set<String> set = redisTemplate.opsForZSet().rangeByScore(DELAY_MESSAGE_QUEUE, 0, System.currentTimeMillis());
        Iterator<String> iterator = set.iterator();
        while (iterator.hasNext()) {
            String value = iterator.next();
            // 遍历取出每一个score
            Double score = redisTemplate.opsForZSet().score(DELAY_MESSAGE_QUEUE, value);
            // 达到时间进行消费 当前时间大于
            if (System.currentTimeMillis() > score) {
                log.info("消费了" + value + "消费时间 " + simpleDateFormat.format(System.currentTimeMillis()));
                // 移除该队列的值
                redisTemplate.opsForZSet().remove(DELAY_MESSAGE_QUEUE, value);
            }
        }
    }

}

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值