关于golang:Go学会wire依赖注入cron定时任务其实就这么简单

2次阅读

共计 9211 个字符,预计需要花费 24 分钟才能阅读完成。

前言

嗨,我小 asong 又回来了。托了两周没有更新,最近比较忙,再加上本人懒,所以嘛,嗯嗯,你们懂的。不过我明天的带来的分享,相对干货,在理论我的项目中开发也是须要用到的,所以为了可能讲明确,我特意写了一个样例,仅供参考。本文会围绕样例进行开展学习,已上传 github,可自行下载。好了,不说废话了,晓得你们急不可待了,咱们间接开始吧!!!

wire

依赖注入

在介绍 wire 之前,咱们先来理解一下什么是依赖注入。应用过 Spring 的同学对这个应该不会生疏。其中管制反转(IOC)最常见的形式就叫做依赖注入。将依赖的类作为行参放入依赖中的类就成为依赖注入。这么说可能你们不太懂。用一句大白话来说,一个实例化的对象,原本我承受各种参数来结构一个对象,当初只承受一个参数,对对象的依赖是注入进来的,和它的结构形式解耦了。结构他这个管制操作也交给了第三方,即管制反转。举个例子:go 中是没有类的概念的,以构造体的模式体现。假如咱们当初船类,有个浆类,咱们当初想要设置该船有 12 个浆,那咱们能够写出如下代码:

package mainimport ("fmt")type ship struct {pulp *pulp}func NewShip(pulp *pulp) *ship{return &ship{  pulp: pulp,}}type pulp struct {count int}func Newpulp(count int) *pulp{return &pulp{  count: count,}}func main(){ p:= Newpulp(12) s := NewShip(p) fmt.Println(s.pulp.count)}

置信你们一眼就看出问题了,每当需要变动时,咱们都要从新创立一个对象来指定船桨,这样的代码不易保护,咱们变通一下。

package mainimport ("fmt")type ship struct {pulp *pulp}func NewShip(pulp *pulp) *ship{return &ship{  pulp: pulp,}}type pulp struct {count int}func Newpulp() *pulp{ return &pulp{}}func (c *pulp)set(count int)  {c.count = count}func (c *pulp)get() int { return c.count}func main(){ p:= Newpulp() s := NewShip(p) s.pulp.set(12) fmt.Println(s.pulp.get())}

这个代码的益处就在于代码松耦合,易保护,还易测试。如果咱们当初更换需要了,须要 20 个船桨,间接 s.pulp.set(20) 就能够了。

wire 的应用

wire有两个根底概念,Provider(结构器)和 Injector(注入器)。Provider 实际上就是创立函数,大家意会一下。咱们下面 InitializeCron 就是 Injector。每个注入器实际上就是一个对象的创立和初始化函数。在这个函数中,咱们只须要通知wire 要创立什么类型的对象,这个类型的依赖,wire工具会为咱们生成一个函数实现对象的创立和初始化工作。

拉了这么长,就是为了引出 wire,下面的代码尽管是实现了依赖注入,这是在代码量少,构造不简单的状况下,咱们本人来实现依赖是没有问题的,当构造之间的关系变得非常复杂的时候,这时候手动创立依赖,而后将他们组装起来就会变的异样繁琐,并且很容出错。所以 wire 的作用就来了。在应用之前咱们先来装置一下 wire。

$ go get github.com/google/wire/cmd/wire

执行该命令会在 $GOPATH/bin 中生成一个可执行程序 wire,这个就是代码生成器。别忘了吧$GOPATH/bin 退出零碎环境变量 $PATH 中。

先依据下面的简略例子,咱们先来看看 wire 怎么用。咱们先创立一个 wire 文件,文件内容如下:

//+build wireinjectpackage mainimport ("github.com/google/wire")type Ship struct {Pulp *Pulp}func NewShip(pulp *Pulp) *Ship {return &Ship{  pulp: pulp,}}type Pulp struct {Count int}func NewPulp() *Pulp { return &Pulp{}}func (c *Pulp)set(count int)  {c.count = count}func (c *Pulp)get() int { return c.count}func InitShip() *Ship { wire.Build(  NewPulp,  NewShip,) return &Ship{}}func main(){}

其中 InitShip 这个函数的返回值就是咱们须要创立的对象类型,wire只须要晓得类型,返回什么不重要。在函数中咱们调用了 wire.Build() 将创立 ship 所依赖的的类型结构器传进去。这样咱们就编写好了,当初咱们须要到控制台执行wire

$ wirewire: asong.cloud/Golang_Dream/wire_cron_example/ship: wrote /Users/asong/go/src/asong.cloud/Golang_Dream/wire_cron_example/ship/wire_gen.go

