分布式计划任务设计与实现

http://netkiller.github.io/journal/scheduler.html

Mr. Neo Chen (陈景峯), netkiller, BG7NYT


中国广东省深圳市龙华新区民治街道溪山美地
518131
+86 13113668890


版权声明

转载请与作者联系,转载时请务必标明文章原始出处和作者信息及本声明。

文档出处:
http://netkiller.github.io
http://netkiller.sourceforge.net

微信扫描二维码进入 Netkiller 微信订阅号

QQ群:128659835 请注明“读者”

2014-09-30

摘要

本文主要通过分布式计划任务软件设计讲述分布式软件开发。


目录

1. 什么是分布式计划任务

首先我们解释一下计划任务,计划任务是指有计划的定时运行或者周期性运行的程序,我们最常见的就是Linux “crontab”与Windows “计划任务程序”,我们也常常借助他们实现我们的计划任务,因它们的时间调度程序非常成熟,无需我们再开发一套。

2. 为什么采用分布式计划任务

起初,我们也跟大多数人一样采用crontab调度程序,但随着项目越来越大,系统越来越复杂,就抱漏出许多问题。

首先是高可用HA需求,当运行计划任务的服务器一旦出现故障,所有的计划任务将停止工作。

其次是性能问题,越来越多的大型计划任务程序出现,对CPU/IO密集操作,单个节点已经不能满足我们的需求。

让计划任务7*24*365不间断运行,必需有一套行之有效的方案才行,我意识到必须开发一个全新的分布式计划任务框架,这样开发人员无需关注怎样实现分布式运行,集中写任务即可。

我首先提出这个框架必需具备几个特性:

分布式计划任务需具备以下特性
  1. 故障转移,我们至少使用两个节点,当一个节点出现问题,通过健康状态检查程序,另一个节点会自动接管任务。

  2. 分布式运行,一个任务可以运行在多个节点之上,能够同时运行,能够调整运行的前后顺序,能够并发互斥控制。

  3. 节点可动态调整,最少两个节点,可以随时新增节点,卸载节点。

  4. 状态共享,任务可能会涉及的通信,例如状态同步等等。

3. 何时使用分布式计划任务

何时使用分布式计划任务
  1. 遇到性能问题,遇到性能问题你可能首先想到的是分服务器,但很多应用不具备跨服务器运行。

  2. 高可用,一个节点出现故障,另一个节点将接管并继续运行。

  3. 灾备,你可以将两个或两个以上的计划任务节点分别部署在两个以上的机房,通过HA特性任何一个机房出现故障,其他机房仍会继续运行。

4. 分布式计划任务的部署

两个节点部署

两个节点可以实现“主”、“备”方案,队列(排队)运行方案与并行方案,其中并行方案又分为不同运行于异步运行,还涉及到互斥运行。

两个以上节点部署

多节点建议采用队列运行方案,并行方案,但不建议使用互斥并行方案(浪费资源)

5. 谁来写分布式计划任务

当我们的分布式计划任务框架一旦完成,任务的编写部分非常轻松,只需继承框架程序便具备分布式运行的特性。

6. 怎么实现分布式计划任务

计划任务是一个相当复杂的一块,有操作系统计划任务,有运用程序计划任务,有基于TCP/IP的访问的,有基于命令行访问的,有定时执行的,有周期运行的,还有基于某些条件触发运行的。总之解决计划任务灾备,要比web,cache, database 复杂的多。

图 1. 分时方案
分时方案

严格划分时间片,交替运行计划任务,当主系统宕机后,备用系统仍然工作,只不过处理周期拉长了。缺点:周期延长了

图 2. HA 高可用方案
HA 高可用方案

正常情况下主系统工作,备用系统守候,心跳检测发现主系统出现故障,备用传统启动。缺点:单一系统,不能负载均衡,只能垂直扩展(硬件升级),无法水平扩展

图 3. 多路心跳方案
多路心跳方案

上面的HA是三层的基于VIP技术实现,下面这个方案我采用多路心跳,做服务级,进程级,IP与端口级别的心跳检测,做正常情况下主系统工作,备用系统守候,心跳检测发现主系统出现故障,备用传统启动,当再次检测到主系统工作,将执行权交回主系统.缺点:开发复杂,程序健壮性要求高

图 4. 任务抢占方案
任务抢占方案

A,B 两台服务器同时工作,启动需要一前一后,谁先启动谁率先加锁,其他服务器只能等待,他们同时对互斥锁进行监控,一旦发现锁被释放,其他服务谁先抢到谁运行,运行前首先加排他锁。 优点:可以进一步优化实现多服务器横向扩展。 缺点:开发复杂,程序健壮性要求高,有时会出现不释放锁的问题。

图 5. 任务轮循或任务轮循+抢占排队方案
任务轮循或任务轮循+抢占排队方案

任务轮循或任务轮循+抢占排队方案
  1. 每个服务器首次启动时加入队列。

  2. 每次任务运行首先判断自己是否是当前可运行任务,如果是便运行。

  3. 否则检查自己是否在队列中,如果在,便推出,如果不在队列中,便加入队列。

6.1. 分布式互斥锁

互斥锁也叫排它锁,用于并发时管理多进程或多线程同一时刻只能有一个进程或者线程操作一个功能。如果你理解什么是互斥锁,便很容易理解分布式锁。

我们将进程,线程中的锁延伸到互联网上,实现对一个节点运行的进程或线程加锁,解锁操作。这样便能控制节点上进程或线程的并发。

			
+------------------+                             +------------------+
| Server A         |                             | Server B         |
+------------------+      +---------------+      +------------------+
| Thread-1         |      | Cluster Mutex |      | Thread-1         |
| Thread-2         |----> +---------------+ <----| Thread-2         |
| Thread-3         |      | A Thread-2    |      | Thread-3         |
+------------------+      +---------------+      +------------------+
                                  |
                                  V
                          +---------------+
                          | Cluster Mutex | 
                          +---------------+
                          | A Thread-2    |
                          +---------------+
			
			

上图中有两台服务器上运行任务,其中Server A 的 Thread-2 做了加锁操作,其他程序必须等待它释放锁才能运行。

你会问如果 Server A 宕机怎么办,是否会一直处于被锁状态?我的答案是每个锁都有一个超时阀值,一旦超时便自动解锁。

另外我们还要考虑“域”的问题,你也可以叫它命令空间,主要是防止锁出现同名被覆盖。

6.2. 队列

排队运行

			
			
			
+------------------+                             +------------------+
| Server A         |                             | Server B         |
+------------------+      +---------------+      +------------------+
| Thread-1         |      | Task Queue A  |      | Thread-1         |
| Thread-2         |----> +---------------+ <----| Thread-2         |
| Thread-3         |      | A Thread-2    |      | Thread-3         |
+------------------+      | B Thread-1    |      +------------------+
                          | B Thread-3    |
                          | A Thread-3    |
                          +---------------+
                                  |
                                  | <sync>
                                  V
                          +---------------+
                          | Task Queue B  |
                          +---------------+
                          | A Thread-2    |
                          | B Thread-1    |
                          | B Thread-3    |
                          | A Thread-3    |
                          +---------------+
			
			

从上图中我可以看到Task Queue中排队情况,运行是自上而下的。

注意Task Queue 需要两个节点,它们是主从结构,A 节点实时向 B 节点同步sh状态。如果 A 节点出现故障, B 节点立即取代 A 节点。

6.3. 其他

计划任务可以分布式运行了,但并不能保证万无一失,配套其他服务器也要做调整。例如数据库,缓存等等。