在京东下单,订单创建成功,等待支付,一般会给30分钟的时间,开始倒计时。如果在这段时间内用户没有支付,则默认订单取消。
该如何实现?
-
定期轮询(数据库等)
用户下单成功,将订单信息放入数据库,同时将支付状态放入数据库,用户付款更
改数据库状态。定期轮询数据库支付状态,如果超过30分钟就将该订单取消。
优点:设计实现简单
缺点:需要对数据库进行大量的IO操作,效率低下。 -
Timer
SimpleDateFormat simpleDateFormat = new
SimpleDateFormat("HH:mm:ss");
Timer timer = new Timer();
TimerTask timerTask = new TimerTask() {
@Override
public void run() {
System.out.println("用户没有付款,交易取消:" +
simpleDateFormat.format(new
Date(System.currentTimeMillis())));
timer.cancel();
}
};
System.out.println("等待用户付款:" +
simpleDateFormat.format(new
Date(System.currentTimeMillis())));
// 10秒后执行timerTask
timer.schedule(timerTask, 10 * 1000);
缺点:
Timers没有持久化机制.
Timers不灵活 (只可以设置开始时间和重复间隔,对等待支付貌似够用)
Timers 不能利用线程池,一个timer一个线程
Timers没有真正的管理计划
- ScheduledExecutorService
SimpleDateFormat format = new SimpleDateFormat("HH:mm:ss");
// 线程工厂
ThreadFactory factory = Executors.defaultThreadFactory();
// 使用线程池
ScheduledExecutorService service = new
ScheduledThreadPoolExecutor(10, factory);
System.out.println("开始等待用户付款10秒:" + format.format(new
Date()));
service.schedule(new Runnable() {
@Override
public void run() {
System.out.println("用户未付款,交易取消:" +
format.format(new Date()));
}// 等待10s 单位秒
}, 10, TimeUnit.SECONDS);
优点:可以多线程执行,一定程度上避免任务间互相影响,单个任务异常不影响其
它任务。
在高并发的情况下,不建议使用定时任务去做,因为太浪费服务器性能,不建议。
- RabbitMQ TTL
- Quartz
- Redis Zset
- JCronTab
- SchedulerX
TTL,Time to Live 的简称,即过期时间。
RabbitMQ 可以对消息和队列两个维度来设置TTL。
任何消息中间件的容量和堆积能力都是有限的,如果有一些消息总是不被消费掉,那么需要有一种
过期的机制来做兜底。
目前有两种方法可以设置消息的TTL。
- 通过Queue属性设置,队列中所有消息都有相同的过期时间。
- 对消息自身进行单独设置,每条消息的TTL 可以不同
如果两种方法一起使用,则消息的TTL 以两者之间较小数值为准。通常来讲,消息在队列中的生存
时间一旦超过设置的TTL 值时,就会变成“死信”(Dead Message),消费者默认就无法再收到该消息。当
然,“死信”也是可以被取出来消费的
原生 API
try(Connection connection = factory.newConnection();
Channel channel = connection.createChannel()) {
// 创建队列(实际上使用的是AMQP default这个direct类型的交换器)
// 设置队列属性
Map<String, Object> arguments = new HashMap<>();
// 设置队列的TTL
arguments.put("x-message-ttl", 30000);
// 设置队列的空闲存活时间(如该队列根本没有消费者,一直没有使用,队列可以存活多久)
arguments.put("x-expires", 10000);
channel.queueDeclare(QUEUE_NAME, false, false, false, arguments);
for (int i = 0; i < 1000000; i++) {
String message = "Hello World!" + i;
channel.basicPublish(
"",
QUEUE_NAME,
new
AMQP.BasicProperties().builder().expiration("30000").build(),
message.getBytes()
);
System.out.println(" [X] Sent '" + message + "'");
}
} catch (TimeoutException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
此外,还可以通过命令行方式设置全局TTL,执行如下命令
rabbitmqctl set_policy TTL ".*" '{"message-ttl":30000}' --apply-to queues
还可以通过restful api方式设置,这里不做过多介绍。
默认规则:
- 如果不设置TTL,则表示此消息不会过期;
- 如果TTL设置为0,则表示除非此时可以直接将消息投递到消费者,否则该消息会被立即丢弃
注意理解 message-ttl 、 x-expires 这两个参数的区别,有不同的含义。但是这两个参数属性都遵循上面的默认规则。一般TTL相关的参数单位都是毫秒(ms)
Spring-boot
- pom
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-amqp</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.springframework.amqp</groupId>
<artifactId>spring-rabbit-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
- 配置文件
spring.application.name=ttl
spring.rabbitmq.host=node1
spring.rabbitmq.virtual-host=/
spring.rabbitmq.username=root
spring.rabbitmq.password=123456
spring.rabbitmq.port=5672
- RabbitConfig 代码
@Configuration
public class RabbitConfig {
@Bean
public Queue queueTTLWaiting() {
Map<String, Object> props = new HashMap<>();
// 对于该队列中的消息,设置都等待10s
props.put("x-message-ttl", 10000);
Queue queue = new Queue("q.pay.ttl-waiting", false, false,
false, props);
return queue;
} @
Bean
public Queue queueWaiting() {
Queue queue = new Queue("q.pay.waiting", false, false,
false);
return queue;
} @
Bean
public Exchange exchangeTTLWaiting() {
DirectExchange exchange = new DirectExchange("ex.pay.ttlwaiting", false, false);
return exchange;
} /
**
* 该交换器使用的时候,需要给每个消息设置有效期
* @return
*/
@Bean
public Exchange exchangeWaiting() {
DirectExchange exchange = new
DirectExchange("ex.pay.waiting", false, false);
return exchange;
} @
Bean
public Binding bindingTTLWaiting() {
return
BindingBuilder.bind(queueTTLWaiting()).to(exchangeTTLWaiting()).wit
h("pay.ttl-waiting").noargs();
} @
Bean
public Binding bindingWaiting() {
return
BindingBuilder.bind(queueWaiting()).to(exchangeWaiting()).with("pay
.waiting").noargs();
}
}
- Controller
@RestController
public class PayController {
@Autowired
private AmqpTemplate rabbitTemplate;
@RequestMapping("/pay/queuettl")
public String sendMessage() {
rabbitTemplate.convertAndSend("ex.pay.ttl-waiting",
"pay.ttl-waiting", "发送了TTL-WAITING-MESSAGE");
return "queue-ttl-ok";
} @
RequestMapping("/pay/msgttl")
public String sendTTLMessage() throws
UnsupportedEncodingException {
MessageProperties properties = new MessageProperties();
properties.setExpiration("5000");
Message message = new Message("发送了WAITINGMESSAGE".getBytes("utf-8"), properties);
rabbitTemplate.convertAndSend("ex.pay.waiting",
"pay.waiting", message);
return "msg-ttl-ok";
}
}