咱们看到生成了 wire_gen.go 这个文件:

// Code generated by Wire. DO NOT EDIT.//go:generate wire//+build !wireinjectpackage main// Injectors from mian.go:func InitShip() *Ship { pulp := NewPulp() ship := NewShip(pulp) return ship}// mian.go:type Ship struct {pulp *Pulp}func NewShip(pulp *Pulp) *Ship {return &Ship{  pulp: pulp,}}type Pulp struct {count int}func NewPulp() *Pulp { return &Pulp{}}func (c *Pulp) set(count int) {c.count = count}func (c *Pulp) get() int { return c.count}func main() {}

能够看进去,生成的这个文件依据方才定义的生成了 InitShip() 这个函数,依赖绑定关系也都实现了,咱们间接调用这个函数,就能够了,省去了大量代码本人去实现依赖绑定关系。

留神:如果你是第一次应用 wire,那么你肯定会遇到一个问题,生成的代码和原来的代码会呈现抵触,因为都定义雷同的函数func InitShip() *Ship,所以这里须要在原文件中首行增加//+build wireinject,并且还要与包名有空行,这样就解决了抵触。

下面的例子还算是简略,上面咱们来看一个比拟多一点的例子,咱们在日常 web 后盾开发时,代码都是有分层的,比拟相熟的有 daoservicecontrollermodel 等等。其实 daoservicecontroller,是有调用先后顺序的。controller 调用 service 层,service层调用 dao 层,这就造成了依赖关系,咱们在理论开发中,通过分层依赖注入的形式,更加层次分明,且代码是易于保护的。所以,我写了一个样例,让咱们来学习一下怎么应用。这个采纳 cron 定时工作代替 controller 来代替 controllercron 定时工作我会在后文进行解说。

//+build wireinjectpackage wireimport ("github.com/google/wire" "asong.cloud/Golang_Dream/wire_cron_example/config" "asong.cloud/Golang_Dream/wire_cron_example/cron" "asong.cloud/Golang_Dream/wire_cron_example/cron/task" "asong.cloud/Golang_Dream/wire_cron_example/dao" "asong.cloud/Golang_Dream/wire_cron_example/service")func InitializeCron(mysql *config.Mysql)  *cron.Cron{wire.Build(  dao.NewClientDB,  dao.NewUserDB,  service.NewUserService,  task.NewScanner,  cron.NewCron,) return &cron.Cron{}}

咱们来看看这段代码,dao.NewClientDB即创立一个 *sql.DB 对象,依赖于 mysql 的配置文件,dao.NewUserDB即创立一个 *UserDB 对象,他依赖于 *sql.DBservice.NewUserService 即创立一个 UserService 对象,依赖于 *UserDB 对象,task.NewScanner创立一个 *Scanner 对象,他依赖于 *UserService 对象,cron。NewCron创立一个 *Cron 对象,他依赖于 *Scanner 对象,其实这里是层层绑定关系,一层调一层,层次分明,且易于代码保护。

好啦,根本应用就介绍到这里,咱们接下来咱们学习一下cron

cron

根底学习

咱们在日常开发或运维中,常常遇到一些周期性执行的工作或需要,例如:每一段时间执行一个脚本,每个月执行一个操作。linux 给咱们提供了一个便捷的形式—— crontab 定时工作;crontab 就是一个自定义定时器,咱们能够利用 crontab 命令在固定的间隔时间执行指定的零碎指令或 shell script 脚本。而这个工夫距离的写法与咱们平时用到的 cron 表达式类似。作用都是通过利用字符或命令去设置定时周期性地执行一些操作.

晓得了基本概念,咱们就来介绍一下 cron 表达式。罕用的 cron 标准格局有两种:一种是“规范”cron 格局,由 cron linux 零碎程序应用,还有一种是 Quartz Scheduler 应用 cron 格局。这两种的差异就在一个是反对 seconds 字段的,一个是不反对的,不过差距不是很大,咱们接下来的解说都带上 seconds 这个字段,没有影响的。

cron 表达式是一个字符串,该字符串由 6 个空格分为 7 个域,每一个域代表一个工夫含意。格局如下:

[秒] [分] [时] [日] [月] [周] [年]

[年]的局部通常是能够省略的,实际上由前六局部组成。

对于各局部的定义,咱们以一个表格的模式出现:

是否必填

值以及范畴

通配符

0-59

, – * /

0-59

, – * /

0-23

, – * /

1-31

, – * ? / L W

1-12 或 JAN-DEC

, – * /

1-7 或 SUN-SAT

, – * ? / L #

1970-2099

, – * /

