go-cron源码分析(一、由初始化引起的一些其他事)

最近在实现go的定时功能,所以先来了解一波cron的实现,看看是否可以借鉴。

1.1 Cron用法

我们先来看看用法:

c := cron.New()     // 新建一个定时器任务
c.AddFunc("30 * * * *", func() { fmt.Println("Every hour on the half hour") })   // 绑定定时任务
c.AddFunc("30 3-6,20-23 * * *", func() { fmt.Println(".. in the range 3-6am, 8-11pm") })
c.AddFunc("CRON_TZ=Asia/Tokyo 30 04 * * *", func() { fmt.Println("Runs at 04:30 Tokyo time every day") })
c.AddFunc("@hourly",      func() { fmt.Println("Every hour, starting an hour from now") })
c.AddFunc("@every 1h30m", func() { fmt.Println("Every hour thirty, starting an hour thirty from now") })
c.Start()    // 开始调度
..
// Funcs are invoked in their own goroutine, asynchronously.
...
// Funcs may also be added to a running Cron
c.AddFunc("@daily", func() { fmt.Println("Every day") })
..
// Inspect the cron job entries' next and previous run times.
inspect(c.Entries())  // 看一下下一个任务和上一个任务
..
c.Stop()  // Stop the scheduler (does not stop any jobs already running).

1.2 cron.new()

创建一个cron的对象,没看源码之前,一直觉得new()就可以创建一个对象了,没想到看了源码之后才发现,这个是需要带参的。

// New returns a new Cron job runner, modified by the given options.
// New返回一个新的Cron作业运行器,由给定选项修改。
//
// Available Settings
// 可用的设置
//
//   Time Zone
//     Description: The time zone in which schedules are interpreted
//     描述:解释时间表的时区
//     Default:     time.Local
//
//   Parser
//     Description: Parser converts cron spec strings into cron.Schedules.
//     描述:解析器将cron规范字符串转换为cron. schedules。
//     Default:     Accepts this spec: https://en.wikipedia.org/wiki/Cron
//
//   Chain
//     Description: Wrap submitted jobs to customize behavior.
//     描述:对提交的作业进行包装以定制行为。
//     Default:     A chain that recovers panics and logs them to stderr.
//
// See "cron.With*" to modify the default behavior.
func New(opts ...Option) *Cron {
	c := &Cron{
		entries:   nil,
		chain:     NewChain(),
		add:       make(chan *Entry),
		stop:      make(chan struct{}),
		snapshot:  make(chan chan []Entry),
		remove:    make(chan EntryID),
		running:   false,
		runningMu: sync.Mutex{},
		logger:    DefaultLogger,
		location:  time.Local,
		parser:    standardParser,
	}
	for _, opt := range opts {
		opt(c)			// 可变参数都是函数,这样执行
	}
	return c
}

这个函数接受可变参数,这个可变参数其实是函数,由下面的opt©来执行。

1.2.1 time zone

下面我们先看简单的time zone:

// 代码路径 option_test.go
func TestWithLocation(t *testing.T) {	// 其实这是一个测试用例
	c := New(WithLocation(time.UTC))	
	if c.location != time.UTC {
		t.Errorf("expected UTC, got %v", c.location)
	}
}

// WithLocation overrides the timezone of the cron instance.
func WithLocation(loc *time.Location) Option {
	return func(c *Cron) {    // 这个函数也比较简单,就是返回一个匿名函数,这个匿名函数去修改c的location的值
		c.location = loc
	}
}

1.2.2 Parser

func TestWithParser(t *testing.T) {
	var parser = NewParser(Dow)			// 主动申请一个解析器,这个比较难,之后在看
	c := New(WithParser(parser))        // 通过这个函数修改默认的解析器
	if c.parser != parser {
		t.Error("expected provided parser")
	}
}

// WithParser overrides the parser used for interpreting job schedules.
func WithParser(p ScheduleParser) Option {		// 这个解析器也比较简单
	return func(c *Cron) {
		c.parser = p
	}
}

// 不过在这里我看到一个比较有意思的解析器,有带秒的解析器
// WithSeconds overrides the parser used for interpreting job schedules to
// include a seconds field as the first one.
func WithSeconds() Option {						// 我们上面的例子,并没有秒的定时任务,有了这个自定义解析器,我们就可以实现秒定时了
	return WithParser(NewParser(
		Second | Minute | Hour | Dom | Month | Dow | Descriptor,
	))
}

实现秒定时:

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

