如何将CRON任务设置为每组实例仅运行一次?

UpH*_*lix 5 java cron tomcat cluster-computing scheduled-tasks

我们在AWS托管我们的网站.我们目前在一个集群中有3个EC2实例,使用AWS负载均衡器.

服务器有linux,apache,java,mysql和tomcat 6.0.

我们正在决定如何设置每小时运行一次的任务.显而易见的地方是Java代码,但有一个问题.

问题是,由于我们在集群中有3个实例(所有实例都相同),因此任务将在每小时运行3次,而不是每小时运行一次,每个实例一次.

我有一些想法可以克服这一点,但我希望有一个更好的,可能是行业标准,如何管理这个.

一个想法是在DB中存储它已经运行的.任务将看到它已经运行了今天.我看到那里的bug.

另一个想法是使用安装在本机操作系统中的一个实例上的cron,在Tomcat中的代码之外.这将使用wget来调用调用java方法的网页.因为那只会调用其中一个实例,所以它应该只运行一次.

两种方式看起来都像黑客,容易出错.有没有真正的方法来做到这一点?

sou*_*ica 5

我使用了 cron/wget 解决方案,它实际上是解决问题的合理方法。您的系统管理员会很高兴能够控制它。

另一种解决方案是使用 JVM 系统属性来指示哪个实例是运行作业的实例。例如:-DschedulerEnabled=true。仅在其中一个实例上设置该标志,并且只有在设置了该标志时才运行作业调度代码。

最后,Quartz 通过它的集群功能支持您基于数据库的解决方案。这样做的好处是它是一个真正的 HA 解决方案。使用其他解决方案,如果充当作业调度程序的机器出现故障,您必须手动故障转移到另一台机器。


raz*_*zed 5

虽然有一个公认的答案,但有一些简单的、本土的解决方案没有你上面概述的错误。wget 解决方案可以很好地确保单个服务器运行代码,但增加了安全性问题(您应该使用共享的私有访问密钥保护 URL),并且正如@sourcedelica 指出的那样,哪个服务器应该实际调用的问题cron 任务。

我倾向于选择无论您拥有多少系统都有效的解决方案 - 并且不需要针对不同系统进行不同的 cron 配置。

假设未来您可能会添加新机器,而您的主服务器(例如,配置为运行您的 cron 任务的服务器)可能会死亡或终止。

我开发的一个解决方案使用集群数据库锁,它可以通过两个简单的表来完成:

CREATE TABLE `Server` (
    `id` int(11) NOT NULL AUTO_INCREMENT,
    `uname` varchar(32) NOT NULL,
    `created` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
    `alive` timestamp NULL DEFAULT NULL,
    PRIMARY KEY (`id`)
);

CREATE TABLE `Lock` (
    `id` int(10) unsigned NOT NULL AUTO_INCREMENT PRIMARY KEY,
    `code` varchar(128) NOT NULL,
    `pid` int(10) unsigned DEFAULT NULL,
    `server` int(10) unsigned DEFAULT NULL,
    `locked` timestamp NULL DEFAULT NULL,
    `used` timestamp NULL DEFAULT NULL,
    PRIMARY KEY (`id`),
    UNIQUE KEY `code` (`code`)
);
Run Code Online (Sandbox Code Playgroud)

每个系统都有一个唯一的uname,如果不存在则注册一条记录;alive每次更新。

获取锁:

SELECT * FROM Lock WHERE code='cron-cluster';
Run Code Online (Sandbox Code Playgroud)

如果不存在,

INSERT INTO `Lock` ...
Run Code Online (Sandbox Code Playgroud)

一旦你有你Lockid32如果serverpid都是NULL,将其设置为我的服务器id和当前进程ID,使用数据库的原子性,以确保只有一个。

UPDATE Lock SET server=1,pid=4233 WHERE id=32 AND server IS NULL and pid IS NULL;
Run Code Online (Sandbox Code Playgroud)

然后你再做一次选择,看看你是否真的获得了它(假设有 n 台不同的机器同时试图获得锁):

SELECT COUNT(id) FROM Lock WHERE code='cron-cluster' AND server=1 AND pid=4233;
Run Code Online (Sandbox Code Playgroud)

如果结果为 1,则您已获取锁,0 表示另一个进程已获取。

最后需要做的是让每台服务器清除死锁和死服务器;每个服务器负责检查每个锁定的活动进程是否正在运行Lock,并且当某个时间过后 aServer未更新alive时,删除与该服务器及其Server记录关联的所有锁定。

我向Server表中添加了其他服务器属性,以允许监视磁盘空间、CPU 等。

虽然不如 Quartz 集群强大,但它可以解决您的问题。