springboot结合elasticJob

先说一说什么是elasticJob。 ElasticJob是一个分布式任务调度的解决方案,它由俩个相互独立的子项目Elastic-job-lite和Elastic- job-cloud组成。

任务调度:是指系统为了自动完成特定任务,在任务的特定时刻去执行任务的过程。

分布式:分布式架构,将单体结构分为若干服务,服务之间通过网络交互来完成用户的业务处理。

因为elasticJob是在特定时刻去执行任务的解决方案,那么我们就需要学会怎么表示特定的时间。

cron表达式:cron表达式可以方便的表示日历调度。该表达式有7部分:1、seconds(秒);2、minutes(分钟);3、hours(时);4、day-of-month(天);5、month(月);6、day-of-week(周);7、year(年) 。

注意:第7个year可写可不写。其中第4个和第6个必须有一个值是"?",因为不同的月份之间号和星期很可能是冲突的。

例如:" 0 0 12 ?* WED" 意思是每个星期三的中午12点执行。

字符含义:

* :代表所有可能的值;

  • :表示指定范围;

, :表示列出枚举值。minute表达式中 "5,20" 表示第5分钟和第20分钟触发。

/ :被用于指定增量。minute表达式 "0/15 " 表示从0分钟开始,每15分钟执行一次。

? :用在day-of-month或day-of-week中,指没有确定值。

L :用在day-of-month和day-of-weekz,是last的缩写。在这俩个不同表达式中含义是不同的。 在day-of-month中表示一个月的最后一天。在day-of-week中表示一个星期的最后一天。如果L前有具体内容,就有其他含义了。例如:6L 代表这个月倒数第6天。FRIL 表示这个月最后一个星期五。

W :weekday的缩写。只能用在day-of-month字段。用来描述最接近指定天的工作日(周一到周五)。例如:15W 表示最接近这个月15号的工作日,如果15号是周六,则会在14号周五触发;如果15号是周日,则会在16号周一触发。注意:这个用法只会在当前月份计算,不能跨月份。

App:应用程序,内部包含任务调度业务逻辑和Elastic-job-Lite组件,其中执行任务需要实现ElasticJob接口完成与Elastic-job-Lite组件的集成,并进行任务的相关配置。应用程序可启动多个实例。

Elastic-Job-Lite:定位为轻量级无中心化解方案,使用jar包的形式提供分布式任务的协调服务,此组件负责任务的调度,并产生日志及任务调度记录。

无中心化,是指没有调度中心这一概念,每个运行在集群中的作业都是对等的,各个作业节点是自治的、平等的、节点直接通过注册中心进行分布式协调。

Registry:以zookeeper作为elastic-job的注册中心组件,存储了执行任务的相关信息。

Console:Elastic-job提供了运维平台,它通过读取zookeeper数据展现任务执行任务,或更新zookeeper数据修改全局配置 通过Elastic-job-lite组件产生的数据来查看任务执行历史记录。

应用程序启动时,在其内嵌的elastic-job-lite组件会向zookeeper注册该实例的信息,并触发选举,从众多实例中选举出一个leader,让其执行任务。当应用程序的某一个实例宕机时,zookeeper组件会感知并重新触发leader选举。

zookeeper的作用:

elastic-job依赖zookeeper完成对执行任务信息的存储(如任务名称,任务参与实例,任务执行策略)。

elastic-job依赖zookeeper实现选举机制,在任务执行实例数量变化时,会触发选举机制来决定哪个实例去执行该任务。

zookeeper是一个分布式一致性协调任务,主要用来解决分布式应用中遇到的一些数据管理问题。可以把zookeeper想象为一个特殊的数据库,它维护着一个类似文件系统的树形数据结构,zookeeper的客户端可以对数据进行存取。

