延遲任務的11種實現方式(下)!!

接上文:

Redisson的RDelayedQueue

Redisson他是Redis的兒子(Redis son),基于Redis實現了非常多的功能,其中最常使用的就是Redis分布式鎖的實現,但是除了實現Redis分布式鎖之外,它還實現了延遲隊列的功能。

先來個demo

引入pom

<dependency><groupId>org.redisson</groupId><artifactId>redisson</artifactId><version>3.13.1</version>
</dependency>

封裝了一個RedissonDelayQueue類

@Component
@Slf4j
public?class?RedissonDelayQueue?{private?RedissonClient?redissonClient;private?RDelayedQueue<String>?delayQueue;private?RBlockingQueue<String>?blockingQueue;@PostConstructpublic?void?init()?{initDelayQueue();startDelayQueueConsumer();}private?void?initDelayQueue()?{Config?config?=?new?Config();SingleServerConfig?serverConfig?=?config.useSingleServer();serverConfig.setAddress("redis://localhost:6379");redissonClient?=?Redisson.create(config);blockingQueue?=?redissonClient.getBlockingQueue("SANYOU");delayQueue?=?redissonClient.getDelayedQueue(blockingQueue);}private?void?startDelayQueueConsumer()?{new?Thread(()?->?{while?(true)?{try?{String?task?=?blockingQueue.take();log.info("接收到延遲任務:{}",?task);}?catch?(Exception?e)?{e.printStackTrace();}}},?"SANYOU-Consumer").start();}public?void?offerTask(String?task,?long?seconds)?{log.info("添加延遲任務:{}?延遲時間:{}s",?task,?seconds);delayQueue.offer(task,?seconds,?TimeUnit.SECONDS);}}

這個類在創建的時候會去初始化延遲隊列,創建一個RedissonClient對象,之后通過RedissonClient對象獲取到RDelayedQueue和RBlockingQueue對象,傳入的隊列名字叫SANYOU,這個名字無所謂。

當延遲隊列創建之后,會開啟一個延遲任務的消費線程,這個線程會一直從RBlockingQueue中通過take方法阻塞獲取延遲任務。

添加任務的時候是通過RDelayedQueue的offer方法添加的。

controller類,通過接口添加任務,延遲時間為5s

@RestController
public?class?RedissonDelayQueueController?{@Resourceprivate?RedissonDelayQueue?redissonDelayQueue;@GetMapping("/add")public?void?addTask(@RequestParam("task")?String?task)?{redissonDelayQueue.offerTask(task,?5);}}

啟動項目,在瀏覽器輸入如下連接,添加任務

http://localhost:8080/add?task=sanyou

靜靜等待5s,成功獲取到任務。

圖片

實現原理

如下是Redisson延遲隊列的實現原理

圖片

SANYOU前面的前綴都是固定的,Redisson創建的時候會拼上前綴。

  • redisson_delay_queue_timeout:SANYOU,sorted set數據類型,存放所有延遲任務,按照延遲任務的到期時間戳(提交任務時的時間戳 + 延遲時間)來排序的,所以列表的最前面的第一個元素就是整個延遲隊列中最早要被執行的任務,這個概念很重要

  • redisson_delay_queue:SANYOU,list數據類型,也是存放所有的任務,但是研究下來發現好像沒什么用。。

  • SANYOU,list數據類型,被稱為目標隊列,這個里面存放的任務都是已經到了延遲時間的,可以被消費者獲取的任務,所以上面demo中的RBlockingQueue的take方法是從這個目標隊列中獲取到任務的

  • redisson_delay_queue_channel:SANYOU,是一個channel,用來通知客戶端開啟一個延遲任務

任務提交的時候,Redisson會將任務放到redisson_delay_queue_timeout:SANYOU中,分數就是提交任務的時間戳+延遲時間,就是延遲任務的到期時間戳

Redisson客戶端內部通過監聽redisson_delay_queue_channel:SANYOU這個channel來提交一個延遲任務,這個延遲任務能夠保證將redisson_delay_queue_timeout:SANYOU中到了延遲時間的任務從redisson_delay_queue_timeout:SANYOU中移除,存到SANYOU這個目標隊列中。

于是消費者就可以從SANYOU這個目標隊列獲取到延遲任務了。

所以從這可以看出,Redisson的延遲任務的實現跟前面說的MQ的實現都是殊途同歸,最開始任務放到中間的一個地方,叫做redisson_delay_queue_timeout:SANYOU,然后會開啟一個類似于定時任務的一個東西,去判斷這個中間地方的消息是否到了延遲時間,到了再放到最終的目標的隊列供消費者消費。

Redisson的這種實現方式比監聽Redis過期key的實現方式更加可靠,因為消息都存在list和sorted set數據類型中,所以消息很少丟。

上述說的兩種Redis的方案更詳細的介紹,可以查看我之前寫的用Redis實現延遲隊列,我研究了兩種方案,發現并不簡單這篇文章。

Netty的HashedWheelTimer

先來個demo
@Slf4j
public?class?NettyHashedWheelTimerDemo?{public?static?void?main(String[]?args)?{HashedWheelTimer?timer?=?new?HashedWheelTimer(100,?TimeUnit.MILLISECONDS,?8);timer.start();log.info("提交延遲任務");timer.newTimeout(timeout?->?log.info("執行延遲任務"),?5,?TimeUnit.SECONDS);}}

測試結果

圖片

實現原理

圖片

如圖,時間輪會被分成很多格子(上述demo中的8就代表了8個格子),一個格子代表一段時間(上述demo中的100就代表一個格子是100ms),所以上述demo中,每800ms會走一圈。

當任務提交的之后,會根據任務的到期時間進行hash取模,計算出這個任務的執行時間所在具體的格子,然后添加到這個格子中,通過如果這個格子有多個任務,會用鏈表來保存。所以這個任務的添加有點像HashMap儲存元素的原理。

HashedWheelTimer內部會開啟一個線程,輪詢每個格子,找到到了延遲時間的任務,然后執行。

由于HashedWheelTimer也是單線程來處理任務,所以跟Timer一樣,長時間運行的任務會導致其他任務的延時處理。

前面Redisson中提到的客戶端延遲任務就是基于Netty的HashedWheelTimer實現的。

Hutool的SystemTimer

Hutool工具類也提供了延遲任務的實現SystemTimer

demo
@Slf4j
public?class?SystemTimerDemo?{public?static?void?main(String[]?args)?{SystemTimer?systemTimer?=?new?SystemTimer();systemTimer.start();log.info("提交延遲任務");systemTimer.addTask(new?TimerTask(()?->?log.info("執行延遲任務"),?5000));}}

執行結果

圖片

Hutool底層其實也用到了時間輪。

Qurtaz

Qurtaz是一款開源作業調度框架,基于Qurtaz提供的api也可以實現延遲任務的功能。

demo

依賴

<dependency><groupId>org.quartz-scheduler</groupId><artifactId>quartz</artifactId><version>2.3.2</version>
</dependency>

SanYouJob實現Job接口,當任務到達執行時間的時候會調用execute的實現,從context可以獲取到任務的內容

@Slf4j
public?class?SanYouJob?implements?Job?{@Overridepublic?void?execute(JobExecutionContext?context)?throws?JobExecutionException?{JobDetail?jobDetail?=?context.getJobDetail();JobDataMap?jobDataMap?=?jobDetail.getJobDataMap();log.info("獲取到延遲任務:{}",?jobDataMap.get("delayTask"));}
}

測試類

