当前位置:网站首页>Golang分布式应用定时任务如何实现
Golang分布式应用定时任务如何实现
2022-07-30 15:13:00 【亿速云】
Golang分布式应用定时任务如何实现
这篇“Golang分布式应用定时任务如何实现”文章的知识点大部分人都不太理解,所以小编给大家总结了以下内容,内容详细,步骤清晰,具有一定的借鉴价值,希望大家阅读完这篇文章能有所收获,下面我们一起来看看这篇“Golang分布式应用定时任务如何实现”文章吧。
最小堆
最小堆是一种特殊的完全二叉树,任意非叶子节点的值不大于其子节点,如图

通过最小堆,根据任务最近执行时间键堆,每次取堆顶元素即最近需要执行的任务,设置timer定时器,到期后触发任务执行。由于堆的特性每次调整的时间复杂度为O(lgN),相较于普通队列性能更快。
在container/heap中已经实现操作堆的相关函数,我们只需要实现定期任务核心逻辑即可。
// 运行func (c *Cron) Run() error { // 设置cron已启动,atomic.Bool来保证并发安全 c.started.Store(true) // 主循环 for { // 如果停止则退出 if !c.started.Load() { break } c.runTask() } return nil}// 核心逻辑func (c *Cron) runTask() { now := time.Now() duration := infTime // 获取堆顶元素 task, ok := c.tasks.Peek() if ok { // 如果已删除则弹出 if !c.set.Has(task.Name()) { c.tasks.Pop() return } // 计算于当前时间查找,设置定时器 if task.next.After(now) { duration = task.next.Sub(now) } else { duration = 0 } } timer := time.NewTimer(duration) defer timer.Stop() // 当有新元素插入直接返回,防止新元素执行时间小于当前堆顶元素 select { case <-c.new: return case <-timer.C: } // 弹出任务,执行 go task.Exec() // 计算下次执行时间,如果为0说明任务已结束,否则重新入堆 task.next = task.Next(time.Now()) if task.next.IsZero() { c.set.Delete(task.Name()) } else { c.tasks.Push(task) }}主要逻辑可总结为:
将任务按照下次执行时间建最小堆
每次取堆顶任务,设置定时器
如果中间有新加入任务,转入步骤2
定时器到期后执行任务
再次取下个任务,转入步骤2,依次执行
时间轮
另一种实现Cron的方式是时间轮,时间轮通过一个环形队列,每个插槽放入需要到期执行的任务,按照固定间隔转动时间轮,取插槽中任务列表执行,如图所示:

时间轮可看作一个表盘,如图中时间间隔为1秒,总共60个格子,如果任务在3秒后执行则放为插槽3,每秒转动次取插槽上所有任务执行。
如果执行时间超过最大插槽,比如有个任务需要63秒后执行(超过了最大格子刻度),一般可以通过多层时间轮,或者设置一个额外变量圈数,只执行圈数为0的任务。
时间轮插入的时间复杂度为O(1),获取任务列表复杂度为O(1),执行列表最差为O(n)。对比最小堆,时间轮插入删除元素更快。
核心代码如下:
// 定义type TimeWheel struct { interval time.Duration // 触发间隔 slots int // 总插槽数 currentSlot int // 当前插槽数 tasks []*list.List // 环形列表,每个元素为对应插槽的任务列表 set containerx.Set[string] // 记录所有任务key值,用来检查任务是否被删除 tricker *time.Ticker // 定时触发器 logger logr.Logger}func (tw *TimeWheel) Run() error { tw.tricker = time.NewTicker(tw.interval) for { // 通过定时器模拟时间轮转动 now, ok := <-tw.tricker.C if !ok { break } // 转动一次,执行任务列表 tw.RunTask(now, tw.currentSlot) tw.currentSlot = (tw.currentSlot + 1) % tw.slots } return nil}func (tw *TimeWheel) RunTask(now time.Time, slot int) { // 一次执行任务列表 for item := taskList.Front(); item != nil; { task, ok := item.Value.(*TimeWheelTask) // 任务圈数大于0,不需要执行,将圈数减一 if task.circle > 0 { task.circle-- item = item.Next() continue } // 运行任务 go task.Exec() // 计算任务下次运行时间 next := item.Next() taskList.Remove(item) item = next task.next = task.Next(now) if !task.next.IsZero() { tw.add(now, task) } else { tw.Remove(task.Name()) } }}// 添加任务,计算下一次任务执行的插槽与圈数func (tw *TimeWheel) add(now time.Time, task *TimeWheelTask) { if !task.initialized { task.next = task.Next(now) task.initialized = true } duration := task.next.Sub(now) if duration <= 0 { task.slot = tw.currentSlot + 1 task.circle = 0 } else { mult := int(duration / tw.interval) task.slot = (tw.currentSlot + mult) % tw.slots task.circle = mult / tw.slots } tw.tasks[task.slot].PushBack(task) tw.set.Insert(task.Name())}时间轮的主要逻辑如下:
将任务存在对应插槽的时间
通过定时间模拟时间轮转动
每次到期后遍历当前插槽的任务列表,若任务圈数为0则执行
如果任务未结束,计算下次执行的插槽与圈数
转入步骤2,依次执行
以上就是关于“Golang分布式应用定时任务如何实现”这篇文章的内容,相信大家都有了一定的了解,希望小编分享的内容对大家有帮助,若想了解更多相关的知识内容,请关注亿速云行业资讯频道。
边栏推荐
猜你喜欢
随机推荐
【云原生】阿里云ARMS业务实时监控
B+树索引页大小是如何确定的?
TiUP 术语及核心概念
【云原生 • DevOps】influxDB、cAdvisor、Grafana 工具使用详解
Flask introductory learning tutorial
Core Topics under Microservice Architecture (2): Design Principles and Core Topics of Microservice Architecture
EST综述:eDNA的多种状态以及在水环境中持久性的认知
针对 MySQL/InnoDB 刷盘调优
Mysql数据库查询好慢,除了索引,还能因为什么?
Mysql database query is very slow. Besides the index, what else can be caused?
SEATA distributed transaction
Database - SQL
Use of InputStream and OutputStream
Overview of TiUP commands
Troubleshooting TiUP
Flask入门学习教程
Local Transactions vs Distributed Transactions
How do luxury giants such as GUCCI and LV deploy the metaverse, should other brands keep up?
微服务该如何拆分?
When the vite multi-page application refreshes the page, it will not be in the current route and will return to the root route









