精华内容
下载资源
问答
  • 2022-03-07 16:01:27

     一、Timer

    Timer是JAVA自带的定时任务类,实现如下:

    public class MyTimerTask {    
        public static void main(String[] args) {        
            // 定义一个任务       
            TimerTask timerTask = new TimerTask() {            
            @Override            
                public void run() {                
                System.out.println("打印当前时间:" + new Date());    
                }       
             };        
            // 计时器       
            Timer timer = new Timer();       
            // 开始执行任务 (延迟1000毫秒执行,每3000毫秒执行一次)        
            timer.schedule(timerTask, 1000, 3000);    
        }
    }

    Timer 优缺点分析

    优点是使用简单,缺点是当添加并执行多个任务时,前面任务的执行用时和异常将影响到后面任务,这边深海建议谨慎使用。

    二、ScheduledExecutorService

    ScheduledExecutorService 也是Java自带的类,

    它可以实现Timer具备的所有功能,并解决了 Timer类存在的问题

    实现如下:

    public class MyScheduledExecutorService {    
        public static void main(String[] args) {        
            // 创建任务队列   10 为线程数量      
            ScheduledExecutorService scheduledExecutorService = 
                    Executors.newScheduledThreadPool(10); 
            // 执行任务      
            scheduledExecutorService.scheduleAtFixedRate(() -> {          
                System.out.println("打印当前时间:" + new Date());      
            }, 1, 3, TimeUnit.SECONDS); // 1s 后开始执行,每 3s 执行一次   
    
      }
    }
    

    ScheduledExecutorService 优缺点分析

    优点是,该类是JDK1.5自带的类,使用简单,缺点是该方案仅适用于单机环境。

    三、Spring Task

    Spring系列框架中Spring Framework自带的定时任务,

    使用上面两种方式,很难实现某些特定需求,比如每周一执行某任务,但SpringTask可轻松实现。

    以SpringBoot为例来实现:

    1、开启定时任务

    在SpringBoot的启动类上声明 @EnableScheduling:

    @SpringBootApplication
    @EnableScheduling //开启定时任务
    public class DemoApplication {  
         // --  -- 
    }

    2、添加定时任务

    只需使用@Scheduled注解标注即可,

    如果有多个定时任务,可以创建多个@Scheduled标注的方法,示例如下:

    import org.springframework.scheduling.annotation.Scheduled;
    import org.springframework.stereotype.Component;
    @Component // 把此类托管给 Spring,不能省略
    public class TaskUtils {    
        // 添加定时任务    
        @Scheduled(cron = "30 40 23 0 0 5") // cron表达式:每周一 23:40:30 执行    
        public void doTask(){        
            System.out.println("我是定时任务~");    
        }
    }

     Spring Boot 启动后会自动加载并执行定时任务,无需手动操作。

    Cron 表达式

    Spring Task 的实现需要使用 cron 表达式来声明执行的频率和规则,cron 表达式是由 6 位或者 7 位组成的(最后一位可以省略),每位之间以空格分隔,每位从左到右代表的含义如下:

    5bdc076e79dfa867ee79fae9743731e5.png

    其中 * 和 ? 号都表示匹配所有的时间。

    e941509e3f1ac07ebee74c70f368654f.png

    cron 表达式在线生成地址:https://cron.qqe2.com/

    知识扩展:分布式定时任务

    上面的方法都是关于单机定时任务的实现,如果是分布式环境可以使用 Redis 来实现定时任务。

    使用 Redis 实现延迟任务的方法大体可分为两类:通过 ZSet 的方式和键空间通知的方式

    1、ZSet 实现方式

    通过 ZSet 实现定时任务的思路是,将定时任务存放到 ZSet 集合中,并且将过期时间存储到 ZSet 的 Score 字段中,然后通过一个无线循环来判断当前时间内是否有需要执行的定时任务,如果有则进行执行,具体实现代码如下:

    import redis.clients.jedis.Jedis;
    import utils.JedisUtils;
    import java.time.Instant;
    import java.util.Set;
    public class DelayQueueExample {        
        private static final String _KEY = "DelayQueueExample";        
        public static void main(String[] args) throws InterruptedException {        
            Jedis jedis = JedisUtils.getJedis();        
            // 30s 后执行        
            long delayTime = Instant.now().plusSeconds(30).getEpochSecond();       
            jedis.zadd(_KEY, delayTime, "order_1");        
            // 继续添加测试数据        
           jedis.zadd(_KEY, Instant.now().plusSeconds(2).getEpochSecond(), "order_2");       
          jedis.zadd(_KEY, Instant.now().plusSeconds(2).getEpochSecond(), "order_3");        
          jedis.zadd(_KEY, Instant.now().plusSeconds(7).getEpochSecond(), "order_4");        
         jedis.zadd(_KEY, Instant.now().plusSeconds(10).getEpochSecond(), "order_5");        
            // 开启定时任务队列        
            doDelayQueue(jedis);    
        }    
        /**     
        * 定时任务队列消费     
        * @param jedis Redis 客户端     
        */    
        public static void doDelayQueue(Jedis jedis) throws InterruptedException {        
            while (true) {            
                // 当前时间            
                Instant nowInstant = Instant.now();            
                long lastSecond = nowInstant.plusSeconds(-1).getEpochSecond(); 
                // 上一秒时间            
                long nowSecond = nowInstant.getEpochSecond();            
                // 查询当前时间的所有任务            
                Set data = jedis.zrangeByScore(_KEY, lastSecond, nowSecond);            
                for (String item : data) {                
                // 消费任务                
                System.out.println("消费:" + item);            
            }            
            // 删除已经执行的任务            
            jedis.zremrangeByScore(_KEY, lastSecond, nowSecond);            
            Thread.sleep(1000); // 每秒查询一次        
            }    
        }
    }

    2、键空间通知

    我们可以通过 Redis 的键空间通知来实现定时任务,它的实现思路是给所有的定时任务设置一个过期时间,等到了过期之后,我们通过订阅过期消息就能感知到定时任务需要被执行了,此时我们执行定时任务即可。

    默认情况下 Redis 是不开启键空间通知的,需要我们通过 config set notify-keyspace-events Ex 的命令手动开启,开启之后定时任务的代码如下:

    import redis.clients.jedis.Jedis;
    import redis.clients.jedis.JedisPubSub;
    import utils.JedisUtils;
    public class TaskExample {    
        public static final String _TOPIC = "__keyevent@0__:expired"; // 订阅频道名称   
        public static void main(String[] args) {       
            Jedis jedis = JedisUtils.getJedis();       
            // 执行定时任务        
            doTask(jedis);    
        }   
         /**     
           * 订阅过期消息,执行定时任务     
           * @param jedis Redis 客户端     
           */    
        public static void doTask(Jedis jedis) {        
            // 订阅过期消息        
            jedis.psubscribe(new JedisPubSub() {            
                @Override            
     public void onPMessage(String pattern, String channel, String message) {                
                // 接收到消息,执行定时任务                
                System.out.println("收到消息:" + message);            
                }            
            }, _TOPIC);    
        }
    }
    更多相关内容
  • 定时任务简单的3种实现方法(超好用)

    万次阅读 多人点赞 2020-08-18 07:00:00
    这是我的第86篇原创文章作者 | 王磊来源 | Java中文社群(ID:javacn666)转载请联系授权(微信ID:GG_Stone)定时任务在实际的开发中特别常见,比如电商平台 ...

    这是我的第 86 篇原创文章

    作者 | 王磊

    来源 | Java中文社群(ID:javacn666)

    转载请联系授权(微信ID:GG_Stone)

    定时任务在实际的开发中特别常见,比如电商平台 30 分钟后自动取消未支付的订单,以及凌晨的数据汇总和备份等,都需要借助定时任务来实现,那么我们本文就来看一下定时任务最简单的几种实现方式。

    TOP 1:Timer

    Timer 是 JDK 自带的定时任务执行类,无论任何项目都可以直接使用 Timer 来实现定时任务,所以 Timer 的优点就是使用方便,它的实现代码如下:

    public class MyTimerTask {
        public static void main(String[] args) {
            // 定义一个任务
            TimerTask timerTask = new TimerTask() {
                @Override
                public void run() {
                    System.out.println("Run timerTask:" + new Date());
                }
            };
            // 计时器
            Timer timer = new Timer();
            // 添加执行任务(延迟 1s 执行,每 3s 执行一次)
            timer.schedule(timerTask, 1000, 3000);
        }
    }
    

    程序执行结果如下:

    Run timerTask:Mon Aug 17 21:29:25 CST 2020

    Run timerTask:Mon Aug 17 21:29:28 CST 2020

    Run timerTask:Mon Aug 17 21:29:31 CST 2020

    Timer 缺点分析

    Timer 类实现定时任务虽然方便,但在使用时需要注意以下问题。

    问题 1:任务执行时间长影响其他任务

    当一个任务的执行时间过长时,会影响其他任务的调度,如下代码所示:

    public class MyTimerTask {
        public static void main(String[] args) {
            // 定义任务 1
            TimerTask timerTask = new TimerTask() {
                @Override
                public void run() {
                    System.out.println("进入 timerTask 1:" + new Date());
                    try {
                        // 休眠 5 秒
                        TimeUnit.SECONDS.sleep(5);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println("Run timerTask 1:" + new Date());
                }
            };
            // 定义任务 2
            TimerTask timerTask2 = new TimerTask() {
                @Override
                public void run() {
                    System.out.println("Run timerTask 2:" + new Date());
                }
            };
            // 计时器
            Timer timer = new Timer();
            // 添加执行任务(延迟 1s 执行,每 3s 执行一次)
            timer.schedule(timerTask, 1000, 3000);
            timer.schedule(timerTask2, 1000, 3000);
        }
    }
    

    程序执行结果如下:

    进入 timerTask 1:Mon Aug 17 21:44:08 CST 2020

    Run timerTask 1:Mon Aug 17 21:44:13 CST 2020

    Run timerTask 2:Mon Aug 17 21:44:13 CST 2020

    进入 timerTask 1:Mon Aug 17 21:44:13 CST 2020

    Run timerTask 1:Mon Aug 17 21:44:18 CST 2020

    进入 timerTask 1:Mon Aug 17 21:44:18 CST 2020

    Run timerTask 1:Mon Aug 17 21:44:23 CST 2020

    Run timerTask 2:Mon Aug 17 21:44:23 CST 2020

    进入 timerTask 1:Mon Aug 17 21:44:23 CST 2020

    从上述结果中可以看出,当任务 1 运行时间超过设定的间隔时间时,任务 2 也会延迟执行。 原本任务 1 和任务 2 的执行时间间隔都是 3s,但因为任务 1 执行了 5s,因此任务 2 的执行时间间隔也变成了 10s(和原定时间不符)。

    问题 2:任务异常影响其他任务

    使用 Timer 类实现定时任务时,当一个任务抛出异常,其他任务也会终止运行,如下代码所示:

    public class MyTimerTask {
        public static void main(String[] args) {
            // 定义任务 1
            TimerTask timerTask = new TimerTask() {
                @Override
                public void run() {
                    System.out.println("进入 timerTask 1:" + new Date());
                    // 模拟异常
                    int num = 8 / 0;
                    System.out.println("Run timerTask 1:" + new Date());
                }
            };
            // 定义任务 2
            TimerTask timerTask2 = new TimerTask() {
                @Override
                public void run() {
                    System.out.println("Run timerTask 2:" + new Date());
                }
            };
            // 计时器
            Timer timer = new Timer();
            // 添加执行任务(延迟 1s 执行,每 3s 执行一次)
            timer.schedule(timerTask, 1000, 3000);
            timer.schedule(timerTask2, 1000, 3000);
        }
    }
    

    程序执行结果如下:

    进入 timerTask 1:Mon Aug 17 22:02:37 CST 2020

    Exception in thread "Timer-0" java.lang.ArithmeticException: / by zero

        at com.example.MyTimerTask$1.run(MyTimerTask.java:21)

        at java.util.TimerThread.mainLoop(Timer.java:555)

        at java.util.TimerThread.run(Timer.java:505)

    Process finished with exit code 0

    Timer 小结

    Timer 类实现定时任务的优点是方便,因为它是 JDK 自定的定时任务,但缺点是任务如果执行时间太长或者是任务执行异常,会影响其他任务调度,所以在生产环境下建议谨慎使用。

    TOP 2:ScheduledExecutorService

    ScheduledExecutorService 也是 JDK 1.5 自带的 API,我们可以使用它来实现定时任务的功能,也就是说 ScheduledExecutorService 可以实现 Timer 类具备的所有功能,并且它可以解决了 Timer 类存在的所有问题

    ScheduledExecutorService 实现定时任务的代码示例如下:

    public class MyScheduledExecutorService {
        public static void main(String[] args) {
            // 创建任务队列
            ScheduledExecutorService scheduledExecutorService =
                    Executors.newScheduledThreadPool(10); // 10 为线程数量
      // 执行任务
            scheduledExecutorService.scheduleAtFixedRate(() -> {
                System.out.println("Run Schedule:" + new Date());
            }, 1, 3, TimeUnit.SECONDS); // 1s 后开始执行,每 3s 执行一次
        }
    }
    

    程序执行结果如下:

    Run Schedule:Mon Aug 17 21:44:23 CST 2020

    Run Schedule:Mon Aug 17 21:44:26 CST 2020

    Run Schedule:Mon Aug 17 21:44:29 CST 2020

    ScheduledExecutorService 可靠性测试

    ① 任务超时执行测试

    ScheduledExecutorService 可以解决 Timer 任务之间相应影响的缺点,首先我们来测试一个任务执行时间过长,会不会对其他任务造成影响,测试代码如下:

    public class MyScheduledExecutorService {
        public static void main(String[] args) {
            // 创建任务队列
            ScheduledExecutorService scheduledExecutorService =
                    Executors.newScheduledThreadPool(10);
            // 执行任务 1
            scheduledExecutorService.scheduleAtFixedRate(() -> {
                System.out.println("进入 Schedule:" + new Date());
                try {
                    // 休眠 5 秒
                    TimeUnit.SECONDS.sleep(5);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("Run Schedule:" + new Date());
            }, 1, 3, TimeUnit.SECONDS); // 1s 后开始执行,每 3s 执行一次
            // 执行任务 2
            scheduledExecutorService.scheduleAtFixedRate(() -> {
                System.out.println("Run Schedule2:" + new Date());
            }, 1, 3, TimeUnit.SECONDS); // 1s 后开始执行,每 3s 执行一次
        }
    }
    

    程序执行结果如下:

    Run Schedule2:Mon Aug 17 11:27:55 CST 2020

    进入 Schedule:Mon Aug 17 11:27:55 CST 2020

    Run Schedule2:Mon Aug 17 11:27:58 CST 2020

    Run Schedule:Mon Aug 17 11:28:00 CST 2020

    进入 Schedule:Mon Aug 17 11:28:00 CST 2020

    Run Schedule2:Mon Aug 17 11:28:01 CST 2020

    Run Schedule2:Mon Aug 17 11:28:04 CST 2020

    从上述结果可以看出,当任务 1 执行时间 5s 超过了执行频率 3s 时,并没有影响任务 2 的正常执行,因此使用 ScheduledExecutorService 可以避免任务执行时间过长对其他任务造成的影响

    ② 任务异常测试

    接下来我们来测试一下 ScheduledExecutorService 在一个任务异常时,是否会对其他任务造成影响,测试代码如下:

    public class MyScheduledExecutorService {
        public static void main(String[] args) {
            // 创建任务队列
            ScheduledExecutorService scheduledExecutorService =
                    Executors.newScheduledThreadPool(10);
            // 执行任务 1
            scheduledExecutorService.scheduleAtFixedRate(() -> {
                System.out.println("进入 Schedule:" + new Date());
                // 模拟异常
                int num = 8 / 0;
                System.out.println("Run Schedule:" + new Date());
            }, 1, 3, TimeUnit.SECONDS); // 1s 后开始执行,每 3s 执行一次
            // 执行任务 2
            scheduledExecutorService.scheduleAtFixedRate(() -> {
                System.out.println("Run Schedule2:" + new Date());
            }, 1, 3, TimeUnit.SECONDS); // 1s 后开始执行,每 3s 执行一次
        }
    }
    

    程序执行结果如下:

    进入 Schedule:Mon Aug 17 22:17:37 CST 2020

    Run Schedule2:Mon Aug 17 22:17:37 CST 2020

    Run Schedule2:Mon Aug 17 22:17:40 CST 2020

    Run Schedule2:Mon Aug 17 22:17:43 CST 2020

    从上述结果可以看出,当任务 1 出现异常时,并不会影响任务 2 的执行

    ScheduledExecutorService 小结

    在单机生产环境下建议使用 ScheduledExecutorService 来执行定时任务,它是 JDK 1.5 之后自带的 API,因此使用起来也比较方便,并且使用 ScheduledExecutorService 来执行任务,不会造成任务间的相互影响。

    TOP 3:Spring Task

    如果使用的是 Spring 或 Spring Boot 框架,可以直接使用 Spring Framework 自带的定时任务,使用上面两种定时任务的实现方式,很难实现设定了具体时间的定时任务,比如当我们需要每周五来执行某项任务时,但如果使用 Spring Task 就可轻松的实现此需求。

    以 Spring Boot 为例,实现定时任务只需两步:

    1. 开启定时任务;

    2. 添加定时任务。

    具体实现步骤如下。

    ① 开启定时任务

    开启定时任务只需要在 Spring Boot 的启动类上声明 @EnableScheduling 即可,实现代码如下:

    @SpringBootApplication
    @EnableScheduling // 开启定时任务
    public class DemoApplication {
        // do someing
    }
    

    ② 添加定时任务

    定时任务的添加只需要使用 @Scheduled 注解标注即可,如果有多个定时任务可以创建多个 @Scheduled 注解标注的方法,示例代码如下:

    import org.springframework.scheduling.annotation.Scheduled;
    import org.springframework.stereotype.Component;
    
    @Component // 把此类托管给 Spring,不能省略
    public class TaskUtils {
        // 添加定时任务
        @Scheduled(cron = "59 59 23 0 0 5") // cron 表达式,每周五 23:59:59 执行
        public void doTask(){
            System.out.println("我是定时任务~");
        }
    }
    

    注意:定时任务是自动触发的无需手动干预,也就是说 Spring Boot 启动后会自动加载并执行定时任务。

    Cron 表达式

    Spring Task 的实现需要使用 cron 表达式来声明执行的频率和规则,cron 表达式是由 6 位或者 7 位组成的(最后一位可以省略),每位之间以空格分隔,每位从左到右代表的含义如下:

    其中 * 和 ? 号都表示匹配所有的时间。

    cron 表达式在线生成地址:https://cron.qqe2.com/

    知识扩展:分布式定时任务

    上面的方法都是关于单机定时任务的实现,如果是分布式环境可以使用 Redis 来实现定时任务。

    使用 Redis 实现延迟任务的方法大体可分为两类:通过 ZSet 的方式和键空间通知的方式

    ① ZSet 实现方式

    通过 ZSet 实现定时任务的思路是,将定时任务存放到 ZSet 集合中,并且将过期时间存储到 ZSet 的 Score 字段中,然后通过一个无线循环来判断当前时间内是否有需要执行的定时任务,如果有则进行执行,具体实现代码如下:

    import redis.clients.jedis.Jedis;
    import utils.JedisUtils;
    import java.time.Instant;
    import java.util.Set;
    
    public class DelayQueueExample {
        // zset key
        private static final String _KEY = "myTaskQueue";
        
        public static void main(String[] args) throws InterruptedException {
            Jedis jedis = JedisUtils.getJedis();
            // 30s 后执行
            long delayTime = Instant.now().plusSeconds(30).getEpochSecond();
            jedis.zadd(_KEY, delayTime, "order_1");
            // 继续添加测试数据
            jedis.zadd(_KEY, Instant.now().plusSeconds(2).getEpochSecond(), "order_2");
            jedis.zadd(_KEY, Instant.now().plusSeconds(2).getEpochSecond(), "order_3");
            jedis.zadd(_KEY, Instant.now().plusSeconds(7).getEpochSecond(), "order_4");
            jedis.zadd(_KEY, Instant.now().plusSeconds(10).getEpochSecond(), "order_5");
            // 开启定时任务队列
            doDelayQueue(jedis);
        }
    
        /**
         * 定时任务队列消费
         * @param jedis Redis 客户端
         */
        public static void doDelayQueue(Jedis jedis) throws InterruptedException {
            while (true) {
                // 当前时间
                Instant nowInstant = Instant.now();
                long lastSecond = nowInstant.plusSeconds(-1).getEpochSecond(); // 上一秒时间
                long nowSecond = nowInstant.getEpochSecond();
                // 查询当前时间的所有任务
                Set<String> data = jedis.zrangeByScore(_KEY, lastSecond, nowSecond);
                for (String item : data) {
                    // 消费任务
                    System.out.println("消费:" + item);
                }
                // 删除已经执行的任务
                jedis.zremrangeByScore(_KEY, lastSecond, nowSecond);
                Thread.sleep(1000); // 每秒查询一次
            }
        }
    }
    

    ② 键空间通知

    我们可以通过 Redis 的键空间通知来实现定时任务,它的实现思路是给所有的定时任务设置一个过期时间,等到了过期之后,我们通过订阅过期消息就能感知到定时任务需要被执行了,此时我们执行定时任务即可。

    默认情况下 Redis 是不开启键空间通知的,需要我们通过 config set notify-keyspace-events Ex 的命令手动开启,开启之后定时任务的代码如下:

    import redis.clients.jedis.Jedis;
    import redis.clients.jedis.JedisPubSub;
    import utils.JedisUtils;
    
    public class TaskExample {
        public static final String _TOPIC = "__keyevent@0__:expired"; // 订阅频道名称
        public static void main(String[] args) {
            Jedis jedis = JedisUtils.getJedis();
            // 执行定时任务
            doTask(jedis);
        }
    
        /**
         * 订阅过期消息,执行定时任务
         * @param jedis Redis 客户端
         */
        public static void doTask(Jedis jedis) {
            // 订阅过期消息
            jedis.psubscribe(new JedisPubSub() {
                @Override
                public void onPMessage(String pattern, String channel, String message) {
                    // 接收到消息,执行定时任务
                    System.out.println("收到消息:" + message);
                }
            }, _TOPIC);
        }
    }
    

    更多关于定时任务的实现,请点击《史上最全的延迟任务实现方式汇总!附代码》。

    
    
    往期推荐
    

    史上最全的延迟任务实现方式汇总!附代码(强烈推荐)


    磊哥最近面试了好多人,聊聊我的感受!(附面试知识点)

    关注下方二维码,查看更多干货!

    展开全文
  • FreeRTOS系列第15篇---使用任务通知实现命令行解释器

    万次阅读 多人点赞 2016-02-12 09:27:02
    这一方面是因为任务通知使用起来非常简单,另一方面也因为对于嵌入式程序来说,使用命令行解释器来辅助程序调试是非常有用的。程序调试是一门技术,基本上我们需要两种调试手段,一种是可以单步仿真的硬件调试器,...

          虽然这是介绍FreeRTOS系列的文章,但这篇文章偏重于命令行解释器的实现。这一方面是因为任务通知使用起来非常简单,另一方面也因为对于嵌入式程序来说,使用命令行解释器来辅助程序调试是非常有用的。程序调试是一门技术,基本上我们需要两种调试手段,一种是可以单步仿真的硬件调试器,另外一种是可以长期监视程序状态的状态输出,可以通过串口、显示屏等等手段输出异常信息或者某些关键点。这里的命令行解释器就属于后者。

         本文实现的命令行解释器具有以下特性:

    • 支持十进制参数,识别负号;
    • 支持十六进制参数,十六进制以‘0x’开始;
    • 命令名长度可定义,默认最大20个字符;
    • 参数数目可定义,默认最多8个参数;
    • 命令名和参数之间以空格隔开,空格个数任意;
    • 整条命令以回车换行符结束;
    • 整条命令最大长度可定义,默认64字节,包括回车换行符;
    • 如果使用SecureCRT串口工具(推荐),支持该软件的控制字符,比如退格键、左移键、右移键等。

          一个带参数的命令格式如下所示:

                                          参数名 <参数1> <参数2> … <参数3>[回车换行符]

    1.编码风格

          FreeRTOS的编码标准及风格见《FreeRTOS系列第4篇---FreeRTOS编码标准及风格指南》,但我自己的编码风格跟FreeRTOS并不相同,并且我也不打算改变我当前坚持使用的编码风格。所以在这篇或者以后的文章中可能会在一个程序中看到两种不同的编码风格,对于涉及FreeRTOS的代码,我尽可能使用FreeRTOS建议的编码风格,与FreeRTOS无关的代码,我仍然使用自己的编码风格。我可以保证,两种编码风格决不会影响程序的可读性,编写良好可读性的代码,是我一直注重并坚持的。

    2.一些准备工作

    2.1串口硬件驱动

          命令行解释器使用一个硬件串口,需要外部提供两个串口底层函数:一个是串口初始化函数init_cmd_uart(),用于初始化串口波特率、中断等事件;另一个是发送单个字符函数my_putc()。此外,命令行为串口接收中断服务程序提供函数fill_rec_buf(),用于保存接收到的字符,当收到回车换行符后,该函数向命令行分析任务发送通知。

    2.2一个类printf函数

          类printf函数用来格式化输出,我一般用来辅助调试,为了方便的将调试代码从程序中去除,需要将类printf函数进行封装。我的文章《编写优质嵌入式C程序》第5.2节给出了一个完整的类printf函数实现和封装代码,最终我们使用到的类printf函数是如下形式的宏:

     MY_DEBUGF(CMD_LINE_DEBUG,("第%d个参数:%d\n",i+1,arg[i]));    

    3.使用任务通知

          我们将会创建一个任务,用来分析接收到的命令,如果命令有效则调用命令实现函数。这个任务名字为vTaskCmdAnalyze()。串口接收中断用于接收命令,如果接收到回车换行符,则向任务vTaskCmdAnalyze()发送任务通知,表明已经接收到一条完整命令,任务可以去处理了。

           示意框图如图3-1所示。


    4.数据结构

          命令行解释器程序需要涉及两个数据结构:一个与命令有关,包括命令的名字、命令的最大参数数目、命令的回调函数类型、命令帮助信息等;另一个与分析命令有关,包括接收命令字符缓冲区、存放参数缓冲区等。

    4.1与命令有关的数据结构

           定义如下:

       typedef struct {
            char const *cmd_name;                        //命令字符串
            int32_t max_args;                            //最大参数数目
            void (*handle)(int argc,void * cmd_arg);     //命令回调函数
            char  *help;                                 //帮助信息
        }cmd_list_struct;

          需要说明一下命令回调函数的参数,argc保存接收到的参数数目,cmd_arg指向参数缓冲区,目前只支持32位的整形参数,这在绝大多数嵌入式场合是足够的。

    4.2与分析命令有关数据结构

           定义如下:

    #define ARG_NUM     8          //命令中允许的参数个数
    #define CMD_LEN     20         //命令名占用的最大字符长度
    #define CMD_BUF_LEN 60         //命令缓存的最大长度
     
    typedef struct {
        char rec_buf[CMD_BUF_LEN];            //接收命令缓冲区
        char processed_buf[CMD_BUF_LEN];      //存储加工后的命令(去除控制字符)
        int32_t cmd_arg[ARG_NUM];             //保存命令的参数
    }cmd_analyze_struct;

          缓冲区的大小使用宏来定义,通过更改相应的宏定义,可以设置整条命令的最大长度、命令参数最大数目等。

    5.串口接收中断处理函数

          本文使用的串口软件是SecureCRT,在这个软件下敲击的任何键盘字符,都会立刻通过串口硬件发送出去,这与Telnet类似。所以我们无需使用串口的FIFO,每接收到一个字符就产生一次中断。串口中断与硬件关系密切,所以命令行解释器提供了一个与硬件无关的函数fill_rec_buf(),每当串口中断接收到一个字符,就以收到的字符为参数调用这个函数。       fill_rec_buf()函数主要操作变量cmd_analyze,变量的声明原型为:

           cmd_analyze_struct cmd_analyze;

           函数fill_rec_buf()的实现代码为:

    /*提供给串口中断服务程序,保存串口接收到的单个字符*/
    void fill_rec_buf(char data)
    {
        //接收数据 
        static uint32_t rec_count=0;
       
       cmd_analyze.rec_buf[rec_count]=data;
        if(0x0A==cmd_analyze.rec_buf[rec_count] && 0x0D==cmd_analyze.rec_buf[rec_count-1])
        {
           BaseType_t xHigherPriorityTaskWoken = pdFALSE;
           rec_count=0;
           
           /*收到一帧数据,向命令行解释器任务发送通知*/
           vTaskNotifyGiveFromISR (xCmdAnalyzeHandle,&xHigherPriorityTaskWoken);
           
           /*是否需要强制上下文切换*/
           portYIELD_FROM_ISR(xHigherPriorityTaskWoken );
        }
        else
        {
           rec_count++;
           
           /*防御性代码,防止数组越界*/
           if(rec_count>=CMD_BUF_LEN)
           {
               rec_count=0;
           }
        }    
    }

    6.命令行分析任务

          命令行分析任务大部分时间都会因为等待任务通知而处于阻塞状态。当接收到一个通知后,任务首先去除命令行中的无效字符和控制字符,然后找出命令名并分析参数数目、将参数转换成十六进制数并保存到参数缓冲区中,最后检查命令名和参数是否合法,如果合法则调用命令回调函数处理本条命令。

    6.1去除无效字符和控制字符

          串口软件SecureCRT支持控制字符。比如在输入一串命令的时候,发现某个字符输入错误,就要使用退格键或者左右移动键定位到错误的位置进行修改。这里的退格键和左右移动键都属于控制字符,比如退格键的键值为0x08、左移键的键值为0x1B0x5B 0x44。我们之前也说过,在软件SecureCRT中输入字符时,每敲击一个字符,该字符立刻通过串口发送给我们的嵌入式设备,也就是所有键值都会按照敲击键盘的顺序存入到接收缓冲区中,但这里面可能有我们不需要的字符,我们首先需要利用控制字符将不需要的字符删除掉。这个工作由函数get_true_char_stream()实现,代码如下所示:

    /**
    * 使用SecureCRT串口收发工具,在发送的字符流中可能带有不需要的字符以及控制字符,
    * 比如退格键,左右移动键等等,在使用命令行工具解析字符流之前,需要将这些无用字符以
    * 及控制字符去除掉.
    * 支持的控制字符有:
    *   上移:1B 5B 41
    *   下移:1B 5B 42
    *   右移:1B 5B 43
    *   左移:1B 5B 44
    *   回车换行:0D 0A
    *  Backspace:08
    *  Delete:7F
    */
    static uint32_t get_true_char_stream(char *dest,const char *src)
    {
       uint32_t dest_count=0;
       uint32_t src_count=0;
       
        while(src[src_count]!=0x0D && src[src_count+1]!=0x0A)
        {
           if(isprint(src[src_count]))
           {
               dest[dest_count++]=src[src_count++];
           }
           else
           {
               switch(src[src_count])
               {
                    case    0x08:                          //退格键键值
                    {
                        if(dest_count>0)
                        {
                            dest_count --;
                        }
                        src_count ++;
                    }break;
                    case    0x1B:
                    {
                        if(src[src_count+1]==0x5B)
                        {
                            if(src[src_count+2]==0x41 || src[src_count+2]==0x42)
                            {
                                src_count +=3;              //上移和下移键键值
                            }
                            else if(src[src_count+2]==0x43)
                            {
                                dest_count++;               //右移键键值
                                src_count+=3;
                            }
                            else if(src[src_count+2]==0x44)
                            {
                                if(dest_count >0)           //左移键键值
                                {
                                    dest_count --;
                                }
                               src_count +=3;
                            }
                            else
                            {
                                src_count +=3;
                            }
                        }
                        else
                        {
                            src_count ++;
                        }
                    }break;
                    default:
                    {
                        src_count++;
                    }break;
               }
           }
        }
       dest[dest_count++]=src[src_count++];
        dest[dest_count++]=src[src_count++];
        return dest_count;
    }

    6.2参数分析

          接收到的命令中可能带有参数,我们需要知道参数的数目,还需要把字符型的参数转换成整形数并保存到参数缓冲区(这是因为命令回调函数需要这两个参数)。这个工作由函数cmd_arg_analyze()实现,代码如下所示:

    /**
    * 命令参数分析函数,以空格作为一个参数结束,支持输入十六进制数(如:0x15),支持输入负数(如-15)
    * @param rec_buf   命令参数缓存区
    * @param len       命令的最大可能长度
    * @return -1:       参数个数过多,其它:参数个数
    */
    static int32_t cmd_arg_analyze(char *rec_buf,unsigned int len)
    {
       uint32_t i;
       uint32_t blank_space_flag=0;    //空格标志
       uint32_t arg_num=0;             //参数数目
       uint32_t index[ARG_NUM];        //有效参数首个数字的数组索引
       
        /*先做一遍分析,找出参数的数目,以及参数段的首个数字所在rec_buf数组中的下标*/
        for(i=0;i<len;i++)
        {
           if(rec_buf[i]==0x20)        //为空格
           {
               blank_space_flag=1;              
               continue;
           }
            else if(rec_buf[i]==0x0D)   //换行
           {
               break;
           }
           else
           {
               if(blank_space_flag==1)
               {
                    blank_space_flag=0; 
                    if(arg_num < ARG_NUM)
                    {
                       index[arg_num]=i;
                        arg_num++;         
                    }
                    else
                    {
                        return -1;      //参数个数太多
                    }
               }
           }
        }
       
        for(i=0;i<arg_num;i++)
        {
            cmd_analyze.cmd_arg[i]=string_to_dec((unsigned char *)(rec_buf+index[i]),len-index[i]);
        }
        return arg_num;
    }

          在这个函数cmd_arg_analyze()中,调用了字符转整形函数string_to_dec()。我们只支持整形参数,这里给出一个字符转整形函数的简单实现,可以识别负号和十六进制的前缀’0x’。在这个函数中调用了三个C库函数,分别是isdigit()、isxdigit()和tolower(),因此需要包含头文件#include <ctype.h>。函数string_to_dec()实现代码如下:

    /*字符串转10/16进制数*/
    static int32_t string_to_dec(uint8_t *buf,uint32_t len)
    {
       uint32_t i=0;
       uint32_t base=10;       //基数
       int32_t  neg=1;         //表示正负,1=正数
       int32_t  result=0;
       
        if((buf[0]=='0')&&(buf[1]=='x'))
        {
           base=16;
           neg=1;
           i=2;
        }
        else if(buf[0]=='-')
        {
           base=10;
           neg=-1;
           i=1;
        }
        for(;i<len;i++)
        {
           if(buf[i]==0x20 || buf[i]==0x0D)    //为空格
           {
               break;
           }
           
           result *= base;
           if(isdigit(buf[i]))                 //是否为0~9
           {
               result += buf[i]-'0';
           }
           else if(isxdigit(buf[i]))           //是否为a~f或者A~F
           {
                result+=tolower(buf[i])-87;
           }
           else
           {
               result += buf[i]-'0';
           }                                        
        }
       result *= neg;
       
        return result ;
    }

    6.3定义命令回调函数

          我们举两个例子:第一个是不带参数的例子,输入命令后,函数返回一个“Helloworld!”字符串;第二个是带参数的例子,我们输入命令和参数后,函数返回每一个参数值。我们在讲数据结构的时候特别提到过命令回调函数的原型,这里要根据这个函数原型来声明命令回调函数。

    6.3.1不带参数的命令回调函数举例

    /*打印字符串:Hello world!*/
    void printf_hello(int32_t argc,void *cmd_arg)
    {
       MY_DEBUGF(CMD_LINE_DEBUG,("Hello world!\n"));
    }

    6.3.2带参数的命令行回调函数举例

    /*打印每个参数*/
    void handle_arg(int32_t argc,void * cmd_arg)
    {
       uint32_t i;
       int32_t  *arg=(int32_t *)cmd_arg;
       
        if(argc==0)
        {
           MY_DEBUGF(CMD_LINE_DEBUG,("无参数\n"));
        }
        else
        {
           for(i=0;i<argc;i++)
           {
               MY_DEBUGF(CMD_LINE_DEBUG,("第%d个参数:%d\n",i+1,arg[i]));
           }
        }
    }

    6.4定义命令表

          在讲数据结构的时候,我们定义了与命令有关的数据结构。每条命令需要包括命名名、最大参数、命令回调函数、帮助等信息,这里要将每条命令组织成列表的形式。

    /*命令表*/
    const cmd_list_struct cmd_list[]={
    /*   命令    参数数目    处理函数        帮助信息                         */   
    {"hello",   0,      printf_hello,   "hello                      -打印HelloWorld!"},
    {"arg",     8,      handle_arg,      "arg<arg1> <arg2> ...      -测试用,打印输入的参数"},
    };

          如果要定义自己的命令,只需要按照6.3节的格式编写命令回调函数,然后将命令名、参数数目、回调函数和帮助信息按照本节格式加入到命令表中即可。

    6.5命令行分析任务实现

          有了上面的基础,命令行分析任务实现起来就非常轻松了,源码如下:

    /*命令行分析任务*/
    void vTaskCmdAnalyze( void *pvParameters )
    {
       uint32_t i;
       int32_t rec_arg_num;
        char cmd_buf[CMD_LEN];      
       
        while(1)
        {
           uint32_t rec_num;
           
           ulTaskNotifyTake(pdTRUE,portMAX_DELAY);
        rec_num=get_true_char_stream(cmd_analyze.processed_buf,cmd_analyze.rec_buf);
           
           /*从接收数据中提取命令*/
           for(i=0;i<CMD_LEN;i++)
           {
               if((i>0)&&((cmd_analyze.processed_buf[i]==' ')||(cmd_analyze.processed_buf[i]==0x0D)))
               {
                    cmd_buf[i]='\0';        //字符串结束符
                    break;
               }
               else
               {
                    cmd_buf[i]=cmd_analyze.processed_buf[i];
               }
           }
           
           rec_arg_num=cmd_arg_analyze(&cmd_analyze.processed_buf[i],rec_num);
           
           for(i=0;i<sizeof(cmd_list)/sizeof(cmd_list[0]);i++)
           {
               if(!strcmp(cmd_buf,cmd_list[i].cmd_name))       //字符串相等
               {
                    if(rec_arg_num<0 || rec_arg_num>cmd_list[i].max_args)
                    {
                        MY_DEBUGF(CMD_LINE_DEBUG,("参数数目过多!\n"));
                    }
                    else
                    {
                        cmd_list[i].handle(rec_arg_num,(void *)cmd_analyze.cmd_arg);
                    }
                    break;
               }
               
           }
           if(i>=sizeof(cmd_list)/sizeof(cmd_list[0]))
           {
               MY_DEBUGF(CMD_LINE_DEBUG,("不支持的指令!\n"));
           }
        }
    }

    7.使用的串口工具

          推荐使用SecureCRT软件,这是我觉得最适合命令行交互的串口工具。此外,这个软件非常强大,除了支持串口,还支持SSH、Telnet等。对于串口,SecureCRT工具还支持文件发送协议:Xmodem、Ymodem和Zmodem。这在使用串口远程升级时很有用,可以用来发送新的程序二进制文件。我曾经使用Ymodem做过远程升级,以后有时间再详细介绍SecureCRT的Ymodem功能细节。

          要用于本文介绍的命令行解释器,要对SecureCRT软件做一些设置。

    7.1设置串口参数

          选择Serial功能、设置端口、波特率、校验等,特别要注意的是不要勾选任何流控制选项,如图2-1所示。


    图2-1:设置串口参数

    7.2设置新行模式

           依次点击菜单栏的“选项”---“会话选项”,在弹出的“会话选项”界面中,点击左边树形菜单的“终端”---“仿真”---“模式”,在右边的仿真模式区域选中“换行”和“新行模式”,如图2-2所示。


    图2-2:设置新行模式

     

    7.3设置本地回显

           依次点击菜单栏的“选项”---“会话选项”,在弹出的“会话选项”界面中,点击左边树形菜单的“终端”---“仿真”---“高级”,在右边的“高级仿真”区域,选中“本地回显”,如图2-3所示。


    图2-3:设置本地回显

    8.测试

           我们通过6.3节和6.4接定义了两个命令,第一条命令的名字为”hello”,这是一个无参数命令,直接输出字符串”Hello world!”。第二条命令的名字为”arg”,是一个带参数命令,输出每个参数的值。下面对这两个命令进行测试。

    8.1无参数命令测试

           设置好SecureCRT软件,输入字符”hello”后,按下回车键,设备会返回字符串”Hello world!”。如图8-1所示。


    图8-1:无参数命令测试

    8.2带参数命令测试

           设置好SecureCRT软件,输入字符”arg 1 2 -3 0x0a”后,按下回车键,设备会返回每个参数值。如图8-2所示。


    图8-2:带参数命令测试

    展开全文
  • 使用Quartz实现定时任务(包含管理界面)

    万次阅读 多人点赞 2019-01-01 18:43:34
    ")来实现的,至于监控方面的,没有,就是通过在定时任务代码里面打一些日志,特别重要的定时任务,失败了额外发个邮件通知下,人工补偿。然后他又问了下现在需要重构定时任务,你有没有什么想法?然后我就简单的...

    引言

    年底出去面试的的时候,被问到如下问题: 定时任务是如何实现的?定时任务是如何监控的?因为我们项目中的定时任务就是使用Spring的@Scheduled(cron = "0 59 23 * * ?")来实现的,至于监控方面的,没有,就是通过在定时任务代码里面打一些日志,特别重要的定时任务,失败了额外发个邮件通知下,人工补偿。然后他又问了下现在需要重构定时任务,你有没有什么想法?然后我就简单的说了下,最好是提供个可视化界面,可以动态的操作定时任务,尤其是监控方面。当晚回来后找了些资料,无意间在"纯洁的微笑"博主分享的一篇博文中讲到了这个,他的博文原文地址找不到了,他上面是基于spring项目实现的,本人根据quartz的api在springboot中重新实现了个,项目地址:https://github.com/simonsfan/springboot-quartz-demo。该项目用springboot+mybatis+mysql+quartz实现的,提供定时任务可视化动态添加、修改、执行,并提供定时任务监控,界面大致如图:

    定时任务列表展示简图
    定时任务执行情况简图
    定时任务执行失败详情简图

    关于这个项目, 还有些地方需要完善,比如定时任务的查找、分页展示、我这里均没有实现,如果你在实际项目中需要使用,请自行完善下,还有比如更新定时任务执行情况记录表quartz_task_records的时机,需要再斟酌斟酌。如有任何疑问,欢迎留言讨论。


    上手Quartz

    关于quartz,可在官网http://www.quartz-scheduler.org/documentation/quartz-2.2.x/tutorials/查看,这里着重要说的是其中几个比较重要的:

    1、maven引quartz依赖

    <!--quartz依赖-->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-quartz</artifactId>
    </dependency>
    

    2、quartz核心组件包括

    • Scheduler:与quartz schedule交互的主要api
    • Job:Scheduler执行组件需要实现的接口
    • JobDetail:用于定义实现了Job接口的实例
    • Trigger:用于执行给定作业的计划的组件
    • JobBuilder:用于构建JobDetail实例,或者说定义Job的实例
    • TriggerBuilder:用于构建触发器实例

    使用示例代码:

    /**
     * 实现Job接口,重写execute方法
     */
    public class QuartzJobFactory implements Job {
    
    	protected Logger logger = Logger.getLogger(QuartzJobFactory.class);
    	
    	@Override
    	public void execute(JobExecutionContext context) throws JobExecutionException {
    		//TODO 这里是定时任务逻辑
    		logger.info("=================我是定时任务,每隔5秒执行一次==============");
    	}
    }
    /**
     * @ClassName QuartzService
     * @Description 系统初始化触发定时任务
     * @Author simonsfan
     * @Date 2019/1/1
     * Version  1.0
     */
    @Component
    public class QuartzService {
    
        private static final Logger logger = LoggerFactory.getLogger(QuartzService.class);
        private final String CRON_TIME = "*/5 * * * * ?";
        private final String TRIGGER_KEY_NAME = "00000000001";
    
        @PostConstruct
        public void taskInit() {
            logger.info("=========系统初始化加载定时任务开始========");
            try {
                SchedulerFactory schedulerFactory = new StdSchedulerFactory();
                Scheduler scheduler = schedulerFactory.getScheduler();
                TriggerKey triggerKey = TriggerKey.triggerKey(TRIGGER_KEY_NAME, Scheduler.DEFAULT_GROUP);
                JobDetail jobDetail = JobBuilder.newJob(QuartzJobFactory.class).withDescription("quartz测试定制化定时任务").withIdentity(TRIGGER_KEY_NAME, Scheduler.DEFAULT_GROUP).build();
                CronScheduleBuilder cronScheduleBuilder = CronScheduleBuilder.cronSchedule(CRON_TIME);
                CronTrigger cronTrigger = TriggerBuilder.newTrigger().withIdentity(triggerKey).withSchedule(cronScheduleBuilder).build();
                scheduler.scheduleJob(jobDetail, cronTrigger);
                scheduler.start();
                logger.info("=========初始化定时任务加载完成=========");
            } catch (Exception e) {
                logger.info("==============初始化加载定时任务失败==============" + e);
                e.printStackTrace();
            }
        }
    
    }

    1、系统初始化加载这里是使用@PostConstruct注解触发,也可以通过实现InitializingBean接口的方式;

    2、调度器Scheduler实例是通过new构造的,也可以直接注入类SchedulerFactoryBean,如下

    /**
     * @ClassName QuartzService
     * @Description 系统初始化触发定时任务
     * @Author simonsfan
     * @Date 2019/1/1
     * Version  1.0
     */
    @Component
    public class QuartzService implements InitializingBean {
    
        private static final Logger logger = LoggerFactory.getLogger(QuartzService.class);
        private final String CRON_TIME = "*/5 * * * * ?";
        private final String TRIGGER_KEY_NAME = "00000000001";
    
        @Autowired
        private SchedulerFactoryBean schedulerFactoryBean;
    
        @Override
        public void afterPropertiesSet() throws Exception {
            logger.info("=========系统初始化加载定时任务开始========");
            try {
                Scheduler scheduler = schedulerFactoryBean.getScheduler();
                TriggerKey triggerKey = TriggerKey.triggerKey(TRIGGER_KEY_NAME, Scheduler.DEFAULT_GROUP);
                JobDetail jobDetail = JobBuilder.newJob(QuartzJobFactory.class).withDescription("quartz测试定制化定时任务").withIdentity(TRIGGER_KEY_NAME, Scheduler.DEFAULT_GROUP).build();
                CronScheduleBuilder cronScheduleBuilder = CronScheduleBuilder.cronSchedule(CRON_TIME);
                CronTrigger cronTrigger = TriggerBuilder.newTrigger().withIdentity(triggerKey).withSchedule(cronScheduleBuilder).build();
                scheduler.scheduleJob(jobDetail, cronTrigger);
                logger.info("=========初始化定时任务加载完成=========");
            } catch (Exception e) {
                logger.info("==============初始化加载定时任务失败==============" + e);
                e.printStackTrace();
            }
        }
    }

    如果是spring配置文件项目,则需要将SchedulerFactoryBean注入到spring中:

     <bean id="schedulerFactoryBean" class="org.springframework.scheduling.quartz.SchedulerFactoryBean" />

    核心功能

    下面来详细讲解下这个springboot-quartz-demo里面是如何做的。还是围绕着功能点来看,如开头截图所示,主要有: 定时任务展示、新增、修改、暂停或启动、立刻运行、详情(监控),首先新建了三张表,如下:

    分别用来记录-定时任务基础信息、执行情况、执行出错信息。然后是主要的执行定时任务逻辑编写,写在实现了Job接口的QuartzMainJobFactory类中:

    定时任务主要逻辑类

    这里设置了采用Http或Kafka的方式来执行定时任务里面的具体逻辑,方式可以自己扩展或替换,主要还是针对你的定时任务类型及现有的项目架构来具体选型。

    books 1、展示和新增功能就不用说了,直接对quartz_task_informations表select和insert即可。

    books 2、初始化加载,就是说,每次重启系统,要把之前建的定时任务加载进quartz的Scheduler调度器,他这里采用的就是@PostConstruct注解,在注入service前就先加载:

    初始化加载定时任务代码简图

    books 3、修改功能,这个要注意的是修改前一定要先暂停这个定时任务才能修改,然后就是注意下并发修改的情况,加入了乐观锁控制

    修改定时任务代码简图

    books 4、实时暂停或启动定时任务,启动表示使定时任务立刻生效,就是把该定时任务加入任务调度系统中;而暂停本质就是从任务调度系统中删除此定时任务

    暂停或启动定时任务代码简图

    books 5、立刻运行定时任务,这里使用到了AtomicInteger原子类来标识"立即运行定时任务"这个操作是否出现成功,关于AtomicInteger的一些知识,请移步至https://blog.csdn.net/fanrenxiang/article/details/80623884

    立即运行一次定时任务代码简图

    books 6、监控详情,如上所说,quartz_task_records表和quartz_task_errors表分别用来记录定时任务每次执行情况和执行失败的定时任务信息,每当定时任务跑成功一次(不管成功与否)都持久化到quartz_task_records表,每失败一次(这里的标志就是AtomicInteger)持久化至quartz_task_errors表,这样每次执行的定时任务都能被比较好的"监控",这两个保存操作夹杂在"立即运行一次" 和 "QuartzMainJobFactory类" 代码中。

    定时任务执行情况简图
    定时任务执行失败原因简图

    补充:

    1、有小伙伴反馈说,当定时任务的执行时间超过了任务执行周期,这个正在执行的定时任务是什么状态?被抛弃杀死进程了?还是正常执行?关于这点,我在上面的项目测试了下,把定时任务的执行时间表达式改为"*/3 * * * * ?"每3秒执行一次定时任务,但是在任务执行逻辑中加入Thread.currentThread().sleep(5000);模拟定时任务执行时长要消耗5秒,测试结果是:超过3s的任务会继续执行,而下一个任务开始时间会变成这个任务执行完成后的5秒,依次类推,也就是说类似于时间表达式改为了"*/5 * * * * ?"的执行效果了。

    2、我自己还想到的一个问题是: 当使用http方式调用定时任务逻辑时,如果接口逻辑过于复杂导致处理时间过长,或者可能是IO密集计算型任务,这个时候怎么办?还没验证,后期再补吧。


    来波广告: 推荐个微信公众号:720电影社,里面可以免费获取电影、电视剧资源,欢迎关注。


    books quartz官网:http://www.quartz-scheduler.org/documentation/quartz-2.2.x/tutorials/

    books springboot版本项目地址:https://github.com/simonsfan/springboot-quartz-demo

    books spring项目版本地址:https://github.com/justdojava/zx-quartz

    books 推荐阅读:elastic search搜索引擎实战demo:https://github.com/simonsfan/springboot-quartz-demo,分支:feature_es

    books 推荐阅读:分布式环境下保证定时任务单点运行:https://blog.csdn.net/fanrenxiang/article/details/79990386

    展开全文
  • 分布式定时任务—xxl-job学习(一):简单demo搭建

    千次阅读 多人点赞 2020-06-19 14:57:13
    分布式定时任务—xxl-job学习(一):简单demo搭建一、xxl-job简介1.1 xxl-job特性介绍1.2 官方仓库+文档地址二、简单搭建一个xxljob_demo2.1 搭建调度中心2.1.1 初始化“调度数据库”2.1.2 配置部署“调度中心”xxl-...
  • GlobalEventExecutor是一个线程的任务执行器,每隔一秒钟回去检测有没有新的任务,有的话就提交到executor执行。 总结 netty为JDK的并发包提供了非常有用的扩展。大家可以直接使用。 本文已收录于 ...
  • 我们在之前有讲过SpringBoot是已经集成了定时任务的,详见:第二十六章:SpringBoot使用@Scheduled创建定时任务,那么我们本章将会采用外置的quartz定时任务框架来完成定时任务的分布式节点持久化,我们为什么要...
  • 利用Azkaban来完成大数据的任务调度

    千次阅读 2018-01-03 15:05:01
    任务完成或失败通知管理员 这样,我们就可以利用azkaban来进行任务调度,主要应用场景就是离线计算了。 在上面,我们已经知道了,大数据离线计算相关内容是存在一种依赖关系的,最简单的例子是先要ETL,然后...
  • ipad支持分屏(多任务) 支持横竖屏强制切换 新建一个工程,做如下配置。 设备方向选择所有方向。不要勾选Requires full screen,该选项用于控制该应用是否支持多任务。 适配iphone和iPad iPad 多任务 ...
  • 最近在做一个线程多任务的断点排队下载的功能,网上确实有很多这样的demo。但是呢我发现大部分网上的demo都是很些不完整的要么就是有缺陷的,可能是我还没找到。今天我给大家带来的一个功能完整的并且可以多界面...
  • xxl-job定时任务

    千次阅读 2022-01-27 14:26:11
    xxl-job定时任务的一点分享
  • Java如何实现定时任务

    千次阅读 2022-03-26 21:01:32
    01、如何简单实现定时功能? 我是看视频入门Java的,那时候学Java基础API的时候,看的视频也带有讲定时功能(JDK原生就支持),我记得视频讲师写了Timer来讲解定时任务。 当时并不知道定时任务有什么实际作用,所以...
  • Quartz可以用来创建简单或复杂的日程安排执行几十,几百,甚至是十万的作业数 - 作业被定义为标准的Java组件,可以执行几乎任何东西,可以编程让它们执行。 Quartz调度包括许多企业级功能,如JTA事务...
  • Quartz,水晶、石英,一个简单朴素有美丽的名字,在Java程序界,Quartz大名鼎鼎,很多Java应用几乎都集成或构建了一个定时任务调度系统,Quartz是一个定时任务调度框架。 何为定时任务调度框架?简而言之,它可以...
  • Django定时任务四种实现方法总结

    千次阅读 2022-03-01 18:31:16
    Django定时任务三种实现方法总结一、使用django-crontab插件来实现定时任务二、使用django-apscheduler插件实现定时任务二、附件部分(django-apscheduler功能详解)三、使用Celery插件实现定时任务四、自建代码实现...
  • 浏览器的多线程与js引擎的线程

    千次阅读 2018-04-10 14:53:43
    我们这里将进程比喻为工厂的车间,它代表CPU所能处理的单个任务。任一时刻,CPU总是运行一个进程,其他进程处于非运行状态。 线程 在早期的操作系统中并没有线程的概念,进程是能拥有资源和独立运
  • 分布式定时任务解决方案

    千次阅读 2019-11-06 21:06:45
    分布式定时任务解决方案 一、背景 服务有定时任务,当服务部署到多个节点时,每个节点在同一个时间点都会执行相同的定时任务,需要做的是,让同一个时间点,每一个定时任务只在一个节点上执行,避免重复执行。 二、 ...
  • 分布式任务调度

    万次阅读 2018-10-01 22:31:50
    什么是定时任务? 指定时间去执行任务 Java实现定时任务方式 1.Thread public class Demo01 { static long count = 0; public static void main(String[] args) { Runnable runnable = new Runnable() { ...
  • ForkJoinPool由Java大师Doug Lea主持编写,它可以将一个大的任务拆分成多个子任务进行并行处理,最后将子任务结果合并成最后的计算结果,并进行输出。本文中对Fork/Join框架的讲解,基于JDK1.8+中的Fork/Join框架...
  • 大家好,我是K哥。最近K哥的交流群里有读者分享了一款非常不错的定时任务管理系统。K哥也去玩了一下,真的非常不错。可能有些小伙伴还不知道有啥用,其实很简单,可以举个简单的小例子。例如我们想要...
  • 已有的分布式任务调度系统如:Saturn、elastic-job、xxl-job都是非常优秀的开源作品,为了学习与交流,我设计了一款轻量级的分布式任务调度系统CronMan,具有一些崭新的特性如:任务编排、任务结果传递、高可用、...
  • 利用数据库存储订单、通知任务,构建高性能队列原文地址:http://www.codeproject.com/Articles/110931/Building-High-Performance-Queue-in-Database-for-st作者:chszs,转载需注明。博客主页:...
  • 浅谈大数据任务调度平台

    千次阅读 热门讨论 2020-12-28 13:54:35
    谈到大数据,避免不了hadoop, hive, spark 这些基础套件,但是在整个大数据开发的时候,我们面对的基本上都是数据开发平台和任务调度系统。数据开发平台一般直接面对业务同学,很大程度上影响业务同学的开发效率和...
  • 别再用定时任务来关闭超时订单了

    千次阅读 2021-12-16 09:42:01
    简单的想法是设置定时任务轮询,但是每个订单的创建时间不一样,定时任务的规则无法设定,如果将定时任务执行的间隔设置的过短,太影响效率。还有一种想法,在用户进入订单界面的时候,判断时间执行相关操作。 一...
  • FreeRTOS高级篇8---FreeRTOS任务通知分析

    万次阅读 多人点赞 2016-06-10 22:38:07
    在FreeRTOS版本V8.2.0中推出了全新的功能:任务通知...我在《 FreeRTOS系列第14篇---FreeRTOS任务通知》一文中介绍了任务通知如何使用以及局限性,今天我们将分析任务通知的实现源码,看一下任务通知是如何做到效率...
  • ElasticJob分布式调度,分布式多个微服务执行只需要执行一个定时任务,基本概念介绍(一)
  • Scheduler的工作流程 Scheduler添加job流程: Scheduler调度流程: 七、使用分布式消息系统Celery实现定时任务 Celery是一个简单,灵活,可靠的分布式系统,用于处理大量消息,同时为操作提供维护此类系统所需的...
  • redis 延时任务 看一篇成高手系列2

    千次阅读 2018-05-31 09:15:24
    引言在开发中,往往会遇到一些关于延时...一共有如下几点区别定时任务有明确的触发时间,延时任务没有定时任务有执行周期,而延时任务在某事件触发后一段时间内执行,没有执行周期定时任务一般执行的是批处理操作...
  • 最近的项目里面涉及到一些Java设计模式,在此简单谈一下自己的看法,以下实例一部分参考同行,大部分自己设计。1.单例模式 如果一个类始终只能创建一个实例,则这个类成为单例类,这种设计模式称为单例模式。class...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 182,688
精华内容 73,075
关键字:

任务通知单简单格式