产品需求:管理定时任务并在定时触发时执行此任务。技术方案:不考虑其它复杂的、增强式的组件和功能实现一套轻量级的定时任务管理
sync.Map介绍
sync.Map是线程安全的map,在并发情况下不需要主动控制锁即可实现对map的并发写和读。它的操作方法有:
-
删除操作
func (m *Map) Delete(key interface{}) -
读操作
func (m *Map) Load(key interface{}) (value interface{}, ok bool) -
读取或写入
func (m *Map) LoadOrStore(key, value interface{}) (actual interface{}, loaded bool) -
写操作
func (m *Map) Store(key, value interface{}) -
遍历
func (m *Map) Range(f func(key, value interface{}) bool)
鉴于本次需求我们只用到其中的Delete、Store和Range方法
需求实现
全局变量
不借助Redis或DB的情况下,将定时任务加载到服务内存中
package global
import (
"sync"
)
var (
PointList sync.Map
)
任务队列管理
package task
import (
"xxxx/global"
"time"
)
type TaskModel struct{
TaskId string `json:"task_id"`
PointAt time.Time `json:"point_at"`
Doing string `json:"doing"`
}
// 添加任务
func Create(t *TaskModel) {
global.PointList.Store(t.TashId, map[string]interface{}{
"point_at": int(t.PointAt.Unix()),
"doing": t.Doing,
})
}
// 删除任务
func Delete(taskId string) {
global.PointList.Delete(taskId)
}
定时器
Golang的定时调度框架较多且比较类似,此处选用的是:cron
package main
import (
"xxxx/taskPoint"
"xxxx/task"
"xxxx/global"
"github.com/robfig/cron/v3"
)
// 检测队列的定时是否符合条件
func PointTrigger() {
now := int(time.Now().Unix())
global.PointList.Range(func(taskId, value any) bool {
v := value.(map[string]interface{})
point_at := v["point_at"].(int)
doing := v["doing"].(string)
if now >= point_at {
// 执行具体业务TODO
// 执行成功后删除队列
task.Delete(taskId)
}
return true
})
}
// 加载全部定时任务到队列中
func PointStart() {
// 加载数据库中的定时任务列表
taskList := []task.TaskModel{}
for _, v := range taskList {
if v.PointAt.IsZero() {
continue
}
task.Create(taskId, map[string]interface{}{
"point_at": int(v.PointAt.Unix()),
"doing": v.Doing,
})
}
}
func main() {
c := cron.New()
PointStart()//服务启动时加载全部定时任务到队列中
c.AddFunc("@every 1s", PointTrigger)// 每秒钟检测一次
c.Start()
}