分布式任务调度

1、 什么是任务调度

首先我们可以先思考一下以下业务场景的解决方案:

  • 电商系统需要在每天上午10点,下午3点,晚上8点发放一批优惠券。
  • 银行系统需要在信用卡到期还款日的前三天进行短信提醒。
  • 12306会在你购票成功后,发车前的两个小时给你发送短信提醒。

这些业务场景的解决方案就是任务调度

任务调度是指系统为了自动完成特定任务,在约定的特定时刻去执行任务的过程。有了任务调度即可解放更多的人力,由系统自动去执行任务。

目前我们常见的分布式任务调度有:

  1. TBSchedule:淘宝推出的一款非常优秀的高性能分布式调度框架,目前被应用于阿里、京东、支付
    宝、国美等很多互联网企业的流程调度系统中。但是已经多年未更新,文档缺失严重,缺少维护。
  2. XXL-Job:大众点评的分布式任务调度平台,是一个轻量级分布式任务调度平台, 其核心设计目标是
    开发迅速、学习简单、轻量级、易扩展。现已开放源代码并接入多家公司线上产品线,开箱即用。
  3. Elastic-job:当当网借鉴TBSchedule并基于quartz 二次开发的弹性分布式任务调度系统,功能丰富
    强大,采用zookeeper实现分布式协调,具有任务高可用以及分片功能。
  4. Saturn: 唯品会开源的一个分布式任务调度平台,基于Elastic-job,可以全域统一配置,统一监
    控,具有任务高可用以及分片功能。

在本章我们主要介绍的是Elastic-Job的使用。

2、Elastic-Job

Elastic-Job是一个分布式调度的解决方案,由当当网开源,它由两个相互独立的子项目Elastic-Job-Lite
和Elastic- Job-Cloud组成,使用Elastic-Job可以快速实现分布式任务调度。

在我们搭建环境,介绍Elastic-Job的使用之前我们需要介绍几个重要的概念:

1. 分片:
任务的分片执行,需要将一个任务分拆分为多个独立的任务然后由分布式的服务器分别执行某一个或几个分片项。
如果现在有一个遍历数据库某张表的任务,现有2台服务器,我们将这一个任务分成十片,那么每台服务器就要对应的执行五个分片任务,如果按照平均分配原则服务器A被分配到分片项0,1,2,3,4;服务器B被分配到分片项5,6,7,8,9。

2. leader选举:
zookeeper会保证在多台服务器中选举出一个leader,leader如果下线会触发重新选举。

3. 分片策略:
AverageAllocationJobShardingStrategy
基于平均分配算法的分片策略,也是默认的分片策略。如果分片不能整除,则不能整除的多余分片将依次追加到序号小的服务器。
OdevitySortByNameJobShardingStrategy
根据作业名的哈希值奇偶数决定IP升降序算法的分片策略。作业名的哈希值为奇数则IP升序。作业名的哈希值为偶数则IP降序。用于不同的作业平均分配负载至不同的服务器。
(注意:一旦分片数小于作业服务器数,作业将永远分配至IP地址靠前的服务器,导致IP地址靠后的服务器空闲。)

4. cron表达式:
cron表达式是一个字符串, 用来设置定时规则, 由七部分组成, 每部分中间用空格隔开;

组成部分 含义 取值范围
第一部分 Seconds (秒) 0-59
第二部分 Minutes(分) 0-59
第三部分 Hours(时) 0-23
第四部分 Day-of-Month(天) 1-31
第五部分 Month(月) 0-11或JAN-DEC
第六部分 Day-of-Week(星期) 1-7(1表示星期日)或SUN-SAT
第七部分 Year(年) 可选 1970-2099

cron表达式还有一些特殊符号