zookeeper被称为一致性协调服务的原因:因为zookeeper拥有数据监听通知机制,客户端注册监听它关系的znode(目录节点),当zonde发生变化(如数据改变、被删除、子目录节点增加删除)时,zookeeper会通知所有客户端。简单来说,当分布式系统的若干个服务都关心一个数据时,当这个数据发生变化,这些服务都能够得知,那么这些服务就针对数据达成了一致。

zookeeper实例选举实现过程:

1、任意一个实例启动时首先创建一个/serve的persistent(持久化节点)节点。

2、多个实例同时启动,会同时去尝试创建一个/serve/leader EPHEMERAL子节点(临时非顺序的节点,就是虽然是临时的,但多个实例只会有一个创建成功)。

3、/serve/leader子节点只能创建一个,后面的会创建失败。

4、所有任务实例监听/serve/leader的变化,一旦节点被删除,就重新积极性选举,抢占式创建/serve/leader节点,谁创建成功谁就是leader。

下面让我们开始准备写代码:需要用到springboot、mybatis、mysql、zookeeper。

过程是:从mysql中获取未备份文件,然后对获取的文件进行处理,修改成已备份状态。

首先准备好mysql中要操作的表:

sql 复制代码
create database elastic_job;
use elastic_job;
create table t_file (
id varchar(11) primary key ,
name varchar(255) not null,
type varchar(255) not null,
content varchar(255) not null,
backedUp varchar(1) not null
);

insert into t_file values
(1,'文件1','text','content1',1),
(2,'文件2','text','content2',1),
(3,'文件3','text','content3',1),
(4,'文件4','image','content4',1),
(5,'文件5','image','content5',1),
(6,'文件6','image','content6',1),
(7,'文件7','radio','content7',1),
(8,'文件8','radio','content8',1),
(9,'文件9','radio','content9',1),
(10,'文件10','video','content10',1),
(11,'文件11','video','content11',1);

接下来准备写mybatis来操作数据库:

在application.yml文件中配好端口号和mysql连接数据:

XML 复制代码
spring:
  application:
    name: elastic-job  # 服务名称
  profiles:
    active: public # 开发环境
  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://localhost:3306/elastic_job
    username: root
    password: quwenhao

logging:
  level:
    root: info

server:
  port: 8081

pojo包:

java 复制代码
package com.example.pojo;

import lombok.Data;

@Data
public class FileCustom {

    private int id;
    private String name;
    private String type;
    private String content;

    private Boolean backedUp=false;

    public FileCustom(int id, String name, String type, String content) {
        this.id = id;
        this.name = name;
        this.type = type;
        this.content = content;
    }
}

dao包:

java 复制代码
package com.example.dao;

import com.example.pojo.FileCustom;
import org.apache.ibatis.annotations.Param;
import org.apache.ibatis.annotations.Select;
import org.apache.ibatis.annotations.Update;
import org.springframework.stereotype.Repository;

import java.util.List;

@Repository
public interface FileDao {
//获取未备份文件
    @Select("select * from t_file where type=#{type} and backedUp=0 limit 0,#{count}")
    List<FileCustom> fetchBackedUpFiles(@Param("type") String fileType, @Param("count") int count);
//把未备份文件改成已备份文件
    @Update("update t_file set backedUp=1 where id=#{id}")
    void backUpFiles(@Param("id") int id);

    @Update("update t_file set backedUp=0")
    void clearData();
}

service层:

java 复制代码
package com.example.service;

import com.example.dao.FileDao;
import com.example.pojo.FileCustom;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.List;

@Service
public class FileService {

    @Autowired
    FileDao fileDao;

    public List<FileCustom> fetchUnBackedUpFiles(String fileType,int count) {
        List<FileCustom> fileCustoms = fileDao.fetchBackedUpFiles(fileType, count);
        System.out.println(fileCustoms);
        return fileCustoms;
    }

    public void backedUp(List<FileCustom> fileCustoms){
        for (FileCustom fileCustom : fileCustoms) {
            fileDao.backUpFiles(fileCustom.getId());
            System.out.println(fileCustom.getName()+" "+fileCustom.getType());
        }
    }
    
}