func main() {
	fmt.Println("time ", time.Now())
	c := cron.New(cron.WithSeconds())   // 指定自定义解析器
    // 这种绑定就是1秒执行一次
	c.AddFunc("*/1 * * * * *", func() { fmt.Println("Every hour on the half hour", time.Now()) })
	c.Start()

	select {}

}

1.2.3 Chain

这个chain并没有简单的例子,可以等到分析Chain再详细说明

// WithChain specifies Job wrappers to apply to all jobs added to this cron.
// Refer to the Chain* functions in this package for provided wrappers.
func WithChain(wrappers ...JobWrapper) Option {   // 只是提供了一个简单的修改chain
	return func(c *Cron) {
		c.chain = NewChain(wrappers...)
	}
}

1.2.4 logger

修改日志,这个可以好好分析分析了

func TestWithVerboseLogger(t *testing.T) {
	var buf syncWriter
	var logger = log.New(&buf, "", log.LstdFlags)			// 自己自定义了一个log
	c := New(WithLogger(VerbosePrintfLogger(logger)))		// 绑定自己指定以的log
	if c.logger.(printfLogger).logger != logger {
		t.Error("expected provided logger")
	}

	c.AddFunc("@every 1s", func() {})
	c.Start()
	time.Sleep(OneSecond)
	c.Stop()
	out := buf.String()
	if !strings.Contains(out, "schedule,") ||
		!strings.Contains(out, "run,") {
		t.Error("expected to see some actions, got:", out)
	}
}

// VerbosePrintfLogger wraps a Printf-based logger (such as the standard library
// "log") into an implementation of the Logger interface which logs everything.
func VerbosePrintfLogger(l interface{ Printf(string, ...interface{}) }) Logger {  // 需要自己使用Printf函数
	return printfLogger{l, true}   // 这是返回一个Logger对象
}

// WithLogger uses the provided logger.
func WithLogger(logger Logger) Option {
	return func(c *Cron) {				// 实际调用的也是这样修改
		c.logger = logger		
	}
}

那我们实现一下自己的日志库,能不能打印出来

package main

// 需要拉取项目:go get github.com/robfig/cron/v3@v3.0.0

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

type MyLog struct {
	buff string
}

func (this *MyLog)Printf(format string, v ...interface{}) {  // 看了下日志源码,只要实现这个接口,就可以传参
	this.buff = fmt.Sprintf("mylog ", time.Now(), "hahahh")
}


func main() {
	fmt.Println("time ", time.Now())
	var mylog MyLog
	// cron.VerbosePrintfLogger(&mylog) 这个就是把自己的日志,转成cron的日志
	c := cron.New(cron.WithSeconds(), cron.WithLogger(cron.VerbosePrintfLogger(&mylog)))
	//c.AddFunc("*/1 * * * * *", func() { fmt.Println("Every hour on the half hour", time.Now()) })
	c.AddFunc("*/1 * * * * *", func() { fmt.Println("log ", mylog.buff) })  // 1秒打一个
	c.Start()


	select {}

}

看看输出:

time  2022-11-03 20:22:31.0890216 +0800 CST m=+0.001725601
log  mylog %!(EXTRA time.Time=2022-11-03 20:22:32.0028412 +0800 CST m=+0.915545201, string=hahahh)
log  mylog %!(EXTRA time.Time=2022-11-03 20:22:33.0008532 +0800 CST m=+1.913557201, string=hahahh)
log  mylog %!(EXTRA time.Time=2022-11-03 20:22:34.0109866 +0800 CST m=+2.923690601, string=hahahh)
log  mylog %!(EXTRA time.Time=2022-11-03 20:22:35.0028254 +0800 CST m=+3.915529401, string=hahahh)
log  mylog %!(EXTRA time.Time=2022-11-03 20:22:36.0023282 +0800 CST m=+4.915032201, string=hahahh)
log  mylog %!(EXTRA time.Time=2022-11-03 20:22:37.0020753 +0800 CST m=+5.914779301, string=hahahh)
log  mylog %!(EXTRA time.Time=2022-11-03 20:22:38.0028339 +0800 CST m=+6.915537901, string=hahahh)
log  mylog %!(EXTRA time.Time=2022-11-03 20:22:39.0023824 +0800 CST m=+7.915086401, string=hahahh)
log  mylog %!(EXTRA time.Time=2022-11-03 20:22:40.0028723 +0800 CST m=+8.915576301, string=hahahh)
log  mylog %!(EXTRA time.Time=2022-11-03 20:22:41.0037012 +0800 CST m=+9.916405201, string=hahahh)
log  mylog %!(EXTRA time.Time=2022-11-03 20:22:42.0035357 +0800 CST m=+10.916239701, string=hahahh)
log  mylog %!(EXTRA time.Time=2022-11-03 20:22:43.0024844 +0800 CST m=+11.915188401, string=hahahh)
log  mylog %!(EXTRA time.Time=2022-11-03 20:22:44.0039952 +0800 CST m=+12.916699201, string=hahahh)
log  mylog %!(EXTRA time.Time=2022-11-03 20:22:45.002594 +0800 CST m=+13.915298001, string=hahahh)

