分布式调度可以被理解为在分布式环境下执行定时任务。那么要先理解一下“定时任务”。
定时任务的使用场景
何为定时任务?定时任务就是在某一时刻或者每隔一定时间去执行的任务。比如XX游戏在本赛季末的周日要统计用户上个赛季的积分等信息、XX相册要每个月清空一次回收箱、XX系统要每周生成销售报表、使用某宝购物,下单15分钟内不付款会自动取消订单等。
上述任务都是有规律的,因此我们可以编写脚本来设置定时做这些事,这就是调度。
分布式调度通俗简介
那么什么是分布式调度?字面意思上,分布式调度就是在分布式环境下执行定时任务,因此它可以有两层含义:
1. 同一个定时任务程序部署多份,在同一时刻只有一个会执行,即定时任务只执行一次
举个例子,当前我有个游戏系统,我写了个定时任务:每周周日0点更新一个版本,次日8点通知用户更新游戏。写完后我将其部署到分布式环境下的各个服务器,理想状态是到了每周一的8点,用户就会被提示更新游戏。
但是实际情况可能是:用户小明第一次打开游戏,服务器A接受处理他的请求,并告知他要更新游戏,小明照做,更新完后再次打开游戏,可能是服务器B接受处理第二次请求,小明被第二次告知需要更新游戏,这时小明可能就蒙圈了:我不是已经更新过一次了吗?即使这么想,小明还是又更新了一次,到第三次,服务器C接受处理他的请求…
设想极限情况,小明可能一整天都在更新游戏,这意味着我要失去一个用户。
导致以上情况发生的原因是我在每一台服务器上都部署了定时任务,所以每一台服务器都会执行一次定时任务。这不是我想要的,我的理想状态应该是不管往多少台服务器部署了定时任务,同一时刻只有一台服务器会去执行该任务,即该任务只执行一次,这就是分布式调度的含义之一:同一个定时任务程序部署多份,在同一时刻只有一个会执行,即定时任务只执行一次
2. 拆分定时任务,大任务拆分为若干个小任务
举个例子,我有一项定时任务需要在某一时刻去进行一百万次计算,那么如果这项任务能被拆分为若干个小任务,同一时刻由若干台服务器去执行,那么每台服务器需要处理的任务都比不拆分时由一台服务器处理的任务少,整体效率就上去了。这就是分布式调度的含义之二:把一个大的任务(作业)分为若干个小的任务(作业),同时执行
分布式调度框架Elastic-Job
以往在单体应用中,我们可能使用Quartz框架来实现定时任务的调度,但是在分布式环境下,Quartz很难实现我们想要的效果,于是这里要提一个专业的分布式框架:Elastic-Job,github:https://github.com/dangdangdotcom/elastic-job。
Elastic-Job简介
Elastic-Job是一款基于Quartz二次开发的开源分布式调度解决方案,由两个子项目Elastic-Job-Lite和Elastic-Job-Cloud组成,Elastic-Job-Lite是轻量级、无中心化的分布式调度方案,Elastic-Job-Cloud则是作用于云环境。本篇作为入门了解demo只以Elastic-Job-Lite做例子,具体内容可以上Elastic-Job-Lite官网看一波。
Elastic-Job-Lite应用
说回来,在分布式环境下,如果有一个定时任务被部署到多台服务器上,且要求这一定时任务只能执行一次,那么问题来了,其中一台服务器执行了任务,其他服务器怎么知道呢?
于是自然而然就涉及到服务器之间的通信,Elastic-Job框架已经做好了这方面的准备,它依赖ZooKeeper来实现服务器的通信需求(关于Zookeeper的详细内容不在本篇重点范围内,可上Zookeeper官网了解)。
任务需求
我们当前有一个业务需求:有一张数据表test_user
,当中存储学生信息。
这些信息的使用频率很低甚至常年不使用,我们需要将这些不常用的数据做一个备份,方便这张数据表在后续使用上能简便一些。具体操作是将记录的state
字段从on
修改为off
,并将记录复制到另一张表test_user_bak
中。
在单体应用中我们可以使用一条语句实现这个需求,但是在分布式环境下就没那么简单了,现在我们就要编写一个定时任务,并且假设定时任务执行时机已到,手动执行它。
引入依赖
要使用Elastic-Job框架,就先引入其依赖支持
<!-- https://mvnrepository.com/artifact/com.dangdang/elastic-job-lite-core -->
<dependency>
<groupId>com.dangdang</groupId>
<artifactId>elastic-job-lite-core</artifactId>
<version>2.1.5</version>
</dependency>
编写数据库交互工具类
与数据库的交互不是本篇重点,所以这个不详述,只提供代码。JdbcUtil工具类提供两个DML操作接口
- 更新:
executeUpdate(String sql,Object...obj)
- 查询:
executeQuery(String sql,Object...obj)
public class JdbcUtil {
//url
private static String url = "jdbc:mysql://localhost:3306/test?characterEncoding=utf8&useSSL=false";
//user
private static String user = "root";
//password
private static String password = "123456";
//驱动程序类
private static String driver = "com.mysql.jdbc.Driver";
static {
try {
Class.forName(driver);
} catch (ClassNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
public static Connection getConnection(