每小时运行后台任务的Golang实现方案

每小时运行后台任务的Golang实现方案 我知道我们通常可以通过两种常见的时间调度方式在后台运行任务:

  1. 在特定的时间段之后,例如 5 分钟后。
  2. 每隔特定的时间间隔,例如每 5 分钟。

这里 有一个简单的例子:

package main

import (
	"fmt"
	"time"
)

func bgTask() {
	ticker := time.NewTicker(1 * time.Second)
	for _ = range ticker.C {
		fmt.Println("Tock")
	}
}

func main() {
	fmt.Println("Go Tickers Tutorial")

	go bgTask()

	// This print statement will be executed before
	// the first `tock` prints in the console
	fmt.Println("The rest of my application can continue")

	// here we use an empty select{} in order to keep
	// our main function alive indefinitely as it would
	// complete before our backgroundTask has a chance
	// to execute if we didn't.
	select {}

}

我的需求有所不同,我希望在每天的特定时间运行一个活动,例如每小时(12:00, 1:00, 2:00, 3:00, …),或者每天午夜(即正好在午夜 00:00)。


更多关于每小时运行后台任务的Golang实现方案的实战教程也可以访问 https://www.itying.com/category-94-b0.html

3 回复

Crontab.guru - cron 计划表达式编辑器

一个易于使用的 crontab 计划编辑器。

我猜你必须使用 github.com/robfig/cron Code behind Cron

更多关于每小时运行后台任务的Golang实现方案的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html


Sibert:

我猜你必须使用 http://github.com/robfig/cron

谢谢,我找到了下面这段无需第三方库即可工作的代码:

package main

import (
	"context"
	"fmt"
	"time"
)

// Schedule calls function `f` with a period `p` offsetted by `o`.
func Schedule(ctx context.Context, p time.Duration, o time.Duration, f func(time.Time)) {
	// Position the first execution
	first := time.Now().Truncate(p).Add(o)
	if first.Before(time.Now()) {
		first = first.Add(p)
	}
	firstC := time.After(first.Sub(time.Now()))

	// Receiving from a nil channel blocks forever
	t := &time.Ticker{C: nil}

	for {
		select {
		case v := <-firstC:
			// The ticker has to be started before f as it can take some time to finish
			t = time.NewTicker(p)
			f(v)
		case v := <-t.C:
			f(v)
		case <-ctx.Done():
			t.Stop()
			return
		}
	}

}

func main() {
	ctx := context.Background() //  .TODO()
	fmt.Println("Let's start:", time.Now())
	current := time.Now()
	hr := current.Hour()
	fmt.Printf("The stated hour "+
		"within the day is: %v\n", hr)
	// Schedule(ctx, time.Minute*2, time.Minute, fn()) Run every 2 minutes, starting 1 minute after the first run of this code
	// if the code started 52:41.26, then first run will be at 53:00 followed by another run at 55:00, and so on

    // Schedule(ctx, time.Hour, time.Hour, func(t time.Time){}) Run every head of each hour, like 20:00:00, 21:00:00, ...
	Schedule(ctx, time.Hour, time.Hour, func(t time.Time) {

		fmt.Println("Hi, it is:", time.Now())
	})
}

目前的输出:

PS D:\Desktop> go run scheduler.go
Let's start: 2022-05-18 19:04:25.9967368 +0300 +03 m=+0.001233101
The stated hour within the day is: 19
Hi, it is: 2022-05-18 20:00:00.0322164 +0300 +03 m=+3334.010983001
Hi, it is: 2022-05-18 21:00:00.0447332 +0300 +03 m=+6934.015473401
Hi, it is: 2022-05-18 22:00:00.053622 +0300 +03 m=+10534.022922201
Hi, it is: 2022-05-18 23:00:00.0611189 +0300 +03 m=+14134.020641301
Hi, it is: 2022-05-19 00:00:00.0722724 +0300 +03 m=+17734.012720101
Hi, it is: 2022-05-19 01:00:00.0873397 +0300 +03 m=+21334.023790201
Hi, it is: 2022-05-19 02:00:00.1060637 +0300 +03 m=+24934.025385401
Hi, it is: 2022-05-19 03:00:00.1107291 +0300 +03 m=+28534.025584401
Hi, it is: 2022-05-19 04:00:00.1168757 +0300 +03 m=+32134.018658401
Hi, it is: 2022-05-19 05:00:00.1334013 +0300 +03 m=+35734.024843001
Hi, it is: 2022-05-19 06:00:00.1393515 +0300 +03 m=+39334.024025801
Hi, it is: 2022-05-19 07:00:00.1362642 +0300 +03 m=+42934.015270601
Hi, it is: 2022-05-19 08:00:00.1512836 +0300 +03 m=+46534.025222701
Hi, it is: 2022-05-19 09:00:00.1654086 +0300 +03 m=+50134.022125001
Hi, it is: 2022-05-19 10:00:00.1721446 +0300 +03 m=+53734.020983001

