跟煎鱼学 Go
  • Introduction
  • 第1课 杂谈
    • 1.1 聊一聊,Go 的相对路径问题
    • 1.2 Go 的 fake-useragent 了解一下
    • 1.3 用 Go 来了解一下 Redis 通讯协议
    • 1.4 使用 Gomock 进行单元测试
    • 1.5 在 Go 中恰到好处的内存对齐
    • 1.6 来,控制一下 goroutine 的并发数量
    • 1.7 for-loop 与 json.Unmarshal 性能分析概要
    • 1.8 简单围观一下有趣的 //go: 指令
    • 1.9 我要在栈上。不,你应该在堆上
    • 1.10 defer 会有性能损耗,尽量不要用
    • 1.11 从实践到原理,带你参透 gRPC
    • 1.12 Go1.13 defer 的性能是如何提高的?
    • 1.13 Go 应用内存占用太多,让排查?(VSZ篇)
    • 1.14 干货满满的 Go Modules 和 goproxy.cn
  • 第2课 包管理
    • 2.1 Go依赖管理工具dep
    • 2.2 如此,用dep获取私有库
  • 第3课 gin
    • 3.1 Golang 介绍与环境安装
    • 3.2 Gin搭建Blog API's (一)
    • 3.3 Gin搭建Blog API's (二)
    • 3.4 Gin搭建Blog API's (三)
    • 3.5 使用JWT进行身份校验
    • 3.6 编写一个简单的文件日志
    • 3.7 优雅的重启服务
    • 3.8 为它加上Swagger
    • 3.9 将Golang应用部署到Docker
    • 3.10 定制 GORM Callbacks
    • 3.11 Cron定时任务
    • 3.12 优化配置结构及实现图片上传
    • 3.13 优化你的应用结构和实现Redis缓存
    • 3.14 实现导出、导入 Excel
    • 3.15 生成二维码、合并海报
    • 3.16 在图片上绘制文字
    • 3.17 用Nginx部署Go应用
    • 3.18 Golang交叉编译
    • 3.19 请入门 Makefile
  • 第4课 grpc
    • 4.1 gRPC及相关介绍
    • 4.2 gRPC Client and Server
    • 4.3 gRPC Streaming, Client and Server
    • 4.4 TLS 证书认证
    • 4.5 基于 CA 的 TLS 证书认证
    • 4.6 Unary and Stream interceptor
    • 4.7 让你的服务同时提供 HTTP 接口
    • 4.8 对 RPC 方法做自定义认证
    • 4.9 gRPC Deadlines
    • 4.10 分布式链路追踪
  • 第5课 grpc-gateway
    • 5.1 介绍与环境安装
    • 5.2 Hello World
    • 5.3 Swagger了解一下
    • 5.4 能不能不用证书?
  • 第6课 常用关键字
    • 6.1 panic and recover
    • 6.2 defer
  • 第7课 数据结构
    • 7.1 slice
    • 7.2 slice:最大容量大小是怎么来的
    • 7.3 map:初始化和访问元素
    • 7.4 map:赋值和扩容迁移
    • 7.5 map:为什么遍历 map 是无序的
  • 第8课 标准库
    • 8.1 fmt
    • 8.2 log
    • 8.3 unsafe
  • 第9课 工具
    • 9.1 Go 大杀器之性能剖析 PProf
    • 9.2 Go 大杀器之跟踪剖析 trace
    • 9.3 用 GODEBUG 看调度跟踪
    • 9.4 用 GODEBUG 看GC
  • 第10课 爬虫
    • 9.1 爬取豆瓣电影 Top250
    • 9.2 爬取汽车之家 二手车产品库
    • 9.3 了解一下Golang的市场行情
Powered by GitBook
On this page
  • 知识点
  • 本文目标
  • 介绍
  • Cron 表达式格式
  • Cron 特殊字符
  • 预定义的 Cron 时间表
  • 安装
  • 实践
  • 编写硬删除代码
  • 编写Cron
  • 验证
  • 小结
  • 问题
  • 参考
  • 本系列示例代码
  • 关于
  • 修改记录
  • ?
  • 我的公众号

