前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >分布式系统中的定时任务全解(二)

分布式系统中的定时任务全解(二)

作者头像
九州暮云
发布2019-08-21 14:24:33
1.1K0
发布2019-08-21 14:24:33
举报
文章被收录于专栏:九州牧云九州牧云

概述

上一篇分布式系统中的定时任务全解(一)中对定时任务和定时任务的基础使用方式进行了说明。这一小节,把分布式场景下的定时任务进行一个大致的讲解。

什么是分布式场景呢,当单台服务器服务能力不够的时候,就需要更多的服务器进行水平向的扩展,由多台服务器分工(任务量上的水平划分,而非业务线上的垂直划分)的方式来增强服务能力,提供更强的并发请求处理,更短的时间响应。

像第一节说到的定时任务使用场景,大多是一次任务执行仅能有一个服务器在执行,如果是所有服务器都在执行相同的任务,一个是会造成错误,就算不会造成错误,很多服务器在做重复的工作也是极大的浪费。

所以,分布式场景下定时任务要做的一个基本难点就是:怎么让某一个定时任务,在一个触发时点上仅有一台服务器在执行。

更进一步,如果,你的定时任务涉及到很多同类型的数据要处理,比如说要处理100个输入文件,处理方式相同;再比如说你的数据库已经做了分库处理,业务数据被写入到了10个数据库实例中,处理方式相同。那么此时,可以让更多台服务器执行定时任务,每台执行其中的一部分,比如10个输入文件;再比如1个数据库实例中的业务数据。

以上两种场景怎么办呢?第一种很简单,后续会提供三种方式去做:1.设置某一台为任务执行服务器,其他服务器不执行;2.使用quartz的集群功能,实现某一台执行;3.使用当当开源的elastic-job,实现某一台执行。第二种场景只有第一种场景中的第3中方式可以做到。

接下来逐个看一下。

实现分布式的方式

设置某一台为任务执行服务器

这种方式可以采用环境变量的方式来实现,定时任务运行时检查本机的环境变量值是否为可执行,如果是则执行定时任务,如果不是则直接返回。

代码语言:javascript
复制
@Value("${ISTIMERRUNNER}")
private String isTimerRunner;

