擁抱Kubernetes,再見了,SpringBoot @Scheduled

項目開發中老是須要執行一些定時任務,好比定時處理數據以後發送郵件,定時更新緩存等等。java

Java定時任務

  1. 基於 java.util.Timer 定時器,實現相似鬧鐘的定時任務
  2. 使用 Quartz、elastic-job、xxl-job 等開源第三方定時任務框架,適合分佈式項目應用
  3. 使用 Spring 提供的一個註解: @Scheduled

項目框架使用的是SpringBoot,因此以前定時任務使用的是SpringBoot中的@Scheduled。但是這種方式並不適合咱們如今的cloud環境,爲了更加cloud native一點,我刪除了使用SpringBoot寫的37個定時任務,改成使用Kubernetes cronjob的方式。node

定時任務代碼編寫

public interface Command {
 /**  * 遵循Unix約定,若是命令執行正常,則返回0;不然爲非0。  */  int execute(String... args); }  複製代碼

首先定義一個接口,全部具體的定時任務都必須實現該接口。接下來是具體的某一個定時任務git

@Component
@Slf4j public class ProjectCommandLineRunner implements CommandLineRunner {   Map<String, Command> commandMap = new HashMap<>();   @Autowired  private SendEmailCommand sendEmailCommand;   @PostConstruct  private void init() {  commandMap.put("sendEmail", sendEmailCommand);  }   @Override  public void run(String... args) throws Exception {   if (args.length == 0) {  return;  }   if (!commandMap.containsKey(args[0])) {  log.error("'{}' command not found", args[0]);  System.exit(-1);  }   Command command = commandMap.get(args[0]);   String[] arguments = Arrays.copyOfRange(args, 1, args.length);   System.exit(command.execute(arguments));  } }  @Component @Slf4j public class SendEmailCommand implements Command {   @Override  public int execute(String... args) {   try {  // 省略業務邏輯代碼   log.info("send email success");   return 0;   } catch (Exception e) {  log.error("send email error", e);  return -1;  }  } }  複製代碼

上面的代碼咱們採用了策略模式,後面即便新增其餘定時任務也只是會改動不多的代碼。github

本地調試

cronjob不用打包成單獨的鏡像,它直接和咱們的web應用公用同一個鏡像,本地調試的時候也是極爲方便的,只須要咱們啓動SpringBoot Application時指定參數便可web

調試定時任務
調試定時任務

對應的定時任務執行完成以後就會,application就會退出。redis

cronjob yaml

一個基本的cronjob yaml以下所示spring

apiVersion: batch/v1beta1
kind: CronJob metadata:  name: send-email-job spec:  failedJobsHistoryLimit: 3  successfulJobsHistoryLimit: 1  startingDeadlineSeconds: 180  concurrencyPolicy: Forbid  schedule: "0 4 * * 1-5"  jobTemplate:  spec:  template:  spec:  containers:  - name: send-email-job  image: harbor.xxx.com/think123/project  imagePullPolicy: Always  command: ["java"]  args: ["-jar","/app/target/think123-task.jar","sendEmail"]  envFrom:  - configMapRef:  name: smcp-config  - secretRef:  name: smcp-service-secret  resources:  requests:  cpu: "250m"  memory: 1024Mi  limits:  cpu: "500m"  memory: 1024Mi  restartPolicy: Never  複製代碼

在定時任務中,可能某個job尚未執行完,另一個job就產生了。這個時候咱們能夠經過spec.concurrencyPolicy字段來定義具體的處理策略。mongodb

  1. concurrencyPolicy=Allow,這也是默認狀況,這意味着這些Job能夠同時存在;
  2. concurrencyPolicy=Forbid,這意味着不會建立新的Pod,該建立週期被跳過;
  3. concurrencyPolicy=Replace,這意味着新產生的Job會替換舊的、沒有執行完的Job

幾個關鍵參數解釋以下:api

