2
0
mirror of https://github.com/hibiken/asynq.git synced 2024-11-14 19:38:49 +08:00

Add the scheduler option HeartbeatInterval (#956)

* Add the scheduler option HeartbeatInterval

* Fix possible premature expiration of scheduler entries
This commit is contained in:
Pior Bastida 2024-11-07 06:34:28 +01:00 committed by GitHub
parent 580d69e88f
commit 4f00f52c1d
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 45 additions and 27 deletions

View File

@ -1437,7 +1437,7 @@ func (r *RDB) ClearServerState(host string, pid int, serverID string) error {
// KEYS[1] -> asynq:schedulers:{<schedulerID>} // KEYS[1] -> asynq:schedulers:{<schedulerID>}
// ARGV[1] -> TTL in seconds // ARGV[1] -> TTL in seconds
// ARGV[2:] -> schedler entries // ARGV[2:] -> scheduler entries
var writeSchedulerEntriesCmd = redis.NewScript(` var writeSchedulerEntriesCmd = redis.NewScript(`
redis.call("DEL", KEYS[1]) redis.call("DEL", KEYS[1])
for i = 2, #ARGV do for i = 2, #ARGV do
@ -1468,10 +1468,10 @@ func (r *RDB) WriteSchedulerEntries(schedulerID string, entries []*base.Schedule
} }
// ClearSchedulerEntries deletes scheduler entries data from redis. // ClearSchedulerEntries deletes scheduler entries data from redis.
func (r *RDB) ClearSchedulerEntries(scheduelrID string) error { func (r *RDB) ClearSchedulerEntries(schedulerID string) error {
var op errors.Op = "rdb.ClearSchedulerEntries" var op errors.Op = "rdb.ClearSchedulerEntries"
ctx := context.Background() ctx := context.Background()
key := base.SchedulerEntriesKey(scheduelrID) key := base.SchedulerEntriesKey(schedulerID)
if err := r.client.ZRem(ctx, base.AllSchedulers, key).Err(); err != nil { if err := r.client.ZRem(ctx, base.AllSchedulers, key).Err(); err != nil {
return errors.E(op, errors.Unknown, &errors.RedisCommandError{Command: "zrem", Err: err}) return errors.E(op, errors.Unknown, &errors.RedisCommandError{Command: "zrem", Err: err})
} }

View File

@ -26,16 +26,17 @@ type Scheduler struct {
state *serverState state *serverState
logger *log.Logger heartbeatInterval time.Duration
client *Client logger *log.Logger
rdb *rdb.RDB client *Client
cron *cron.Cron rdb *rdb.RDB
location *time.Location cron *cron.Cron
done chan struct{} location *time.Location
wg sync.WaitGroup done chan struct{}
preEnqueueFunc func(task *Task, opts []Option) wg sync.WaitGroup
postEnqueueFunc func(info *TaskInfo, err error) preEnqueueFunc func(task *Task, opts []Option)
errHandler func(task *Task, opts []Option, err error) postEnqueueFunc func(info *TaskInfo, err error)
errHandler func(task *Task, opts []Option, err error)
// guards idmap // guards idmap
mu sync.Mutex mu sync.Mutex
@ -48,6 +49,8 @@ type Scheduler struct {
sharedConnection bool sharedConnection bool
} }
const defaultHeartbeatInterval = 10 * time.Second
// NewScheduler returns a new Scheduler instance given the redis connection option. // NewScheduler returns a new Scheduler instance given the redis connection option.
// The parameter opts is optional, defaults will be used if opts is set to nil // The parameter opts is optional, defaults will be used if opts is set to nil
func NewScheduler(r RedisConnOpt, opts *SchedulerOpts) *Scheduler { func NewScheduler(r RedisConnOpt, opts *SchedulerOpts) *Scheduler {
@ -68,6 +71,11 @@ func NewSchedulerFromRedisClient(c redis.UniversalClient, opts *SchedulerOpts) *
opts = &SchedulerOpts{} opts = &SchedulerOpts{}
} }
heartbeatInterval := opts.HeartbeatInterval
if heartbeatInterval <= 0 {
heartbeatInterval = defaultHeartbeatInterval
}
logger := log.NewLogger(opts.Logger) logger := log.NewLogger(opts.Logger)
loglevel := opts.LogLevel loglevel := opts.LogLevel
if loglevel == level_unspecified { if loglevel == level_unspecified {
@ -81,18 +89,19 @@ func NewSchedulerFromRedisClient(c redis.UniversalClient, opts *SchedulerOpts) *
} }
return &Scheduler{ return &Scheduler{
id: generateSchedulerID(), id: generateSchedulerID(),
state: &serverState{value: srvStateNew}, state: &serverState{value: srvStateNew},
logger: logger, heartbeatInterval: heartbeatInterval,
client: NewClientFromRedisClient(c), logger: logger,
rdb: rdb.NewRDB(c), client: NewClientFromRedisClient(c),
cron: cron.New(cron.WithLocation(loc)), rdb: rdb.NewRDB(c),
location: loc, cron: cron.New(cron.WithLocation(loc)),
done: make(chan struct{}), location: loc,
preEnqueueFunc: opts.PreEnqueueFunc, done: make(chan struct{}),
postEnqueueFunc: opts.PostEnqueueFunc, preEnqueueFunc: opts.PreEnqueueFunc,
errHandler: opts.EnqueueErrorHandler, postEnqueueFunc: opts.PostEnqueueFunc,
idmap: make(map[string]cron.EntryID), errHandler: opts.EnqueueErrorHandler,
idmap: make(map[string]cron.EntryID),
} }
} }
@ -106,6 +115,15 @@ func generateSchedulerID() string {
// SchedulerOpts specifies scheduler options. // SchedulerOpts specifies scheduler options.
type SchedulerOpts struct { type SchedulerOpts struct {
// HeartbeatInterval specifies the interval between scheduler heartbeats.
//
// If unset, zero or a negative value, the interval is set to 10 second.
//
// Note: Setting this value too low may add significant load to redis.
//
// By default, HeartbeatInterval is set to 10 seconds.
HeartbeatInterval time.Duration
// Logger specifies the logger used by the scheduler instance. // Logger specifies the logger used by the scheduler instance.
// //
// If unset, the default logger is used. // If unset, the default logger is used.
@ -284,7 +302,7 @@ func (s *Scheduler) Shutdown() {
func (s *Scheduler) runHeartbeater() { func (s *Scheduler) runHeartbeater() {
defer s.wg.Done() defer s.wg.Done()
ticker := time.NewTicker(5 * time.Second) ticker := time.NewTicker(s.heartbeatInterval)
for { for {
select { select {
case <-s.done: case <-s.done:
@ -317,7 +335,7 @@ func (s *Scheduler) beat() {
entries = append(entries, e) entries = append(entries, e)
} }
s.logger.Debugf("Writing entries %v", entries) s.logger.Debugf("Writing entries %v", entries)
if err := s.rdb.WriteSchedulerEntries(s.id, entries, 5*time.Second); err != nil { if err := s.rdb.WriteSchedulerEntries(s.id, entries, s.heartbeatInterval*2); err != nil {
s.logger.Warnf("Scheduler could not write heartbeat data: %v", err) s.logger.Warnf("Scheduler could not write heartbeat data: %v", err)
} }
} }