項目開發中老是須要執行一些定時任務,好比定時處理數據以後發送郵件,定時更新緩存等等。java
項目框架使用的是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以下所示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
幾個關鍵參數解釋以下:api
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/
使用SpringBoot的定時任務不香嗎?爲何要還要引入新的東西。再想這個問題的時候,想一想爲何你在SpringBoot中不寫Servlet,不是同樣能夠嗎?
其實想一想仍是有緣由的,首先咱們的服務是分佈式的,咱們的定時任務應該只須要運行一次,而不是每一個實例都運行一次,若是用SpringBoot的task那麼咱們須要用代碼來保證這個行爲。
若是引入分佈式任務框架,又是引入了一堆其餘新的東西,好比註冊中心等等,並且還要去學習一項新的技術。
而咱們的服務因爲是經過Kubernetes部署的,咱們的job再使用Kubernetes來,更是相得益彰。