Skip to content

gochore/dcron

Repository files navigation

dcron

Go Reference Actions Codecov Go Report Card GitHub go.mod Go version GitHub tag (latest by date)

A distributed cron framework.

Install

go get github.com/gochore/dcron

Quick Start

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)
	}