Quartz學習--三 Hello Jdbc Quartz! 和 demo 結尾


四. Hello JDBC Quartz!

  1. JDBC方式: 就是說通過數據庫的jdbc鏈接來進行quartz的一個配置 Quartz支持了很好的支持

    demo用例 使用mysql作為例子進行演示

    相比簡單配置多出了 :

    • 數據庫

    • 數據庫結構 (需要我們手動去初始化一些表格)

    • 配置 quartz.properties

  2. 實際上是否使用jdbc模式的quartz 完全取決於 業務 , 當定時任務比較多的時候, 可以選擇使用jdbc方式

    簡單來看這種方式的優點就是我們可以進行一些簡單的改造就能達到 動態控制定時任務的效果,缺點就是 他的性能遠不如ram方式 畢竟他是建立在jdbc鏈接上也部分依賴於網絡速度

  3. make it :

    1. mysql 安裝

    2. 導入 初始化數據庫.sql文件 的下載

      我在官網文檔中查看 發現並沒有寫在文檔中

      在下載的quartz壓縮包中的 .\docs\dbTables 有各種數據庫的初始話的.sql文件

      壓縮包我上傳了git: (https://github.com/wunian7yulian/GITHUB_WORKSPACE) 2.23 版本

    3. 選擇 tables_db2_v8.sql 進行導入  

    4.    

      1. 為了防止重復 都加了 qrtz作為表名的前綴 

      2. 表實際上在 第一章 5. 中有過相應的介紹

    5. 代碼編寫

      1. 第一章 7. 中 介紹了quartz相關的可以更改的配置(quartz.properties ):

        //調度標識名 集群中每一個實例都必須使用相同的名稱 (區分特定的調度器實例)
        org.quartz.scheduler.instanceName:DefaultQuartzScheduler
        //ID設置為自動獲取 每一個必須不同 (所有調度器實例中是唯一的)
        org.quartz.scheduler.instanceId :AUTO
        //數據保存方式為持久化
        org.quartz.jobStore.class :org.quartz.impl.jdbcjobstore.JobStoreTX
        //表的前綴 
        org.quartz.jobStore.tablePrefix : QRTZ_
        //設置為TRUE不會出現序列化非字符串類到 BLOB 時產生的類版本問題
        //org.quartz.jobStore.useProperties : true
        //加入集群 true 為集群 false不是集群
        org.quartz.jobStore.isClustered : false
        //調度實例失效的檢查時間間隔
        org.quartz.jobStore.clusterCheckinInterval:20000
        //容許的最大作業延長時間
        org.quartz.jobStore.misfireThreshold :60000
        //ThreadPool 實現的類名
        org.quartz.threadPool.class:org.quartz.simpl.SimpleThreadPool
        //線程數量
        org.quartz.threadPool.threadCount : 10
        //線程優先級
        org.quartz.threadPool.threadPriority : 5(threadPriority 屬性的最大值是常量 java.lang.Thread.MAX_PRIORITY,等於10。最小值為常量 java.lang.Thread.MIN_PRIORITY,為1)
        //自創建父線程
        //org.quartz.threadPool.threadsInheritContextClassLoaderOfInitializingThread: true
        //數據庫別名
        org.quartz.jobStore.dataSource : qzDS
        //設置數據源
        org.quartz.dataSource.qzDS.driver:com.mysql.jdbc.Driver
        org.quartz.dataSource.qzDS.URL:jdbc:mysql://localhost:3306/quartz
        org.quartz.dataSource.qzDS.user:root
        org.quartz.dataSource.qzDS.password:123456
        org.quartz.dataSource.qzDS.maxConnection:10

         

        使用jdbc方式需要配置數據源:

        #my datasource 配置自己的數據庫鏈接 
        org.quartz.dataSource.qzDS.driver:com.mysql.jdbc.Driver
        org.quartz.dataSource.qzDS.URL:jdbc:mysql://localhost:3306/quartz_db
        org.quartz.dataSource.qzDS.user:root
        org.quartz.dataSource.qzDS.password:123456
        org.quartz.dataSource.qzDS.maxConnection:10

         

      1. 先創建job

        package com.ws.quartzdemo1001.job02_JDBC_HelloWorld;
        ​
        import com.ws.quartzdemo1001.job01_HelloWorld.HelloJob;
        import org.quartz.Job;
        import org.quartz.JobExecutionContext;
        import org.quartz.JobExecutionException;
        import org.slf4j.Logger;
        import org.slf4j.LoggerFactory;
        import java.text.SimpleDateFormat;
        import java.util.Date;
        ​
        public class MyJobForJDBCQuartz implements Job {
        ​
            private static Logger log = LoggerFactory.getLogger(MyJobForJDBCQuartz.class);
        ​
            @Override
            public void execute(JobExecutionContext context) throws JobExecutionException {
                log.info("MyJobForJDBCQuartz  is start ..................");
        ​
                log.info("Hello JDBC Quartz !!! "+
                        new SimpleDateFormat("yyyy-MM-dd HH:mm:ss ").format(new Date()));
        ​
                log.info("MyJobForJDBCQuartz  is end .....................");
            }
        }

         

      2. 編寫調度程序

        package com.ws.quartzdemo1001.job02_JDBC_HelloWorld;
        ​
        import org.quartz.*;
        import org.quartz.impl.StdSchedulerFactory;
        ​
        public class QuartzJDBCTest {
            public static void main(String[] args) throws SchedulerException {
                //  1 創建  一個jobDetail 實例
                JobDetail jobDetail = JobBuilder.newJob(MyJobForJDBCQuartz.class)
                        .withIdentity("jdbcJob_01","jdbcGroup_01")
                        .storeDurably(true)
                        .build();
                // 2 創建  簡單的調度器
                SimpleScheduleBuilder simpleScheduleBuilder = SimpleScheduleBuilder
                        //設置執行次數
                        .repeatSecondlyForTotalCount(5);
                // 3 創建  觸發器 Trigger
                Trigger trigger =  TriggerBuilder.newTrigger()
                        .withIdentity("jdbcTrigger_01","jdbcTriggerGroup_01")
                        .startNow().withSchedule(simpleScheduleBuilder).build();
                // 4 獲取  調度器
                Scheduler scheduler = StdSchedulerFactory.getDefaultScheduler();
                scheduler.start();
                // 5 執行  相關調度
                scheduler.scheduleJob(jobDetail,trigger);
                // 6 關閉  調度器
                scheduler.shutdown();
            }
        }

         

        運行時發現啟動不起來 原來忘了沒有數據庫的一個驅動 jar 做path 引入:

                <dependency>
                    <groupId>mysql</groupId>
                    <artifactId>mysql-connector-java</artifactId>
                    <version>5.1.35</version>
                </dependency>

         

        然后啟動 發現 正確執行並打印了5次

        其中JobDetail在創建時 : storeDurably(true) 標識任務將會記錄在數據庫中保存起來

        當下次執行時不需要重復創建jobDetail

        嘗試多次執行: 拋出:

        Exception in thread "main" org.quartz.ObjectAlreadyExistsException: Unable to store Job : 'jdbcGroup_01.jdbcJob_01', because one already exists with this identification.
            at org.quartz.impl.jdbcjobstore.JobStoreSupport.storeJob(JobStoreSupport.java:1108)
            at org.quartz.impl.jdbcjobstore.JobStoreSupport$2.executeVoid(JobStoreSupport.java:1062)
            at org.quartz.impl.jdbcjobstore.JobStoreSupport$VoidTransactionCallback.execute(JobStoreSupport.java:3703)
            at org.quartz.impl.jdbcjobstore.JobStoreSupport$VoidTransactionCallback.execute(JobStoreSupport.java:3701)
            at org.quartz.impl.jdbcjobstore.JobStoreSupport.executeInNonManagedTXLock(JobStoreSupport.java:3787)
            at org.quartz.impl.jdbcjobstore.JobStoreTX.executeInLock(JobStoreTX.java:93)
            at org.quartz.impl.jdbcjobstore.JobStoreSupport.storeJobAndTrigger(JobStoreSupport.java:1058)
            at org.quartz.core.QuartzScheduler.scheduleJob(QuartzScheduler.java:886)
            at org.quartz.impl.StdScheduler.scheduleJob(StdScheduler.java:249)
            at com.ws.quartzdemo1001.job02_JDBC_HelloWorld.QuartzJDBCTest.main(QuartzJDBCTest.java:25)

         

      3. 當我回去看的時候發現日志里面打印了類如:

        14:29:35.085 [QuartzScheduler_DefaultQuartzScheduler-NON_CLUSTERED_MisfireHandler] DEBUG org.quartz.impl.jdbcjobstore.JobStoreTX - MisfireHandler: scanning for misfires...
        14:29:35.086 [QuartzScheduler_DefaultQuartzScheduler-NON_CLUSTERED_MisfireHandler] DEBUG org.quartz.impl.jdbcjobstore.JobStoreTX - Found 0 triggers that missed their scheduled fire-time.
        14:29:55.106 [DefaultQuartzScheduler_QuartzSchedulerThread] DEBUG org.quartz.core.QuartzSchedulerThread - batch acquisition of 0 triggers

        這里其實是一個任務進度的掃描 misfires 也是quartz 的一個獨有 且完善的一個機制 他保證了我們的所有該執行的任務不會丟失掉

        由此看來 quartz的 是一個高度可用 有着非常完美的適用方案的一個調度框架

五.Demo 其他了解

作為簡單demo 我們需要充分了解該demo 適用工作中的業務場景

如:

  • 定時發郵件場景

  • 耗時任務場景 - 並發

  • 集群場景

  • 其他需要注意

 

  1. 定時發郵件場景

    因為我們定時任務的實現類 再編寫的時候 只是去實現Job接口 中的 execute() 接口

      public void execute(JobExecutionContext context) throws JobExecutionException

     

    而再發郵件業務中 我們需要再任務執行的時候知道 接收方郵件的地址

    再execute 的 形參中 我們無法進行自定義處理

    當然,我們可以 設置成 再execute 里面查出列表 一個for 循環搞定

    但是我們可以考慮一下 如何進行傳參 將郵件地址傳入進來

    JobDataMap quartz 提供了一個map來方便我們業務的書寫

    • 設置值: (在jobdetail 創建的時候)

    job.usingJobData("age", 18) //方法一  加入age屬性到JobDataMap
    job.getJobDataMap().put("name", "quertz"); //方法二  加入屬性name到JobDataMap
    • 取值: (在execute 中)

      JobDetail detail = context.getJobDetail();
      JobDataMap map = detail.getJobDataMap(); //方法一:獲得JobDataMap  然后進行取值

    或者

     private int age; 
     public void setAge(String age) { //方法二: 聲明對應屬性的setter 方法   會自動注入
            this.age = age;
     }

    對於同一個JobDetail實例,執行的多個Job實例,是共享同樣的JobDataMap,也就是說,如果你在任務里修改了里面的值,會對其他Job實例(並發的或者后續的)造成影響,因為JobDataMap 引用的是同一個對象

    除了JobDetail,Trigger同樣有一個JobDataMap,共享范圍是所有使用這個Trigger的Job實例。

  2. 耗時任務場景

    Job是有可能並發執行的,比如一個任務要執行10秒中,而調度算法是每秒中觸發1次,那么就有可能多個任務被並發執行。

    有時候我們並不想任務並發執行,比如這個任務要去”獲得數據庫中未進行發放紅包的用戶“,如果是並發執行,就需要一個數據庫鎖去避免一個數據被多次處理。這個時候一個@DisallowConcurrentExecution解決這個問題。

    @DisallowConcurrentExecution
    public class DoNothingJob implements Job {
        public void execute(JobExecutionContext context) throws JobExecutionException {
            System.out.println("do nothing");
        }
    }

    注意,@DisallowConcurrentExecution是對JobDetail實例生效,也就是如果你定義兩個JobDetail,引用同一個Job類,是可以並發執行的。

  3. 集群場景

    http://www.cnblogs.com/zhenyuyaodidiao/p/4755649.html 參考這位大神文章 如果有用到的機會 看一下

    其實各種場景和demo 官方都有提供

    集群的的介紹也是在Terracotta收購后更加完善了

  4. 其他注意點 :(持續補充)

    JobExecutionException

    Job.execute()方法是不允許拋出除JobExecutionException之外的所有異常的(包括RuntimeException),所以編碼的時候,最好是try-catch住所有的Throwable,小心處理。

    Durability(持久)

    如果一個任務不是durable,那么當沒有Trigger關聯它的時候,它就會被自動刪除。

    RequestsRecovery

    如果一個任務是"requests recovery",那么當任務運行過程非正常退出時(比如進程崩潰,機器斷電,但不包括拋出異常這種情況),Quartz再次啟動時,會重新運行一次這個任務實例。

    可以通過JobExecutionContext.isRecovering()查詢任務是否是被恢復的

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM