task/core.go

279 lines
8.1 KiB
Go
Raw Normal View History

2023-08-04 11:49:44 +08:00
// Package task ...
//
// Description : task ...
//
// Author : go_developer@163.com<白茶清欢>
//
// Date : 2023-08-04 11:13
package task
import (
"container/list"
2023-08-04 13:58:56 +08:00
"fmt"
2023-08-04 11:49:44 +08:00
"git.zhangdeman.cn/zhangdeman/easymap"
2023-08-11 18:14:05 +08:00
"git.zhangdeman.cn/zhangdeman/wrapper"
2023-08-04 11:49:44 +08:00
"time"
)
2023-08-11 16:53:19 +08:00
// NewTimeWheel 实例化时间轮
//
// Author : go_developer@163.com<白茶清欢>
//
// Date : 16:50 2023/8/11
func NewTimeWheel(slotCount int, interval time.Duration) *TimeWheel {
2023-08-11 18:18:00 +08:00
if slotCount <= 0 {
// 默认100个槽位
slotCount = 100
}
if interval.Seconds() == 0 {
// 默认60s扫描一次
interval = time.Second * 60
}
2023-08-11 16:53:19 +08:00
tw := &TimeWheel{
2023-09-04 20:09:30 +08:00
interval: interval,
slots: make([]*list.List, slotCount),
2023-08-11 18:23:52 +08:00
ticker: time.NewTicker(interval),
slotCount: slotCount,
addTaskChannel: make(chan *Task, 1000),
removeTaskChannel: make(chan *Task, 1000),
stopChannel: make(chan bool, 1000),
2024-10-08 11:14:50 +08:00
taskRecords: easymap.NewNormal(),
2023-09-04 20:09:30 +08:00
job: nil,
isRunning: false,
2023-08-11 16:53:19 +08:00
}
tw.initSlots()
return tw
}
2023-08-04 11:49:44 +08:00
// TimeWheel 核心时间轮的数据结构
//
// Author : go_developer@163.com<白茶清欢>
//
// Date : 11:14 2023/8/4
type TimeWheel struct {
2023-09-04 20:09:30 +08:00
interval time.Duration // 时间轮精度
slots []*list.List // 时间轮盘每个位置存储的任务列表
2023-08-11 18:23:52 +08:00
ticker *time.Ticker // 定时器
currentPosition int // 时间轮盘当前位置
slotCount int // 时间轮盘的齿数,Interval*SlotCount就是时间轮盘转一圈走过的时间
addTaskChannel chan *Task
removeTaskChannel chan *Task
stopChannel chan bool
2023-09-04 20:09:30 +08:00
taskRecords easymap.EasyMap // Map结构来存储Task对象key是Task.keyvalue是Task在双向链表中的存储对象
2023-08-04 11:49:44 +08:00
// 需要执行的任务如果时间轮盘上的Task执行同一个Job可以直接实例化到TimeWheel结构体中。
// 此处的优先级低于Task中的Job参数
2023-09-04 20:09:30 +08:00
job ITask
isRunning bool // 是否运行中
2023-08-04 11:49:44 +08:00
}
2023-08-04 13:58:56 +08:00
// initSlots 初始化时间轮盘,每个轮盘上的卡槽用一个双向队列表示,便于插入和删除
//
// Author : go_developer@163.com<白茶清欢>
//
// Date : 13:37 2023/8/4
func (tw *TimeWheel) initSlots() {
2023-08-11 18:23:52 +08:00
for i := 0; i < tw.slotCount; i++ {
2023-09-04 20:09:30 +08:00
tw.slots[i] = list.New()
2023-08-04 13:58:56 +08:00
}
}
// Start 启动时间轮盘的内部函数
//
// Author : go_developer@163.com<白茶清欢>
//
// Date : 13:38 2023/8/4
func (tw *TimeWheel) Start() {
2023-09-04 20:09:30 +08:00
tw.isRunning = true
2023-08-04 13:58:56 +08:00
for {
select {
2023-08-11 18:23:52 +08:00
case <-tw.ticker.C:
// 指定时间间隔之后, 调度一次任务
2023-08-04 13:58:56 +08:00
tw.checkAndRunTask()
2023-08-11 18:23:52 +08:00
case task := <-tw.addTaskChannel:
2023-08-04 13:58:56 +08:00
// 此处利用Task.createTime来定位任务在时间轮盘的位置和执行圈数
// 如果直接用任务的周期来定位位置,那么在服务重启的时候,任务周器相同的点会被定位到相同的卡槽,
// 会造成任务过度集中
tw.AddTask(task, false)
2023-08-11 18:23:52 +08:00
case task := <-tw.removeTaskChannel:
2023-08-04 13:58:56 +08:00
tw.RemoveTask(task)
2023-08-11 18:23:52 +08:00
case <-tw.stopChannel:
tw.ticker.Stop()
2023-09-04 20:09:30 +08:00
tw.isRunning = false
// TODO : 重新初始化时间轮实例
2023-08-04 13:58:56 +08:00
return
}
}
}
// checkAndRunTask 检查该轮盘点位上的Task看哪个需要执行
//
// Author : go_developer@163.com<白茶清欢>
//
// Date : 13:39 2023/8/4
func (tw *TimeWheel) checkAndRunTask() {
// 获取该轮盘位置的双向链表
2023-09-04 20:09:30 +08:00
currentList := tw.slots[tw.currentPosition]
2023-08-04 13:58:56 +08:00
if currentList != nil {
for item := currentList.Front(); item != nil; {
task := item.Value.(*Task)
// 如果圈数>0表示还没到执行时间更新圈数
if task.Circle > 0 {
task.Circle--
item = item.Next()
continue
}
// 执行任务时Task.job是第一优先级然后是TimeWheel.job
if task.Job != nil {
2023-08-11 16:47:21 +08:00
go func() {
defer func() {
if r := recover(); nil != r {
}
}()
2024-10-08 11:24:23 +08:00
_ = task.Job.Execute(nil, nil)
2023-08-11 16:47:21 +08:00
}()
2023-09-04 20:09:30 +08:00
} else if tw.job != nil {
2023-08-11 16:47:21 +08:00
go func() {
defer func() {
if r := recover(); nil != r {
}
}()
2024-10-08 11:24:23 +08:00
_ = tw.job.Execute(nil, nil)
2023-08-11 16:47:21 +08:00
}()
2023-08-04 13:58:56 +08:00
} else {
fmt.Println(fmt.Sprintf("The task %v don't have job to run", task.Key))
}
// 执行完成以后,将该任务从时间轮盘删除
next := item.Next()
2023-09-04 20:09:30 +08:00
tw.taskRecords.Del(task.Key)
2023-08-04 13:58:56 +08:00
currentList.Remove(item)
item = next
// 重新添加任务到时间轮盘用Task.interval来获取下一次执行的轮盘位置
// 如果times==0,说明已经完成执行周期,不需要再添加任务回时间轮盘
if task.Times != 0 {
if task.Times < 0 {
tw.AddTask(task, true)
} else {
task.Times--
tw.AddTask(task, true)
}
}
}
}
// 轮盘前进一步
2023-08-11 18:23:52 +08:00
if tw.currentPosition == tw.slotCount-1 {
tw.currentPosition = 0
2023-08-04 13:58:56 +08:00
} else {
2023-08-11 18:23:52 +08:00
tw.currentPosition++
2023-08-04 13:58:56 +08:00
}
}
// AddTask 添加任务的内部函数, 生成Task在时间轮盘位置和圈数的方式true表示利用Task.interval来生成false表示利用Task.createTime生成
//
// Author : go_developer@163.com<白茶清欢>
//
// Date : 13:43 2023/8/4
func (tw *TimeWheel) AddTask(task *Task, byInterval bool) {
2024-10-08 11:24:23 +08:00
if nil == task {
return
}
2023-08-11 18:29:00 +08:00
// 生成 run_id
task.RunID = wrapper.StringFromRandom(128, "").Md5().Value
2024-10-08 11:24:23 +08:00
if nil != task.Job {
2023-08-11 18:29:00 +08:00
task.Key = task.Job.GetFlag() + "_" + task.RunID
2023-08-11 18:14:05 +08:00
}
2023-08-04 13:58:56 +08:00
var pos, circle int
if byInterval {
pos, circle = tw.getPosAndCircleByInterval(task.Interval)
} else {
pos, circle = tw.getPosAndCircleByCreatedTime(task.CreatedTime, task.Interval, task.Key)
}
task.Circle = circle
task.Position = pos
2023-09-04 20:09:30 +08:00
element := tw.slots[pos].PushBack(task)
tw.taskRecords.Set(task.Key, element)
2023-08-04 13:58:56 +08:00
}
// RemoveTask 删除任务的内部函数
//
// Author : go_developer@163.com<白茶清欢>
//
// Date : 13:44 2023/8/4
func (tw *TimeWheel) RemoveTask(task *Task) {
// 从map结构中删除
2023-09-04 20:09:30 +08:00
taskInfo, _ := tw.taskRecords.Get(task.Key)
tw.taskRecords.Del(task.Key)
2023-08-04 13:58:56 +08:00
// 通过TimeWheel.slots获取任务的
2023-09-04 20:09:30 +08:00
currentList := tw.slots[task.Position]
2023-08-04 13:58:56 +08:00
currentList.Remove(taskInfo.(*list.Element))
}
// getPosAndCircleByInterval 该函数通过任务的周期来计算下次执行的位置和圈数
//
// Author : go_developer@163.com<白茶清欢>
//
// Date : 13:46 2023/8/4
func (tw *TimeWheel) getPosAndCircleByInterval(d time.Duration) (int, int) {
delaySeconds := int(d.Seconds())
2023-09-04 20:09:30 +08:00
intervalSeconds := int(tw.interval.Seconds())
2023-08-11 18:23:52 +08:00
circle := delaySeconds / intervalSeconds / tw.slotCount
pos := (tw.currentPosition + delaySeconds/intervalSeconds) % tw.slotCount
2023-08-04 13:58:56 +08:00
// 特殊case当计算的位置和当前位置重叠时因为当前位置已经走过了所以circle需要减一
2023-08-11 18:23:52 +08:00
if pos == tw.currentPosition && circle != 0 {
2023-08-04 13:58:56 +08:00
circle--
}
return pos, circle
}
// getPosAndCircleByCreatedTime 该函数用任务的创建时间来计算下次执行的位置和圈数
//
// Author : go_developer@163.com<白茶清欢>
//
// Date : 13:47 2023/8/4
2024-10-08 11:24:23 +08:00
func (tw *TimeWheel) getPosAndCircleByCreatedTime(createdTime time.Time, d time.Duration, key any) (int, int) {
2023-08-04 13:58:56 +08:00
passedTime := time.Since(createdTime)
passedSeconds := int(passedTime.Seconds())
delaySeconds := int(d.Seconds())
2023-09-04 20:09:30 +08:00
intervalSeconds := int(tw.interval.Seconds())
2023-08-04 13:58:56 +08:00
2023-08-11 18:23:52 +08:00
circle := delaySeconds / intervalSeconds / tw.slotCount
pos := (tw.currentPosition + (delaySeconds-(passedSeconds%delaySeconds))/intervalSeconds) % tw.slotCount
2023-08-04 13:58:56 +08:00
// 特殊case当计算的位置和当前位置重叠时因为当前位置已经走过了所以circle需要减一
2023-08-11 18:23:52 +08:00
if pos == tw.currentPosition && circle != 0 {
2023-08-04 13:58:56 +08:00
circle--
}
return pos, circle
}
2023-08-04 11:49:44 +08:00
// Task 任务数据结构
//
// Author : go_developer@163.com<白茶清欢>
//
// Date : 11:44 2023/8/4
type Task struct {
2023-08-11 18:29:00 +08:00
RunID string // 每一次运行的run id
2023-08-04 11:49:44 +08:00
Key string // 用来标识task对象是唯一的
Interval time.Duration // 任务周期
CreatedTime time.Time // 任务的创建时间
Position int // 任务在轮盘的位置
Circle int // 任务需要在轮盘走多少圈才能执行
Job ITask // 任务需要执行的Job优先级高于TimeWheel中的Job
Times int // 任务需要执行的次数,如果需要一直执行,设置成-1
MaxExecuteTime int64 // 最大执行时长, 超时自动取消. 如不限制时长, 设置成0
}