虽然日志没有什么实际用处,但可以熟悉一个cron的日志使用。

1.3 Logger.go

看着字数很少,就跟着分析一下日志吧,上面说过只要实现Printf函数即可,那我们就来看看。

1.3.1 DefaultLogger

首先来看看缺省日志

// DefaultLogger is used by Cron if none is specified.
var DefaultLogger Logger = PrintfLogger(log.New(os.Stdout, "cron: ", log.LstdFlags))

// 分析PrintfLogger这个函数
// PrintfLogger wraps a Printf-based logger (such as the standard library "log")
// into an implementation of the Logger interface which logs errors only.
// PrintfLogger将基于printf的记录器(如标准库“日志”)封装到logger接口的实现中,该实现只记录错误。
// 这个函数接受一个Printf函数作为接口l,我们上面的log对象中就有Printf函数,可以追踪进去看看
func PrintfLogger(l interface{ Printf(string, ...interface{}) }) Logger {
	return printfLogger{l, false}		// 这是一个结构体,返回结构题的对象
}

// 这里还差了一个Logger对象是啥?
// Logger is the interface used in this package for logging, so that any backend
// can be plugged in. It is a subset of the github.com/go-logr/logr interface.
type Logger interface {
	// Info logs routine messages about cron's operation.
	Info(msg string, keysAndValues ...interface{})
	// Error logs an error condition.
	Error(err error, msg string, keysAndValues ...interface{})
}

看到这里就明白了,原来搞了这么半天,原来是为了实现Logger接口,是通过那两个函数来实现的

1.3.2 printfLogger结构体

// 通过分析Logger接口,这个必然会出Info Error两个函数,才能显示Logger接口
type printfLogger struct {
	logger  interface{ Printf(string, ...interface{}) }
	logInfo bool
}

func (pl printfLogger) Info(msg string, keysAndValues ...interface{}) {	 // 果然有
	if pl.logInfo {
		keysAndValues = formatTimes(keysAndValues)
		pl.logger.Printf(
			formatString(len(keysAndValues)),
			append([]interface{}{msg}, keysAndValues...)...)
	}
}

func (pl printfLogger) Error(err error, msg string, keysAndValues ...interface{}) {  // 果然有
	keysAndValues = formatTimes(keysAndValues)
	pl.logger.Printf(
		formatString(len(keysAndValues)+2),
		append([]interface{}{msg, "error", err}, keysAndValues...)...)
}

自此我们日志系统分析完成。

1.3.3 cron支持zap日志

因为我们项目一般都是使用zap做为日志管理,那zap能不能作为cron的日志呢?

答案明显是可以的,下面我来写一个demo:

package main

// 需要拉取项目:go get github.com/robfig/cron/v3@v3.0.0

import (
	"fmt"
	"github.com/robfig/cron/v3"
	"go.uber.org/zap"
	"time"
)

type MyZap struct {
	logger *zap.Logger
}

// 自己把zap封装了一层,然后自己实现了Info接口
func (this *MyZap)Info(format string,  keysAndValues ...interface{}) {
	this.logger.Info(fmt.Sprintf(format, keysAndValues))
}

// 这也是自己实现了Error接口
func (this *MyZap)Error(err error, format string,  keysAndValues ...interface{}) {
	this.logger.Error(fmt.Sprintf(format, keysAndValues))
}

// new了一个对象
func NewMyzap() *MyZap{
	mzap := &MyZap{}
	mzap.logger, _ = zap.NewProduction()
	return mzap
}

func main() {
	fmt.Println("time ", time.Now())

	mzap := NewMyzap()
	c := cron.New(cron.WithSeconds(), cron.WithLogger(mzap))  // 直接把mzap对象指针传入,不用那个转函数函数了,因为我直接实现了Logger接口
	c.AddFunc("*/1 * * * * *", func() { fmt.Println("log ", mylog.buff) })
	c.Start()

	select {}

}

这个代码其实只是一个简单的Demo,实际项目上不会这么用的,只是在这样提供了一种方法。(主要是真实项目也不敢上传,哈哈哈)

1.4 总结

cron的使用和一些简单的指定cron的参数就介绍到这里,最后还顺带介绍了一下日志系统,因为这个日志系统比较简单。明天继续。

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值