quartz學習四--存儲、集羣

一、Quartz的存儲和持久化
    1、存儲機制
       Quartz 用 JobStores 對 Job、Trigger、calendar 和 Schduler 數據提供一種存儲機制。

       Scheduler 應用已配置的JobStore 來存儲和獲取到部署信息,並決定正被觸發執行的 Job 的職責。

       所有的關於哪個 Job 要執行和以什麼時間表來執行他們的信息都來存儲在 JobStore。

    2、存儲類型
       內存(非持久化) 存儲

       持久化存儲

    3、JobStore 接口
       (1)、作用
            所有的Job存儲機制,不管是在哪裏或是如何存儲他們的信息的,都必須實現這個接口。

       (2)、JobStore 接口的 API 分類
            Job 相關的 API

            Trigger 相關的 API

            Calendar 相關的 API

            Scheduler 相關的 API

    4、內存(非持久化) JobStore
       (1)、org.quartz.simple.RAMJobStore
            a、作用
           使用內存來存儲 Scheduler 信息 , 是 Quartz 的默認的解決方案。

            b、優勢
           RAMJobStore是配置最簡單的 JobStore:默認已經配置好了。見 quartz.jar:org.quartz.quartz.properties

           RAMJobStore的速度非常快。所有的 quartz存儲操作都在計算機內存中

    5、持久化 JobStore
       (1)、組成
            持久化 JobStore = JDBC + 關係型數據庫

       (2)、org.quartz.impl.jdbcjobstore.JobStoreSupport 類
            Quartz 所有的持久化的 JobStore 都擴展自 JobStoreSupport 類。

       (3)、JobStoreSupport實現類
            a、每一個設計爲針對特定的數據庫環境和配置

            b、org.quartz.impl.jdbcjobstore.JobStoreTX
           類設計爲用於獨立環境中。這裏的 "獨立",我們是指這樣一個環境,在其中不存在與應用容器的事務集成。

            c、org.quartz.impl.jdbcjobstore.JobStoreCMT
           類設計爲與程序容器事務集成,容器管理的事務(Container Managed Transactions (CMT))

            d、JobStore 配置
           #properties配置
           org.quartz.jobStore.class = org.quartz.ompl.jdbcjobstore.JobStoreTX

       (4)、數據庫結構 -- 表名描述
            QRTZ_CALENDARS                    --->        以 Blob 類型存儲 Quartz 的 Calendar 信息
            QRTZ_CRON_TRIGGERS                    --->        存儲 Cron Trigger,包括 Cron 表達式和時區信息
            QRTZ_FIRED_TRIGGERS                    --->        存儲與已觸發的 Trigger 相關的狀態信息,以及相聯 Job 的執行信息
            QRTZ_PAUSED_TRIGGER_GRPS                --->        存儲已暫停的 Trigger 組的信息
            QRTZ_SCHEDULER_STATE                --->        存儲少量的有關 Scheduler 的狀態信息,和別的 Scheduler 實例(假如是用於一個集羣中)
            QRTZ_LOCKS                        --->        存儲程序的悲觀鎖的信息(假如使用了悲觀鎖)
            QRTZ_JOB_DETAILS                    --->        存儲每一個已配置的 Job 的詳細信息
            QRTZ_JOB_LISTENERS                    --->        存儲有關已配置的 JobListener 的信息
            QRTZ_SIMPLE_TRIGGERS                --->        存儲簡單的 Trigger,包括重複次數,間隔,以及已觸的次數
            QRTZ_BLOG_TRIGGERS                    --->        Trigger 作爲 Blob 類型存儲(用於 Quartz 用戶用 JDBC 創建他們自己定製的 Trigger 類型,JobStore 並不知道如何存儲實例的時候)
            QRTZ_TRIGGER_LISTENERS                --->        存儲已配置的 TriggerListener 的信息
            QRTZ_TRIGGERS                    --->        存儲已配置的 Trigger 的信息

            注: 所有的表默認以前綴QRTZ_開始。
             可以通過在 quartz.properties 配置修改(org.quartz.jobStore.tablePrefix = QRTZ_)。

             可以對不同的Scheduler實例使用多套的表,通過改變前綴來實現。

       (5)、優化 quartz數據表結構
            a、對關鍵查詢路徑字段建立索引
           create index idx_qrtz_t_next_fire_time on QRTZ_TRIGGERS(NEXT_FIRE_TIME);
           create index idx_qrtz_t_state on QRTZ_TRIGGERS(TRIGGER_STATE);
           create index idx_qrtz_t_nf_st on QRTZ_TRIGGERS(TRIGGER_STATE,NEXT_FIRE_TIME);
           create index idx_qrtz_ft_trig_group on QRTZ_FIRED_TRIGGERS(TRIGGER_GROUP);
           create index idx_qrtz_ft_trig_name on QRTZ_FIRED_TRIGGERS(TRIGGER_NAME);
           create index idx_qrtz_ft_trig_n_g on QRTZ_FIRED_TRIGGERS(TRIGGER_NAME,TRIGGER_GROUP);
           create index idx_qrtz_ft_trig_inst_name on QRTZ_FIRED_TRIGGERS(INSTANCE_NAME);
           create index idx_qrtz_ft_job_name on QRTZ_FIRED_TRIGGERS(JOB_NAME);
           create index idx_qrtz_ft_job_group on QRTZ_FIRED_TRIGGERS(JOB_GROUP);

            b、根據Mysql innodb表結構特性,調整主鍵,降低二級索引的大小
           ALTER TABLE QRTZ_TRIGGERS
           ADD UNIQUE KEY IDX_NAME_GROUP(TRIGGER_NAME,TRIGGER_GROUP),
           DROP PRIMARY KEY,
           ADD ID INT UNSIGNED NOT NULL AUTO_INCREMENT FIRST,
           ADD PRIMARY KEY (ID);
           ALTER TABLE QRTZ_JOB_DETAILS
           ADD UNIQUE KEY IDX_NAME_GROUP(JOB_NAME,JOB_GROUP),
           DROP PRIMARY KEY,
           ADD ID INT UNSIGNED NOT NULL AUTO_INCREMENT FIRST,
           ADD PRIMARY KEY (ID);

