LTS簡介以及與SpringBoot的簡單集成

LTS簡介以及與SpringBoot的簡單集成

一 什麼是LTS

關於定時任務,雖然Spring提供了基於註解@EnableScheduling @Scheduled的實現方式。其實現是通過線程池ScheduledThreadPoolExecutor的方式,具體這裏就不多做介紹啦,有興趣的小夥伴可以自行了解下~
但是以上只適用於單機的情況下,如果是分佈式項目的話,就會顯得有些力不從心了。所以這裏給大家介紹一個分佈式任務調度框架 -> LTS,它是阿里巴巴的一個開源項目。
借用官方文檔的一句話就是:

LTS 着力於解決分佈式任務調度問題,將任務的提交者和執行者解耦,解決任務執行的單點故障,支持動態擴容,出錯重試等機制。代碼程序設計上,參考了優秀開源項目Dubbo,Hadoop的部分思想。

二 LTS架構總覽

以下圖片來自官方文檔
在這裏插入圖片描述
乍一看這都啥跟啥啊,有點麻煩的樣子,別急,且聽我慢慢道來,咱們看圖說話。

從上圖可以看出,LTS包含以下幾種節點類型:

  1. JobClient -> 負責提交任務,並接收任務執行反饋結果。
  2. JobTracker -> 負責任務調度,接收並分配任務。
  3. TaskTracker -> 負責執行任務,執行完之後將任務執行結果反饋給 JobTracker
  4. Monitor -> 負責收集各個節點的監控信息,包括任務監控信息,節點JVM監控信息。
  5. Admin -> 則是後臺管理,負責節點管理,任務隊列管理,監控管理等。

LTS的這五種節點都是無狀態的,都可以部署多個,動態擴容,來實現負載均衡,實現更大的負載量, 並且框架採用FailStore策略使LTS具有很好的容錯能力。

既然節點可以以集羣的方式部署,那麼肯定少不了註冊中心啦!如上圖第一行所示:
註冊中心可以使Zookeeper或者Redis官方推薦使用Zookeeper作爲註冊中心(劃重點,要考的)。

繼續看圖,FailStore -> 顧名思義就是失敗存儲,主要用於在部分場景遠程RPC調用失敗的情況,採取現存儲本地KV文件系統,待遠程通信恢復的時候再進行數據補償。
主要用於節點容錯,當遠程數據交互失敗之後,存儲在本地,等待遠程通信恢復的時候,再將數據提交。

接着是FailStore的右邊,有個QueueManager -> 任務隊列 ,主要用於存儲任務數據和任務執行日誌等。支持mysqlmongodb實現,官方推薦使用mysql,當然也可以自己擴展實現,例如Oracle。

然後是節點組NodeGroup,每個節點組的節點都是平等的,對外提供相同的服務。每個節點組中都有一個master節點,動態選舉得到的。

最後是ClusterName,也就是LTS集羣,就如上圖所示,整個圖就是一個集羣,包含LTS的五種節點。

OK,大概瞭解了LTS的架構之後,讓我們繼續瞭解一下LTS的執行流程吧!

三 LTS執行流程

以下圖片來自官方文檔
在這裏插入圖片描述
說了這麼多,那麼LTS都支持什麼類型的任務呢?目前支持以下幾種任務:

實時任務,提交之後就會馬上執行的任務;
定時任務,在指定時間點執行的任務,譬如 今天3點執行(單次)。
Cron任務:CronExpression,和quartz類似(但是不是使用quartz實現的)譬如 0 0/1 * ?
Repeat任務(重複任務):譬如每隔5分鐘執行一次,重複50次就停止。

由於篇幅有限,關於LTS的介紹就先到這裏,更多介紹小夥伴可以參閱 官方文檔 ~
LTS gihub地址 -> LTS項目源碼
LTS 例子地址 -> LTS使用實例

你可以直接下載LTS使用實例,按照官方指示,在本地運行起來。 裏面集成了spring以及springboot等的使用~

好了,接下來,咱們就正式進入SprigBoot和LTS的集成。

四 SpringBoot集成LTS

特別說明:本示例的主要目的僅僅是告訴大家如何使用LTS,所以偷了個懶,將所有節點都揉合到了一個工程,實際項目是分開部署的,因需而定。
整個工程其實很簡單,不信你看:
在這裏插入圖片描述

