Java & Go 定時任務

FunTester發表於2024-07-25

定時任務概括

定時任務是軟體開發中用於自動執行週期性任務的技術。它允許開發者設定特定的時間點或間隔來觸發預定的操作,如資料備份、清理快取、傳送通知等。這種機制可以減少人工干預,提高系統效率和穩定性。定時任務在不同的作業系統和程式設計環境中有不同的實現方式,例如 Linux 的 cron 作業、Windows 的任務計劃程式,或是程式語言中的相關庫。它們幫助自動化重複性工作,確保關鍵任務按時執行,是現代應用程式不可或缺的組成部分。

定時任務使用場景

定時任務在軟體開發當中使用非常廣泛。主要有以下場景:

  1. 資料備份。定時備份重要資料,防止丟失。
  2. 日誌清理。定時歸檔和清理日誌檔案,釋放磁碟空間
  3. 效能監控。定時收集、處理和上報效能資料。
  4. 資料同步。定時將最新資料同步給其他消費者。
  5. 資源管理。定時清理、回收系統資源,提升利用率和效能。

當然列舉的這幾個有些寬泛,在實際開發當中,會有多種多樣的定時任務場景。下面我們先來看看 Java 語言都有哪些實現定時任務的類庫。

Java 語言實現定時任務

相信很多小夥伴接觸最多的定時任務就是定時自動化迴歸測試了。通常會有專門的開發和測試框架來完成具體的設定和執行定時任務。在 Java 語言中,實現定時任務有幾種常用的方法:

  1. java.util.Timer:這是 Java 標準庫提供的一個類,可以用來安排任務以後在後臺執行緒中執行。使用Timer類,你可以建立一個TimerTask任務,然後使用schedulescheduleAtFixedRate方法來安排任務的執行。
  2. ScheduledExecutorService 介面:這是 Java 併發包中的一部分,提供了更靈活的定時任務排程能力。你可以使用Executors類建立一個ScheduledExecutorService例項,然後使用schedulescheduleAtFixedRate方法來安排任務。
  3. Spring 框架的@Scheduled註解:如果你在使用 Spring 框架,可以利用@Scheduled註解來簡化定時任務的配置。Spring 的排程器會根據註解的引數來執行相應的方法。
  4. Quartz Scheduler:這是一個開源的作業排程庫,提供了比 Java 標準庫更強大的定時任務功能。Quartz 允許你配置複雜的排程策略,如 cron 表示式,並支援叢集。

java.util.Timer

利用 Spring 框架支援相對來說比較常見。下面我寫了一個 java.util.Timer 實現每秒列印一次時間的定時任務的簡單案例。可以按以下步驟編寫程式碼:

  1. 建立一個繼承自TimerTask的類,在其中實現run方法。
  2. 建立一個Timer物件。
  3. 使用Timer物件的schedule方法安排任務。

以下是具體的示例程式碼:


package com.funtest.temp;


import com.funtester.frame.SourceCode;

import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Timer;
import java.util.TimerTask;

public class FunTester extends SourceCode {

    public static void main(String[] args) {
        Timer timer = new Timer();// 例項化Timer類
        TimerTask task = new TimerTask() {
            @Override
            public void run() {// 例項化TimerTask類
                // 任務程式碼,列印當前時間,並指定執行緒名稱
                SimpleDateFormat formatter = new SimpleDateFormat("HH:mm:ss");
                Date date = new Date(System.currentTimeMillis());
                System.out.println(Thread.currentThread().getName() + "      " + formatter.format(date));
            }
        };
        // 0表示立即執行,1000表示每隔1秒執行一次
        timer.scheduleAtFixedRate(task, 0, 1000);
    }

}

下面是控制檯列印的資訊:

Timer-0      10:04:07
Timer-0      10:04:08
Timer-0      10:04:09
Timer-0      10:04:10

使用 java.util.Timer 實現定時任務,雖然具有簡單易用優點,但在我的經驗範圍內極少,大多數都是包裝成服務化,使用 Spring 自帶的定時任務執行。其主要缺點就是:單執行緒執行、異常處理不夠優雅、不支援併發。總體來講不如 ScheduledExecutorService 功能強大。

ScheduledExecutorService

下面是 ScheduledExecutorService 的簡單案例。

package com.funtest.temp;  


import com.funtester.frame.SourceCode;  

import java.text.SimpleDateFormat;  
import java.util.Date;  
import java.util.concurrent.Executors;  
import java.util.concurrent.ScheduledExecutorService;  
import java.util.concurrent.TimeUnit;  

public class FunTester extends SourceCode {  

