Golang 定时任务管理

本文基于Golang Crontab 实现了一个Crontab Job Manager。更加容易使用,同时也能够满足更加复杂的场景。

仓储地址, 如果有用,欢迎点赞,欢迎讨论,欢迎找茬。

需求

在开发中,经常遇到一些需要定时任务的场景。各个语言都有定时语言的库,Golang Cron 提供了Crontab Golang语言版本。这个库非常不错,提供最基本的定时任务编排的功能。但是一些复杂需求无法满足,比如

  • 任何定时任务都有可能失败,失败了就panic了,这样非常不友好。最起码能够让我控制,失败是重试还是停止
  • 某些任务执行周期要10s, 而用户设置的5s一执行,我能不能保证任何时间这个任务只执行一次
  • 我想实时的看到任务的状态,比如是不是在运行?下次运行时间?上次运行时间?
  • 我想看到任务执行了多少次,成功了多少次
  • 我想要限制最大任务数量,比如超过10个任务在执行,不运行新的任务执行
  • 任务执行完了可以告诉我逻辑上有错误,还是有结果。我还可以加上一些钩子函数来处理任务执行的结果

以上的需求都非常常见,可惜这个库都不支持^_^.

完全没用的例子

复杂定义任务的场景模型抽象出来大概也就是下面几个功能点,这个没用的例子可以很好的体现出来

  • 用户通过接口,告诉后台我要做一个什么定时工作,schedule是什么
  • 查看所有定时任务的状态
  • 查看所有定时任务的工作结果

本地运行

通过以下命令本地运行

go get -u "github.com/OhBonsai/croner"
go get -u "github.com/gin-gonic/gin"
go get -u "github.com/gorilla/websocket"
cd $GOPATH/src/github.com/OhBonsai/croner/example

go run server.go 
# 打开localhost:8000
完全没用的例子

前端解释

原谅我的狗屎前端。怕大家看不懂,我还是解释一下前端各个部分什么意思。

  1. 图中①的区域,是计划定义区,可以设置一些参数,表示谁多久往聊天室说一句什么话。第二个表单可以输入1-10的数字,表示每隔几秒说话。当然cron支持六位的crontab周期定义。
  2. 图中②的区域,是执行任务状态区,每秒刷新一次
  3. 图中3的区域,就是我们的聊天室啦。后台定时任务钩子函数会定时把消息推到channel中,如果websocket服务端收到消息就发送到浏览器

后端逻辑

  1. 实现定时计划接口func Run() croner.JobRunReturn
type JobS struct {
    Duration int    `json:"duration"`
    Who      string `json:"who"`
    What     string `json:"what"`
}

func (j JobS) Run() croner.JobRunReturn {
    return croner.JobRunReturn{
        Value: fmt.Sprintf("[%s] %s: %s", time.Now().Format(time.RFC850), j.Who, j.What),
    }
}

  1. 初始化设置
var manager = croner.NewCronManager(croner.CronManagerConfig{
    true, false, 0, 0,
})
  1. 加上钩子函数,如果接收到任务执行结果,将结果传到ch channel
croner.OnJobReturn(func(runReturn *croner.JobRunReturnWithEid) {
    say := runReturn.Value.(string)
    ch <- say
})
  1. 每当接受到post请求,就创建一个任务
_, err = manager.Add(fmt.Sprintf("@every %ds", curJob.Duration), curJob, nil)
  1. 轮询获区ch传过来的值,通过websocket传到前端
for {
    select {
    case msg := <-ch:
        conn.WriteMessage(websocket.TextMessage, []byte(msg))
    default:
        continue
    }
}

实现

详细的使用可以查看测试文件

任务接口

任务只要实现run()函数就行啦。这样我就可以包装你这个函数

type JobRunReturn struct {
    Value interface{}
    Error error
}

type JobInf interface {
    Run() JobRunReturn
}

任务失败控制

Cron没有失败控制,通过包装run()函数来实现cron的job接口来增加一些逻辑。加上一个defer来恢复panic, 通过设置配置ignorePanic来控制是否忽略错误继续执行,还是发生错误就是STOP

    defer func() {
        j.TotalCount += 1
        if err := recover(); err != nil {
            errString := fmt.Sprintf("WrappedJob-%d %s  execute fail. error is %s", j.Id, j.Name, err)
            println(errString)
            atomic.StoreUint32(&j.status, FAIL)
            if !j.father.ignorePanic {
                j.father.DisActive(j.Id)
            }
            j.father.jobReturnsWithEid <- JobRunReturnWithEid{
                JobRunReturn{nil, JobRunError{errString}},
                j.Id,
            }
        }
        return
    }()

单任务周期时间只执行一次

这个主要靠锁来实现,任务运行时就锁住,直到完成之后才释放

j.running.Lock()
defer j.running.Unlock()

任务状态变更

通过原子操作来变更任务状态

atomic.StoreUint32(&(j.status), RUNNING)
defer atomic.StoreUint32(&(j.status), IDLE)

最大任务数量

通过buffered channel来实现最大任务数量

permit = make(chan struct{}, c.PoolSize)
permit <- struct{}{}
defer func() { <-permit }()

钩子

不断获取任务回传结果,然后遍历执行钩子函数

    go func(){
        for {
            select {
            case value := <-r.jobReturnsWithEid:
                jobReturnHooks.Run(&value)
            case <-r.stop:
                return
            }
        }
    }()

缺陷

超时停止,本来尝试做的,配置里面都预留了这个字段。结果发现有问题。这个貌似要修改croner的源码,我不想这么做,但又想不出其他实现方案,我毕竟刚使用golang编程。如果有读者碰到类似问题或者有想法留言提醒我呀

OnlyOne 单次执行的时候,下次执行的时间就无法预测了。这个时候把任务的Next设置为一个不可能的值,比如1970-0-0。但如果在周期内执行完了,下次执行时间就准了...这貌似没办法解决。我也不知道任务什么时候执行完。

学习强大的APScheduler, Quartz

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,591评论 6 501
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,448评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,823评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,204评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,228评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,190评论 1 299
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,078评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,923评论 0 274
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,334评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,550评论 2 333
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,727评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,428评论 5 343
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,022评论 3 326
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,672评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,826评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,734评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,619评论 2 354

推荐阅读更多精彩内容