文章目录

  • 1 摘要
  • 2 核心 Maven 依赖
  • 2 核心代码
  • 2.1 配置文件
  • 2.2 延时队列工具类
  • 3.3 使用示例(Controller)
  • 3.4 其他相关类(请求参数)
  • 4 测试
  • 5 推荐参考资料
  • 6 Github 源码



1 摘要

延时消息队列的功能除了使用 RabbitMQ 这类专业的消息队列工具实现外,如果应对小规模简单的业务,也可以 Redis 实现延时队列的功能。本文将介绍 Springboot 基于 Redis 实现延时队列功能。


2 核心 Maven 依赖

<!-- redis -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
            <version>${springboot.version}</version>
        </dependency>

这里作为演示的 springboot 版本为:

<springboot.version>2.0.6.RELEASE</springboot.version>

辅助工具

<!-- hutool,集成java 工具包 -->
        <dependency>
            <groupId>cn.hutool</groupId>
            <artifactId>hutool-all</artifactId>
            <version>${hutool.version}</version>
        </dependency>

版本信息为:

<hutool.version>5.3.9</hutool.version>


2 核心代码

2.1 配置文件
./demo-web/src/main/resources/application-dev.yml
spring:
  # redis
  redis:
    database: 1
    host: 172.16.140.10
    port: 7749
    password: 66666
    timeout: 10000ms
    jedis:
      pool:
        max-active: 200
        max-idle: 500
        min-idle: 50
        max-wait: 100s
2.2 延时队列工具类
./demo-base-web/src/main/java/com/ljq/demo/springboot/baseweb/util/RedisDelayQueueUtil.java
package com.ljq.demo.springboot.baseweb.util;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.thread.ThreadUtil;
import cn.hutool.json.JSONUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;

import javax.annotation.PostConstruct;
import java.util.Set;

/**
 * @Description: Redis 延时队列工具类
 * @Author: junqiang.lu
 * @Date: 2021/10/14
 */
@Slf4j
@Component
public class RedisDelayQueueUtil {

    @Autowired
    private RedisTemplate redisTemplate;

    /**
     * 队列名称
     */
    public static final String QUEUE_NAME_ORDER = "orderDelayQueue";

    /**
     * 延时时长
     */
    public static final long QUEUE_DELAY_TIME_ORDER = 30000;

    /**
     * 设置订单延时任务
     *
     * @param orderMsg 订单消息
     * @param delayTime 延时时间,距离当前时间的时间间隔(单位:毫秒)
     */
    public void setOrderDelayTask(Object orderMsg, long delayTime) {
        long expireTime = System.currentTimeMillis() + delayTime;
        boolean addFlag = redisTemplate.opsForZSet().add(QUEUE_NAME_ORDER, JSONUtil.toJsonStr(orderMsg), expireTime);
        if (addFlag) {
            // TODO 记录订单状态

            log.info("订单延时消息创建成功,{},过期时间: {}", orderMsg, expireTime);
        }
    }

    /**
     * 消费订单延时队列
     */
    @PostConstruct
    public void consumeOrderQueue() {
        log.info("订单延时队列扫描已启动.....");
        ThreadUtil.newSingleExecutor().execute(() -> {
            while (true) {
                Set<String> set = redisTemplate.opsForZSet().rangeByScore(QUEUE_NAME_ORDER, 0,
                        System.currentTimeMillis(), 0L, 1L);
                // 如果没有需要消费的消息,则间隔一段时间再扫描
                if (CollUtil.isEmpty(set)) {
                    try {
                        Thread.sleep(1000L);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    continue;
                }
                String orderMsgStr = set.iterator().next();
                // TODO 将 orderMsgStr 转化为 orderMsg 对象
//                JSONUtil.toBean(orderMsgStr, OrderMsgObject.class);

                boolean deleteFlag = redisTemplate.opsForZSet().remove(QUEUE_NAME_ORDER, orderMsgStr) > 0;
                if (deleteFlag) {
                    // TODO 消费订单消息

                    log.info("订单延时消息已成功消费,{}", orderMsgStr);
                }
            }
        });

    }



}
3.3 使用示例(Controller)
./demo-web/src/main/java/com/ljq/demo/springboot/web/controller/OrderController.java
package com.ljq.demo.springboot.web.controller;

import com.ljq.demo.springboot.baseweb.api.ApiResult;
import com.ljq.demo.springboot.baseweb.util.RedisDelayQueueUtil;
import com.ljq.demo.springboot.vo.order.OrderDelayCreateParam;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

/**
 * @Description: 订单控制层
 * @Author: junqiang.lu
 * @Date: 2021/10/14
 */
@Slf4j
@RestController
@RequestMapping(value = "/api/order")
@Api(value = "订单控制层", tags = "订单控制层")
public class OrderController {

    @Autowired
    private RedisDelayQueueUtil redisDelayQueueUtil;