@Scheduled(cron="0 0 0 * * ? ")
public void task(){
    try {
        if("true".equals(isTimerRunner)){
                //do something.....
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

这里有一个需要注意的事项,如果集群环境下,你使用了脚本部署的方式,而且是类似于作者的方式。也就是先把文件拷贝到一台服务器,启动好后,调用脚本(脚本参见:http://www.voidcn.com/blog/BuquTianya/article/p-5760349.html),逐个部署到其他服务器。那么,你就需要注意一下了。

远程ssh调用startup.sh时,tomcat取不到环境变量,这里需要把startup.sh的顶部进行修改:

代码语言:javascript
复制
#!/bin/sh --login

当然这里还有另外一种方式,就是在tomcat的bin目录中添加一个setenv.sh文件,startup.sh执行时会加载其中的内容。

代码语言:javascript
复制
export ISTIMERRUNNER=true

这种方式有十分明显的缺陷:1.单点,当任务执行节点出现问题时,整个定时任务全部over;2.资源分配不均衡,随着定时任务的增多,任务执行服务器的资源占用压力会越来越大。

当然了,这是在技术能力不够的时候,最简单有效的实现方式。

使用quartz的集群功能

quartz这个老牌的定时任务执行工具,在集群方面也提供了很好的支持。quartz的集群是借助数据库来实现的,所有的服务器实例共享一套数据库表中存储的任务、触发器和调度器信息,实现一个时间点,同一个任务仅有一台服务器在执行。而且提供了负载均衡和failover失败转移功能。

quartz的负载均衡大概是这样的策略:对于需要调度很多任务的调度器,会近似随机的选择服务器执行;对于调度的任务数比较少的(1个或者2个)的触发器,会尽量的在同一台服务器上执行。(可以参见这里:http://www.quartz-scheduler.org/documentation/quartz-2.2.x/tutorials/tutorial-lesson-11.html)

quartz的集群使用也不复杂,接下来一起看一下:

1.导入数据库表

quartz的集群是基于数据库实现的,所以首先要把数据库表结构创建好。创建脚本在quartz的完整下载包里可以找到(官网下载地址:http://www.quartz-scheduler.org/downloads/)。

解压之后,在docs/dbTable目录下可以找到你想要的所有常见数据库类型的创建脚本。

我使用的是sql数据库,所以使用了tables_mysql_innodb.sql。

这里导入的时候遇到了一个问题,就是创建索引时索引字段过长。这里我采用的方法是把所有的scheduler的长度变成了50,修改之后也就是会限制所有的scheduler的名字在50个字节以内。

2.创建支持集群的scheduler

集群和非集群的配置不同,关键就在于scheduler,集群时需要给scheduler配置数据源、将org.quartz.jobStore.isClustered设置为true、以及配置quartz.properties属性文件。详细的实现方式可以参见:http://sundoctor.iteye.com/blog/486055

使用elastic-job

elastic-job是基于quartz实现的,最大的不同点是elastic-job把做为共享中心的数据库换成了zookeeper。所以要使用elastic-job首先要安装zookeeper。

安装好zookeeper之后,使用elastic-job也不难,它做了很好的spring集成支持,只需要配置注册中心和执行任务的job即可。

以下是一个配置文件的示例:

代码语言:javascript
复制
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:context="http://www.springframework.org/schema/context" 
    xmlns:reg="http://www.dangdang.com/schema/ddframe/reg" 
    xmlns:job="http://www.dangdang.com/schema/ddframe/job" 
    xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd http://www.dangdang.com/schema/ddframe/reg http://www.dangdang.com/schema/ddframe/reg/reg.xsd http://www.dangdang.com/schema/ddframe/job http://www.dangdang.com/schema/ddframe/job/job.xsd ">
    <context:component-scan base-package="com.dangdang.example.elasticjob" />
    <context:property-placeholder location="classpath:conf/*.properties" />

    <reg:zookeeper id="regCenter" server-lists="${serverLists}" namespace="${namespace}" base-sleep-time-milliseconds="${baseSleepTimeMilliseconds}" max-sleep-time-milliseconds="${maxSleepTimeMilliseconds}" max-retries="${maxRetries}" nested-port="${nestedPort}" nested-data-dir="${nestedDataDir}" />

    <job:simple id="simpleElasticJob" class="com.dangdang.example.elasticjob.spring.job.SimpleJobDemo" registry-center-ref="regCenter" sharding-total-count="${simpleJob.shardingTotalCount}" cron="${simpleJob.cron}" sharding-item-parameters="${simpleJob.shardingItemParameters}" monitor-execution="${simpleJob.monitorExecution}" monitor-port="${simpleJob.monitorPort}" failover="${simpleJob.failover}" description="${simpleJob.description}" disabled="${simpleJob.disabled}" overwrite="${simpleJob.overwrite}" />
</beans>

更详细的示例可以到github下载:https://github.com/ZhangShufan15/elastic-job

这里简单的说一下elastic-job相对于quartz的优势:

1.使用zookeeper做为协调,更加轻量级,这一点对于使用者来说也是一个困难项,因为无论再小的服务也有一个数据库,所以定时任务也使用数据库,那么用起来省事一点。但使用zookeeper一方面速度快,另一方面是不占用现有数据库的连接和计算资源。

2.支持任务的分片,quartz同一时点,同一任务只能在一台机器上运行,但是elastic-job可以在多台机器上运行,并且能够指定每台服务器上运行的输入分片。比如业务数据在10个数据库,这里总共有5台服务器,那么每台服务器在同一个时点,仅处理其中的2个数据库。做到将可纵向切分的任务,切分给不同的服务器,充分利用资源,加快计算速度。

elastic-job怎么做到的分片,在不同的场景下我们该怎么使用elastic-job,接下来的一节将从源代码的角度进行讲解。

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 实现分布式的方式
    • 设置某一台为任务执行服务器
      • 使用quartz的集群功能
        • 使用elastic-job
        相关产品与服务
        数据库
        云数据库为企业提供了完善的关系型数据库、非关系型数据库、分析型数据库和数据库生态工具。您可以通过产品选择和组合搭建,轻松实现高可靠、高可用性、高性能等数据库需求。云数据库服务也可大幅减少您的运维工作量,更专注于业务发展,让企业一站式享受数据上云及分布式架构的技术红利!
        领券
        问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档