关闭

quartz学习四--存储、集群

166人阅读 评论(0) 收藏 举报
分类:
一、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);
                    }
                }
            }


0
0

猜你在找
【直播】机器学习&数据挖掘7周实训--韦玮
【套餐】系统集成项目管理工程师顺利通关--徐朋
【直播】3小时掌握Docker最佳实战-徐西宁
【套餐】机器学习系列套餐(算法+实战)--唐宇迪
【直播】计算机视觉原理及实战--屈教授
【套餐】微信订阅号+服务号Java版 v2.0--翟东平
【直播】机器学习之矩阵--黄博士
【套餐】微信订阅号+服务号Java版 v2.0--翟东平
【直播】机器学习之凸优化--马博士
【套餐】Javascript 设计模式实战--曾亮
查看评论
* 以上用户言论只代表其个人观点,不代表CSDN网站的观点或立场
    个人资料
    • 访问:1875次
    • 积分:82
    • 等级:
    • 排名:千里之外
    • 原创:7篇
    • 转载:0篇
    • 译文:0篇
    • 评论:0条
    文章分类
    文章存档