  1. schedule : Unix Cron格式的表達式,cron表達式中的五個部分分別表明:分鐘、小時、日、月、星期。
  2. startingDeadlineSeconds : 表示在過去的多少秒(這裏設置的180)裏,若是job建立失敗的數據達到了100次,那麼這個job就不會被建立執行了。
  3. restartPolicy: 重啓策略(有Never和OnFailure兩個選項)。當job正常結束以後是否須要重啓

restartPolicy在Job對象裏只容許被設置爲Never和OnFailure;而在Deployment對象裏,restartPolicy則只容許被設置爲Always。緩存

實際上在jobTemplate.spec.template中能夠像pod中那樣,指定volume,指定nodeSelector,都是能夠的。這個template實際上就是指的pod的template。好比上面示例咱們就指定了環境變量,咱們的一些參數就能夠經過環境變量進行注入,好比redis地址,mongodb用戶名密碼等。

實際使用

上面的yaml雖然能夠直接使用,可是咱們用不着每一個job都去寫一份一樣的模板,實際中咱們會使用Kustomize控制模板來生成job。好比咱們有一個新的任務,是計算熱點文章並更新redis

對於這個任務而言,變化的主要有兩個地方,第一個是定時任務的時間不一樣,第二個是指定的參數不一樣。因此咱們的每一個任務只須要更新這兩個參數就好了

關於kustomize的使用能夠參考我以前的kustomize的介紹,打包的話能夠看看springboot build的文章

對於模板設定,咱們造成了下面的目錄結構

$ tree
. |-- base | |-- cronjob.yaml | `-- kustomization.yaml `-- overlay  `-- beta  |-- kustomization.yaml  `-- send-email-patch-args.yaml  複製代碼

cronjob.yaml做爲全部job的模板,而send-emial-patch-args.yaml則是針對具體的job的一個替換。涉及到的yaml文件內容以下:

# base/kustomization.yaml  apiVersion: kustomize.config.k8s.io/v1beta1 kind: Kustomization resources: - cronjob.yaml   # base/cronjob.yaml apiVersion: batch/v1beta1 kind: CronJob metadata:  name: think123- spec:  failedJobsHistoryLimit: 3  successfulJobsHistoryLimit: 1  startingDeadlineSeconds: 180  concurrencyPolicy: Forbid  schedule: "0 0 1 * *"  jobTemplate:  spec:  template:  spec:  containers:  - name: cron-job  image: harbor.xxx.com/think123/my-task  imagePullPolicy: Always  args:  - "help"  envFrom:  - configMapRef:  name: smcp-config  - secretRef:  name: smcp-service-secret  resources:  requests:  cpu: "250m"  memory: 1024Mi  limits:  cpu: "500m"  memory: 1024Mi  restartPolicy: Never   # overlay/beta/kustomization.yaml apiVersion: kustomize.config.k8s.io/v1beta1 kind: Kustomization  nameSuffix: send-email-job  resources: - ../../base/  patchesStrategicMerge:  - send-email-patch-args.yaml   # overlay/beta/send-email-patch-args.yaml apiVersion: batch/v1beta1 kind: CronJob metadata:  name: think123- spec:  schedule: "0 4 * * 1-5"  jobTemplate:  spec:  template:  spec:  containers:  - name: send-email-job  args: ["sendEmail"]  複製代碼

你可使用kustomize build beta > send-email-cron-job.yaml命令,而後查看send-email-cron-job.yaml文件,就能夠看到生成的具體的cronjob的詳細。

kustomize的文檔能夠參考: https://kubernetes-sigs.github.io/kustomize/api-reference/

爲何要用Kubernetes Cron Job

使用SpringBoot的定時任務不香嗎?爲何要還要引入新的東西。再想這個問題的時候,想一想爲何你在SpringBoot中不寫Servlet,不是同樣能夠嗎?

其實想一想仍是有緣由的,首先咱們的服務是分佈式的,咱們的定時任務應該只須要運行一次,而不是每一個實例都運行一次,若是用SpringBoot的task那麼咱們須要用代碼來保證這個行爲。

若是引入分佈式任務框架,又是引入了一堆其餘新的東西,好比註冊中心等等,並且還要去學習一項新的技術。

而咱們的服務因爲是經過Kubernetes部署的,咱們的job再使用Kubernetes來,更是相得益彰。

相關文章
相關標籤/搜索