符号 含义
? 表示不确定的值。当两个子表达式其中一个被指定了值以后,为了避免冲突,需要将另外一个的值设为“?”。例如:想在每月20日触发调度,不管20号是星期几,只能用如下写法:0 0 0 20 * ?,其中最后以为只能用“?”
* 代表所有可能的值
, 设置多个值,例如”26,29,33”表示在26分,29分和33分各自运行一次任务
- 设置取值范围,例如”5-20”,表示从5分到20分钟每分钟运行一次任务
/ 设置频率或间隔,如"1/15"表示从1分开始,每隔15分钟运行一次任务
L 用于每月,或每周,表示每月的最后一天,或每个月的最后星期几,例如"6L"表示"每月的最后一个星期五"
W 表示离给定日期最近的工作日,例如"15W"放在每月(day-of-month)上表示"离本月15日最近的工作日"
# 表示该月第几个周X。例如”6#3”表示该月第3个周五

简单的举例说明,让我们有更加深刻的印象

cron表达式 含义
*/5 * * * * ? 每隔5秒运行一次任务
0 0 23 * * ? 每天23点运行一次任务
0 0 1 1 * ? 每月1号凌晨1点运行一次任务
0 0 23 L * ? 每月最后一天23点运行一次任务
0 26,29,33 * * * ? 在26分、29分、33分运行一次任务
0 0/30 9-17 * * ? 朝九晚五工作时间内每半小时运行一次任务
0 15 10 ? * 6#3 每月的第三个星期五上午10:15运行一次任务

3、入门案例

环境要求:

  • JDK要求1.7及以上版本
  • Maven要求3.0.4及以上版本
  • zookeeper要求采用3.4.6及以上版本(开箱即用:把conf目录下的zoo_sample.cfg改为zoo.cfg,运行bin目录下的zkServer.cmd)

我们需要准备好数据库和对应的表,这里大家可以随意使用自己已经有的数据库和表来完成操作,我这里使用自己的数据库来演示
1、新建maven工程
分布式任务调度_第1张图片
2、引入依赖

   <dependencies>
       <dependency>
           <groupId>org.springframework.bootgroupId>
           <artifactId>spring-boot-starter-webartifactId>
       dependency>

       <dependency>
        <groupId>com.dangdanggroupId>
           <artifactId>elastic-job-lite-springartifactId>
           <version>2.1.5version>
       dependency>
   
       <dependency>
           <groupId>org.projectlombokgroupId>
           <artifactId>lombokartifactId>
           <optional>trueoptional>
    dependency>
   
       <dependency>
           <groupId>com.baomidougroupId>
           <artifactId>mybatis-plus-boot-starterartifactId>
           <version>3.1.1version>
       dependency>
   
       <dependency>
           <groupId>mysqlgroupId>
           <artifactId>mysql-connector-javaartifactId>
           <version>5.1.47version>
       dependency>
   
       <dependency>
           <groupId>org.slf4jgroupId>
           <artifactId>slf4j-log4j12artifactId>
       dependency>
   dependencies>

3、编写springBoot配置文件及启动类

server.port=${PORT:57081}
spring.application.name = elastic-job
logging.level.root = info

spring.datasource.driver-class-name=com.mysql.jdbc.Driver
spring.datasource.url=jdbc:mysql://192.168.200.128:3306/mp?useUnicode=true&characterEncoding=utf8&autoReconnect=true&allowMultiQueries=true&useSSL=false
spring.datasource.username=root
spring.datasource.password=root

# 设置Mapper接口所对应的XML文件位置
mybatis-plus.mapper-locations = classpath*:dao/*.xml
# 设置别名包扫描路径
mybatis-plus.type-aliases-package = com.itxw.elasticjob.pojo

# zookeeper服务地址
zookeeper.connString = localhost:2181
# 名称空间
myjob.namespace = elastic-job-example
# 分片总数
myjob.count = 3
# cron表达式(定时策略)
myjob.cron = 0/5 * * * * ?
@MapperScan("com.itxw.elasticjob.dao")
@SpringBootApplication
public class ElasticJobApp {
    public static void main(String[] args) {
     SpringApplication.run(ElasticJobApp.class,args);
    }
}

4、实体类

@Data
@NoArgsConstructor
@AllArgsConstructor
@TableName("tb_user")
public class User {
    @TableId("ID")
    private Long id;
    @TableField("USER_NAME")
    private String userName;
    @TableField("PASSWORD")
    private String password;
    @TableField("NAME")
    private String name;
    @TableField("AGE")
    private Integer age;
}

5、数据访问层

public interface UserMapper extends BaseMapper<User> {
    //对id通过mod函数取余进行分片
    @Select("SELECT *  FROM `tb_user` WHERE MOD(id,#{shardingTotalCount})=#{shardingItem}")
    List<User> queryUserById(@Param("shardingTotalCount") int count, @Param("shardingItem") int item);
 }

6、Elastic-Job任务类

/**
 * 数据查询任务
 **/