二、Quartz集羣
    1、前提
       只有使用持久的JobStore才能完成Quqrtz集羣

    2、結構圖
       一個 Quartz 集羣中的每個節點是一個獨立的 Quartz 應用,它又管理着其他的節點。
       需要分別對每個節點分別啓動或停止。不像應用服務器的集羣,獨立的 Quartz 節點並不與另一個節點或是管理節點通信。
       Quartz 應用是通過數據庫表來感知到另一應用。

    3、配置集羣
       <prop key="org.quartz.jobStore.class">org.quartz.impl.jdbcjobstore.JobStoreTX</prop>
       <!-- 集羣配置 -->
       <prop key="org.quartz.jobStore.isClustered">true</prop>
       <prop key="org.quartz.jobStore.clusterCheckinInterval">15000</prop>
       <prop key="org.quartz.jobStore.maxMisfiresToHandleAtATime">1</prop>
       <!-- 數據源配置 使用DBCP連接池 數據源與dataSource一致 -->
       <prop key="org.quartz.jobStore.dataSource">myDS</prop>
       <prop key="org.quartz.dataSource.myDS.driver">${database.driverClassName}</prop>
       <prop key="org.quartz.dataSource.myDS.URL">${database.url}</prop>
       <prop key="org.quartz.dataSource.myDS.user">${database.username}</prop>
       <prop key="org.quartz.dataSource.myDS.password">${database.password}</prop>
       <prop key="org.quartz.dataSource.myDS.maxConnections">5</prop>

    4、配置解釋
       (1)、org.quartz.jobStore.class=JobStoreTX
            將任務持久化到數據中。因爲集羣中節點依賴於數據庫來傳播Scheduler實例的狀態,你只能在使用 JDBC JobStore 時應用 Quartz 集羣。

       (2)、org.quartz.jobStore.isClustered=true
            通知Scheduler實例要它參與到一個集羣當中。

       (3)、org.quartz.jobStore.clusterCheckinInterval
            定義了Scheduler 實例檢入到數據庫中的頻率(單位:毫秒)。

            Scheduler 檢查是否其他的實例到了它們應當檢入的時候未檢入;這能指出一個失敗的 Scheduler 實例,且當前 Scheduler 會以此來接管任何執行失敗並可恢復的 Job。

            通過檢入操作,Scheduler 也會更新自身的狀態記錄。clusterChedkinInterval 越小,Scheduler 節點檢查失敗的 Scheduler 實例就越頻繁。默認值是 15000 (即15 秒)