看这个值的范畴,还是很好了解的,最难了解的是通配符,咱们着重来讲一下通配符。

  • , 这里指的是在两个以上的工夫点中都执行,如果咱们在“分”这个域中定义为 5,10,15,则示意别离在第 5 分,第 10 分 第 15 分执行该定时工作。
  • - 这个比拟好了解就是指定在某个域的间断范畴,如果咱们在“时”这个域中定义 6-12,则示意在 6 到 12 点之间每小时都触发一次,用 , 示意 6,7,8,9,10,11,12
  • * 示意所有值,可解读为“每”。如果在“日”这个域中设置 *, 示意每一天都会触发。
  • ? 示意不指定值。应用的场景为不须要关怀以后设置这个字段的值。例如: 要在每月的 8 号触发一个操作,但不关怀是周几,咱们能够这么设置 0 0 0 8 * ?
  • / 在某个域上周期性触发,该符号将其所在域中的表达式分为两个局部,其中第一局部是起始值,除了秒以外都会升高一个单位,比方 在“秒”上定义 5/10 示意从 第 5 秒开始 每 10 秒执行一次,而在“分”上则示意从 第 5 秒开始 每 10 分钟执行一次。
  • L 示意英文中的LAST 的意思,只能在“日”和“周”中应用。在“日”中设置,示意当月的最初一天(根据以后月份,如果是二月还会根据是否是润年), 在“周”上示意周六,相当于”7”或”SAT”。如果在”L”前加上数字,则示意该数据的最初一个。例如在“周”上设置”7L”这样的格局, 则示意“本月最初一个周六”
  • W 示意离指定日期的最近那个工作日 (周一至周五) 触发,只能在“日”中应用且只能用在具体的数字之后。若在“日”上置”15W”,示意离每月 15 号最近的那个工作日触发。如果 15 号正好是周六,则找最近的周五 (14 号) 触发, 如果 15 号是周未,则找最近的下周一 (16 号) 触发. 如果 15 号正好在工作日(周一至周五),则就在该天触发。如果是“1W”就只能往本月的下一个最近的工作日推不能跨月往上一个月推。
  • # 示意每月的第几个周几,只能作用于“周”上。例如”2#3”示意在每月的第三个周二。

学习了通配符,上面咱们来看几个例子:

  • 每天 10 点执行一次:0 0 10 * * *
  • 每隔 10 分钟执行一次:0 */10 * * *
  • 每月 1 号凌晨 3 点执行一次:0 0 3 1 * ?
  • 每月最初一天 23 点 30 分执行一次:0 30 23 L * ?
  • 每周周六凌晨 3 点履行一次:0 0 3 ? * L
  • 在 30 分、50 分执行一次:0 30,50 * * * ?

go 中应用 cron

后面咱们学习了根底,当初咱们想要在 go 我的项目中应用定时工作,咱们该怎么做呢?github上有一个星星比拟高的一个 cron 库,咱们能够应用 robfig/cron 这个库开发咱们的定时工作。

学习之前,咱们先来装置一下cron

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

这是目前比较稳定的版本,当初这个版本是采纳标准规范的,默认是不带seconds,如果想要带上字段,咱们须要创立 cron 对象是去指定。一会展现。咱们先来看一个简略的应用:

package mainimport ("fmt"  "time"  "github.com/robfig/cron/v3")func main() {  c := cron.New()  c.AddFunc("@every 1s", func() {fmt.Println("task start in 1 seconds")  })  c.Start()  select{}}

这里咱们应用 cron.New 创立一个 cron 对象,用于治理定时工作。调用 cron 对象的 AddFunc() 办法向管理器中增加定时工作。AddFunc()承受两个参数,参数 1 以字符串模式指定触发工夫规定,参数 2 是一个无参的函数,每次触发时调用。@every 1s示意每秒触发一次,@every后加一个工夫距离,示意每隔多长时间触发一次。例如 @every 1h 示意每小时触发一次,@every 1m2s示意每隔 1 分 2 秒触发一次。time.ParseDuration()反对的格局都能够用在这里。调用 c.Start() 启动定时循环。

留神一点,因为 c.Start() 启动一个新的 goroutine 做循环检测,咱们在代码最初加了一行 select{} 避免主 goroutine 退出。

下面咱们定义工夫时,应用的是 cron 预约义的工夫规定,那咱们就学习一下他都有哪些预约义的一些工夫规定:

  • @yearly:也能够写作@annually,示意每年第一天的 0 点。等价于0 0 1 1 *
  • @monthly:示意每月第一天的 0 点。等价于0 0 1 * *
  • @weekly:示意每周第一天的 0 点,留神第一天为周日,即周六完结,周日开始的那个 0 点。等价于0 0 * * 0
  • @daily:也能够写作@midnight,示意每天 0 点。等价于0 0 * * *
  • @hourly:示意每小时的开始。等价于0 * * * *

cron也是反对固定工夫距离的,格局如下:

@every <duration>

含意为每隔 duration 触发一次。<duration>会调用 time.ParseDuration() 函数解析,所以 ParseDuration 反对的格局都能够。

我的项目应用

因为我本人写的我的项目是通过实现 job 接口来退出定时工作,所以上面咱们再来介绍一下 Job 接口的应用,除了间接将无参函数作为回调外,cron还反对 job 接口:

type Job interface{Run()}

咱们须要实现这个接口,这里我就以我写的例子来做演示吧,我当初这个定时工作是周期扫 DB 表中的数据,实现工作如下:

package taskimport ("fmt" "asong.cloud/Golang_Dream/wire_cron_example/service")type Scanner struct {lastID uint64 user *service.UserService}const  (ScannerSize = 10)func NewScanner(user *service.UserService)  *Scanner{return &Scanner{  user: user,}}func (s *Scanner)Run()  { err := s.scannerDB() if err != nil{fmt.Errorf(err.Error()) }}func (s *Scanner)scannerDB()  error{ s.reset() flag := false for {users,err:=s.user.MGet(s.lastID,ScannerSize)  if err != nil{return err}  if len(users) < ScannerSize{flag = true}  s.lastID = users[len(users) - 1].ID  for k,v := range users{fmt.Println(k,v)  }  if flag{return nil} }}func (s *Scanner)reset()  { s.lastID = 0}

下面是实现 Run 办法的局部,之后咱们还须要调用 cron 对象的 AddJob 办法将 Scanner 对象增加到定时管理器中。

package cronimport ("github.com/robfig/cron/v3" "asong.cloud/Golang_Dream/wire_cron_example/cron/task")type Cron struct {Scanner *task.Scanner Schedule *cron.Cron}func NewCron(scanner *task.Scanner) *Cron {return &Cron{  Scanner: scanner,  Schedule: cron.New(), }}func (s *Cron)Start()  error{ _,err := s.Schedule.AddJob("*/1 * * * *",s.Scanner) if err != nil{return err} s.Schedule.Start() return nil}

实际上 AddFunc() 办法外部也调用了 AddJob() 办法。首先,cron基于 func() 类型定义一个新的类型FuncJob

// cron.gotype FuncJob func()

而后让 FuncJob 实现 Job 接口:

// cron.gofunc (f FuncJob) Run() {  f()}

AddFunc() 办法中,将传入的回调转为 FuncJob 类型,而后调用 AddJob() 办法:

func (c *Cron) AddFunc(spec string, cmd func()) (EntryID, error) {return c.AddJob(spec, FuncJob(cmd))}

好啦,根本的应用到这里咱们就解说完了,最初再补充一下最初一个知识点,也就工夫标准的问题,默认 v3 版本是不带 seconds 字段的,要想应用须要这样应用

cron.New(cron.WithSeconds())

创建对象的传入这个参数就能够了。

好啦。我想要解说的完事了,代码就不运行了,已上传 github,可自行下载学习:https://github.com/asong2020/…

总结

明天的文章就到这里了,这一篇总结的并不全,只是达到入门的一个成果,想要持续深刻,还须要各位小伙伴自行看文档学习呦。学会看官网文档,能力提高更多的呦。就比方工夫标准这里,如果不看文档,我就不会晓得当初应用的工夫标准是什么的,所以还是要养成看文档的好习惯。打个预报,下一期是 go-elastic 的教程,有须要的小伙伴能够关注一下。

结尾给大家发一个小福利吧,最近我在看 [微服务架构设计模式] 这一本书,讲的很好,本人也收集了一本 PDF,有须要的小伙能够到自行下载。获取形式:关注公众号:[Golang 梦工厂],后盾回复:[微服务],即可获取。

我翻译了一份 GIN 中文文档,会定期进行保护,有须要的小伙伴后盾回复 [gin] 即可下载。

我是 asong,一名普普通通的程序猿,让我一起缓缓变强吧。欢送各位的关注,咱们下期见~~~

举荐往期文章:

  • 据说你还不会 jwt 和 swagger- 饭我都不吃了带着实际我的项目我就来了
  • 把握这些 Go 语言个性,你的程度将进步 N 个品位(二)
  • go 实现多人聊天室,在这里你想聊什么都能够的啦!!!
  • grpc 实际 - 学会 grpc 就是这么简略
  • go 规范库 rpc 实际
  • 2020 最新 Gin 框架中文文档 asong 又捡起来了英语,用心翻译
  • 基于 gin 的几种热加载形式
  • boss: 这小子还不会应用 validator 库进行数据校验,开了~~~
正文完
 0