@Component
public class MyJob implements SimpleJob {

    @Autowired
    private UserMapper userMapper;

    //执行定时任务
    @Override
    public void execute(ShardingContext shardingContext) {
        //得到分片总数
        int count = shardingContext.getShardingTotalCount();
        //得到分片项
        int item = shardingContext.getShardingItem();
        //查询数据
        List<User> userList = userMapper.queryUserById(count,item);
        //输出结果
        userList.forEach(user -> {
            System.out.println("作业分片:" + item + "--->" + user);
        });
    }
}

7、zookeeper注册中心

@Configuration
public class ZKRegistryCenterConfig {

    //zookeeper服务器地址
    @Value("${zookeeper.connString}")
    private String ZOOKEEPER_CONNECTION_STRING;

    //定时任务的名称空间
    @Value("${myjob.namespace}")
    private String JOB_NAMESPACE;

    //zk的配置及创建注册中心
    @Bean(initMethod = "init")
    public ZookeeperRegistryCenter setUpRegistryCenter() {
        //zk配置
        ZookeeperConfiguration zookeeperConfiguration = new 
        ZookeeperConfiguration(ZOOKEEPER_CONNECTION_STRING, JOB_NAMESPACE);

        //创建注册中心
        ZookeeperRegistryCenter zookeeperRegistryCenter = new ZookeeperRegistryCenter(zookeeperConfiguration);

        return zookeeperRegistryCenter;
    }
}

8、elastic-job配置类

@Configuration
public class ElasticJobConfig {

    @Autowired
    private MyJob myJob;

    @Autowired
    private ZookeeperRegistryCenter zkRegistryCenterConfig;

    @Value("${myjob.count}")
    private int shardingCount;

    @Value("${myjob.cron}")
    private String cron;

    /**
     * 配置任务详细信息
     *
     * @param jobClass           任务执行类
     * @param cron               执行策略
     * @param shardingTotalCount 分片数量
     * @return
     */
    private LiteJobConfiguration createJobConfiguration(final Class<? extends SimpleJob> jobClass,
                                                        final String cron,
                                                        final int shardingTotalCount) {
        //创建JobCoreConfigurationBuilder
        JobCoreConfiguration.Builder jobCoreConfigurationBuilder = JobCoreConfiguration.newBuilder(jobClass.getName(), cron, shardingTotalCount);
        JobCoreConfiguration jobCoreConfiguration = jobCoreConfigurationBuilder.build();

        //创建SimpleJobConfiguration
        SimpleJobConfiguration simpleJobConfiguration = new SimpleJobConfiguration(jobCoreConfiguration, jobClass.getCanonicalName());

        //创建LiteJobConfiguration
        LiteJobConfiguration liteJobConfiguration = LiteJobConfiguration.newBuilder(simpleJobConfiguration).jobShardingStrategyClass("com.dangdang.ddframe.job.lite.api.strategy.impl.AverageAllocationJobShardingStrategy").overwrite(true).build();

        return liteJobConfiguration;
    }

    @Bean(initMethod = "init")
    public SpringJobScheduler initSimpleElasticJob() {
        SpringJobScheduler springJobScheduler = new SpringJobScheduler(myJob, zkRegistryCenterConfig, createJobConfiguration(myJob.getClass(), cron, shardingCount));
        return springJobScheduler;
    }
}

以上代码完成后,我们就可以启动微服务去观察对应的数据了
我这里启用了三个服务,分配不同的端口号
分布式任务调度_第2张图片
等待一段时间后可以看到的效果如下:
分布式任务调度_第3张图片
分布式任务调度_第4张图片
分布式任务调度_第5张图片

你可能感兴趣的:(分布式,java,云计算)