1. 準備工作
  • 新建SpringBoot工程(這不廢話嗎);
  • 導入相應的依賴,如果你是導入的官方example,則不需要做這些工作。如果是新建SpringBoot工程,我的版本是 2.2.1.RELEASE,則不要直接把官方的example pom依賴複製過來,因爲版本兼容問題,可能會導致錯誤~ 以下是我的pom文件依賴
<dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <!--lts-->
        <dependency>
            <groupId>com.github.ltsopensource</groupId>
            <artifactId>lts</artifactId>
            <version>1.7.0</version>
        </dependency>
        <dependency>
            <groupId>io.netty</groupId>
            <artifactId>netty-all</artifactId>
            <version>4.1.25.Final</version>
        </dependency>
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
            <version>1.2.58</version>
        </dependency>
        <dependency>
            <groupId>org.mapdb</groupId>
            <artifactId>mapdb</artifactId>
            <version>2.0-beta10</version>
        </dependency>
        <dependency>
            <groupId>com.101tec</groupId>
            <artifactId>zkclient</artifactId>
            <version>0.3</version>
        </dependency>
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid</artifactId>
            <version>1.0.14</version>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>5.1.26</version>
        </dependency>
        <dependency>
            <groupId>org.javassist</groupId>
            <artifactId>javassist</artifactId>
            <version>3.20.0-GA</version>
        </dependency>

    </dependencies>

注意如果你用的是Redis作爲註冊中心,mongodb作爲任務隊列,那麼請引入相應的依賴,我這裏用的是Zookeeper和mysql。

  • 然後是配置文件信息,如下:
##########################################
# jobclient->負責提交任務以及接收任務執行結果 #
##########################################
#集羣名稱
lts.jobclient.cluster-name=test_cluster
#註冊中心
lts.jobclient.registry-address=zookeeper://127.0.0.1:2181
#JobClient節點組名稱
lts.jobclient.node-group=test_jobClient
#是否使用RetryClient
lts.jobclient.use-retry-client=true
#失敗存儲,用於服務正常後再次執行(容錯處理)
lts.jobclient.configs.job.fail.store=mapdb

#######################################
# jobtracker->負責調度任務 接收並分配任務 #
#######################################
lts.jobtracker.cluster-name=test_cluster
lts.jobtracker.listen-port=35001
lts.jobtracker.registry-address=zookeeper://127.0.0.1:2181
lts.jobtracker.configs.job.logger=mysql
lts.jobtracker.configs.job.queue=mysql
lts.jobtracker.configs.jdbc.url=jdbc:mysql://127.0.0.1:3306/lts
lts.jobtracker.configs.jdbc.username=root
lts.jobtracker.configs.jdbc.password=root

###########################################################
# tasktracker->負責執行任務 執行完任務將執行結果反饋給JobTracker #
###########################################################
lts.tasktracker.cluster-name=test_cluster
lts.tasktracker.registry-address=zookeeper://127.0.0.1:2181
#TaskTracker節點組默認是64個線程用於執行任務
#lts.tasktracker.work-threads=64
lts.tasktracker.node-group=test_trade_TaskTracker
#lts.tasktracker.dispatch-runner.enable=true
#lts.tasktracker.dispatch-runner.shard-value=taskId
lts.tasktracker.configs.job.fail.store=mapdb


################################################################
# jmonitor->負責收集各個節點的監控信息,包括任務監控信息,節點JVM監控信息 #
################################################################
lts.monitor.cluster-name=test_cluster
lts.monitor.registry-address=zookeeper://127.0.0.1:2181
lts.monitor.configs.job.logger=mysql
lts.monitor.configs.job.queue=mysql
lts.monitor.configs.jdbc.url=jdbc:mysql://127.0.0.1:3306/lts
lts.monitor.configs.jdbc.username=root
lts.monitor.configs.jdbc.password=root

肯定有人要問了,哪來的lts數據庫,所以,還需要新建一個數據庫,名爲lts ,其他不用管,因爲表信息在項目啓動之後會自動創建的。

除了application.properties配置文件,還需要新建log4j.properties日誌配置文件,如下:

log4j.rootLogger=INFO,stdout
log4j.appender.stdout.Threshold=INFO
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=%d [%t] (%F:%L) %-5p %c %x - %m%n

說明:以上配置信息,均可以在官方示例lts-example找到。

完成以上準備工作之後,接着便是實現任務提交以及任務執行了。

2. 啓動類設置

其實很簡單,加上相應的註解即可:

@SpringBootApplication
@EnableJobClient        //JobClient
@EnableTaskTracker      //TaskTracker
@EnableJobTracker       //JobTracker 
@EnableMonitor          //Monitor
public class LtstestApplication {

    public static void main(String[] args) {
        SpringApplication.run(LtstestApplication.class, args);
    }

}

哈哈,是不是和集成微服務很像啊,也是需要添加相應的註解,具體作用一目瞭然,就不多做贅述啦~

3. JobClient提交任務

關於Jobclient使用的官方建議

一般在一個JVM中只需要一個JobClient實例即可,不要爲每種任務都新建一個JobClient實例,這樣會大大的浪費資源,因爲一個JobClient可以提交多種任務。

本示例中,我直接寫在了TestController中,模擬了提交兩個不同的任務:

 	@Autowired
    private JobClient jobClient;	

	@GetMapping("test01")
    public Map<String, Object> test01() {
        //模擬提交一個任務
        Job job = new Job();
        job.setTaskId("task-AAAAAAAAAAAAAAA");
        job.setCronExpression("0/3 * * * * ?");
        //設置任務類型 區分不同的任務 執行不同的業務邏輯
        job.setParam("type", "aType");
        job.setNeedFeedback(true);
        //任務觸發時間 如果設置了 cron 則該設置無效
//        job.setTriggerTime(DateUtils.addDay(new Date(), 1).getTime());
        //任務執行節點組
        job.setTaskTrackerNodeGroup("test_trade_TaskTracker");
        //當任務隊列中存在這個任務的時候,是否替換更新
        job.setReplaceOnExist(false);
        Map<String, Object> submitResult = new HashMap<String, Object>(4);
        try {
            //任務提交返回值 response
            Response response = jobClient.submitJob(job);
            submitResult.put("success", response.isSuccess());
            submitResult.put("msg", response.getMsg());
            submitResult.put("code", response.getCode());
        } catch (Exception e) {
            log.error("提交任務失敗", e);
            throw new RuntimeException("提交任務失敗");
        }
        return submitResult;
    }

    @GetMapping("test02")
    public Map<String, Object> test02() {
        //模擬提交一個任務
        Job job = new Job();
        job.setTaskId("task-BBBBBBBBBBBBBBB");
        job.setCronExpression("0/6 * * * * ?");
        //設置任務類型 區分不同的任務 執行不同的業務邏輯
        job.setParam("type", "bType");
        job.setNeedFeedback(true);
        //任務觸發時間 如果設置了 cron 則該設置無效
//        job.setTriggerTime(DateUtils.addDay(new Date(), 1).getTime());
        //任務執行節點組
        job.setTaskTrackerNodeGroup("test_trade_TaskTracker");
        //當任務隊列中存在這個任務的時候,是否替換更新
        job.setReplaceOnExist(false);
        Map<String, Object> submitResult = new HashMap<String, Object>(4);
        try {
            Response response = jobClient.submitJob(job);
            submitResult.put("success", response.isSuccess());
            submitResult.put("msg", response.getMsg());
            submitResult.put("code", response.getCode());
        } catch (Exception e) {
            log.error("提交任務失敗", e);
            throw new RuntimeException("提交任務失敗");
        }
        return submitResult;
    }

注意看:JobClient我們可以直接引入,然後構建一個Job,通過JobClient進行提交。任務提交之後,JobTracker會對任務進行分發,分發方式有如下兩種:

TaskTracker會定時發送pull請求給JobTracker, 默認1s一次, 在發送pull請求之前,會檢查當前TaskTracker是否有可用的空閒線程,如果沒有則不會發送pull請求,同時也會檢查本節點機器資源是否足夠,主要是檢查cpu和內存使用率,默認超過90%就不會發送pull請求,當JobTracker收到TaskTracker節點的pull請求之後,再從任務隊列中取出相應的已經到了執行時間點的任務 push給TaskTracker,這裏push的個數等於TaskTracker的空餘線程數。

還有一種途徑是,每個TaskTracker線程處理完當前任務之後,在反饋給JobTracker的時候,同時也會詢問JobTracker是否有新的任務需要執行,如果有JobTracker會同時返回給TaskTracker一個新的任務執行。所以在任務量足夠大的情況下,每個TaskTracker基本上是滿負荷的執行的。

4. TaskTracker執行任務

關於TaskTracker使用的官方建議

一個JVM一般也儘量保持只有一個TaskTracker實例即可,多了就可能造成資源浪費。
當遇到一個TaskTracker要運行多種任務的時候,在一個JVM中,最好使用一個TaskTracker去運行多種任務,因爲一個JVM中使用多個TaskTracker實例比較浪費資源(當然當你某種任務量比較多的時候,可以將這個任務單獨使用一個TaskTracker節點來執行)。

上面提交了兩個任務,分別是任務A任務B,所以這裏演示的是一個TaskTracker執行多種不同的任務
任務的執行必須實現JobRunner接口,如下任務A

public class JobRunnerA implements JobRunner {

    @Override
    public Result run(JobContext jobContext) throws Throwable {
        //  TODO A類型Job的邏輯
        System.out.println("我是Runner A");
        return null;
    }

}

任務B同理,就不重複貼出代碼了。

需要指出的是,在SpringBoot中,任務的執行需要添加@JobRunner4TaskTracker註解,但是有且只能有一個@JobRunner4TaskTracker註解。所以,對於同一個TaskTracker執行不同的任務,需要進行調度執行,如下:

/**
 * 總入口,在 taskTracker.setJobRunnerClass(JobRunnerDispatcher.class)
 * JobClient 提交 任務時指定 Job 類型  job.setParam("type", "aType")
 */
@JobRunner4TaskTracker
public class JobRunnerDispatcher implements JobRunner {

    private static final Logger log = LoggerFactory.getLogger(JobRunnerDispatcher.class);

    private static final ConcurrentHashMap<String/*type*/, JobRunner>
            JOB_RUNNER_MAP = new ConcurrentHashMap<String, JobRunner>();

    static {
        JOB_RUNNER_MAP.put("aType", new JobRunnerA()); // 也可以從Spring中拿
        JOB_RUNNER_MAP.put("bType", new JobRunnerB());
    }

    @Override
    public Result run(JobContext jobContext) throws Throwable {
        Job job = jobContext.getJob();
        String type = job.getParam("type");
        return JOB_RUNNER_MAP.get(type).run(jobContext);
    }

}

說明:該JobRunnerDispatcher 類同樣實現了JobRunner接口,並且添加了 @JobRunner4TaskTracker註解,表示該類纔是真正會執行任務的地方。通過該類,實現不同的任務執行。

實際上,到這裏基本整個LTS任務從提交到執行就已經完成了,也就是簡單的集成完成了。
可以直接啓動項目了~

5. master節點監聽以及任務完成處理類

這個不必多說,直接看代碼好了(來自lts-example):

/**
 * 主節點監聽
 */
@MasterNodeListener
public class MasterNodeChangeListener implements MasterChangeListener {

    private static final Logger log = LoggerFactory.getLogger(MasterNodeChangeListener.class);


    /**
     * @param master   master節點
     * @param isMaster 表示當前節點是不是master節點
     */
    @Override
    public void change(Node master, boolean isMaster) {
        // 一個節點組master節點變化後的處理 , 譬如我多個JobClient, 但是有些事情只想只有一個節點能做。
        if (isMaster) {
            log.info("我變成了節點組中的master節點了, 恭喜, 我要放大招了");
        } else {
            log.info(StringUtils.format("master節點變成了{},不是我,我不能放大招,要猥瑣", master));
        }
    }

}
/**
 * 任務完成處理類
 */
@Component
public class JobCompletedHandlerImpl implements JobCompletedHandler {

    private static final Logger log = LoggerFactory.getLogger(JobCompletedHandlerImpl.class);

    @Override
    public void onComplete(List<JobResult> jobResults) {
        //對任務執行結果進行處理 打印相應的日誌信息
        if (CollectionUtils.isNotEmpty(jobResults)) {
            for (JobResult jobResult : jobResults) {
                String time = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date());
                log.info("任務執行完成taskId={}, 執行完成時間={}, job={}",
                        jobResult.getJob().getTaskId(), time, jobResult.getJob().toString());
            }
        }
    }

}
6. Admin後臺管理

這個直接去官方源碼複製下來admin模塊,然後丟到Tomcat本地啓動就OK了~

7.項目啓動

啓動項目,分別調用接口 /test01() /test02()
在這裏插入圖片描述
可以看到,master節點的變化是在監聽中的,以及不同的任務分別在執行。
OK,那麼這些信息,在後臺管理能看到嗎,答案是肯定的如圖:
在這裏插入圖片描述
可以看到,通過後臺管理,我們可以暫停或者刪除任務,以及添加新的任務。當然還有很多其他功能…

OK,關於LTS的簡單介紹,以及與SpringBoot的集成,就到這裏啦~
希望對看過的小夥伴能有幫助~

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章