├── cron_test.go ├── .gitignore ├── lib.go ├── LICENSE ├── README.md ├── example_test.go └── cron.go /cron_test.go: -------------------------------------------------------------------------------- 1 | package litecron 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Binaries for programs and plugins 2 | *.exe 3 | *.dll 4 | *.so 5 | *.dylib 6 | 7 | # Test binary, build with `go test -c` 8 | *.test 9 | 10 | # Output of the go coverage tool, specifically when used with LiteIDE 11 | *.out 12 | 13 | # Project-local glide cache, RE: https://github.com/Masterminds/glide/issues/736 14 | .glide/ 15 | -------------------------------------------------------------------------------- /lib.go: -------------------------------------------------------------------------------- 1 | package litecron 2 | 3 | var defaultCron *Cron 4 | 5 | func InitDefaultCron(config *MutexConfig) *Cron { 6 | if defaultCron != nil { 7 | panic("[LiteCron][Error] defaultCron init twice.") 8 | } 9 | defaultCron = NewCron(config) 10 | return defaultCron 11 | } 12 | 13 | func Register(c string,f handle) { 14 | if defaultCron == nil { 15 | panic("[LiteCron][Error] can't register cron before InitDefaultCron") 16 | } 17 | defaultCron.Register(c,f) 18 | } 19 | 20 | func Run() { 21 | if defaultCron == nil { 22 | panic("[LiteCron][Error] can't run cron before InitDefaultCron") 23 | } 24 | defaultCron.Run() 25 | } 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 misko_lee 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ### LiteCron 2 | ------ 3 | 4 | #### What's LiteCron 5 | 6 | LiteCron is a In-Processing distributed cron job processor. its easy to handle you cron job into you web app. 7 | you don't need any special machine for run cron job. 8 | 9 | #### UseCase 10 | 11 | * running cron job into my web app. 12 | * build a distributed cron job service(for replace system cron service). 13 | 14 | 15 | #### UseAge 16 | 17 | ```go 18 | //Step 1: init default cron client. 19 | litecron.InitDefaultCron(&litecron.MutexConfig{ 20 | RedisConfig:&litecron.RedisConfig{ 21 | DNS:"127.0.0.1:6379", 22 | }, 23 | Prefix:"litecron/examples/defaults/", 24 | Factor:0.01, 25 | }) 26 | //Register a cron job for every 2 seconds. 27 | litecron.Register("@every 2s",exampleHandle) 28 | go litecron.Run() 29 | time.Sleep(10 * time.Second) 30 | // Output: 31 | ``` 32 | 33 | #### Examples 34 | 35 | 1. [default cron client](example_test.go#L14) 36 | 2. [without default cron client](example_test.go#L30) 37 | 3. [mock multi processor](example_test.go#L44) 38 | 39 | #### Deps & Thanks 40 | 41 | * [RedSync - Distributed mutual exclusion lock ](https://github.com/go-redsync/redsync) 42 | * [Cron - cron lib](https://github.com/robfig/cron) 43 | 44 | 45 | -------------------------------------------------------------------------------- /example_test.go: -------------------------------------------------------------------------------- 1 | package litecron_test 2 | 3 | import ( 4 | "log" 5 | "time" 6 | 7 | "github.com/imiskolee/litecron" 8 | ) 9 | 10 | func exampleHandle() { 11 | log.Print("Hello,LiteCron.") 12 | } 13 | 14 | func ExampleDefault() { 15 | //Step 1: init default cron client. 16 | litecron.InitDefaultCron(&litecron.MutexConfig{ 17 | RedisConfig:&litecron.RedisConfig{ 18 | DNS:"127.0.0.1:6379", 19 | }, 20 | Prefix:"litecron/examples/defaults/", 21 | Factor:0.01, 22 | }) 23 | //Register a cron job for every 2 seconds. 24 | litecron.Register("@every 2s",exampleHandle) 25 | go litecron.Run() 26 | time.Sleep(10 * time.Second) 27 | // Output: 28 | } 29 | 30 | func ExampleNew() { 31 | cron := litecron.NewCron(&litecron.MutexConfig{ 32 | RedisConfig:&litecron.RedisConfig{ 33 | DNS:"127.0.0.1:6379", 34 | }, 35 | Prefix:"litecron/examples/advances/", 36 | Factor:0.01, 37 | }) 38 | cron.Register("@every 5s",exampleHandle) 39 | go cron.Run() 40 | time.Sleep(10 * time.Second) 41 | // Output: 42 | } 43 | 44 | func ExampleMulti() { 45 | f := func() { 46 | cron := litecron.NewCron(&litecron.MutexConfig{ 47 | RedisConfig: &litecron.RedisConfig{ 48 | DNS: "127.0.0.1:6379", 49 | }, 50 | Prefix: "litecron/examples/multi/", 51 | Factor: 0.01, 52 | }) 53 | cron.Register("@every 3s", exampleHandle) 54 | go cron.Run() 55 | } 56 | go f() // processor 1 57 | go f() // processor 2 58 | time.Sleep(10 * time.Second) 59 | // Output: 60 | } 61 | 62 | -------------------------------------------------------------------------------- /cron.go: -------------------------------------------------------------------------------- 1 | package litecron 2 | 3 | import ( 4 | "fmt" 5 | "log" 6 | "reflect" 7 | "runtime" 8 | "time" 9 | "github.com/garyburd/redigo/redis" 10 | "github.com/robfig/cron" 11 | "gopkg.in/redsync.v1" 12 | ) 13 | 14 | const ( 15 | DefaultMutexPrefix = "litecron/defaults" 16 | DefaultMutexFator = 0.05 17 | ) 18 | 19 | type handle func() 20 | 21 | func (h handle) Name() string { 22 | return runtime.FuncForPC(reflect.ValueOf(h).Pointer()).Name() 23 | } 24 | 25 | type Handler struct { 26 | cron string 27 | handle handle 28 | } 29 | 30 | func newHandler(cron string,f handle) *Handler { 31 | return &Handler{ 32 | cron: cron, 33 | handle:f, 34 | } 35 | } 36 | 37 | type RedisConfig struct { 38 | DNS string 39 | } 40 | 41 | type MutexConfig struct { 42 | RedisConfig *RedisConfig 43 | Prefix string 44 | Factor float64 45 | } 46 | 47 | type Cron struct { 48 | cronClient *cron.Cron 49 | sync *redsync.Redsync 50 | MutexConfig *MutexConfig 51 | } 52 | 53 | func NewCron(config *MutexConfig) *Cron { 54 | c := new(Cron) 55 | 56 | p := &redis.Pool{ 57 | MaxIdle: 5, 58 | IdleTimeout: 30 * time.Second, 59 | Dial: func() (redis.Conn, error) { 60 | return redis.Dial("tcp",config.RedisConfig.DNS) 61 | }, 62 | } 63 | var pools []redsync.Pool 64 | pools = append(pools,p) 65 | c.sync = redsync.New(pools) 66 | 67 | if config.Prefix == "" { 68 | config.Prefix = DefaultMutexPrefix 69 | } 70 | if config.Factor <= 0 { 71 | config.Factor = DefaultMutexFator 72 | } 73 | c.MutexConfig = config 74 | c.cronClient = cron.New() 75 | return c 76 | } 77 | 78 | func (c *Cron) Register(cronScheue string,h handle) { 79 | c.cronClient.AddFunc(cronScheue,wrapperHandle(c,newHandler(cronScheue,h))) 80 | } 81 | 82 | func (c *Cron) Run() { 83 | c.cronClient.Run() 84 | } 85 | 86 | func (c *Cron) lock(h *Handler) (bool,error) { 87 | schedule, err := cron.Parse(h.cron) 88 | if err != nil { 89 | return false,err 90 | } 91 | now := time.Now() 92 | d := schedule.Next(now).Sub(now) 93 | d = d - time.Duration(float64(d)*c.MutexConfig.Factor) 94 | mutex := c.sync.NewMutex(fmt.Sprintf("%s/%s",c.MutexConfig.Prefix,h.handle.Name()),redsync.SetExpiry(d),redsync.SetTries(1)) 95 | if err := mutex.Lock(); err != nil { 96 | return false,err 97 | } 98 | log.Printf("[LiteCron][Info] job will locking still:%s %s %s\n",h.cron,h.handle.Name(),schedule.Next(now)) 99 | return true,nil 100 | } 101 | 102 | func wrapperHandle(c *Cron,h *Handler) handle { 103 | return func() { 104 | log.Printf("[LiteCron] start run job:%s %s\n",h.cron,h.handle.Name()) 105 | s,err := c.lock(h) 106 | if err != nil { 107 | log.Printf("[LiteCron][Error] can't run job:%s %s %s\n",h.cron,h.handle.Name(),err.Error()) 108 | return 109 | } 110 | if !s { 111 | log.Printf("[LiteCron][Info] job done with other processor:%s %s\n",h.cron,h.handle.Name()) 112 | return 113 | } 114 | h.handle() 115 | log.Printf("[LiteCron] job done:%s %s",h.cron,h.handle.Name()) 116 | } 117 | } --------------------------------------------------------------------------------