用Golang的sync.Map实现轻量级定时任务

206 阅读2分钟

产品需求:管理定时任务并在定时触发时执行此任务。技术方案:不考虑其它复杂的、增强式的组件和功能实现一套轻量级的定时任务管理

sync.Map介绍

sync.Map是线程安全的map,在并发情况下不需要主动控制锁即可实现对map的并发写和读。它的操作方法有:

  1. 删除操作

    func (m *Map) Delete(key interface{})
    
  2. 读操作

    func (m *Map) Load(key interface{}) (value interface{}, ok bool)
    
  3. 读取或写入

    func (m *Map) LoadOrStore(key, value interface{}) (actual interface{}, loaded bool)
    
  4. 写操作

    func (m *Map) Store(key, value interface{})
    
  5. 遍历

    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()
}