文章目录
- 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"}
从日志可以看出消息被延时消费