    public static void main(String[] args) {  
        // 建立一個具有單個執行緒的排程程式  
        ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1);  
        // 建立一個Runnable任務,每秒列印一次當前時間  
        Runnable task = new Runnable() {  
            @Override  
            public void run() {  
                SimpleDateFormat sdf = new SimpleDateFormat("HH:mm:ss");  
                System.out.println(Thread.currentThread().getName() + "      " + sdf.format(new Date()));  
            }  
        };  
        // 從現在開始1秒鐘之後,每隔1秒鐘執行一次  
        scheduler.scheduleAtFixedRate(task, 0, 1, TimeUnit.SECONDS);  
    }  

}

明顯看到用到了執行緒池的使用,有點不用多說,簡潔性我感覺不輸 java.util.Timer

Go 語言定時任務

Go 語言定時任務實現,也是比較簡單的也是利用 Go SDK 自帶的 time 包。下面是簡單的案例:


package main

import (
    "fmt"
    "time"
)

func main() {
    // 建立一個定時器,每隔1秒鐘執行一次
    ticker := time.NewTicker(1 * time.Second)
    // 延遲關閉定時器
    defer ticker.Stop()
    // 無限迴圈
    for {
        // 從定時器的通道中接收資料
        select {
        case t := <-ticker.C:
            fmt.Println("Current time:", t)
        }
    }
}

這個案例基本是最佳實現,如果大家有需求都可以參考,而且 Go 語言 goroutine 的原因,不用很關心協程效能的問題,我自己就直接使用 goroutine 直接起定時任務了。這種對於複雜的定時任務,這種方式就顯得力不從心了。

Go 語言 time 包裡面還有一種方法實現:

package main  

import (  
    "fmt"  
    "time")  

func main() {  
    // 建立一個定時器,1秒後觸發  
    timer := time.AfterFunc(1*time.Second, func() {  
       fmt.Println("Current time:", time.Now())  
    })  

    // 防止程式退出  
    select {  
    case <-timer.C:  
    }  
}

語法上看好像區別不是很大,各位自己選用吧。

cron 包

下面分享一下 Go 第三方包的實現。cron 是一個在 Go 語言中實現定時任務的流行庫,它允許你使用 cron 表示式來定義任務的執行時間。PS:這個 cron 目測只支援了分鐘級別的。

package main  

import (  
    "fmt"  
    "time"  
    "github.com/robfig/cron/v3")  

func main() {  
    // 建立一個定時任務  
    c := cron.New()  
    // 新增一個定時任務,每隔1分鐘執行一次  
    c.AddFunc("0/1 * * * *", func() {  
       fmt.Println("Current time:", time.Now())  
    })  
    // 啟動定時任務  
    c.Start()  
    // 防止程式退出  
    select {}  
}

AddFunc 方法引數 spec 不僅支援 cron 語法,為了方便使用還增加了一個 @every 語法,後面可以跟類似 @every 1m2stime.ParseDuration() 支援的格式都可以用在這裡。除此之外 cron 預定義了一些時間規則:

  • @yearly:也可以寫作@annually,表示每年第一天的 0 點。等價於0 0 1 1 *
  • @monthly:表示每月第一天的 0 點。等價於0 0 1 * *
  • @weekly:表示每週第一天的 0 點,注意第一天為週日,即週六結束,週日開始的那個 0 點。等價於0 0 * * 0
  • @daily:也可以寫作@midnight,表示每天 0 點。等價於0 0 * * *
  • @hourly:表示每小時的開始。等價於0 * * * *

gocron 包

還有一個更加靈活的庫 gocron 功能更加強大,但使用起來存在一定門檻。下面是一個案例:

package main

import (
    "fmt"
    "time"

    "github.com/go-co-op/gocron"
)

func main() {
    // 定時任務
    s := gocron.NewScheduler(time.Local)
    // 每秒執行一次
    s.Every(1).Minutes().Do(func() {
        fmt.Println("Current time:", time.Now())
    })
    s.StartBlocking()
    fmt.Println("任務結束")
}

gocron 設定時間的話,都可以透過呼叫不同的 API 來組裝實現。對於 API 學習需要一定的學習成本。同時 gocron 也支援 cron 語法的。下面是個例子:

package main

import (
    "fmt"
    "time"

    "github.com/go-co-op/gocron"
)

func main() {
    // 定時任務
    s := gocron.NewScheduler(time.Local)
    // 新增一個每週二上午10點每分鐘執行一次的任務,使用 cron 表示式
    s.Cron("0 * 10 ? * 2").Do(func() {
        fmt.Println("Task executed at:", time.Now())
    })
    s.StartBlocking()
    fmt.Println("任務結束")
}
  • 服務端功能測試
  • 效能測試專題
  • Java、Groovy、Go、Python
  • 單元&白盒&工具合集
  • 測試方案&BUG&爬蟲&UI 自動化
  • 測試理論雞湯
  • 社群風采&影片合集
如果覺得我的文章對您有用,請隨意打賞。您的支援將鼓勵我繼續創作!
打賞支援
暫無回覆。