go框架進階——定時任務 goCron

goCron是一個Golang作業調度工具,可以使用簡單的語法定期執行go函數。

github:https://github.com/jasonlvhit/gocron

api doc:https://godoc.org/github.com/jasonlvhit/gocron#Every

使用實例

package main

import (
"fmt"
"github.com/jasonlvhit/gocron"
)

func task() {
	fmt.Println("I am runnning task.")
}

func taskWithParams(a int, b string) {
	fmt.Println(a, b)
}

func main() {
	//可併發運行多個任務
	//注意 interval>1時調用sAPi
	gocron.Every(2).Seconds().Do(task)
	gocron.Every(1).Second().Do(taskWithParams, 1, "hi")
	//在cron所有操作最後調用 start函數,否則start之後調用的操作無效不執行
	//<-gocron.Start()

	//在task執行過程中 禁止異常退出
	gocron.Every(1).Minute().DoSafely(taskWithParams, 1, "hello")

	// 支持在具體某一天、某天的某一時刻、每y-M-d h-m-s 執行任務
	gocron.Every(1).Monday().Do(task)
	gocron.Every(1).Thursday().Do(task)
	// function At() take a string like 'hour:min'
	gocron.Every(1).Day().At("10:30").Do(task)
	gocron.Every(1).Monday().At("18:30").Do(task)

	// 刪除某一任務
	gocron.Remove(task)

	//刪除所有任務
	gocron.Clear()

	//可同時創建一個新的任務調度 2個schedulers 同時執行
	s := gocron.NewScheduler()
	s.Every(3).Seconds().Do(task)
	<-s.Start()

	//防止多個集羣中任務同時執行 task 實現lock接口
	//兩行代碼,對cron 設置lock實現,執行task時調用Lock方法再Do task
	gocron.SetLocker(lockerImplementation)
	gocron.Every(1).Hour().Lock().Do(task)

	<-gocron.Start()
}

 

源碼淺析

 輕量 簡潔的鏈式調用,看工程源碼,簡潔的……只有一個類。gocron當前支持最多1w個任務數,核心對象就是維護了job對象,所有執行的任務都放在jobs數組中,start方法底層調用go time包下的NewTicker,新啓一個線程執行task方法。

外部調用的gocron.start func調用鏈

// Start all the pending jobs
// Add seconds ticker
func (s *Scheduler) Start() chan bool {
	stopped := make(chan bool, 1)
	ticker := time.NewTicker(1 * time.Second)

	go func() {
		for {
			select {
			case <-ticker.C:
				s.RunPending()    //調用RunPending 執行數組有序的任務隊列
			case <-stopped:
				ticker.Stop()
				return
			}
		}
	}()

	return stopped
}

// RunPending runs all the jobs that are scheduled to run.
func (s *Scheduler) RunPending() {
	runnableJobs, n := s.getRunnableJobs()

	if n != 0 {
		for i := 0; i < n; i++ {
			runnableJobs[i].run()
		}
	}
}

//run方法,反射獲取job的各屬性,最終調用function.call方法執行任務函數
//Run the job and immediately reschedule it
func (j *Job) run() (result []reflect.Value, err error) {
	if j.lock {
		if locker == nil {
			err = fmt.Errorf("trying to lock %s with nil locker", j.jobFunc)
			return
		}
		key := getFunctionKey(j.jobFunc)

		if ok, err := locker.Lock(key); err != nil || !ok {
			return nil, err
		}

		defer func() {
			if e := locker.Unlock(key); e != nil {
				err = e
			}
		}()
	}

	f := reflect.ValueOf(j.funcs[j.jobFunc])
	params := j.fparams[j.jobFunc]
	if len(params) != f.Type().NumIn() {
		err = errors.New("the number of param is not adapted")
		return
	}
	in := make([]reflect.Value, len(params))
	for k, param := range params {
		in[k] = reflect.ValueOf(param)
	}
	result = f.Call(in)
	j.lastRun = time.Now()
	j.scheduleNextRun()
	return
}

這裏需要關注一下,gocron對lock的實現,從代碼上看Job結構體的lock屬性,用於控制多實例job併發執行。但項目woner提到的 multiple instances 指的並不是跨服務器的多實例,而是在同一應用服務 裏的多任務實例(也就是1個app服務中多個任務,粒度是隻在統一應用內)。如果跨server則lock需要自行依賴redis或其他分佈式鎖來管理。通過讀源碼的run方法,j.lock來控制job併發,但一旦跨server job.lock屬性是沒法共享的。這裏doc上給的解釋有點歧義,需要注意。

If you need to prevent a job from running at the same time from multiple cron instances (like running a cron app from multiple servers), you can provide a Locker implementation and lock the required jobs. 然後owner給出了一個基於redis來做的lock 

Job結構體

// Job struct keeping information about job
type Job struct {
	interval uint64                   // pause interval * unit bettween runs
	jobFunc  string                   // the job jobFunc to run, func[jobFunc]
	unit     string                   // time units, ,e.g. 'minutes', 'hours'...
	atTime   time.Duration            // optional time at which this job runs
	lastRun  time.Time                // datetime of last run
	nextRun  time.Time                // datetime of next run
	startDay time.Weekday             // Specific day of the week to start on
	funcs    map[string]interface{}   // Map for the function task store
	fparams  map[string][]interface{} // Map for function and  params of function
	lock     bool                     // lock the job from running at same time form multiple instances
}

scheduler內維護JOBs array數組和大小,gocron scheduler最大可執行1w個job(大小可重寫)。

// Scheduler struct, the only data member is the list of jobs.
// - implements the sort.Interface{} for sorting jobs, by the time nextRun
type Scheduler struct {
	jobs [MAXJOBNUM]*Job // Array store jobs ,const MAXJOBNUM = 10000
	size int             // Size of jobs which jobs holding.
}

對於執行時間的控制,均通過對job的unit屬性進行設置,代碼如下

// Seconds set the unit with seconds
func (j *Job) Seconds() *Job {
	return j.setUnit("seconds")
}

// Minutes set the unit with minute
func (j *Job) Minutes() *Job {
	return j.setUnit("minutes")
}

// Second set the unit with second
func (j *Job) Second() *Job {
	j.mustInterval(1)
	return j.Seconds()
}

// Minute set the unit  with minute, which interval is 1
func (j *Job) Minute() *Job {
	j.mustInterval(1)
	return j.Minutes()
}

// Sunday sets the job start day Sunday
func (j *Job) Sunday() *Job {
	return j.Weekday(time.Sunday)
}

// Every schedule a new periodic job with interval
func (s *Scheduler) Every(interval uint64) *Job {
	job := NewJob(interval)
	s.jobs[s.size] = job
	s.size++
	return job
}

簡單總結

gocron代碼總共570行,在java中但凡涉及到一點“通用工具”或“架構”的實現,除了多依賴之外,擼代碼是少不了的。但在go中要實現滿足基本功能的cron任務調度,沒有多餘依賴,純基於gosdk本身,575行代碼打完收工。這是在不接觸go語言之前設想不到的事情。輕量好用,爲中間件而生。

編程語言跟人類溝通語言一樣,屬性都是工具,透過這個工具無論作爲人或是工程師,給我們打開的是另一個世界和景象。在對比中揚長避短,可對比的資源越多,越是能找到最優方案。不怕不知道,就怕不知道。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章