1. 程式人生 > 其它 >分散式任務排程的解決方案

分散式任務排程的解決方案

簡介

 隨著系統規模的發展,定時任務數量日益增多,任務也變得越來越複雜,尤其是在分散式環境下,存在多個業務系統,每個業務系統都有定時任務的需求,如果都在自身系統中排程,一方面增加業務系統的複雜度,另一方面也不方便管理,因此需要有一個任務平臺對分散的任務進行統一管理排程,基於目前的情況,任務平臺需要支援以下幾個方面:

1、任務統一管理,提供圖形化介面對任務進行配置和排程。
2、任務併發控制,同一個任務在同一時間只能允許一個執行。
3、任務彈性擴容,可根據繁忙情況動態增減伺服器分攤壓力,對大任務進行分片處理。
4、任務依賴問題,能夠處理任務包含子任務的情況,前一個完成後觸發子任務執行。
5、支援多型別的任務,支援Spring Bean、Shell等。
6、任務節點高可用,任務節點異常或者繁忙時能夠轉移到其他節點執行。
7、排程中心高可用,支援叢集部署,避免出現單點故障。
8、執行狀態監控,方便檢視任務執行狀態,異常情況告警,支援多渠道通知。

發展史

定時任務隨著技術發展,從單執行緒排程到多執行緒排程,從單機部署到叢集部署,從獨立執行到多工協同執行。

第一階段
單執行緒排程,在Java1.5之前,基於執行緒的等待(sleep或wait)機制定時執行,需要開發者實現排程邏輯,單個執行緒(Thread)處理單個任務有些浪費,但是一個執行緒(Timer)處理多個任務容易因為某個任務繁忙導致其他任務阻塞。

第二階段
執行緒池排程,在Java1.5開始提供ScheduledExecutorService排程執行緒池,排程執行緒池支援固定的延時和固定間隔模式,對於需要在某天或者某月的時間點執行就不大方便,需要計算時間間隔,轉換成啟動延時和固定間隔,處理起來比較麻煩。

第三階段
Spring任務排程,Spring簡化了任務排程,通過@Scheduled註解支援將某個Bean的方法定時執行,除了支援固定延時和固定間隔模式外,還支援cron表示式,使得定時任務的開發變得極其簡單。

第四階段
Quartz任務排程,在任務服務叢集部署下,Quartz通過資料庫鎖,實現任務的排程併發控制,避免同一個任務同時執行的情況。Quartz通過Scheduler提供了任務排程API,開發可以基於此開發自己的任務排程管理平臺。

第五階段
分散式任務平臺,提供一個統一的平臺,無需再去做和排程相關的開發,業務系統只需要實現具體的任務邏輯,自動註冊到任務排程平臺,在上面進行相關的配置就完成了定時任務的開發。

解決方案

現在分散式下任務排程有很多解決方案,可以基於Quartz開發任務管理平臺,也可以使用開源的任務排程平臺,比如xxl-job,elastic-job。

XXL-JOB
大眾點評員工徐雪裡於2015年釋出的分散式任務排程平臺,是一個輕量級分散式任務排程框架,其核心設計目標是開發迅速、學習簡單、輕量級、易擴充套件。官方地址:https://www.xuxueli.com/xxl-job/

ELASTIC-JOB
噹噹開發的彈性分散式任務排程系統,功能豐富強大,採用zookeeper實現分散式協調,實現任務高可用以及分片,並且可以支援雲開發,由兩個相互獨立的子專案Elastic-Job-Lite和Elastic-Job-Cloud組成。官方地址:http://elasticjob.io/docs/elastic-job-lite/00-overview/

方案對比

整合示例

下面以整合xxl-job為例,xxl-job將定時任務分為兩個部分:1、排程中心;2、執行器。因此整合xxl-job需要分成兩個步驟,1、部署排程中心,2、業務系統對接(執行器)。架構圖如下:

部署排程中心

