• Go: 关于定时任务


    写在前面

    记录 Go 的一些关于定时任务的处理

    内容

    基础库

    for + sleep

    func main() {
    	for {
    		time.Sleep(5 * time.Second)
    		fmt.Println("every 5s")
    	}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    Ticker

    func main() {
    	ticker := time.NewTicker(5 * time.Second)
    	for range ticker.C {
    		fmt.Println("every 5s")
    	}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    第三方库

    cron

    前面两个都是基础库里的东西,可以实现一些基本的定时功能,但对于一些稍显复杂的定时任务需求,就需要我们自己去做更多的内容来支持,比如说需求需要在每个月的 1 号,或是每周一之类的来进行任务,就需要我们自己去写代码来判断了。而 cron 这个第三方库,则能很好地支撑我们这类的需求。

    安装
    go get github.com/robfig/cron/v3@v3.0.0
    
    • 1

    cron 对于定时的设置有几种写法,都能达到同样的目的。

    @表达式
    func descriptors() {
    
    	c := cron.New()
    	// @every
    	// 支持秒级别,会在启动 6s 后执行
    	_, errEveryS := c.AddFunc("@every 6s", func() {
    		fmt.Println("every 6s")
    	})
    	if errEveryS != nil {
    		fmt.Printf("errEveryS: %v\n", errEveryS)
    	}
    	// 0.1 min = 6s
    	_, errEveryM := c.AddFunc("@every 0.1m", func() {
    		fmt.Println("every 0.1m")
    	})
    
    	if errEveryM != nil {
    		fmt.Printf("errEveryM: %v\n", errEveryM)
    	}
    
    	// @yearly @annually 每年执行一次,即 1 月 1 日的时候开始执行
    	_, errYearly := c.AddFunc("@yearly", func() {
    		fmt.Println("yearly")
    	})
    	if errYearly != nil {
    		fmt.Printf("errYearly: %v\n", errYearly)
    	}
    
    	_, errAnnually := c.AddFunc("@annually", func() {
    		fmt.Println("annually")
    	})
    	if errAnnually != nil {
    		fmt.Printf("errAnnually: %v\n", errAnnually)
    	}
    
    	// @monthly 每个月的第一天的零点执行
    	_, errMonthly := c.AddFunc("@monthly", func() {
    		fmt.Println("monthly")
    	})
    	if errMonthly != nil {
    		fmt.Printf("errMonthly: %v\n", errMonthly)
    	}
    
    	// @weekly 每周的第一天零点执行,这个第一天可能是周六,也可能是周日
    	_, errWeekly := c.AddFunc("@weekly", func() {
    		fmt.Println("weekly")
    	})
    	if errWeekly != nil {
    		fmt.Printf("errWeekly: %v\n", errWeekly)
    	}
    
    	// @hourly 每小时执行一次,启动后一小时执行
    	_, errHourly := c.AddFunc("@hourly", func() {
    		fmt.Println("hourly")
    	})
    	if errHourly != nil {
    		fmt.Printf("errHourly: %v\n", errHourly)
    	}
    
    	// @daily @midnight,每天执行一次,在每天的零点执行
    	_, errDaily := c.AddFunc("@daily", func() {
    		fmt.Println("daily")
    	})
    	if errDaily != nil {
    		fmt.Printf("errDaily: %v\n", errDaily)
    	}
    
    	_, errMidnight := c.AddFunc("@midnight", func() {
    		fmt.Println("midnight")
    	})
    	if errMidnight != nil {
    		fmt.Printf("errMidnight: %v\n", errMidnight)
    	}
    
    	c.Start()
    	defer c.Stop()
    
    	select {}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    linux cron 表达式

    如果我们想要更灵活的一些处理,就需要学习下 Linux 的 cron 表达式,参照如下:

        *    *    *    *    *
        -    -    -    -    -
        |    |    |    |    |
        |    |    |    |    +----- day of week (0 - 7) (Sunday=0 or 7) OR sun,mon,tue,wed,thu,fri,sat
        |    |    |    +---------- month (1 - 12) OR jan,feb,mar,apr ...
        |    |    +--------------- day of month (1 - 31)
        |    +-------------------- hour (0 - 23)
        +------------------------- minute (0 - 59)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    只要我们学会了 Linux 的 cron 表达式的使用,也就可以直接用在 cron 库上,我们可以看到 Linux cron 表达式的标准实现里,最小的时间单位是支持到分钟级别,秒级别的话我们可以用上面的方案处理。

    func linux() {
    	c := cron.New()
    
    	// 每1分钟执行,等同于 */1 * * * * 和 @every 1m
    	_, err := c.AddFunc("* * * * *", func() {
    		fmt.Println("* * * * *")
    	})
    	if err != nil {
    		fmt.Printf("err: %v\n", err)
    	}
    
    	// 每 5 分钟执行,等同于 @every 5m
    	// / 表示频率
    	_, err1 := c.AddFunc("*/5 * * * *", func() {
    		fmt.Println("* * * * *")
    	})
    	if err1 != nil {
    		fmt.Printf("err1: %v\n", err1)
    	}
    
    	// 每个小时的 01 分执行
    	_, err2 := c.AddFunc("1 * * * *", func() {
    		fmt.Println("1 * * * *")
    	})
    	if err2 != nil {
    		fmt.Printf("err2: %v\n", err2)
    	}
    
    	// 每个月的第一天的 0 点 0 分运行,相当于 @monthly
    	_, err3 := c.AddFunc("0 0 1 * *", func() {
    		fmt.Println("0 0 1 * *")
    	})
    	if err3 != nil {
    		fmt.Printf("err3: %v\n", err3)
    	}
    
    	// 每周的第一天(周六或周日)的 0 点 0 分运行,相当于 @weekly
    	// 像一些发奖需求,在每周一进行发奖,就可以写成 0 0 * * 1
    	_, err4 := c.AddFunc("0 0 * * 0", func() {
    		fmt.Println("0 0 * * 0")
    	})
    	if err4 != nil {
    		fmt.Printf("err4: %v\n", err4)
    	}
    
    	// 每天的 0 点 0 分运行一次,相当于 @daily 和 @midnight
    	_, err5 := c.AddFunc("0 0 * * *", func() {
    		fmt.Println("0 0 * * *")
    	})
    	if err5 != nil {
    		fmt.Printf("err5: %v\n", err5)
    	}
    
    	// 每年运行一次,在 1 月 1 日 0 点 0 分,相当于 @yearly @annually
    	_, err6 := c.AddFunc("0 0 1 1 *", func() {
    		fmt.Println("0 0 1 1 *")
    	})
    	if err6 != nil {
    		fmt.Printf("err6: %v\n", err6)
    	}
    
    	// 每小时运行一次,相当于 @hourly
    	_, err7 := c.AddFunc("0 * * * *", func() {
    		fmt.Println("0 * * * *")
    	})
    	if err7 != nil {
    		fmt.Printf("err7: %v\n", err7)
    	}
    
    	// - 表示范围 , 表示列表
    	// 这里的意思是,在每天的 3 点到 6 点和 20 点到 23 点这两个范围内,每个 30 分执行一次
    	// 也就是 3 点 30,4 点 30,5 点 30 这样
    	_, err8 := c.AddFunc("30 3-6,20-23 * * *", func() {
    		fmt.Println("30 3-6,20-23 * * *")
    	})
    	if err8 != nil {
    		fmt.Printf("err8: %v\n", err8)
    	}
    
    	c.Start()
    	defer c.Stop()
    
    	select {}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84

    更进一步的,假如我们在做海外的应用,有时需要根据时区来进行定时任务,那么可以这么写:

    func timezone() {
    	c := cron.New()
    
    	// 在东京时区,每天的 4 点 30 分运行
    	_, err := c.AddFunc("CRON_TZ=Asia/Tokyo 30 04 * * *", func() {
    		fmt.Println("CRON_TZ=Asia/Tokyo 30 04 * * *")
    	})
    	if err != nil {
    		fmt.Printf("err: %v\n", err)
    	}
    
    	c.Start()
    	defer c.Stop()
    
    	// 或者在 cron 初始化的时候指定时区
    	tc, _ := time.LoadLocation("Asia/Tokyo")
    	c1 := cron.New(cron.WithLocation(tc))
    	_, err1 := c1.AddFunc("30 04 * * *", func() {
    		fmt.Println("30 04 * * *")
    	})
    	if err1 != nil {
    		fmt.Printf("err1: %v\n", err1)
    	}
    
    	c1.Start()
    	defer c1.Stop()
    
    	select {}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    job

    在工作中,为了做到封装的效果,我们可以实现 Job 接口,把任务给封装起来:

    func job() {
    	c := cron.New()
    
    	j1 := MyJob{name: "1"}
    	_, errJob1 := c.AddJob("@every 5s", &j1)
    	if errJob1 != nil {
    		fmt.Printf("errJob1: %v", errJob1)
    	}
    
    	j2 := MyJob{name: "2"}
    	_, errJob2 := c.AddJob("@every 6s", &j2)
    	if errJob2 != nil {
    		fmt.Printf("errJob2: %v", errJob2)
    	}
    	c.Start()
    	select {}
    }
    
    // MyJob 对定时任务进行包装
    type MyJob struct {
    	name string
    }
    
    // Run 实现 Job 接口里的 Run 方法
    func (j *MyJob) Run() {
    	fmt.Printf("myjob : %s\n", j.name)
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    schedule

    如果我们想动态的调整任务的下一次运行的时间,就可以通过实现 Schedule 接口来处理:

    func schedule() {
    	c := cron.New()
    
        // 为了方便,这里用 FuncJob,就不去创建 Job 结构体了 
    	c.Schedule(&MySchedule{}, cron.FuncJob(func() {
    		fmt.Println("hello")
    	}))
    
    	c.Start()
    
    	select {}
    
    }
    
    type MySchedule struct {
    }
    
    // Next 实现 Schedule 接口的 Next 方法
    // Start 的时候,会触发 Next 方法,我们可以在这个的基础上,返回下一次的运行时间
    // 之后每次触发都会调用 Next,我们就可以在这里做文章,动态修改下一次的触发时间
    func (s *MySchedule) Next(t time.Time) time.Time {
    	fmt.Printf("now time: %v\n", t)
    	result := t.Add(5 * time.Second)
    	return result
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25

    参考

    crontab执行时间计算
    cron github

  • 相关阅读:
    基于django和爬虫的影视评价分析系统
    【笔试题】【day16】
    Mysql的SQL调优-面试
    Go基础语法:基本数据类型
    为什么要使用消息队列?
    css 三栏布局的实现?
    基于架构软件设计-架构真题(五十八)
    Redis核心结构以及渐进式扩容
    Memlab,一款分析 JavaScript 堆并查找浏览器和 Node.js 中内存泄漏的开源框架
    数据结构与算法之排序: 快速排序 (Javascript版)
  • 原文地址:https://blog.csdn.net/u013066292/article/details/133484381