Redis延时队列方案

总体方案

创建一个定时任务,每一次执行完后间隔一定时间就会扫描缓存,缓存中一旦添加了任务,就会被扫描到,然后发送到消息队列,监听器一旦监听到消息就会进行处理,如果处理失败,则再次生成任务(次数加1,时间戳会根据规则增加),到达规定次数后则不在执行

具体细节

首先创建一个执行完后间隔’${webhook.fixedDelay}’后执行下一次,从缓存中取出数据,一旦取到数据就发送到消息队列中,并且删除掉缓存里面的数据

@Scheduled(fixedDelayString = "${webhook.fixedDelay}")
    public void process() {
        // ZRANGEBYSCORE 取score小于等于当前时间的数据
        Date date = new Date();
        Set<String> list = stringRedisTemplate.opsForZSet().rangeByScore(key, 0, date.getTime());
        if (list == null || list.size() == 0) {
            return;
        }
        try {
            // 发送队列
            for (String json : list) {
                amqpTemplate.convertAndSend(routingKey, json);
            }
            // ZREMRANGEBYSCORE() 删除score小于等于当前时间的数据
            stringRedisTemplate.opsForZSet().removeRangeByScore(key, 0, date.getTime());
        } catch (Exception e) {
            // TODO: handle exception
            logger.error("队列发送失败", e);
        }
    }

监听器一旦监听到消息就会进行处理,如果处理失败,则再次生成任务(次数加1,时间戳会根据规则增加),到达规定次数后则不在执行

@RabbitListener(queues = "${webhook.queue}")
    // 参数中使用@Header获取mesage
    public void helloReply(String json) {
        logger.debug("task:{}", json);
        Task task = gson.fromJson(json, Task.class);
        try {
            handler.handler(task.getData());
        } catch (Exception e) {
            if (webhookUtil.getInterval().size() > task.getTimes()) {
                Task next = new Task();
                next.setTimes(task.getTimes() + 1);
                next.setTimestamp(task.getTimestamp() + webhookUtil.getInterval().get(task.getTimes())*1000);
                next.setData(task.getData());
                webhookUtil.addNext(next);
                if (logger.isDebugEnabled()) {
                    logger.debug("处理失败,添加到下次执行:" + gson.toJson(task), e);
                }
            } else {
                logger.error("处理{}次失败,停止处理", webhookUtil.getInterval().size(), e);
            }
        }

    }

测试

向缓存中添加一条数据,定时任务会到时间自动检测到这条数据

public boolean add(String data) {
        Task task = new Task();
        task.setData("data");
        task.setTimes(0);
        task.setTimestamp(new Date().getTime() + getInterval().get(task.getTimes())*1000);
        try {
            stringRedisTemplate.opsForZSet().add(key, gson.toJson(task), task.getTimestamp());
            return true;
        } catch (Exception e) {
            logger.error("redis新增失败", e);
            return false;
        }
    }

配置文件

spring-mq.xml

    <!-- 连接服务配置  -->  
    <rabbit:connection-factory id="connectionFactory"
        addresses="${rabbitmq.addresses}" username="${rabbitmq.username}"
        password="${rabbitmq.password}" channel-cache-size="${rabbitmq.channel.cache.size}" />

    <rabbit:admin connection-factory="connectionFactory" />

    <!-- 将queue和routingKey进行绑定 --><!-- queue 队列声明 --> 
    <rabbit:queue name="${webhook.queue}" />

    <!-- exchange queue binging key 绑定 -->
    <!-- direct方式:根据routingKey将消息发送到所有绑定的queue中 -->
    <rabbit:direct-exchange name="${rabbitmq.direct.exchange}">
        <rabbit:bindings>
            <rabbit:binding queue="${webhook.queue}" key="${webhook.routing.key}" />
        </rabbit:bindings>
    </rabbit:direct-exchange>

    <!-- 消费者配置开始 -->
    <rabbit:annotation-driven />

    <bean id="rabbitListenerContainerFactory"
        class="org.springframework.amqp.rabbit.config.SimpleRabbitListenerContainerFactory">
        <property name="messageConverter" ref="messageConverter" />
        <property name="connectionFactory" ref="connectionFactory" />
        <property name="concurrentConsumers" value="3" />
        <property name="maxConcurrentConsumers" value="10" />
    </bean>
    <!-- 消费者配置结束 -->

    <!-- 将生产者生产的数据转换为json存入消息队列 -->
    <bean id="messageConverter" class="org.springframework.amqp.support.converter.Jackson2JsonMessageConverter" />

    <!-- spring template声明 -->  
    <rabbit:template id="amqpTemplate" message-converter="messageConverter"
        connection-factory="connectionFactory" reply-timeout="2000" retry-template="retryTemplate" 
        exchange="${rabbitmq.direct.exchange}"  />

    <!--发送失败后重新发送的模板  -->
    <bean id="retryTemplate" class="org.springframework.retry.support.RetryTemplate">
        <property name="backOffPolicy">
            <bean class="org.springframework.retry.backoff.ExponentialBackOffPolicy">
                <property name="initialInterval" value="500" />
                <property name="multiplier" value="10.0" />
                <property name="maxInterval" value="10000" />
            </bean>
        </property>
    </bean>

webhook.properties

#数据存入缓存中的名字
webhook.key=eisp_webhook_key
#消息队列的key值
webhook.routing.key=eisp_webhook_routing_key
#消息队列的队列名
webhook.queue=webhook_queue
#下次请求的时间间隔(单位:秒)
webhook.interval=5, 20,30
#定时器每次执行完后的间隔时间(单位:毫秒)
webhook.fixedDelay=1000

其中’webhook.interval=5, 20,30’是以数组的方式注入

@Value("#{'${webhook.interval}'.split(',')}")
    private List<Integer> interval;
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值