部署前先確定部署方案,測試環境可以使用1個排程中心 + 1個mysql服務,生產環境建議使用2個排程中心 + mysql主從服務,保證高可用。部署前需確保已經準備:Jdk1.8,Maven、mysql,部署步驟如下:

下載xxl-job原始碼:http://gitee.com/xuxueli0323/xxl-job/,使用maven編譯打包,生成部署的xxl-job-admin.jar。

建立資料庫,並初始化相關的表,指令碼參考原始碼目錄doc/db/tables_xxl_job.sql

建立部署目錄,並配置資料庫等配置,可在打包之前,在原始碼裡面application.properties進行配置,也可以在部署目錄裡面單獨建立application.properties檔案裡面進行配置(推薦,spring boot優先載入啟動目錄下的配置,可以避免以後更改資料庫等配置時還需要重新打包原始碼)。

執行管理平臺(請先確保已經配置好Java執行環境,Jdk1.8或者以上)

具體步驟參考許雪裡部落格:https://www.cnblogs.com/xuxueli/p/5021979.html,部署可參考以下指令碼:

#下載原始碼
cd /root/
wget https://github.com/xuxueli/xxl-job/archive/v2.0.1.zip
unzip v2.0.1.zip
mv xxl-job-2.0.1 xxl-job

#修改配置檔案
vim /root/xxl-job/xxl-job-admin/src/main/resources/application.properties
## 修改mysql、郵件等配置

#編譯
cd /root/xxl-job
mvn clean package

# 建立部署目錄
mkdir -p /xxl-job
cd /root/xxl-job/xxl-job-admin/target/
cp /root/xxl-job/xxl-job-admin/target/xxl-job-admin-2.0.1.jar /xxl-job/xxl-job-admin-2.0.1.jar


#mysql 資料初始化(使用者名稱和密碼為root,資料庫編碼推薦使用utf8mb4)
mysql -u root -proot -e "source /root/xxl-job/doc/db/tables_xxl_job.sql;"

#啟動
nohup java -jar /xxl-job/xxl-job-admin-2.0.1.jar > /dev/null >& 1 &

#檢查 admin 123456
curl http://localhost:8080/xxl-job-admin

排程中心啟動成功登入後如下,預設使用者名稱admin,預設密碼123456,密碼可在配置檔案中更改。

業務系統對接

業務系統對接排程中心,需要根據當前專案的框架進行配置,可以參考原始碼xxl-job-executor-samples下例子,下面以業務系統基於spring boot框架進行整合。

配置執行器

@Configuration
public class XxlJobConfig {
    @Value("${spring.application.name:}")
    private String springAppName;

    @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:9999}")
    private int port;

    @Value("${xxl.job.accessToken:}")
    private String accessToken;

    @Value("${xxl.job.executor.logpath:job-logs}")
    private String logPath;

    @Value("${xxl.job.executor.logretentiondays:7}")
    private int logRetentionDays;

    @Bean
    public XxlJobSpringExecutor xxlJobExecutor() {
        XxlJobSpringExecutor xxlJobSpringExecutor = new XxlJobSpringExecutor();
        xxlJobSpringExecutor.setAdminAddresses(adminAddresses);
        if (StringUtils.isEmpty(appName)) {
            if (StringUtils.isEmpty(springAppName)) {
                throw new IllegalStateException("missing xxl-job appname config");
            }
            appName = springAppName;
        }
        xxlJobSpringExecutor.setAppName(appName);
        xxlJobSpringExecutor.setIp(ip);
        xxlJobSpringExecutor.setPort(port);
        xxlJobSpringExecutor.setAccessToken(accessToken);
        xxlJobSpringExecutor.setLogPath(logPath);
        xxlJobSpringExecutor.setLogRetentionDays(logRetentionDays);

        return xxlJobSpringExecutor;
    }
}

簡化配置說明:
1、spring boot應用基本都有appname,預設使用spring app name配置。
2、ip地址在多網絡卡、容器的時候需要指定,否則的話,使用預設就可以,spring-cloud-commons中提供了InetUtils工具類,可以幫助獲取IP
3、port可以預設指定一個,如果多個服務部署在同一臺伺服器上,可以通過檢測獲取或者規劃分配。
4、logpath最好指定在應用目錄下,最好不要使用絕對路徑,避免和其應用衝突。
5、logretentiondays日誌保留天數不用太大,根據需要設定,預設給一個較短的時間即可。

開發定時任務

定義定時任務有兩種方式:1、2.1.2或者之後版本可以直接在方法上加@XxlJob來宣告任務;2、2.1.2之前版本每個任務需要單獨開發一個Bean,實現IJobHandler介面,並且在類上加@JobHandler註解。第二種方式較麻煩,推薦使用第一種方式(目前還沒穩定版)。

基於@XxlJob註解程式碼方式(建議制定名稱,和排程中心配置保持一致)

@Component
public class SampleXxlJob {
    private static Logger logger = LoggerFactory.getLogger(SampleXxlJob.class);

    /**
     * 1、簡單任務示例(Bean模式)
     */
    @XxlJob("demoJobHandler")
    public ReturnT<String> demoJobHandler(String param) throws Exception {
        XxlJobLogger.log("XXL-JOB, Hello World.");
        for (int i = 0; i < 5; i++) {
            XxlJobLogger.log("beat at:" + i);
            TimeUnit.SECONDS.sleep(2);
        }
        return ReturnT.SUCCESS;
    }

    /**
     * 2、分片廣播任務
     */
    @XxlJob("shardingJobHandler")
    public ReturnT<String> shardingJobHandler(String param) throws Exception {
        // 分片引數
        ShardingUtil.ShardingVO shardingVO = ShardingUtil.getShardingVo();
        XxlJobLogger.log("分片引數:當前分片序號 = {}, 總分片數 = {}", shardingVO.getIndex(), shardingVO.getTotal());
        // 業務邏輯
        for (int i = 0; i < shardingVO.getTotal(); i++) {
            if (i == shardingVO.getIndex()) {
                XxlJobLogger.log("第 {} 片, 命中分片開始處理", i);
            } else {
                XxlJobLogger.log("第 {} 片, 忽略", i);
            }
        }
        return ReturnT.SUCCESS;
    }
}
基於@JobHandler程式碼方式

@JobHandler(value="demoJobHandler")
@Component
public class DemoJobHandler extends IJobHandler {
	@Override
	public ReturnT<String> execute(String param) throws Exception {
		XxlJobLogger.log("XXL-JOB, Hello World.");
		for (int i = 0; i < 5; i++) {
			XxlJobLogger.log("beat at:" + i);
			TimeUnit.SECONDS.sleep(2);
		}
		return SUCCESS;
	}
}

配置定時任務

1、配置定時任務,需要先配置執行器,推薦使用自動註冊方式,避免叢集部署時還需要調整機器地址,新增介面如下(注意appname要和業務系統中配置一致):

2、新增完執行器後,新增任務,JobHandler要和程式碼中配置的名稱一致,執行器叢集部署可以通過配置路由方式來控制執行,xxl-job排程只支援cron表示式。

3、啟動或者執行任務,查詢執行日誌、註冊節點等

整合踩坑記錄

1、任務伺服器必須做時鐘同步,執行器時鐘不能排程中心180秒,否則將會導致排程失敗(RPC框架限制)
2、排程任務的時間間隔低於實際執行耗時,導致產生較大的排程日誌;
3、儘量避免短任務,比如秒級的任務會導致大量資料庫鎖影響效能;
4、排程日誌量偏大導致查詢慢,由於日誌都記錄在資料庫,需要定時清理;
5、自動註冊時伺服器多網絡卡導致排程失敗,註冊時需指定網絡卡IP;

參考:
http://blog.freshfood.cn/article/39
https://www.cnblogs.com/xuxueli/p/5021979.html
https://www.xuxueli.com/page/projects.html