本文將從以下幾個方面展開介紹elastic-job
簡介
Elastic-job是由當當網架構師張亮,曹昊和江樹建基于Zookepper、Quartz開發并開源的一個Java分布式定時任務,由兩個相互獨立的子項目Elastic-Job-Lite和Elastic-Job-Cloud組成。
Elastic-job-lite:
定位為輕量級無中心化解決方案,使用jar包的形式提供分布式任務的協調服務。
僅關注點分布式調度、協調以及分片等核心功能
Elastic-Job-Cloud :
提供一體化私有云服務,將分布式調度、作業部署、資源分配、監控、日志處理等提供完善的解決方案。
功能更加完善,但使用復雜度較高。使用Mesos + Docker(TBD)的解決方案,額外提供資源治理、應用分發以及進程隔離等服務
功能特點:
功能 | Elastic-job-lite | Elastic-Job-Cloud |
---|---|---|
分布式調度協調 | 有 | 有 |
彈性擴容縮容 | 有 | 有 |
失效轉移 | 有 | 有 |
錯過執行作業重觸發 | 有 | 有 |
作業分片一致性,保證同一分片在分布式環境中僅一個執行實例 | 有 | 有 |
支持并行調度 | 有 | 有 |
支持作業生命周期操作 | 有 | 有 |
豐富的作業類型 | 有 | 有 |
Spring整合以及命名空間提供 | 有 | 有 |
運維平臺 | 有 | 有 |
自診斷并修復分布式不穩定造成的問題 | 有 | |
應用自動分發 | 有 | |
基于Fenzo的彈性資源分配 | 有 | |
基于Docker的進程隔離(TBD) | 有 |
開發指南
基于目前流行的springboot框架來講解如何在springboot中集成elastic-job,所以在進行下面的操作之前我們先得創建一個springboot項目,至于如何創建springboot項目這里就不做介紹了,訪問這個網站:https://start.spring.io/
-
引入maven依賴
<dependency> <groupId>com.dangdang</groupId> <artifactId>elastic-job-lite-core</artifactId> <version>2.1.5</version> </dependency>
-
作業開發
Elastic-Job提供Simple、Dataflow和Script 3種作業類型。 方法參數shardingContext包含作業配置、片和運行時信息。可通過getShardingTotalCount(), getShardingItem()等方法分別獲取分片總數,運行在本作業服務器的分片序列號等。
2.1.Simple類型作業
意為簡單實現,未經任何封裝的類型。需實現SimpleJob接口。該接口僅提供單一方法用于覆蓋,此方法將定時執行。與Quartz原生接口相似,但提供了彈性擴縮容和分片等功能。
public class CourseSimpleJob implements SimpleJob { @Override public void execute(ShardingContext context) { switch (context.getShardingItem()) { case 0: // do something by sharding item 0 break; case 1: // do something by sharding item 1 break; case 2: // do something by sharding item 2 break; // case n: ... } } }
這里的
context.getShardingItem()
為當前分片序號,最大值為分片總數減1(n-1)。例如:分片總數為10,那么context.getShardingItem()
的最大值則為9。2.2.Dataflow類型作業
Dataflow類型用于處理數據流,需實現DataflowJob接口。該接口提供2個方法可供覆蓋,分別用于抓取(fetchData)和處理(processData)數據。
public class SpringDataflowJob implements DataflowJob<Foo> { @Override public List<Foo> fetchData(ShardingContext context) { switch (context.getShardingItem()) { case 0: List<Foo> data = // get data from database by sharding item 0 return data; case 1: List<Foo> data = // get data from database by sharding item 1 return data; case 2: List<Foo> data = // get data from database by sharding item 2 return data; // case n: ... } } @Override public void processData(ShardingContext shardingContext, List<Foo> data) { // process data // ... } }
流式處理
可通過DataflowJobConfiguration配置是否流式處理。
流式處理數據只有fetchData方法的返回值為null或集合長度為空時,作業才停止抓取,否則作業將一直運行下去; 非流式處理數據則只會在每次作業執行過程中執行一次fetchData方法和processData方法,隨即完成本次作業。
如果采用流式作業處理方式,建議processData處理數據后更新其狀態,避免fetchData再次抓取到,從而使得作業永不停止。 流式數據處理參照TbSchedule設計,適用于不間歇的數據處理。
2.3.Script類型作業
Script類型作業意為腳本類型作業,支持shell,python,perl等所有類型腳本。只需通過控制臺或代碼配置scriptCommandLine即可,無需編碼。執行腳本路徑可包含參數,參數傳遞完畢后,作業框架會自動追加最后一個參數為作業運行時信息。
#!/bin/bash echo sharding execution context is $*
-
作業配置
3.1.zookeeper配置
新建RegistryCenterConfig類
@Configuration @ConditionalOnExpression("'${regCenter.serverList}'.length() > 0") public class RegistryCenterConfig { private final Logger log = LoggerFactory.getLogger(RegistryCenterConfig.class); @Bean(initMethod = "init") public ZookeeperRegistryCenter regCenter(@Value("${regCenter.serverList}") final String serverList, @Value("${regCenter.namespace}") final String namespace) { log.info("---------regCenter.serverList: {}---------", serverList); return new ZookeeperRegistryCenter(new ZookeeperConfiguration(serverList, namespace)); } }
在配置文件application.yml中添加這兩個屬性
regCenter: serverList: 127.0.0.1:2181 #zookeeper地址 namespace: elastic-job-lite-springboot #命名空間
3.2.配置事件追蹤
關于事件追蹤這里只做代碼展示,具體詳細請參考:http://elasticjob.io/docs/elastic-job-lite/02-guide/event-trace/
新建JobEventConfig類:
@Configuration public class JobEventConfig { @Resource private DataSource dataSource; @Bean public JobEventConfiguration jobEventConfiguration() { return new JobEventRdbConfiguration(dataSource); } }
在配置文件application.yml中添加數據源相關的配置
spring: datasource: url: jdbc:mysql://localhost:3306/test?useUnicode=true&characterEncoding=utf-8&verifyServerCertificate=false&useSSL=false&requireSSL=false driver-class-name: com.mysql.jdbc.Driver username: root password: 123456 tomcat: max-wait: 10000 min-idle: 0 initial-size: 25 validation-query: SELECT 1 test-on-borrow: false test-while-idle: true time-between-eviction-runs-millis: 18800 remove-abandoned: true remove-abandoned-timeout: 180
這里是在demo中這么配置數據源,一般在現成的項目中都會有相應的數據源配置,無需更改,只需直接將DataSource注入JobEventConfig中即可。
官方配置
這里我們先看看官方給的通用配置:
// 定義作業核心配置 JobCoreConfiguration simpleCoreConfig = JobCoreConfiguration.newBuilder("demoSimpleJob", "0/15 * * * * ?", 10).build(); // 定義SIMPLE類型配置 SimpleJobConfiguration simpleJobConfig = new SimpleJobConfiguration(simpleCoreConfig, SimpleDemoJob.class.getCanonicalName()); // 定義Lite作業根配置 JobRootConfiguration simpleJobRootConfig = LiteJobConfiguration.newBuilder(simpleJobConfig).build(); // 定義作業核心配置 JobCoreConfiguration dataflowCoreConfig = JobCoreConfiguration.newBuilder("demoDataflowJob", "0/30 * * * * ?", 10).build(); // 定義DATAFLOW類型配置 DataflowJobConfiguration dataflowJobConfig = new DataflowJobConfiguration(dataflowCoreConfig, DataflowDemoJob.class.getCanonicalName(), true); // 定義Lite作業根配置 JobRootConfiguration dataflowJobRootConfig = LiteJobConfiguration.newBuilder(dataflowJobConfig).build(); // 定義作業核心配置配置 JobCoreConfiguration scriptCoreConfig = JobCoreConfiguration.newBuilder("demoScriptJob", "0/45 * * * * ?", 10).build(); // 定義SCRIPT類型配置 ScriptJobConfiguration scriptJobConfig = new ScriptJobConfiguration(scriptCoreConfig, "test.sh"); // 定義Lite作業根配置 JobRootConfiguration scriptJobRootConfig = LiteJobConfiguration.newBuilder(scriptCoreConfig).build();
這里官方只是告訴你他的api是這么用的,但是實際工作中我們肯定不能生搬硬套,不然每一個job都要寫一個配置,那多麻煩,有時候還會忘記。因為在實際工作中肯定是有很多個job,其實我們完全可以寫一個通用的配置類,然后將job配置的相關參數提取到配置文件中或者直接在job類中添加自定義注解,然后在注解中添加相關配置,最后在配置類中去掃描這些job的相關配置并遍歷它生成對應的job配置實例,這樣作業的配置代碼只需要編寫一次即可。
3.3.Simple類型作業配置
這里以Simple類型作業為例來簡單的編寫一個Simple類型通用的作業配置
@Configuration public class SimpleJobConfig { private static final String JOB_TYPE = "simpleJob"; public static final String CRON = "cron"; public static final String SHARDING_TOTAL_COUNT = "shardingTotalCount"; public static final String SHARDING_ITEM_PARAMETERS = "shardingItemParameters"; private final Environment env; @Resource private ZookeeperRegistryCenter regCenter; @Resource private JobEventConfiguration jobEventConfiguration; @Autowired public SimpleJobConfig(Environment env) { this.env = env; } @Bean public List<JobScheduler> simpleJobScheduler() { List<SimpleJob> simpleJobs = schedulers(); List<JobScheduler> jobSchedulers = new ArrayList<>(); Joiner joiner = Joiner.on("."); JobScheduler scheduler; for (SimpleJob job : simpleJobs) { String propertiesPrefix = joiner.join(JOB_TYPE, job.getClass().getCanonicalName()); String cron = env.getProperty(joiner.join(propertiesPrefix, CRON)); int shardingTotalCount = Integer.valueOf(env.getProperty(joiner.join(propertiesPrefix, SHARDING_TOTAL_COUNT))); String shardingItemParameters = env.getProperty(joiner.join(propertiesPrefix, SHARDING_ITEM_PARAMETERS)); if (shardingTotalCount > 0 && StringUtils.isNoneBlank(cron) && StringUtils.isNoneBlank(shardingItemParameters) ) { scheduler = new SpringJobScheduler(job, regCenter, getLiteJobConfiguration(job.getClass(), cron, shardingTotalCount, shardingItemParameters), jobEventConfiguration); scheduler.init(); jobSchedulers.add(scheduler); } } return jobSchedulers; } private LiteJobConfiguration getLiteJobConfiguration(final Class<? extends SimpleJob> jobClass, final String cron, final int shardingTotalCount, final String shardingItemParameters) { return LiteJobConfiguration.newBuilder(new SimpleJobConfiguration(JobCoreConfiguration.newBuilder( jobClass.getName(), cron, shardingTotalCount).shardingItemParameters(shardingItemParameters).build(), jobClass.getCanonicalName())).overwrite(true).build(); } private List<SimpleJob> schedulers() { List<SimpleJob> jobList = new ArrayList<>(ApplicationContextProvider.getBeansOfType(SimpleJob.class).values()); jobList.forEach(job -> System.out.println(job.getClass().getCanonicalName())); return jobList; } }
在application.yml配置文件中添加以下配置:
simpleJob: com.dangdang.ddframe.job.example.job.simple: #job的包名 CourseSimpleJob: #job的類名 cron: 0/30 * * * * ? shardingTotalCount: 1 shardingItemParameters: 0=Beijing,1=Shanghai,2=Guangzhou SpringSimpleJob: #job的類名 cron: 0/25 * * * * ? shardingTotalCount: 3 shardingItemParameters: 0=Beijing,1=Shanghai,2=Guangzhou
很顯然這里的做法是通過將job的相關配置參數集中到一個文件里面,好處是配置參數可以統一起來集中管理。
3.4.Dataflow類型作業配置
@Configuration public class DataflowJobConfig { @Resource private ZookeeperRegistryCenter regCenter; @Resource private JobEventConfiguration jobEventConfiguration; @Bean public DataflowJob dataflowJob() { return new SpringDataflowJob(); } @Bean(initMethod = "init") public JobScheduler dataflowJobScheduler(final DataflowJob dataflowJob, @Value("${dataflowJob.cron}") final String cron, @Value("${dataflowJob.shardingTotalCount}") final int shardingTotalCount, @Value("${dataflowJob.shardingItemParameters}") final String shardingItemParameters) { return new SpringJobScheduler(dataflowJob, regCenter, getLiteJobConfiguration(dataflowJob.getClass(), cron, shardingTotalCount, shardingItemParameters), jobEventConfiguration); } private LiteJobConfiguration getLiteJobConfiguration(final Class<? extends DataflowJob> jobClass, final String cron, final int shardingTotalCount, final String shardingItemParameters) { return LiteJobConfiguration.newBuilder(new DataflowJobConfiguration(JobCoreConfiguration.newBuilder( jobClass.getName(), cron, shardingTotalCount).shardingItemParameters(shardingItemParameters).build(), jobClass.getCanonicalName(), true)).overwrite(true).build(); } }
這里在
dataflowJobScheduler
方法上的@Bean
注解中有一個initMethod = "init"
,這表示在創建JobScheduler
的實例之后會調用JobScheduler
的init
方法。而在前面2.3.3.Simple類型作業配置中我們是直接顯示的調用init
方法的。3.5.Script類型作業配置
腳本類型的作業配置這里就不做展開了,直接參考上面的官方配置即可,或者根據上面的經驗進行簡單的修改。
-
作業運行
正常地啟動springboot項目即可,作業會根據
cron
參數的配置自動執行。
部署
正常部署應用即可
- 啟動Elastic-Job-Lite指定注冊中心的Zookeeper。
- 運行包含Elastic-Job-Lite和業務代碼的jar文件。不限與jar或war的啟動方式。
運維平臺(可選)
解壓縮elastic-job-lite-console-${version}.tar.gz并執行bin\start.sh。
-
打開瀏覽器訪問http://localhost:8899/即可訪問控制臺。8899為默認端口號,可通過啟動腳本輸入-p自定義端口號。
elastic-job-lite-console-${version}.tar.gz可通過mvn install編譯獲取。
最后附上官方文檔大全地址:http://elasticjob.io/docs/elastic-job-lite/00-overview/