二、集羣實現分析
    1、Quartz源碼分析:
       (1)、原理
            基於數據庫表鎖實現多Quartz_Node 對Job,Trigger,Calendar等同步機制

       (2)、數據庫鎖定表 -- QRTZ_LOCKS
            -- 數據庫鎖定表
            CREATE TABLE `QRTZ_LOCKS` (
              `LOCK_NAME` varchar(40) NOT NULL,
              PRIMARY KEY (`LOCK_NAME`)
            ) ENGINE=InnoDB DEFAULT CHARSET=utf8;

            -- 記錄
            +-----------------+
            | LOCK_NAME       |
            +-----------------+
            | CALENDAR_ACCESS |
            | JOB_ACCESS      |
            | MISFIRE_ACCESS  |
            | STATE_ACCESS    |
            | TRIGGER_ACCESS  |
            +-----------------+

       (3)、org.quartz.impl.jdbcjobstore.StdRowLockSemaphore   (繼承 於 org.quartz.impl.jdbcjobstore.DBSemaphore)
            a、作用
           通過行級別鎖實現多節點處理

            b、常量SQL -- 鎖定SQL語句
           public static final String SELECT_FOR_LOCK = "SELECT * FROM " + TABLE_PREFIX_SUBST + TABLE_LOCKS + " WHERE " + COL_LOCK_NAME + " = ? FOR UPDATE";

            c、繼承方法( 源至 org.quartz.impl.jdbcjobstore.DBSemaphore)
           獲取QRTZ_LOCKS行級鎖            ---->        public boolean obtainLock(Connection conn, String lockName)

           釋放QRTZ_LOCKS行級鎖            ---->        public void releaseLock(Connection conn, String lockName)

            d、核心方法
           指定鎖定SQL                ---->        protected void executeSQL(Connection conn, String lockName, String expandedSQL)

       (5)、org.quartz.impl.jdbcjobstore.JobStoreTX
            a、作用
           控制併發代碼,自身管理事務!

            b、繼承方法( 源至 org.quartz.impl.jdbcjobstore.JobStoreSupport)
           /**
            * 執行給定的回調任選具有獲得性給定的鎖。
            * @param lockName
            */
            protected Object executeInNonManagedTXLock(String lockName, TransactionCallback txCallback) throws JobPersistenceException {
                boolean transOwner = false;
                Connection conn = null;
                try {
                    if (lockName != null) {
                        // If we aren't using db locks, then delay getting DB connection
                        // until after acquiring the lock since it isn't needed.
                        if (getLockHandler().requiresConnection()) {
                            conn = getNonManagedTXConnection();
                        }
                        //獲取鎖
                        transOwner = getLockHandler().obtainLock(conn, lockName);
                    }
                    if (conn == null) {
                        conn = getNonManagedTXConnection();
                    }
                    //回調需要執行的sql語句如:(更新Trigger爲運行中(ACQUIRED),刪除執行過的Trigger等)
                    Object result = txCallback.execute(conn);
                    //JobStoreTX自身維護事務
                    commitConnection(conn);
                    Long sigTime = clearAndGetSignalSchedulingChangeOnTxCompletion();
                    if(sigTime != null && sigTime >= 0) {
                        signalSchedulingChangeImmediately(sigTime);
                    }
                    return result;
                } catch (JobPersistenceException e) {
                    rollbackConnection(conn);
                    throw e;
                } catch (RuntimeException e) {
                    rollbackConnection(conn);
                    throw new JobPersistenceException("Unexpected runtime exception: " + e.getMessage(), e);
                } finally {
                    try {
                        //釋放鎖
                        releaseLock(conn, lockName, transOwner);
                    } finally {
                        cleanupConnection(conn);
                    }
                }
            }

            c、核心方法
           protected Object executeInLock(String lockName, TransactionCallback txCallback) throws JobPersistenceException {
               return executeInNonManagedTXLock(lockName, txCallback);
           }

       (6)、JobStoreCMT
            a、作用
           控制併發代碼,事務交由容器管理!

            b、核心方法
            /**
             * 執行具有選擇性的收購給鎖定的回調。
             */
            protected Object executeInLock(String lockName, TransactionCallback txCallback) throws JobPersistenceException {
                boolean transOwner = false;
                Connection conn = null;
                try {
                    if (lockName != null) {
                        // If we aren't using db locks, then delay getting DB connection
                        // until after acquiring the lock since it isn't needed.
                        if (getLockHandler().requiresConnection()) {
                            conn = getConnection();
                        }
                        transOwner = getLockHandler().obtainLock(conn, lockName);
                    }

                    if (conn == null) {
                        conn = getConnection();
                    }
                    //沒有事務提交操作,與任務共享一個事務
                    return txCallback.execute(conn);
                } finally {
                    try {
                        releaseLock(conn, LOCK_TRIGGER_ACCESS, transOwner);
                    } finally {
                        cleanupConnection(conn);
                    }
                }
            }


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章