    /**
     * 创建延时订单
     *
     * @param orderDelayCreateParam
     * @return
     */
    @PostMapping(value = "/delay")
    @ApiOperation(value = "创建延时订单", notes = "创建延时订单")
    public ResponseEntity<ApiResult<Void>> createDelayOrder(@RequestBody @Validated OrderDelayCreateParam
                                                                        orderDelayCreateParam) {
        redisDelayQueueUtil.setOrderDelayTask(orderDelayCreateParam, RedisDelayQueueUtil.QUEUE_DELAY_TIME_ORDER);
        HttpHeaders headers = new HttpHeaders();
        headers.setContentType(MediaType.APPLICATION_JSON_UTF8);
        return new ResponseEntity<>(ApiResult.success(), headers, HttpStatus.OK);
    }
}
3.4 其他相关类(请求参数)
./demo-model/src/main/java/com/ljq/demo/springboot/vo/order/OrderDelayCreateParam.java
package com.ljq.demo.springboot.vo.order;

import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;

import javax.validation.constraints.Pattern;
import java.io.Serializable;

/**
 * @Description: 创建延时订单
 * @Author: junqiang.lu
 * @Date: 2021/10/14
 */
@Data
@ApiModel(value = "创建延时订单", description = "创建延时订单")
public class OrderDelayCreateParam implements Serializable {

    private static final long serialVersionUID = 3692411340443934479L;

    /**
     * 订单编号
     */
    @Pattern(regexp = "^[a-zA-Z0-9]{5,64}$", message = "订单编号格式错误")
    @ApiModelProperty(value = "订单编号", name = "orderNo", required = true)
    private String orderNo;

}


4 测试

启动项目,发送请求参数

请求路径:

http://127.0.0.1:8088/api/order/delay

请求参数(body):

{
    "orderNo": "NO123760"
}

后台日志:

2021-10-18 14:27:25 | INFO  | http-nio-8088-exec-5 | c.ljq.demo.springboot.web.acpect.SimpleInterceptor 29| preHandle
2021-10-18 14:27:25 | INFO  | http-nio-8088-exec-5 | com.ljq.demo.springboot.baseweb.log.LogService 30| [LOG-REQUEST]
	requestIP: 127.0.0.1
	contentType:application/json
	requestUrl: http://127.0.0.1:8088/api/order/delay
	requestMethod: POST
	requestParams: {}
	requestBody: OrderDelayCreateParam(orderNo=NO123760)
2021-10-18 14:27:25 | INFO  | http-nio-8088-exec-5 | com.ljq.demo.springboot.web.acpect.LogAspect 66| [AOP-LOG-START]
	requestMark: 5abf7a2d-d855-4c2a-b063-f12d6865a2dc
	requestIP: 127.0.0.1
	contentType:application/json
	requestUrl: http://127.0.0.1:8088/api/order/delay
	requestMethod: POST
	requestParams: {"orderNo":"NO123760"}
	targetClassAndMethod: com.ljq.demo.springboot.web.controller.OrderController#createDelayOrder
2021-10-18 14:27:25 | INFO  | http-nio-8088-exec-5 | c.l.d.springboot.baseweb.util.RedisDelayQueueUtil 48| 订单延时消息创建成功,OrderDelayCreateParam(orderNo=NO123760),过期时间: 1634538475824
2021-10-18 14:27:25 | INFO  | http-nio-8088-exec-5 | com.ljq.demo.springboot.web.acpect.LogAspect 72| [AOP-LOG-END]
	requestMark: 5abf7a2d-d855-4c2a-b063-f12d6865a2dc
	requestUrl: http://127.0.0.1:8088/api/order/delay
	response: <200 OK,ApiResult(code=200, msg=成功, data=null, extraData=null, timestamp=1634538445839),{Content-Type=[application/json;charset=UTF-8]}>
2021-10-18 14:27:25 | INFO  | http-nio-8088-exec-5 | com.ljq.demo.springboot.baseweb.log.LogService 44| [LOG-RESPONSE]
	requestIp: 127.0.0.1
	requestUrl: http://127.0.0.1:8088/api/order/delay
	response: ApiResult(code=200, msg=成功, data=null, extraData=null, timestamp=1634538445839)
2021-10-18 14:27:25 | INFO  | http-nio-8088-exec-5 | c.ljq.demo.springboot.web.acpect.SimpleInterceptor 38| postHandle
2021-10-18 14:27:25 | INFO  | http-nio-8088-exec-5 | c.ljq.demo.springboot.web.acpect.SimpleInterceptor 44| afterCompletion
2021-10-18 14:27:56 | INFO  | pool-6-thread-1 | c.l.d.springboot.baseweb.util.RedisDelayQueueUtil 79| 订单延时消息已成功消费,{"orderNo":"NO123760"}

spring boot redis消息队列 springboot redis消息队列持久化_delayed message

从日志可以看出消息被延时消费