对数据库的操作写好了,下面就该配zookeeper的信息了

首先创建一个zookeeper注册中心:

java 复制代码
package com.example.config;

import com.dangdang.ddframe.job.reg.base.CoordinatorRegistryCenter;
import com.dangdang.ddframe.job.reg.zookeeper.ZookeeperConfiguration;
import com.dangdang.ddframe.job.reg.zookeeper.ZookeeperRegistryCenter;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class ElasticJobRegistryCenterConfig {
    //zookeeper链接字符串
    private String ZOOKEEPER_CONNECTION_STRING = "localhost:2181";
    //定义任务命名空间
    private String JOB_NAMESPACE = "elastic-job-example-java";

    @Bean(initMethod = "init")
    //注册中心配置
    public CoordinatorRegistryCenter setUpRegistryCenter() {

        //注册中心配置
        ZookeeperConfiguration zookeeperConfiguration = new ZookeeperConfiguration(ZOOKEEPER_CONNECTION_STRING, JOB_NAMESPACE);
        //减少zookeeper超时时间
        zookeeperConfiguration.setSessionTimeoutMilliseconds(100);
        //创建注册中心
        CoordinatorRegistryCenter registryCenter = new ZookeeperRegistryCenter(zookeeperConfiguration);
        registryCenter.init();
        return registryCenter;
    }
}

下面就该配我们的作业任务调度的内容了:

我写来解释一下什么是分片: 作业分片是指任务的分布式执行,需要将一个任务拆分为多个独立的任务项,然后由分布式的应用实例分别执行某一个或几个分片。

分片项与业务处理解释:

ElasticJob并不直接提供数据处理的功能,框架只会将分片项至各位中的作业服务器,开发者需要自行处理分片项与真实数据的对应关系。为了最大限度利用资源,我们要将分片项设置为大于服务器的数量,最好是大于服务器倍数的位置,作业将会合理的利用分布式资源,动态的分配分片项。

下面开始写任务内容:

java 复制代码
package com.example.job;

import com.dangdang.ddframe.job.api.ShardingContext;
import com.dangdang.ddframe.job.api.simple.SimpleJob;
import com.dangdang.ddframe.job.lite.api.strategy.JobInstance;
import com.example.pojo.FileCustom;
import com.example.service.FileService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import java.lang.management.ManagementFactory;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;

@Component
public class FileBackUpJobDb implements SimpleJob {

    @Autowired
    FileService service;

    //每次任务执行要备份文件的数量
    private int FETCH_SIZE = 1;
    //文件列表

    //任务调度执行方法
    @Override
    public void execute(ShardingContext shardingContext) {
        //作业分片信息
        int shardingItem = shardingContext.getShardingItem();
        System.out.println("作业分片" + shardingItem);
        //获取分片参数
        String shardingParameter = shardingContext.getShardingParameter();
        System.out.println("分片参数" + shardingParameter);

        //获取未备份文件
        List<FileCustom> fileCustoms = fetchUnBackUpFiles(shardingParameter, FETCH_SIZE);
        //文件备份
        backUpFiles(fileCustoms);
    }

    //获取未备份文件方法
    public List<FileCustom> fetchUnBackUpFiles(String typeFile,int count) {
        //要获取的文件列表
        List<FileCustom> fileCustoms = service.fetchUnBackedUpFiles(typeFile, count);
        System.out.println("获取到文件"+fileCustoms);
        return fileCustoms;
    }

    //备份文件方法
    public void backUpFiles(List<FileCustom> files){
        service.backedUp(files);
        System.out.println("文件被备份");
    }
}

下面就是用zookeeper对我们写好的任务进行任务调度:就是对该任务进行时间追踪并配置执行时间

java 复制代码
package com.example.config;

