集群环境下，如何使用锁（Locker）来避免计划任务（Quartz）执行冲突?
李小翔
2013-11-25 16:18

内容列表

默认情况下：
Quartz 任务都是保存在内存中的，因此在集群环境下，不可避免的会出现相同的任务同时执行的情况；
如果在 Config.groovy 中启用 quartz.jdbcStore 将任务保存到数据库，并设置任务（XxxxJob）的属性 concurrent 为 false，则可以支持集群环境下不允许同时运行多个相同的任务；
进一步，如果多个应用设置不同的实例名称（quartz.props.scheduler.instanceName），则可以支持同名应用的集群不允许同时运行多个相同的任务，而不同应用的集群可以同时运行相同的任务。
但是，在实际运行过程中，效果却不甚理想，总是出现一些莫名的错误。
所以在常规的 J2EE 应用开发中，通常是把 quartz 任务单独作为一个 war 包部署，甚至部署到独立的 jvm 中，以避免各种冲突。
BroFramework 中提供了一套基于数据库的Lock API，使用这套API，可以灵活的处理 Quartz 中的任务执行冲突，并且实现各种 Quartz 原本无法实现的策略。
考虑下面的几个场景中，应用 Foo 和应用 Bar 中，有一个同名的 FoobarJob，这两个应用使用同一套 BroFramework 数据库：
1、希望同时只有一个 FoobarJob 执行：
import bropen.framework.core.Locker;
class FoobarJob { 
   /**
     * 执行定时任务
     */
   def execute() { 
        String lockKey = ”FoobarJob”;    // 按任务名加锁
        if ( !Locker.lockExit(lockKey, false) ) return;    // 加锁失败则直接退出
        try { 
           // 任务执行代码
        }  finally { 
            Locker.unlock(lockKey, false);    // 执行完毕，解锁
        } 
   } 
   
} 
2、希望 Foo 和 Bar 的多个集群节点中只有一个 FoobarJob 执行，但是Foo、Bar两个应用可以同时运行 FoobarJob
import bropen.framework.core.Locker;
import bropen.framework.core.security.DomainApplication;
class FoobarJob { 
   /**
     * 执行定时任务
     */
   def execute() { 
        String lockKey = ”FoobarJob_” + DomainApplication.current().id;    // 按任务名和应用加锁
        if ( !Locker.lockExit(lockKey, false) ) return;    // 加锁失败则直接退出
        try { 
           // 任务执行代码
        }  finally { 
            Locker.unlock(lockKey, false);    // 执行完毕，解锁
        } 
   } 
   
} 
3、如果有多个域（启用域管理后），可以在 lockKey 中添加域的ID（Domain.current().id）做为锁的关键字，以实现一个域的多个应用中，只允许运行一个同名的任务。
4、如果有两个任务会争用相同的资源，可以为两个任务设置相同的 lockKey，并且可以使用 lock 方法来代替 lockExit 方法，实现等待功能，如：
Locker.lock(lockKey, false, null, -1) // 一直等待
5、如果 FoobarJob 执行时，有自动发出邮件、生成待办等操作（比如每周三凌晨，给所有员工发出一个工作周报的待办任务），而且执行时长不等、执行间隔比较长（如大于半个小时），则在 execute 的 finally 中不解锁，而是让锁自动超时失效，以避免集群间极小的时间差导致任务多次重复执行，如：
def execute() { 
    String lockKey = ”FoobarJob”;    // 按任务名加锁
    if ( !Locker.lockExit(lockKey, false, 30*60*1000) ) return;    // 加锁失败则直接退出，否则加锁并设置超时时间为 30 分钟（默认）
    // 任务执行代码，执行完后不解锁，让其自动超时失效
    ....
}