Was this helpful?

  1. 第3课 gin

3.11 Cron定时任务

Previous3.10 定制 GORM CallbacksNext3.12 优化配置结构及实现图片上传

Last updated 5 years ago

Was this helpful?

项目地址:

知识点

  • 完成定时任务的功能

本文目标

在实际的应用项目中,定时任务的使用是很常见的。你是否有过 Golang 如何做定时任务的疑问,莫非是轮询,在本文中我们将结合我们的项目讲述 Cron。

介绍

我们将使用 这个包,它实现了 cron 规范解析器和任务运行器,简单来讲就是包含了定时任务所需的功能

Cron 表达式格式

字段名

是否必填

允许的值

允许的特殊字符

秒(Seconds)

Yes

0-59

* / , -

分(Minutes)

Yes

0-59

* / , -

时(Hours)

Yes

0-23

* / , -

一个月中的某天(Day of month)

Yes

1-31

* / , - ?

月(Month)

Yes

1-12 or JAN-DEC

* / , -

星期几(Day of week)

Yes

0-6 or SUN-SAT

* / , - ?

Cron表达式表示一组时间,使用 6 个空格分隔的字段

可以留意到 Golang 的 Cron 比 Crontab 多了一个秒级,以后遇到秒级要求的时候就省事了

Cron 特殊字符

1、星号 ( * )

星号表示将匹配字段的所有值

2、斜线 ( / )

斜线用户 描述范围的增量,表现为 “N-MAX/x”,first-last/x 的形式,例如 3-59/15 表示此时的第三分钟和此后的每 15 分钟,到59分钟为止。即从 N 开始,使用增量直到该特定范围结束。它不会重复

3、逗号 ( , )

逗号用于分隔列表中的项目。例如,在 Day of week 使用“MON,WED,FRI”将意味着星期一,星期三和星期五

4、连字符 ( - )

连字符用于定义范围。例如,9 - 17 表示从上午 9 点到下午 5 点的每个小时

5、问号 ( ? )

不指定值,用于代替 “ * ”,类似 “ _ ” 的存在,不难理解

预定义的 Cron 时间表

输入

简述

相当于

@yearly (or @annually)

1月1日午夜运行一次

0 0 0 1 1 *

@monthly

每个月的午夜,每个月的第一个月运行一次

0 0 0 1

@weekly

每周一次,周日午夜运行一次

0 0 0 0

@daily (or @midnight)

每天午夜运行一次

0 0 0 *

@hourly

每小时运行一次

0 0

安装

$ go get -u github.com/robfig/cron

实践

就是我怎么硬删除,我什么时候硬删除?这个往往与业务场景有关系,大致为

  • 另外有一套硬删除接口

  • 定时任务清理(或转移、backup)无效数据

在这里我们选用第二种解决方案来进行实践

编写硬删除代码

打开 models 目录下的 tag.go、article.go文件,分别添加以下代码

1、tag.go

func CleanAllTag() bool {
    db.Unscoped().Where("deleted_on != ? ", 0).Delete(&Tag{})

    return true
}

2、article.go

func CleanAllArticle() bool {
    db.Unscoped().Where("deleted_on != ? ", 0).Delete(&Article{})

    return true
}

注意硬删除要使用 Unscoped(),这是 GORM 的约定

编写Cron

在 项目根目录下新建 cron.go 文件,用于编写定时任务的代码,写入文件内容

package main

import (
    "time"
    "log"

    "github.com/robfig/cron"

    "github.com/EDDYCJY/go-gin-example/models"
)

func main() {
    log.Println("Starting...")

    c := cron.New()
    c.AddFunc("* * * * * *", func() {
        log.Println("Run models.CleanAllTag...")
        models.CleanAllTag()
    })
    c.AddFunc("* * * * * *", func() {
        log.Println("Run models.CleanAllArticle...")
        models.CleanAllArticle()
    })

    c.Start()

    t1 := time.NewTimer(time.Second * 10)
    for {
        select {
        case <-t1.C:
            t1.Reset(time.Second * 10)
        }
    }
}