import com.dangdang.ddframe.job.api.ElasticJob;
import com.dangdang.ddframe.job.config.JobCoreConfiguration;
import com.dangdang.ddframe.job.config.simple.SimpleJobConfiguration;
import com.dangdang.ddframe.job.event.rdb.JobEventRdbConfiguration;
import com.dangdang.ddframe.job.lite.config.LiteJobConfiguration;
import com.dangdang.ddframe.job.lite.spring.api.SpringJobScheduler;
import com.dangdang.ddframe.job.reg.base.CoordinatorRegistryCenter;
import com.example.job.FileBackUpJobDb;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.util.StringUtils;

import javax.sql.DataSource;

@Configuration
public class ElasticJobConfig {

  
    @Autowired
    FileBackUpJobDb fileBackUpJob;

    @Autowired
    CoordinatorRegistryCenter registryCenter;

    //配置任务详细信息 第一个参数为我们前面写好的任务的名称,第二个参数为cron表达式,用于指定执行特定时间。第三个参数为分片数量。
    private LiteJobConfiguration createLiteConfiguration(Class<? extends ElasticJob> jobClass,
                                                         String shardingItemParameters) {
        //定义作业核心配置
        JobCoreConfiguration.Builder builder = JobCoreConfiguration.newBuilder(jobClass.getName(), "0/3 * * * * ?", 4);
        //设置shardingItemParameters
        if (!StringUtils.isEmpty(shardingItemParameters)) {
            builder.shardingItemParameters(shardingItemParameters);
        }
        JobCoreConfiguration jobCoreConfiguration = builder.build();
        //定义simple类型配置
        SimpleJobConfiguration JobConfiguration = new SimpleJobConfiguration(jobCoreConfiguration, jobClass.getCanonicalName());
        //DataflowJobConfiguration JobConfiguration = new DataflowJobConfiguration(jobCoreConfiguration, jobClass.getCanonicalName(), true);
        //定义Lite作业根配置
        LiteJobConfiguration liteJobConfiguration = LiteJobConfiguration.newBuilder(JobConfiguration)
                .overwrite(true)
                //设置dump端口
                //.monitorPort(9888)
                .build();
        return liteJobConfiguration;
    }

    @Bean(initMethod = "init")
    public SpringJobScheduler initSimpleElasticJob() {
       
        //创建SpringJobScheduler
        SpringJobScheduler springJobScheduler = new SpringJobScheduler(
                fileBackUpJob,//任务内容类
                registryCenter,//注册中心类
                createLiteConfiguration(
                        fileBackUpJob.getClass(),
                        "0=text,1=image,2=radio,3=video"));//4个分片,依次的参数。
        return springJobScheduler;
    }
}

这下我们的任务调度就写完了,运行我们的启动类就可以了。

当我们开启一个实例运行该任务调度时,这一个实例会一起执行这4个分片。

当我们同时开启俩个实例时,这个4个分片就会随机分配到这2个实例中,这俩个实例同时对数据库进行修改,且数据共享。

开启多个实例的方法:

写好前缀 -Dserver.port=端口号 然后点ok就好了

相关推荐
苍何20 分钟前
30分钟用 Agent 搓出一家跨境网店,疯了
后端
ssshooter34 分钟前
Tauri 2 iOS 开发避坑指南:文件保存、Dialog 和 Documents 目录的那些坑
前端·后端·ios
追逐时光者1 小时前
一个基于 .NET Core + Vue3 构建的开源全栈平台 Admin 系统
后端·.net
程序员飞哥1 小时前
90后大龄程序员失业4个月终于上岸了
后端·面试·程序员
zs宝来了1 小时前
Playwright 自动发布 CSDN 的完整实践
java
彭于晏Yan2 小时前
Redisson分布式锁
spring boot·redis·分布式
吴声子夜歌3 小时前
TypeScript——基础类型(三)
java·linux·typescript
GetcharZp3 小时前
Git 命令行太痛苦?这款 75k Star 的神级工具,让你告别“合并冲突”恐惧症!
后端
Victor3564 小时前
MongoDB(69)如何进行增量备份?
后端