爲什麼我們需要定時任務
很多業務場景需要我們某一特定的時刻去做某件任務,定時任務解決的就是這種業務場景。一般來說,系統可以使用消息傳遞代替部分定時任務,兩者有很多相似之處,可以相互替換場景。如,上面發貨成功發短信通知客戶的業務場景,我們可以在發貨成功後發送MQ消息到隊列,然後去消費mq消息,發送短信。
但在某些場景下不能互換:
a)時間驅動/事件驅動:內部系統一般可以通過時間來驅動,但涉及到外部系統,則只能使用時間驅動。如怕取外部網站價格,每小時爬一次
b)批量處理/逐條處理:批量處理堆積的數據更加高效,在不需要實時性的情況下比消息中間件更有優勢。而且有的業務邏輯只能批量處理。如移動每個月結算我們的話費
c)實時性/非實時性:消息中間件能夠做到實時處理數據,但是有些情況下並不需要實時,比如:vip升級
d)系統內部/系統解耦:定時任務調度一般是在系統內部,而消息中間件可用於兩個系統間
如果你的項目後期部署到集羣環境下,如果不做處理,就會出現意想不到的問題,原因:由於我們項目同時部署在多臺集羣機器上,因此到達指定的定時時間時,多臺機器上的定時器可能會同時啓動,造成重複數據或者程序異常等問題
java有哪些定時任務的框架
單機
- timer:是一個定時器類,通過該類可以爲指定的定時任務進行配置。TimerTask類是一個定時任務類,該類實現了Runnable接口,缺點異常未檢查會中止線程
- ScheduledExecutorService:相對延遲或者週期作爲定時任務調度,缺點沒有絕對的日期或者時間
- spring定時框架:配置簡單功能較多,如果系統使用單機的話可以優先考慮spring定時器
分佈
- Quartz:Java事實上的定時任務標準。但Quartz關注點在於定時任務而非數據,並無一套根據數據處理而定製化的流程。雖然Quartz可以基於數據庫實現作業的高可用,但缺少分佈式並行調度的功能
- TBSchedule:阿里早期開源的分佈式任務調度系統。代碼略陳舊,使用timer而非線程池執行任務調度。衆所周知,timer在處理異常狀況時是有缺陷的。而且TBSchedule作業類型較爲單一,只能是獲取/處理數據一種模式。還有就是文檔缺失比較嚴重
- elastic-job:噹噹開發的彈性分佈式任務調度系統,功能豐富強大,採用zookeeper實現分佈式協調,實現任務高可用以及分片,目前是版本2.15,並且可以支持雲開發
- Saturn:是唯品會自主研發的分佈式的定時任務的調度平臺,基於噹噹的elastic-job 版本1開發,並且可以很好的部署到docker容器上。
- xxl-job: 是大衆點評員工徐雪裏於2015年發佈的分佈式任務調度平臺,是一個輕量級分佈式任務調度框架,其核心設計目標是開發迅速、學習簡單、輕量級、易擴展。
elastic-job的上次更新時間是兩年前, 所以我們果斷使用xxl-job了.
- 下載xxl-job的源碼, http://www.xuxueli.com/xxl-job/
這次下載的是2.1.0的版本
- 解壓源碼, 導入源碼到idea中去
- 執行數據庫的文件
- 打開xxl-job-admin 項目,修改配置文件,修改application.properties中的數據庫的配置信息, 然後啓動xxl-job-admin 項目, 訪問 http://localhost:8080/xxl-job-admin 就可以了, 默認登錄賬號 "admin/123456"
- 把xxl-job-core 項目達成一個jar 包, 放入到一個新的springboot 暫時名字叫做max-demo項目中去, 添加依賴到pom文件中去,
<!-- xxl-job-core -->
<dependency>
<groupId>com.xuxueli</groupId>
<artifactId>xxl-job-core</artifactId>
<version>2.1.0</version>
</dependency>
- 修改max-demo的配置文件
# web port
server.port=8081
# log config
logging.config=classpath:logback.xml
### xxl-job admin address list, such as "http://address" or "http://address01,http://address02"
xxl.job.admin.addresses=http://127.0.0.1:8080/xxl-job-admin
### xxl-job executor address
xxl.job.executor.appname=xxl-job-executor-sample
xxl.job.executor.ip=
xxl.job.executor.port=9999
### xxl-job, access token
xxl.job.accessToken=
### xxl-job log path
xxl.job.executor.logpath=/data/applogs/xxl-job/jobhandler
### xxl-job log retention days
xxl.job.executor.logretentiondays=-1
- 添加日誌的配置文件: logback.xml
<?xml version="1.0" encoding="UTF-8"?>
<configuration debug="false" scan="true" scanPeriod="1 seconds">
<contextName>logback</contextName>
<property name="log.path" value="/data/applogs/xxl-job/xxl-job-executor-sample-springboot.log"/>
<appender name="console" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>%d{HH:mm:ss.SSS} %contextName [%thread] %-5level %logger{36} - %msg%n</pattern>
</encoder>
</appender>
<appender name="file" class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>${log.path}</file>
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<fileNamePattern>${log.path}.%d{yyyy-MM-dd}.zip</fileNamePattern>
</rollingPolicy>
<encoder>
<pattern>%date %level [%thread] %logger{36} [%file : %line] %msg%n
</pattern>
</encoder>
</appender>
<root level="info">
<appender-ref ref="console"/>
<appender-ref ref="file"/>
</root>
</configuration>
- 添加配置文件:
import com.xxl.job.core.executor.impl.XxlJobSpringExecutor;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* xxl-job config
*
* @author xuxueli 2017-04-28
*/
@Configuration
public class XxlJobConfig {
private Logger logger = LoggerFactory.getLogger(XxlJobConfig.class);
@Value("${xxl.job.admin.addresses}")
private String adminAddresses;
@Value("${xxl.job.executor.appname}")
private String appName;
@Value("${xxl.job.executor.ip}")
private String ip;
@Value("${xxl.job.executor.port}")
private int port;
@Value("${xxl.job.accessToken}")
private String accessToken;
@Value("${xxl.job.executor.logpath}")
private String logPath;
@Value("${xxl.job.executor.logretentiondays}")
private int logRetentionDays;
@Bean(initMethod = "start", destroyMethod = "destroy")
public XxlJobSpringExecutor xxlJobExecutor() {
logger.info(">>>>>>>>>>> xxl-job config init.");
XxlJobSpringExecutor xxlJobSpringExecutor = new XxlJobSpringExecutor();
xxlJobSpringExecutor.setAdminAddresses(adminAddresses);
xxlJobSpringExecutor.setAppName(appName);
xxlJobSpringExecutor.setIp(ip);
xxlJobSpringExecutor.setPort(port);
xxlJobSpringExecutor.setAccessToken(accessToken);
xxlJobSpringExecutor.setLogPath(logPath);
xxlJobSpringExecutor.setLogRetentionDays(logRetentionDays);
return xxlJobSpringExecutor;
}
/**
* 針對多網卡、容器內部署等情況,可藉助 "spring-cloud-commons" 提供的 "InetUtils" 組件靈活定製註冊IP;
*
* 1、引入依賴:
* <dependency>
* <groupId>org.springframework.cloud</groupId>
* <artifactId>spring-cloud-commons</artifactId>
* <version>${version}</version>
* </dependency>
*
* 2、配置文件,或者容器啓動變量
* spring.cloud.inetutils.preferred-networks: 'xxx.xxx.xxx.'
*
* 3、獲取IP
* String ip_ = inetUtils.findFirstNonLoopbackHostInfo().getIpAddress();
*/
}
- 添加自己的定時任務處理類
import com.xxl.job.core.biz.model.ReturnT;
import com.xxl.job.core.handler.IJobHandler;
import com.xxl.job.core.handler.annotation.JobHandler;
import org.springframework.stereotype.Component;
/**
* @author zk
* @Description:
* @date 2019-10-09 9:47
*/
@Component
@JobHandler("myJobhandler2")
public class MyJobhandler extends IJobHandler {
@Override
public ReturnT<String> execute(String param) throws Exception {
System.out.println("這是自定義的第二個任務" + param);
return ReturnT.SUCCESS;
}
}
- 在網頁上新建自己的定時任務.
分別是cron 表達式, jobhandker, 就是自定義的一個jobhandler, 名字對應就可以了.
執行就是把這個任務執行一次, 啓動就是直接啓動這個定時任務了, 根據cron表達式去執行.
調度中心集羣
調度中心支持集羣部署,提升調度系統容災和可用性。
調度中心集羣部署時,幾點要求和建議:
- DB配置保持一致;
- 登陸賬號配置保持一致;
- 集羣機器時鐘保持一致(單機集羣忽視);
- 建議:推薦通過nginx爲調度中心集羣做負載均衡,分配域名。調度中心訪問、執行器回調配置、調用API服務等操作均通過該域名進行。
在原來服務的基礎上新建一個啓動文件, 註釋掉原項目中的端口信息,才用配置文件的形式.增加token,其他不改動.
就可以啓動兩個調度中心了.在任意一個調度中心上新建一個任務, 另一個都會有的.
執行器集羣
執行器支持集羣部署,提升調度系統可用性,同時提升任務處理能力。
執行器集羣部署時,幾點要求和建議:
- 執行器回調地址(xxl.job.admin.addresses)需要保持一致;執行器根據該配置進行執行器自動註冊等操作。
- 同一個執行器集羣內AppName(xxl.job.executor.appname)需要保持一致;調度中心根據該配置動態發現不同集羣的在線執行器列表。
Web 的端口號和executor的端口號都不能重複.
修改配置文件,添加多個調度中心的地址, 以及token,修改端口號,註釋掉端口號
新建一個springboot 啓動類,然後啓動兩個服務,開始執行定時任務,執行了3次任務, 都發到同一個客戶端執行了, 沒有問題,在關閉了一個客戶端之後,再次執行,任務失敗. 過了一會,再執行,任務成功, 已經自動切換到另外一臺應用上了,關閉一個調度中心也是沒有問題的.