延遲隊(duì)列是一種數(shù)據(jù)結(jié)構(gòu),用于處理需要在未來(lái)某個(gè)特定時(shí)間執(zhí)行的任務(wù)。這些任務(wù)被添加到隊(duì)列中,并且指定了一個(gè)執(zhí)行時(shí)間,只有到達(dá)指定的時(shí)間點(diǎn)時(shí)才能從隊(duì)列中取出并執(zhí)行。
在實(shí)際應(yīng)用中,延遲隊(duì)列可以用于處理各種需要延遲處理的任務(wù),例如發(fā)送郵件提醒、訂單自動(dòng)取消、對(duì)超時(shí)任務(wù)的處理等。由于任務(wù)的執(zhí)行是在未來(lái)的某個(gè)時(shí)間點(diǎn),因此這些任務(wù)不會(huì)立即執(zhí)行,而是存儲(chǔ)在隊(duì)列中,直到它的預(yù)定執(zhí)行時(shí)間才會(huì)被執(zhí)行。
在 Go 語(yǔ)言中,我們可以使用 time 包提供的計(jì)時(shí)器功能,通過(guò)使用 Go 中的 slice 存儲(chǔ)延遲處理的任務(wù),實(shí)現(xiàn)一個(gè)簡(jiǎn)單的延遲隊(duì)列的功能。
示例代碼:
type Task struct { ExecuteTime time.Time Job func()}
首先,我們定義一個(gè)結(jié)構(gòu)體 Task,它包含一個(gè)可以執(zhí)行任務(wù)的函數(shù) Job,和一個(gè)執(zhí)行時(shí)間 ExecuteTime,這是期望執(zhí)行該函數(shù)的時(shí)間。
示例代碼:
type DelayQueue struct { TaskQueue []Task}
接下來(lái),我們定義一個(gè) DelayQueue 結(jié)構(gòu)體,它擁有一個(gè) TaskQueue,這是一個(gè) Task 類型的切片,用于保存待執(zhí)行任務(wù)的列表。
示例代碼:
// 添加任務(wù)func (d *DelayQueue) AddTask(t Task) { d.TaskQueue = append(d.TaskQueue, t)}// 移除任務(wù)func (d *DelayQueue) RemoveTask() { d.TaskQueue = d.TaskQueue[1:]}// 執(zhí)行任務(wù)func (d *DelayQueue) ExecuteTasks() { for len(d.TaskQueue) > 0 { // 獲取隊(duì)列最頂部的任務(wù) currentTask := d.TaskQueue[0] // 如果執(zhí)行時(shí)間還沒到,等待 if time.Now().Before(currentTask.ExecuteTime) { time.Sleep(currentTask.ExecuteTime.Sub(time.Now())) } // 執(zhí)行任務(wù) currentTask.Job() // 移除已執(zhí)行的任務(wù) d.RemoveTask() }}
DelayQueue 包含三個(gè)方法:
示例代碼:
func main() { fmt.Println("Start DelayQueue") queue := DelayQueue{} firstTask := Task{ ExecuteTime: time.Now().Add(4 * time.Second), Job: func() { fmt.Println("Executed task 1 after delay") }, } queue.AddTask(firstTask) secondTask := Task{ ExecuteTime: time.Now().Add(10 * time.Second), Job: func() { fmt.Println("Executed task 2 after delay") }, } queue.AddTask(secondTask) queue.ExecuteTasks() fmt.Println("Done!")}
輸出結(jié)果:
Start DelayQueueExecuted task 1 after delayExecuted task 2 after delayDone!
在示例代碼中,我們創(chuàng)建了一個(gè)延時(shí)隊(duì)列,將任務(wù)添加到隊(duì)列中,并在指定的延時(shí)后執(zhí)行它們。
通過(guò)使用這些結(jié)構(gòu)體和方法,我們可以在 Go 中實(shí)現(xiàn)簡(jiǎn)單的延遲執(zhí)行任務(wù)的功能。
但是,當(dāng) Go 程序重啟時(shí),存儲(chǔ)在 slice 中的延遲處理的任務(wù)將全部丟失。
在 Go 程序中,如果想在重啟后保留數(shù)據(jù),我們可以將數(shù)據(jù)持久化到 Redis,可以使用 go-redis/redis 庫(kù)[1]與 Redis 交互。而對(duì)于延遲隊(duì)列的需求,則可以使用 Redis 的 ZSET(有序集合)特性來(lái)實(shí)現(xiàn)。
示例代碼:
// 定義一個(gè)全局的redisdb變量var redisdb *redis.Client// 初始化連接func initClient() (err error) { redisdb = redis.NewClient(&redis.Options{ Addr: "localhost:6379", Password: "", // no password set DB: 0, // use default DB }) _, err = redisdb.Ping().Result() if err != nil { return err } return nil}
全局變量 redisdb 是 redis.Client 類型的指針,用來(lái)保存到 Redis 客戶端的引用。
initClient 函數(shù)初始化連接到 Redis 服務(wù)器,該服務(wù)器在本地主機(jī)的 6379 端口運(yùn)行。它將一個(gè)新的 Redis 客戶端分配給 redisdb 變量。如果連接成功,它就會(huì) ping Redis 服務(wù)器以測(cè)試連接。
示例代碼:
// 向隊(duì)列中添加任務(wù)func addTaskToQueue(task string, executeTime int64) { err := redisdb.ZAdd("delay-queue", redis.Z{ Score: float64(executeTime), Member: task, }).Err() if err != nil { panic(err) }}
addTaskToQueue 函數(shù)將具有執(zhí)行時(shí)間的任務(wù)添加到 Redis 等待排序的集合 "delay-queue"。執(zhí)行時(shí)間是一個(gè) UNIX 時(shí)間戳,作為排序集合中的項(xiàng)目的 score,允許 Redis 按照他們應(yīng)該執(zhí)行的時(shí)間來(lái)排序項(xiàng)目。
示例代碼:
// 從隊(duì)列中獲取并處理任務(wù)func getAndExecuteTasks() { for { // 使用 ZRANGEBYSCORE 命令獲取分?jǐn)?shù)(時(shí)間戳)<= 當(dāng)前時(shí)間的任務(wù) tasks, err := redisdb.ZRangeByScore("delay-queue", redis.ZRangeBy{ Min: "-inf", Max: fmt.Sprintf("%d", time.Now().Unix()), }).Result() if err != nil { time.Sleep(1 * time.Second) continue } // 處理任務(wù) for _, task := range tasks { fmt.Println("Executing task: ", task) // 執(zhí)行完任務(wù)后,用 ZREM 移除該任務(wù) redisdb.ZRem("delay-queue", task) } // 暫停一秒 time.Sleep(1 * time.Second) }}
getAndExecuteTasks 函數(shù)不斷檢查 "delay-queue"。它提取隊(duì)列中 score 小于或等于當(dāng)前時(shí)間戳的任務(wù),意味著這些任務(wù)現(xiàn)在應(yīng)該執(zhí)行或者他們應(yīng)該在過(guò)去就已經(jīng)執(zhí)行。獲取任務(wù)后,它打印任務(wù)(模擬執(zhí)行)并從隊(duì)列中刪除任務(wù)。
示例代碼:
func main() { err := initClient() if err != nil { fmt.Println("redis connect error:", err) return } // 添加一些測(cè)試任務(wù) addTaskToQueue("task1", time.Now().Add(10*time.Second).Unix()) addTaskToQueue("task2", time.Now().Add(20*time.Second).Unix()) // 執(zhí)行延遲隊(duì)列中的任務(wù) getAndExecuteTasks()}
輸出結(jié)果:
Executing task: task1Executing task: task2
main 函數(shù)調(diào)用這些函數(shù)。首先,它初始化 Redis 客戶端。如果初始化和連接成功,它將一些測(cè)試任務(wù)添加到隊(duì)列中,并啟動(dòng)任務(wù)執(zhí)行循環(huán)。
總結(jié)一下,這段 Go 代碼使用 Redis 的 Sorted Set 數(shù)據(jù)類型創(chuàng)建了一個(gè)延時(shí)隊(duì)列系統(tǒng),其中的任務(wù)按照他們的執(zhí)行時(shí)間進(jìn)行排序,一個(gè)任務(wù)工作者循環(huán)獲取并執(zhí)行隊(duì)列中的任務(wù)。這是一個(gè)簡(jiǎn)單而高效地實(shí)現(xiàn)作業(yè)調(diào)度系統(tǒng)的方法。
本文我們分別實(shí)現(xiàn)簡(jiǎn)單版和復(fù)雜版的延遲隊(duì)列,其中簡(jiǎn)單版延遲隊(duì)列,只使用 Go 實(shí)現(xiàn),復(fù)雜版延遲隊(duì)列,使用 Go 和 Redis 實(shí)現(xiàn)。
(1) 只使用 Go 實(shí)現(xiàn)延遲隊(duì)列:
優(yōu)點(diǎn):
缺點(diǎn):
(2) 使用 Go + Redis 實(shí)現(xiàn)延遲隊(duì)列:
優(yōu)點(diǎn):
缺點(diǎn):
總的來(lái)說(shuō),如果我們對(duì)延遲隊(duì)列的持久性、準(zhǔn)確性和并發(fā)性有高要求,那么 Go + Redis 的方案可能會(huì)更適合。如果我們想要一個(gè)更簡(jiǎn)單的解決方案,并且可以容忍在程序崩潰時(shí)部分?jǐn)?shù)據(jù)丟失,那么只使用 Go 實(shí)現(xiàn)可能會(huì)更合適。
本文鏈接:http://www.tebozhan.com/showinfo-26-70436-0.htmlGo 語(yǔ)言實(shí)戰(zhàn):構(gòu)建強(qiáng)大的延遲任務(wù)隊(duì)列
聲明:本網(wǎng)頁(yè)內(nèi)容旨在傳播知識(shí),若有侵權(quán)等問題請(qǐng)及時(shí)與本網(wǎng)聯(lián)系,我們將在第一時(shí)間刪除處理。郵件:2376512515@qq.com