buguge - Keep it simple,stupid

知识就是力量,但更重要的,是运用知识的能力why buguge?

导航

利用J.U.C.ScheduledThreadPoolExecutor来实现订单状态的实时变更

来看一个场景:

一个叫车应用里,用户下单叫车,行程单初始状态为“行程中”,当行程结束后,状态自动变更为“待支付”。

叫车单有开始时间、结束时间。需求是要求当用户查看订单的时候,要及时体现出来最新的状态。就是说,如果是行程结束的订单,那么,展示的状态应该是“待支付”。 “行程中”状态的订单可以分享,“待支付”状态的订单可以发起支付。那么,程序怎么实现这个要求呢?

 

用定时任务?

不靠谱吧,间隔设置的再小也难免有延迟。况且,间隔设置太小,不停地操作数据库,会同时给应用服务器和数据库服务器带来压力。

 

如下几种方案:

【方案1】

最直观的实现方式,这也往往是大家想不到的方式。每个涉及到订单表的select操作,都先执行改状态的操作。更新状态的sql很简单,类似于:

update t_order set order_status= 'DaiZhiFu' where endTime≥now() and order_status='XingChengZhong'

加切面类是不错的实现方式。

只要用户操作或系统操作每次触发查询订单表,都执行这个update。显然,满足了需求。不过呢,弊端也显而易见,每次查单的时候并非都有需要变更状态的订单。如果查询频繁,因此而造成的系统负荷,可能得不偿失。

 

【方案2】中间件延迟消息队列

比如RabbitMQ,定义延迟消息队列。消息进入broker后,是先进入了延迟队列,当到了指定的ttl后,消息进入正常的消息队列进行消费。分布式系统适合用这种消息中间件的方式提高系统可用性。参考我的博客:利用延迟消息队列取代定时任务、 rabbitmq延迟队列demo

延迟消息队列

 

【方案3】

如题,利用juc下的ScheduledThreadPoolExecutor。ScheduledThreadPoolExecutor是java线程池的一员,jdk1.5之后提供的用来支持周期性任务的调度。ScheduledThreadPoolExecutor主要有两个成员,DelayedWorkQueue和ScheduledFutureTask。DelayedWorkQueue是一个延迟的阻塞队列(heap-based data structure,基于堆的数据结构),它会将入队的任务按照执行时间进行升序排序;ScheduledFutureTask,用来描述要执行的任务。通过调用delayedExecute方法来延时执行任务。 不说内部实现了,具体参考大神的blog:【JUC源码解析】ScheduledThreadPoolExecutor /  深入理解Java线程池:ScheduledThreadPoolExecutor。接下来直接看如何使用吧。

 

ThreadPoolBean.java定义ScheduledThreadPoolExecutor线程池对象:

package scheduledthreadpool;

import com.google.common.util.concurrent.ThreadFactoryBuilder;
import lombok.extern.slf4j.Slf4j;import java.util.concurrent.ScheduledThreadPoolExecutor;
import java.util.concurrent.ThreadFactory;

@Slf4j
@Component
public class ThreadPoolBean {
    @Bean
    public ScheduledThreadPoolExecutor bean() {
        ThreadFactory namedThreadFactory = new ThreadFactoryBuilder()
                .setNameFormat("demo_pool_%d").build();

        ScheduledThreadPoolExecutor poolExecutor = new ScheduledThreadPoolExecutor(5, namedThreadFactory);
        return poolExecutor;
    }
}

 

 

OrderTask.java是修改订单状态的任务:

package scheduledthreadpool;

@Slf4j
public class OrderTask implements Runnable {
    private String orderNo;

    public OrderTask(String orderNo) {
        log.info("{} insert", orderNo);
        this.orderNo = orderNo;
    }

    @Override
    public void run() {
        log.info("{} updated", orderNo);
    }
}

 

OrderController.java模拟用户下单叫车的api。

package scheduledthreadpool;

import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestController;
import java.util.concurrent.ScheduledThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

@Slf4j
@RestController
public class OrderController {
    @Autowired
    private ScheduledThreadPoolExecutor orderStatusUpdatePool;

    @ResponseBody
    @GetMapping("/order/add")
    public String add(@RequestParam("sec") int sec) {
        String orderNo = System.currentTimeMillis()+"_"+sec;
        orderStatusUpdatePool.schedule(new OrderTask(orderNo), sec, TimeUnit.SECONDS);
        return "ok:" + orderNo;
    }
}

 

MainApp.java是springboot启动程序。启动程序后,浏览器里访问http://localhost:8080/order/add?sec=**,多次改变sec参数的值,来模拟订单提交。从打印的log可以看出来,到了sec参数所指定的时间后,就会执行OrderTask的逻辑。

 【The End】

posted on 2020-09-03 09:00  buguge  阅读(416)  评论(1编辑  收藏  举报