public?class?QuartzDemo?{public?static?void?main(String[]?args)?throws?SchedulerException,?InterruptedException?{//?1.創建Scheduler的工廠SchedulerFactory?sf?=?new?StdSchedulerFactory();//?2.從工廠中獲取調度器實例Scheduler?scheduler?=?sf.getScheduler();//?6.啟動?調度器scheduler.start();//?3.創建JobDetail,Job類型就是上面說的SanYouJobJobDetail?jb?=?JobBuilder.newJob(SanYouJob.class).usingJobData("delayTask",?"這是一個延遲任務").build();//?4.創建TriggerTrigger?t?=?TriggerBuilder.newTrigger()//任務的觸發時間就是延遲任務到的延遲時間.startAt(DateUtil.offsetSecond(new?Date(),?5)).build();//?5.注冊任務和定時器log.info("提交延遲任務");scheduler.scheduleJob(jb,?t);}
}

執行結果:

圖片

實現原理

核心組件

  • Job:表示一個任務,execute方法的實現是對任務的執行邏輯

  • JobDetail:任務的詳情,可以設置任務需要的參數等信息

  • Trigger:觸發器,是用來觸發業務的執行,比如說指定5s后觸發任務,那么任務就會在5s后觸發

  • Scheduler:調度器,內部可以注冊多個任務和對應任務的觸發器,之后會調度任務的執行

圖片

啟動的時候會開啟一個QuartzSchedulerThread調度線程,這個線程會去判斷任務是否到了執行時間,到的話就將任務交給任務線程池去執行。

無限輪詢延遲任務

無限輪詢的意思就是開啟一個線程不停的去輪詢任務,當這些任務到達了延遲時間,那么就執行任務。

demo
@Slf4j
public?class?PollingTaskDemo?{private?static?final?List<DelayTask>?DELAY_TASK_LIST?=?new?CopyOnWriteArrayList<>();public?static?void?main(String[]?args)?{new?Thread(()?->?{while?(true)?{try?{for?(DelayTask?delayTask?:?DELAY_TASK_LIST)?{if?(delayTask.triggerTime?<=?System.currentTimeMillis())?{log.info("處理延遲任務:{}",?delayTask.taskContent);DELAY_TASK_LIST.remove(delayTask);}}TimeUnit.MILLISECONDS.sleep(100);}?catch?(Exception?e)?{}}}).start();log.info("提交延遲任務");DELAY_TASK_LIST.add(new?DelayTask("三友的java日記",?5L));}@Getter@Setterpublic?static?class?DelayTask?{private?final?String?taskContent;private?final?Long?triggerTime;public?DelayTask(String?taskContent,?Long?delayTime)?{this.taskContent?=?taskContent;this.triggerTime?=?System.currentTimeMillis()?+?delayTime?*?1000;}}}

任務可以存在數據庫又或者是內存,看具體的需求,這里我為了簡單就放在內存里了。

執行結果:

圖片

這種操作簡單,但是就是效率低下,每次都得遍歷所有的任務。

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/diannao/70397.shtml
繁體地址,請注明出處:http://hk.pswp.cn/diannao/70397.shtml
英文地址,請注明出處:http://en.pswp.cn/diannao/70397.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

BS5852英國家具防火安全條款主要包括哪幾個方面呢?

什么是BS5852檢測&#xff1f; BS5852是英國針對家用家具的強制性安全要求&#xff0c;主要測試家具在受到燃燒香煙和火柴等火源時的可燃性。這個標準通常分為四個部分進行測試&#xff0c;但實際應用中主要測試第一部分和第二部分&#xff0c;包括煙頭測試和利用乙炔火焰模擬…

如何使用Spark SQL進行復雜的數據查詢和分析

使用Spark SQL進行復雜的數據查詢和分析是一個涉及多個步驟和技術的過程。以下是如何使用Spark SQL進行復雜數據查詢和分析的詳細指南&#xff1a; 一、準備階段 環境搭建&#xff1a; 確保已經安裝并配置好了Apache Spark環境。準備好數據源&#xff0c;可以是CSV文件、JSON…

iOS事件傳遞和響應

背景 對于身處中小公司且業務不怎么復雜的程序員來說&#xff0c;很多技術不常用&#xff0c;你可能看過很多遍也都大致了解&#xff0c;但是實際讓你講&#xff0c;不一定講的清楚。你可能說&#xff0c;我以獨當一面&#xff0c;應對自如了&#xff0c;但是技術的知識甚多&a…

FFmpeg 源碼編譯安裝

參考&#xff1a; https://trac.ffmpeg.org/wiki/CompilationGuide/Ubuntu Linux (Ubuntu) 下載 FFmpeg 源碼&#xff0c;并將其解壓&#xff0c;這里我將它放在 ~/ffmpeg_source 目錄下&#xff1b; cd ~/ffmpeg_sources wget -O ffmpeg-snapshot.tar.bz2 https://ffmpeg.org…

【pytest】編寫自動化測試用例命名規范README

API_autoTest 項目介紹 1. pytest命名規范 測試文件&#xff1a; 文件名需要以 test_ 開頭或者以 _test.py 結尾。例如&#xff0c;test_login.py、user_management_test.py 這樣的命名方式&#xff0c;pytest 能夠自動識別并將其作為測試文件來執行其中的測試用例。 測試類…

Windows桌面系統管理5:Windows 10操作系統注冊表

Windows桌面系統管理0&#xff1a;總目錄-CSDN博客 Windows桌面系統管理1&#xff1a;計算機硬件組成及組裝-CSDN博客 Windows桌面系統管理2&#xff1a;VMware Workstation使用和管理-CSDN博客 Windows桌面系統管理3&#xff1a;Windows 10操作系統部署與使用-CSDN博客 Wi…

llama.cpp將sensor格式的大模型轉化為gguf格式

前言 ollama本地只能導入gguf格式的大模型文件&#xff0c;將safetensors 文件轉化為gguf格式。需要使用 llama.cpp 這個開源工具。以下是使用 llama.cpp 轉換 .safetensors 格式模型到 .gguf 格式的詳細步驟: 1. 首先克隆并編譯 llama.cpp: 克隆項目 git clone https://gi…

【運維】源碼編譯安裝cmake

背景&#xff1a; 已經在本地源碼編譯安裝gcc/g&#xff0c;現在源碼安裝cmake 下載源碼 下載地址&#xff1a;CMake - Upgrade Your Software Build System 安裝步驟&#xff1a; ./bootstrap --prefix/usr/local/cmake make make install 錯誤處理 1、提示找不到libmpc.…

如何通過AI優化敏捷開發中的任務管理與分配?

用ChatGPT做軟件測試 在現代軟件開發中&#xff0c;敏捷開發&#xff08;Agile&#xff09;已成為一種廣泛采用的開發方法論&#xff0c;其核心思想是強調快速響應變化、與客戶的持續溝通以及團隊協作的高效性。然而&#xff0c;隨著項目規模的不斷擴大&#xff0c;敏捷開發面臨…

petalinux高版本設置自動登錄和開機自啟動配置

petalinux-config -c rootfs 依次選擇 Image Features -> serial-autologin-root 這是配置 進來就是root權限 創建并安裝名為 myapp-init 的新建應用程序 petalinux-create -t apps --template install -n myapp-init --enable 編輯 project-spec/meta-user/recipes-…

STM32 USB 設備的描述信息作用

在使用 STM32 USB 功能時 usbd_desc.c 文件中定義了一段宏&#xff0c;以下解每段宏的用途。 #define USBD_VID 1155 #define USBD_LANGID_STRING 1033 #define USBD_MANUFACTURER_STRING "STMicroelectronics" #define US…

React通用登錄/注銷功能實現方案(基于shadcn/ui)

React通用登錄/注銷功能實現方案&#xff08;基于shadcn/ui&#xff09; 一、功能需求分析二、通用功能封裝1. 通用登錄表單組件2. 認證Hook封裝 三、功能使用示例1. 登錄頁面實現2. 用戶菜單實現 四、路由保護實現五、方案優勢 一、功能需求分析 需要實現以下核心功能&#x…

jEasyUI 創建學校課程表

jEasyUI 創建學校課程表 引言 隨著信息技術的飛速發展,教育行業也迎來了數字化轉型的浪潮。學校課程表的創建和管理作為教育信息化的重要組成部分,其效率和準確性直接影響到學校的教學秩序。jEasyUI,作為一款優秀的開源UI框架,憑借其易用性、靈活性和豐富的組件,成為了許…

Linux 內核中的 container_of 宏:以 ipoib_rx_poll_rss 函數為例

在 Linux 內核編程中,container_of 是一個非常實用的宏,主要用于通過結構體的成員指針來獲取包含該成員的整個結構體的指針。rx_ring = container_of(napi, struct ipoib_recv_ring, napi); 在代碼中就是利用了這個宏,下面我們詳細分析它的作用和工作原理。 背景知識 在內…

【論文學習】RVS-FDSC:一種基于四方向條帶卷積的視網膜血管分割方法以增強特征提取

寫在前面&#xff1a;本博客僅作記錄學習之用&#xff0c;部分圖片來自網絡&#xff0c;如需引用請注明出處&#xff0c;同時如有侵犯您的權益&#xff0c;請聯系刪除&#xff01; 文章目錄 前言論文論文內容RSC模塊MSPF2 模塊RPDA模塊 實驗效果 總結互動致謝參考往期回顧 前言…

藍橋杯篇---IAP15F2K61S2矩陣鍵盤

文章目錄 前言簡介矩陣鍵盤的工作原理1.行掃描2.檢測列狀態3.按鍵識別 硬件連接1.行線2.列線 矩陣鍵盤使用步驟1.初始化IO口2.掃描鍵盤3.消抖處理4.按鍵識別 示例代碼&#xff1a;4x4矩陣鍵盤掃描示例代碼&#xff1a;優化后的矩陣鍵盤掃描注意事項1.消抖處理2.掃描頻率3.IO口配…

【ISO 14229-1:2023 UDS診斷(ECU復位0x11服務)測試用例CAPL代碼全解析?】

ISO 14229-1:2023 UDS診斷【ECU復位0x11服務】_TestCase19 作者&#xff1a;車端域控測試工程師 更新日期&#xff1a;2025年02月19日 關鍵詞&#xff1a;UDS診斷協議、ECU復位服務、0x11服務、ISO 14229-1:2023 TC11-019測試用例 用例ID測試場景驗證要點參考條款預期結果TC…

Vue 3 30天精進之旅:Day 29 - 項目實戰

在學習了近一個月的Vue 3知識后&#xff0c;今天是我們學習旅程的第29天。在這一天&#xff0c;我們將專注于實踐&#xff0c;通過一個小型項目來鞏固之前的學習成果&#xff0c;并為之后的展示做好準備。 一、項目目標 我們將構建一個簡單的個人博客應用&#xff0c;具備以下…

Windows Docker運行Implicit-SVSDF-Planner

Windows Docker運行GitHub - ZJU-FAST-Lab/Implicit-SVSDF-Planner: [SIGGRAPH 2024 & TOG] 1. 設置環境 我將項目git clone在D:/Github目錄中。 下載ubuntu20.04 noetic鏡像 docker pull osrf/ros:noetic-desktop-full-focal 啟動容器&#xff0c;掛載主機的D:/Github文…

PHP 安全與加密:守護 Web 應用的基石

PHP 學習資料 PHP 學習資料 PHP 學習資料 在當今數字化時代&#xff0c;Web 應用無處不在&#xff0c;而 PHP 作為一種廣泛使用的服務器端腳本語言&#xff0c;承載著無數網站和應用的核心邏輯。然而&#xff0c;隨著網絡攻擊手段日益復雜&#xff0c;PHP 應用面臨著諸多安全…