听说你要用redis来处理超时支付订单?redis就像一个住在你内存里的闪电侠,它跑得飞快,但记性有点差(断电就失忆)。它是个键值对存储的社交恐惧症患者,就喜欢简单直接的交流。不过对付订单超时这种“限时任务”,它可是专业的“时间管理大师”!
为什么选redis来做这个?
你开了一家网红奶茶店,顾客下单后30分钟不付款,订单就自动取消。你总不能雇个店员盯着每个订单看30分钟吧?redis的过期键和发布订阅功能,就是那个不知疲倦的“自动取消专员”!
详细步骤:让我们开始组装这个“订单取消机器人”
第1步:引入redis依赖包
<!-- pom.xml 里加入这个“能量饮料” -->
<dependency>
<groupid>org.springframework.boot</groupid>
<artifactid>spring-boot-starter-data-redis</artifactid>
</dependency>
<dependency>
<groupid>org.apache.commons</groupid>
<artifactid>commons-pool2</artifactid>
</dependency>第2步:配置redis连接
# application.yml
spring:
redis:
# redis的地址,默认是本地6379端口
host: localhost
port: 6379
# 密码(如果设置了的话)
password:
# 数据库索引,就像给闪电侠安排的第几个房间
database: 0
lettuce:
pool:
# 连接池配置,别让闪电侠累着了
max-active: 8
max-idle: 8
min-idle: 0
max-wait: 100ms第3步:配置redistemplate
import com.fasterxml.jackson.annotation.jsontypeinfo;
import com.fasterxml.jackson.databind.objectmapper;
import com.fasterxml.jackson.datatype.jsr310.javatimemodule;
import org.springframework.context.annotation.bean;
import org.springframework.context.annotation.configuration;
import org.springframework.data.redis.connection.redisconnectionfactory;
import org.springframework.data.redis.core.redistemplate;
import org.springframework.data.redis.serializer.jackson2jsonredisserializer;
import org.springframework.data.redis.serializer.stringredisserializer;
@configuration
public class redisconfig {
@bean
public redistemplate<string, object> redistemplate(redisconnectionfactory factory) {
redistemplate<string, object> template = new redistemplate<>();
template.setconnectionfactory(factory);
// 键的序列化 - 字符串序列化
template.setkeyserializer(new stringredisserializer());
template.sethashkeyserializer(new stringredisserializer());
// 值的序列化 - json序列化
jackson2jsonredisserializer<object> serializer = new jackson2jsonredisserializer<>(object.class);
objectmapper mapper = new objectmapper();
mapper.registermodule(new javatimemodule());
mapper.activatedefaulttyping(
mapper.getpolymorphictypevalidator(),
objectmapper.defaulttyping.non_final,
jsontypeinfo.as.property
);
serializer.setobjectmapper(mapper);
template.setvalueserializer(serializer);
template.sethashvalueserializer(serializer);
template.afterpropertiesset();
return template;
}
}第4步:订单实体类
import lombok.data;
import java.time.localdatetime;
@data
public class order {
private string orderid; // 订单id
private string userid; // 用户id
private double amount; // 订单金额
private integer status; // 订单状态:0-待支付,1-已支付,2-已取消
private localdatetime createtime;// 创建时间
private localdatetime expiretime;// 过期时间
// 判断是否已过期
public boolean isexpired() {
return localdatetime.now().isafter(expiretime);
}
}第5步:redis服务类
import org.springframework.beans.factory.annotation.autowired;
import org.springframework.data.redis.core.redistemplate;
import org.springframework.data.redis.core.stringredistemplate;
import org.springframework.stereotype.service;
import java.util.concurrent.timeunit;
@service
public class redisorderservice {
@autowired
private redistemplate<string, object> redistemplate;
@autowired
private stringredistemplate stringredistemplate;
// 订单前缀,避免键冲突
private static final string order_key_prefix = "order:pay:";
private static final string order_expire_channel = "order.expire";
/**
* 创建订单并设置30分钟过期时间
* 就像给闪电侠说:“盯着这个订单,30分钟后提醒我”
*/
public void createorderwithexpire(order order, int expireminutes) {
string orderkey = order_key_prefix + order.getorderid();
// 保存订单到redis,30分钟后自动删除
redistemplate.opsforvalue().set(
orderkey,
order,
expireminutes,
timeunit.minutes
);
// 同时设置一个简单的标志,用于监听过期事件
stringredistemplate.opsforvalue().set(
orderkey + ":flag",
"1",
expireminutes,
timeunit.minutes
);
system.out.println("订单 " + order.getorderid() + " 已放入redis,设置" +
expireminutes + "分钟后过期");
}
/**
* 用户支付成功,删除过期键
* 相当于告诉闪电侠:“不用盯了,顾客付钱了!”
*/
public void handlepaymentsuccess(string orderid) {
string orderkey = order_key_prefix + orderid;
// 手动删除订单和标志
redistemplate.delete(orderkey);
stringredistemplate.delete(orderkey + ":flag");
system.out.println("订单 " + orderid + " 支付成功,已从redis移除");
}
/**
* 检查订单是否还存在(是否已过期)
*/
public boolean isorderexist(string orderid) {
string orderkey = order_key_prefix + orderid;
return boolean.true.equals(redistemplate.haskey(orderkey));
}
/**
* 获取订单信息
*/
public order getorder(string orderid) {
string orderkey = order_key_prefix + orderid;
return (order) redistemplate.opsforvalue().get(orderkey);
}
}第6步:redis过期监听配置
import org.springframework.context.annotation.bean;
import org.springframework.context.annotation.configuration;
import org.springframework.data.redis.connection.redisconnectionfactory;
import org.springframework.data.redis.listener.patterntopic;
import org.springframework.data.redis.listener.redismessagelistenercontainer;
import org.springframework.data.redis.listener.adapter.messagelisteneradapter;
@configuration
public class redisexpireconfig {
@bean
public redismessagelistenercontainer container(
redisconnectionfactory connectionfactory,
messagelisteneradapter listeneradapter) {
redismessagelistenercontainer container = new redismessagelistenercontainer();
container.setconnectionfactory(connectionfactory);
// 监听所有key过期事件
container.addmessagelistener(listeneradapter,
new patterntopic("__keyevent@0__:expired"));
return container;
}
@bean
public messagelisteneradapter listeneradapter(rediskeyexpirelistener receiver) {
return new messagelisteneradapter(receiver, "handlemessage");
}
}第7步:过期事件监听器
import org.springframework.beans.factory.annotation.autowired;
import org.springframework.data.redis.connection.message;
import org.springframework.data.redis.listener.adapter.messagelisteneradapter;
import org.springframework.stereotype.component;
import java.nio.charset.standardcharsets;
@component
public class rediskeyexpirelistener extends messagelisteneradapter {
@autowired
private orderservice orderservice;
/**
* 当redis键过期时,这个方法会被调用
* 闪电侠会喊:“嘿!那个订单过期了!”
*/
@override
public void handlemessage(message message, byte[] pattern) {
string expiredkey = new string(message.getbody(), standardcharsets.utf_8);
// 只处理我们的订单过期键
if (expiredkey.startswith("order:pay:")) {
// 去掉":flag"后缀获取订单id
string orderid = expiredkey
.replace("order:pay:", "")
.replace(":flag", "");
system.out.println("redis报告:订单 " + orderid + " 已超时!");
// 处理订单超时逻辑
orderservice.cancelexpiredorder(orderid);
}
}
}第8步:订单服务层
import org.springframework.beans.factory.annotation.autowired;
import org.springframework.stereotype.service;
import org.springframework.transaction.annotation.transactional;
@service
public class orderservice {
@autowired
private redisorderservice redisorderservice;
@autowired
private orderrepository orderrepository;
/**
* 创建订单
*/
@transactional
public order createorder(string userid, double amount) {
order order = new order();
order.setorderid(generateorderid());
order.setuserid(userid);
order.setamount(amount);
order.setstatus(0); // 待支付
order.setcreatetime(localdatetime.now());
order.setexpiretime(localdatetime.now().plusminutes(30));
// 保存到数据库
orderrepository.save(order);
// 保存到redis并设置30分钟过期
redisorderservice.createorderwithexpire(order, 30);
return order;
}
/**
* 处理支付回调
*/
@transactional
public void handlepaymentcallback(string orderid) {
// 检查订单是否已过期
if (!redisorderservice.isorderexist(orderid)) {
throw new runtimeexception("订单已超时,请重新下单");
}
// 更新订单状态为已支付
orderrepository.updateorderstatus(orderid, 1);
// 从redis移除过期键
redisorderservice.handlepaymentsuccess(orderid);
system.out.println("订单 " + orderid + " 支付处理完成");
}
/**
* 取消超时订单
*/
@transactional
public void cancelexpiredorder(string orderid) {
// 再次检查,防止重复处理
order order = orderrepository.findbyid(orderid);
if (order != null && order.getstatus() == 0) {
order.setstatus(2); // 已取消
orderrepository.save(order);
// 可以在这里添加其他逻辑,比如释放库存、发送通知等
system.out.println("订单 " + orderid + " 因超时未支付已被自动取消");
// 发送取消通知
sendcancelnotification(order);
}
}
/**
* 发送取消通知(模拟)
*/
private void sendcancelnotification(order order) {
// 这里可以集成消息队列、邮件、短信等
system.out.println("发送通知:亲爱的用户" + order.getuserid() +
",您的订单" + order.getorderid() + "因超时未支付已取消");
}
private string generateorderid() {
return "ord" + system.currenttimemillis() +
(int)(math.random() * 1000);
}
}第9步:控制器层
import org.springframework.beans.factory.annotation.autowired;
import org.springframework.web.bind.annotation.*;
@restcontroller
@requestmapping("/orders")
public class ordercontroller {
@autowired
private orderservice orderservice;
@autowired
private redisorderservice redisorderservice;
/**
* 创建订单
*/
@postmapping("/create")
public apiresult createorder(@requestparam string userid,
@requestparam double amount) {
try {
order order = orderservice.createorder(userid, amount);
return apiresult.success("订单创建成功", order);
} catch (exception e) {
return apiresult.error("订单创建失败:" + e.getmessage());
}
}
/**
* 模拟支付
*/
@postmapping("/pay")
public apiresult payorder(@requestparam string orderid) {
try {
// 模拟支付处理时间
thread.sleep(1000);
orderservice.handlepaymentcallback(orderid);
return apiresult.success("支付成功");
} catch (interruptedexception e) {
thread.currentthread().interrupt();
return apiresult.error("支付处理中断");
} catch (exception e) {
return apiresult.error("支付失败:" + e.getmessage());
}
}
/**
* 检查订单状态
*/
@getmapping("/status/{orderid}")
public apiresult checkorderstatus(@pathvariable string orderid) {
boolean exists = redisorderservice.isorderexist(orderid);
if (exists) {
return apiresult.success("订单有效,请尽快支付");
} else {
return apiresult.success("订单已超时或不存在");
}
}
}
// 简单的返回结果类
class apiresult {
private boolean success;
private string message;
private object data;
// 构造方法和getter/setter省略...
public static apiresult success(string message) {
return new apiresult(true, message, null);
}
public static apiresult success(string message, object data) {
return new apiresult(true, message, data);
}
public static apiresult error(string message) {
return new apiresult(false, message, null);
}
}第10步:别忘了开启redis的键空间通知(重要!)
在redis配置文件(redis.conf)中或通过redis命令行开启:
# 方式1:配置文件 notify-keyspace-events "ex" # 方式2:命令行(临时生效) redis-cli config set notify-keyspace-events ex
或者在你的spring boot应用启动时自动配置:
import org.springframework.beans.factory.annotation.autowired;
import org.springframework.data.redis.core.redistemplate;
import org.springframework.boot.commandlinerunner;
import org.springframework.stereotype.component;
@component
public class redisconfigrunner implements commandlinerunner {
@autowired
private stringredistemplate stringredistemplate;
@override
public void run(string... args) {
// 开启键过期事件通知
stringredistemplate.getconnectionfactory()
.getconnection()
.servercommands()
.configset("notify-keyspace-events", "ex");
system.out.println("redis键空间通知已开启");
}
}完整的工作流程
- 顾客下单:
post /orders/create→ 订单存入数据库和redis,开始30分钟倒计时 - redis盯梢:闪电侠开始计时,30分钟寸步不离
- 顾客支付:
- 30分钟内支付:
post /orders/pay→ redis删除订单,交易完成 - 超过30分钟:redis键自动过期 → 触发过期事件 → 自动取消订单
- 30分钟内支付:
- 系统通知:给顾客发送“订单已取消”的贴心小提示
一些高级玩法
方案优化:使用redisson的延迟队列(更可靠)
import org.redisson.api.rblockingdeque;
import org.redisson.api.rdelayedqueue;
import org.redisson.api.redissonclient;
import org.springframework.beans.factory.annotation.autowired;
import org.springframework.stereotype.service;
import javax.annotation.postconstruct;
import java.util.concurrent.timeunit;
@service
public class redissonorderservice {
@autowired
private redissonclient redissonclient;
private rblockingdeque<string> orderqueue;
private rdelayedqueue<string> delayedqueue;
@postconstruct
public void init() {
orderqueue = redissonclient.getblockingdeque("orderdelayqueue");
delayedqueue = redissonclient.getdelayedqueue(orderqueue);
// 启动消费者线程
new thread(this::consumeexpiredorders).start();
}
/**
* 添加延迟订单
*/
public void adddelayorder(string orderid, long delay, timeunit unit) {
delayedqueue.offer(orderid, delay, unit);
system.out.println("订单 " + orderid + " 已加入延迟队列,"
+ delay + " " + unit + "后过期");
}
/**
* 消费过期订单
*/
private void consumeexpiredorders() {
while (true) {
try {
// 阻塞获取过期订单
string orderid = orderqueue.take();
system.out.println("延迟队列报告:订单 " + orderid + " 已过期");
// 处理订单取消逻辑...
} catch (interruptedexception e) {
thread.currentthread().interrupt();
break;
}
}
}
}注意事项:防重复处理(幂等性)
// 在orderservice中添加防重复处理
@transactional
public void cancelexpiredorder(string orderid) {
// 使用redis分布式锁,防止多个实例同时处理同一个订单
string lockkey = "order:cancel:lock:" + orderid;
boolean locked = redistemplate.opsforvalue()
.setifabsent(lockkey, "1", 30, timeunit.seconds);
if (boolean.true.equals(locked)) {
try {
// 再次检查订单状态(双重校验)
order order = orderrepository.findbyid(orderid);
if (order != null && order.getstatus() == 0) {
// 更新订单状态
order.setstatus(2);
orderrepository.save(order);
system.out.println("订单 " + orderid + " 已取消");
}
} finally {
// 释放锁
redistemplate.delete(lockkey);
}
}
}总结
- 性能爆表:redis基于内存操作,处理速度堪比闪电侠跑步
- 精准定时:redis的过期机制精准可靠,误差极小
- 解耦神器:业务逻辑和定时任务分离,代码清爽不油腻
- 扩展性强:轻松应对高并发,加个redis集群就能撑起双11
- 资源友好:不需要额外的定时任务中间件,省心省力
但也要注意这些“坑”
- redis持久化:记得配置rdb/aof,不然闪电侠“失忆”就麻烦了
- 网络波动:redis挂了怎么办?要有降级方案
- 事件丢失:redis的过期事件可能丢失,重要业务要有补偿机制
- 时钟同步:多服务器时间要同步,别自己人跟自己人“打架”
最后
想象一下:
- 没有redis时:你的数据库被定时任务扫得气喘吁吁,每次都要问:“哪些订单超时了?”
- 有了redis后:redis主动报告:“嘿!这几个订单超时了,快处理!”
这就好比从“挨家挨户查水表”变成了“水表自己打电话报警”,效率提升不是一点点!好的架构,就是让合适的工具做合适的事。redis就是这个场景下的“时间管理大师”!

到此这篇关于别再用定时任务扫库了!springboot集成redis实现订单超时管理的文章就介绍到这了,更多相关springboot集成redis内容请搜索代码网以前的文章或继续浏览下面的相关文章希望大家以后多多支持代码网!
发表评论