多节点服务器定时任务重复处理的问题

项目中有使用Spring定时执行任务的需求,用户可以自定义时间(半小时或整点)去生成需要的报表并发送邮件到用户自己的邮箱。
项目里面提供的时间是半小时或整点去执行Spring定时任务,查询数据库中有哪些Schedule是满足要求的,然后去执行那些符合条件的任务。
一切功能表现正常,但是项目部署在服务器上后,用户反映在同一时间会收到两封相同的邮件。我们检查了代码和Spring Schedule本身的机制后,发现这并不是代码层面的问题,于是我们将目光转移到了服务器上。

公司使用的服务器是Websphere,我们检查服务器的配置后发现。为了提高用户响应效率,服务器本身使用了两个节点(node)来实现负载均衡。也就是说用户的请求会随机分配到两个节点的任意一个节点上,从而达到优化的目的。但是对于Spring定时任务的这种情况,其实是脱离的负载均衡的概念,反而会导致每个节点上都会在同一时间执行相同的代码。

我们想要达成的目标是: 对于一个用户任务,如果当前任务已经被某一个节点处理后,另外一个几点就不需要执行这个任务了
每个节点都是一个独立的Server,它们的JVM是相互独立的。也就是说在内存方面我们是没办法做到节点之间的相互通信。所以需要一个第三方的媒介去完成两个节点的通信。查询了一些相关的资料后,发现要么太复杂,要么代价太昂贵。所以,我们将切入点放在数据库上,因为两个节点都是连接同一个数据库,如果在处理的过程中,给数据库里的任务标记相应的标签,那么就可以变相的实现两个节点的通信。

所以,我做了如下如下尝试
1)在数据库的scheuleTask表中,添加了execute_flag字段,用来存放执行代码的节点生成的UUID
2)在代码层面,在执行任务的时候,首先生成一个UUID,然后将UUID存储在当前任务的记录上。然后再从数据库里查询当前记录的UUID,如果数据库中的UUID与当前节点生成UUID相匹配,则执行任务的具体逻辑,反之,则什么都不做处理。
伪代码如下:
String uuid = UUIDGenerator.getUUID();

userTaskDao.markFlag(taskId, uuid);

Thread.sleep(100);

String existUuid = userTaskDao.getExecuteFlag(taskId);

if(uuid.equals(existUuid)) {

    // execute the task logic

   ....

}


这么处理之后,情况有了好转。但是还是会出现某个客户有可能收到两封相同的邮件的情况。我检查了Log日志,发现某些情况下,某些任务并不是在定点时间去执行的,由于每个服务器的具体情况不一样,比如线程消费情况,在执行上述代码时会有几秒钟的时间差。从而导致了如下情况:
node1: 标记Flag-> 查询数据库中的Flag-> 发现Flag相匹配,执行用户任务
node2:.............................获取可用线程或其他原因....->标记Flag->查询数据库中的Flag->发现Flag相匹配,执行用户任务

这样还是无法避免多个节点处理同一个用户任务的请求。针对于之前的上面的特殊情况,我们又做了一些改进,考虑到两个节点执行时相差的时间不会很多,我就定了一个粗略的阈值5min.又做了如下改动
1)在scheduleTask表中,又添加了executeTime字段,用于记录标记时的时间戳,也可以大致理解为上一次任务执行的时间戳
2)在做标记前,首先检查当前任务的上一次执行时间离当前时间超过阈值,如果超过则表明还没有其他节点执行该任务,然后为task保存标签和当前运行时间。当然如果上一次运行时间为空的情况下,也是允许标记的
3)从数据库里查询当前记录的UUID,如果数据库中的UUID与当前节点生成UUID相匹配,则执行任务的具体逻辑

伪代码如下:
String uuid = UUIDGenerator.getUUID();


Date stamp = new Date();


Task task = userTaskDao.getTask(taskId);

if(task.getExecuteTime() == null || Math.abs(stamp.getTime() -  task.getExecuteTime().getTime())  > 300 * 1000)) {

    userTaskDao.markFlag(taskId, uuid, stamp);

} else {

  log.info("task :" + taskId +" has been executed by other nodes");

}

Thread.sleep(1000);

String existUuid = userTaskDao.getExecuteFlag(taskId);

if(uuid.equals(existUuid)) {

    // execute the task logic

   ....

}


为了处理node1,node2同时在执行标记的过程中,先完成标记的node读到是无效的数据,这里在执行读的操作前休眠1秒的时间,用来解决可能出现的Race Condition问题。

这样就达到了自己预期的效果。


  • 6
    点赞
  • 25
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
在使用多台负载均衡服务器来进行quartz定时任务的部署和管理时,我们可以采用以下的一些方案和策略。 首先,为了实现负载均衡,我们可以使用负载均衡器(Load Balancer)来将请求均匀地分发到多台quartz定时任务服务器上。负载均衡器可以根据不同的算法,如轮询、最少连接、源IP等,将任务请求分发到相应的服务器,实现资源的平衡利用。 其次,为了保证任务的高可用性和容错性,我们可以将quartz定时任务服务器设置为多节点集群(Cluster)。各个节点之间通过集群管理协议来协调任务的调度和执行,实现任务的弹性扩展和容灾备份。当某个节点出现故障时,其他节点可以接管故障节点的任务,确保任务的持续执行。 另外,为了确保数据的一致性和可靠性,我们可以采用分布式数据库来存储和管理quartz定时任务的相关数据。通过将任务数据分布在多个数据库实例上,并采用数据同步和故障恢复策略,可以避免单点故障和数据丢失的风险。 此外,为了方便任务的监控和管理,我们可以借助一些监控工具和平台来对多台负载均衡服务器上的quartz定时任务进行实时监控和性能分析。通过查看任务的执行情况、日志输出和运行指标,可以及时发现并解决任务执行异常和性能瓶颈问题。 最后,为了提升任务的执行效率和响应速度,我们可以采用异步执行和任务分片的方式来并行执行quartz定时任务。通过将任务拆分成多个子任务,并由多个执行器同时执行,可以减少任务的等待时间和执行时间,提高任务的整体处理能力。 综上所述,通过使用负载均衡器、集群管理、分布式数据库、监控工具和异步执行等技术手段,我们可以实现quartz定时任务多台负载均衡服务器上的高效部署和管理。这将为我们提供一个稳定、可靠和高性能的定时任务管理平台。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值