A distributed cron framework.
go get github.com/gochore/dcron
First, implement a distributed atomic operation that only requires support for one method: SetIfNotExists
.
You can implement it in any way you prefer, such as using Redis SetNX
.
import "github.com/redis/go-redis/v9"
type RedisAtomic struct {
client *redis.Client
}
func (m *RedisAtomic) SetIfNotExists(ctx context.Context, key, value string) bool {
ret := m.client.SetNX(ctx, key, value, time.Hour)
return ret.Err() == nil && ret.Val()
}
Now you can create a cron with that:
func main() {
atomic := &RedisAtomic{
client: redis.NewClient(&redis.Options{
Addr: "localhost:6379",
}),
}
cron := dcron.NewCron(dcron.WithKey("TestCron"), dcron.WithAtomic(atomic))
}
Then, create a job and add it to the cron.
job1 := dcron.NewJob("Job1", "*/15 * * * * *", func(ctx context.Context) error {
if task, ok := dcron.TaskFromContext(ctx); ok {
log.Println("run:", task.Job.Spec(), task.Key)
}
// do something
return nil
})
if err := cron.AddJobs(job1); err != nil {
log.Fatal(err)
}
Finally, start the cron:
cron.Start()
log.Println("cron started")
time.Sleep(time.Minute)
<-cron.Stop().Done()
If you start the program multiple times, you will notice that the cron will run the job once every 15 seconds on only one of the processes.
process 1 | process 2 | process 3 |
---|---|---|
2023/10/13 11:39:45 cron started | 2023/10/13 11:39:47 cron started | 2023/10/13 11:39:48 cron started |
2023/10/13 11:40:00 run: */15 * * * * * dcron:TestCron.Job1@1697168400 | ||
2023/10/13 11:40:15 run: */15 * * * * * dcron:TestCron.Job1@1697168415 | ||
2023/10/13 11:40:30 run: */15 * * * * * dcron:TestCron.Job1@1697168430 | ||
2023/10/13 11:40:45 run: */15 * * * * * dcron:TestCron.Job1@1697168445 |
One more thing, since dcron.WithAtomic(atomic)
is optional, it's also a good idea to use it as a local cron.
cron := dcron.NewCron()
job2 := dcron.NewJob("A local job", "*/15 * * * * *", func(ctx context.Context) error {
// do something
return nil
})
if err := cron.AddJobs(job2); err != nil {
log.Fatal(err)
}