在这段程序中,我们做了如下的事情

cron.New()

会根据本地时间创建一个新(空白)的 Cron job runner

func New() *Cron {
    return NewWithLocation(time.Now().Location())
}

// NewWithLocation returns a new Cron job runner.
func NewWithLocation(location *time.Location) *Cron {
    return &Cron{
        entries:  nil,
        add:      make(chan *Entry),
        stop:     make(chan struct{}),
        snapshot: make(chan []*Entry),
        running:  false,
        ErrorLog: nil,
        location: location,
    }
}

c.AddFunc()

AddFunc 会向 Cron job runner 添加一个 func ,以按给定的时间表运行

func (c *Cron) AddJob(spec string, cmd Job) error {
    schedule, err := Parse(spec)
    if err != nil {
        return err
    }
    c.Schedule(schedule, cmd)
    return nil
}

会首先解析时间表,如果填写有问题会直接 err,无误则将 func 添加到 Schedule 队列中等待执行

func (c *Cron) Schedule(schedule Schedule, cmd Job) {
    entry := &Entry{
        Schedule: schedule,
        Job:      cmd,
    }
    if !c.running {
        c.entries = append(c.entries, entry)
        return
    }

    c.add <- entry
}

3、c.Start()

在当前执行的程序中启动 Cron 调度程序。其实这里的主体是 goroutine + for + select + timer 的调度控制哦

func (c *Cron) Run() {
    if c.running {
        return
    }
    c.running = true
    c.run()
}

time.NewTimer + for + select + t1.Reset

如果你是初学者,大概会有疑问,这是干嘛用的?

(1)time.NewTimer

会创建一个新的定时器,持续你设定的时间 d 后发送一个 channel 消息

(2)for + select

阻塞 select 等待 channel

(3)t1.Reset

会重置定时器,让它重新开始计时

注:本文适用于 “t.C已经取走,可直接使用 Reset”。

总的来说,这段程序是为了阻塞主程序而编写的,希望你带着疑问来想,有没有别的办法呢?

有的,你直接 select{} 也可以完成这个需求 :)

验证

$ go run cron.go 
2018/04/29 17:03:34 [info] replacing callback `gorm:update_time_stamp` from /Users/eddycjy/go/src/github.com/EDDYCJY/go-gin-example/models/models.go:56
2018/04/29 17:03:34 [info] replacing callback `gorm:update_time_stamp` from /Users/eddycjy/go/src/github.com/EDDYCJY/go-gin-example/models/models.go:57
2018/04/29 17:03:34 [info] replacing callback `gorm:delete` from /Users/eddycjy/go/src/github.com/EDDYCJY/go-gin-example/models/models.go:58
2018/04/29 17:03:34 Starting...
2018/04/29 17:03:35 Run models.CleanAllArticle...
2018/04/29 17:03:35 Run models.CleanAllTag...
2018/04/29 17:03:36 Run models.CleanAllArticle...
2018/04/29 17:03:36 Run models.CleanAllTag...
2018/04/29 17:03:37 Run models.CleanAllTag...
2018/04/29 17:03:37 Run models.CleanAllArticle...

检查输出日志正常,模拟已软删除的数据,定时任务工作OK

小结

定时任务很常见,希望你通过本文能够熟知 Golang 怎么实现一个简单的定时任务调度管理

可以不依赖系统的 Crontab 设置,指不定哪一天就用上了呢

问题

如果你手动修改计算机的系统时间,是会导致定时任务错乱的,所以一般不要乱来。

参考

本系列示例代码

关于

修改记录

  • 第一版:2018年02月16日发布文章

  • 第二版:2019年10月02日修改文章

?

我的公众号

在上一章节 中,我们使用了 GORM 的回调实现了软删除,同时也引入了另外一个问题

如果有任何疑问或错误,欢迎在 进行提问或给予修正意见,如果喜欢或对你有所帮助,欢迎 Star,对作者是一种鼓励和推进。

image
https://github.com/EDDYCJY/go-gin-example
cron
Gin实践 连载十 定制 GORM Callbacks
go-gin-example
issues