对于在每天特定时间运行后台任务的需求,Go语言中有几种实现方案。以下是两种常用方法:

方案一:使用time.Ticker配合时间计算

package main

import (
    "fmt"
    "time"
)

func scheduleTaskAtSpecificTime(hour, minute, second int) {
    for {
        now := time.Now()
        next := time.Date(
            now.Year(), now.Month(), now.Day(),
            hour, minute, second, 0, now.Location(),
        )
        
        if now.After(next) {
            next = next.Add(24 * time.Hour)
        }
        
        duration := next.Sub(now)
        fmt.Printf("Next execution in: %v\n", duration)
        
        <-time.After(duration)
        
        // 执行任务
        fmt.Printf("Task executed at %s\n", time.Now().Format("15:04:05"))
        
        // 等待一秒钟避免立即重复执行
        time.Sleep(1 * time.Second)
    }
}

func main() {
    // 每小时运行(在每小时的第0分钟第0秒)
    go scheduleTaskAtSpecificTime(0, 0, 0)
    
    // 或者每天午夜运行
    // go scheduleTaskAtSpecificTime(0, 0, 0)
    
    select {}
}

方案二:使用cron表达式(推荐)

使用第三方库如robfig/cron可以更灵活地处理复杂调度:

package main

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

func main() {
    c := cron.New(cron.WithSeconds()) // 支持秒级精度
    
    // 每小时运行(每小时的第0分钟第0秒)
    _, err := c.AddFunc("0 0 * * * *", func() {
        fmt.Printf("Hourly task executed at %s\n", 
            time.Now().Format("2006-01-02 15:04:05"))
    })
    
    if err != nil {
        fmt.Printf("Error adding cron job: %v\n", err)
        return
    }
    
    // 每天午夜运行
    _, err = c.AddFunc("0 0 0 * * *", func() {
        fmt.Printf("Midnight task executed at %s\n", 
            time.Now().Format("2006-01-02 15:04:05"))
    })
    
    // 每5分钟运行
    _, err = c.AddFunc("0 */5 * * * *", func() {
        fmt.Printf("Every 5 minutes task executed at %s\n", 
            time.Now().Format("2006-01-02 15:04:05"))
    })
    
    c.Start()
    
    // 保持主程序运行
    select {}
}

方案三:使用time.Ticker的精确小时调度

package main

import (
    "fmt"
    "time"
)

func hourlyTask() {
    // 计算到下一个整点的时间
    now := time.Now()
    nextHour := now.Truncate(time.Hour).Add(time.Hour)
    duration := nextHour.Sub(now)
    
    fmt.Printf("First execution in: %v\n", duration)
    
    // 等待到下一个整点
    <-time.After(duration)
    
    // 创建每小时触发的ticker
    ticker := time.NewTicker(time.Hour)
    defer ticker.Stop()
    
    for t := range ticker.C {
        fmt.Printf("Hourly task executed at %s\n", 
            t.Format("2006-01-02 15:04:05"))
    }
}

func main() {
    go hourlyTask()
    select {}
}

方案四:带错误处理的完整示例

package main

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

func taskWrapper(taskName string, taskFunc func()) func() {
    return func() {
        start := time.Now()
        fmt.Printf("[%s] Starting at %s\n", 
            taskName, start.Format("15:04:05"))
        
        defer func() {
            if r := recover(); r != nil {
                log.Printf("[%s] Panic recovered: %v", taskName, r)
            }
        }()
        
        taskFunc()
        
        duration := time.Since(start)
        fmt.Printf("[%s] Completed in %v\n", taskName, duration)
    }
}

func main() {
    c := cron.New(
        cron.WithLogger(
            cron.VerbosePrintfLogger(log.New(log.Writer(), 
                "cron: ", log.LstdFlags)),
        ),
    )
    
    // 每小时运行
    c.AddFunc("@hourly", taskWrapper("hourly-task", func() {
        // 实际业务逻辑
        fmt.Println("Processing hourly data...")
        time.Sleep(100 * time.Millisecond) // 模拟任务执行
    }))
    
    // 每天午夜运行
    c.AddFunc("@midnight", taskWrapper("midnight-task", func() {
        fmt.Println("Processing daily reports...")
        time.Sleep(200 * time.Millisecond)
    }))
    
    // 自定义时间:每天10:30运行
    c.AddFunc("0 30 10 * * *", taskWrapper("custom-time", func() {
        fmt.Println("Running at 10:30 AM daily...")
    }))
    
    c.Start()
    
    // 优雅关闭
    defer c.Stop()
    
    select {}
}

安装cron库

go get github.com/robfig/cron/v3

推荐使用robfig/cron方案,它提供了更丰富的调度表达式和更好的错误处理机制,特别适合生产环境使用。

回到顶部