SpringBoot時(shí)間輪實(shí)現(xiàn)延時(shí)任務(wù)的示例代碼
傳統(tǒng)方案的困境
在日常開發(fā)中,我們經(jīng)常需要處理各種定時(shí)任務(wù):用戶注冊(cè)后的歡迎郵件、訂單超時(shí)自動(dòng)取消、緩存定期刷新等。傳統(tǒng)的定時(shí)器方案在面對(duì)大規(guī)模定時(shí)任務(wù)時(shí)往往力不從心:
性能瓶頸日益凸顯
ScheduledExecutor在處理上千個(gè)任務(wù)時(shí)性能急劇下降Timer類不僅線程不安全,還存在單點(diǎn)故障風(fēng)險(xiǎn)- 每次調(diào)度都要在堆中查找最小元素,時(shí)間復(fù)雜度O(log n)
- 頻繁的GC壓力導(dǎo)致系統(tǒng)吞吐量受限
業(yè)務(wù)需求日益復(fù)雜
- 消息重試需要指數(shù)退避策略
- 分布式系統(tǒng)需要精確的延遲調(diào)度
- 會(huì)話管理需要?jiǎng)討B(tài)添加刪除任務(wù)
- 限流器需要高效的時(shí)間窗口控制
時(shí)間輪的誕生
時(shí)間輪(Timing Wheel) 靈感來源于我們?nèi)粘J褂玫臅r(shí)鐘。想象一下老式機(jī)械表的工作原理
- 表盤被分成12個(gè)小時(shí)刻度,時(shí)針每12小時(shí)轉(zhuǎn)一圈
- 分針每分鐘移動(dòng)一格,60分鐘轉(zhuǎn)一圈
- 秒針每秒移動(dòng)一格,60秒轉(zhuǎn)一圈
時(shí)間輪借鑒了這個(gè)思想,將時(shí)間劃分成固定的槽位,通過指針的移動(dòng)來調(diào)度任務(wù)。這種巧妙的設(shè)計(jì)將時(shí)間維度空間化,用簡(jiǎn)單的指針移動(dòng)代替了復(fù)雜的堆操作。
核心設(shè)計(jì)哲學(xué)
時(shí)間離散化:將連續(xù)時(shí)間分割成等長(zhǎng)的tick間隔
空間映射:每個(gè)時(shí)間間隔對(duì)應(yīng)一個(gè)槽位
批量觸發(fā):同一槽位的任務(wù)一起執(zhí)行
輪次計(jì)數(shù):多圈任務(wù)通過輪數(shù)計(jì)算
為什么時(shí)間輪如此高效?
時(shí)間輪的巧妙之處在于它徹底改變了任務(wù)調(diào)度的思路:
時(shí)間復(fù)雜度的革命 從O(log n)到O(1)
- 傳統(tǒng)方案:需要在堆中查找最小元素
- 時(shí)間輪:直接計(jì)算目標(biāo)槽位,一次到位
內(nèi)存訪問的優(yōu)化
- 順序訪問數(shù)組元素,CPU緩存命中率高
- 相比堆結(jié)構(gòu)的隨機(jī)訪問,性能提升明顯
批量處理的威力
- 同一槽位的多個(gè)任務(wù)批量觸發(fā)
- 減少線程切換和調(diào)度開銷
- 提高系統(tǒng)的整體吞吐量
算法設(shè)計(jì):從時(shí)鐘模型到數(shù)據(jù)結(jié)構(gòu)
時(shí)間輪的工作原理
想象一個(gè)真實(shí)的時(shí)鐘
- 時(shí)鐘有12個(gè)小時(shí)刻度,每個(gè)刻度代表一個(gè)小時(shí)
- 秒針每秒移動(dòng)一格,分針每分鐘移動(dòng)一格,時(shí)針每小時(shí)移動(dòng)一格
- 我們可以通過指針的位置確定當(dāng)前時(shí)間
時(shí)間輪采用了類似的概念
- 將時(shí)間劃分為N個(gè)槽位(通常是2的冪次,如512個(gè))
- 每個(gè)槽位代表一個(gè)固定的時(shí)間間隔(如100ms)
- 一個(gè)指針周期性地移動(dòng)到下一個(gè)槽位
- 當(dāng)指針到達(dá)某個(gè)槽位時(shí),執(zhí)行該槽位中的所有任務(wù)
核心數(shù)據(jù)結(jié)構(gòu)設(shè)計(jì)
時(shí)間輪主體結(jié)構(gòu)
時(shí)間輪數(shù)組: [Slot0][Slot1][Slot2]...[Slot511] ↑ ↑ ↑ ↑ 指針 100ms后 200ms后 51.1秒后
槽位(Slot)設(shè)計(jì)
- 使用
ConcurrentLinkedQueue存儲(chǔ)任務(wù)列表,保證線程安全 - 維護(hù)任務(wù)計(jì)數(shù)器,便于快速統(tǒng)計(jì)
- 記錄最后訪問時(shí)間,用于監(jiān)控和清理
任務(wù)包裝器(TimerTaskWrapper)
- 包裝原始任務(wù)和相關(guān)信息
- 記錄任務(wù)的到期時(shí)間和需要經(jīng)過的輪數(shù)
- 維護(hù)任務(wù)狀態(tài)(等待、運(yùn)行、完成、失敗、取消)
- 提供任務(wù)進(jìn)度和剩余時(shí)間計(jì)算
算法關(guān)鍵步驟
任務(wù)調(diào)度算法:
- 計(jì)算任務(wù)需要經(jīng)過的tick數(shù)量:
ticks = delayMs / tickDuration - 計(jì)算目標(biāo)槽位:
targetSlot = (currentSlot + ticks) % slotSize - 計(jì)算需要經(jīng)過的輪數(shù):
rounds = ticks / slotSize - 將任務(wù)包裝后放入目標(biāo)槽位
指針移動(dòng)算法:
- 周期性地將指針移動(dòng)到下一個(gè)槽位
- 處理當(dāng)前槽位中的所有任務(wù)
- 對(duì)于未到期的任務(wù),輪數(shù)減1后重新入槽
- 對(duì)于到期的任務(wù),提交到工作線程池執(zhí)行
多輪任務(wù)處理:
- 當(dāng)任務(wù)延遲時(shí)間超過一圈時(shí),需要記錄剩余輪數(shù)
- 每次指針經(jīng)過時(shí),如果輪數(shù)>0,則輪數(shù)減1
- 只有輪數(shù)為0的任務(wù)才會(huì)被執(zhí)行
核心實(shí)現(xiàn):高性能調(diào)度引擎
線程模型設(shè)計(jì)
雙線程池架構(gòu)是時(shí)間輪高性能的關(guān)鍵
調(diào)度線程池(單線程):
↓
周期性移動(dòng)指針
↓
處理槽位任務(wù)
↓
提交到工作線程池
工作線程池(多線程):
↓
并發(fā)執(zhí)行任務(wù)
↓
處理業(yè)務(wù)邏輯
↓
更新任務(wù)狀態(tài)
調(diào)度線程池
- 使用單線程避免并發(fā)問題
- 負(fù)責(zé)指針移動(dòng)和槽位處理
- 通過
scheduleAtFixedRate實(shí)現(xiàn)周期性調(diào)度 - 設(shè)置為守護(hù)線程,不阻止JVM退出
SpringBoot的完整實(shí)現(xiàn)
服務(wù)層
TimingWheelService設(shè)計(jì)
@Service
public class TimingWheelService {
// 任務(wù)管理
public String scheduleTask(TimerTask task, long delayMs);
public boolean cancelTask(String taskId);
public List<TimerTaskWrapper> getActiveTasks();
// 統(tǒng)計(jì)信息
public TimingWheelStats getStats();
public TaskExecutionStats getExecutionStats();
// 任務(wù)清理
public int cleanupCompletedTasks();
// 示例任務(wù)
public String createSampleTask(String type, long delayMs);
public List<String> createBatchTasks(int count, long minDelay, long maxDelay);
}
核心配置類
/**
* 時(shí)間輪配置類
*/
@Configuration
@EnableConfigurationProperties(TimingWheelProperties.class)
public class TimingWheelConfig {
@Bean
public TimingWheel timingWheel(TimingWheelProperties properties, MeterRegistry meterRegistry) {
log.info("Creating timing wheel with properties: {}", properties);
return new TimingWheel(properties, meterRegistry);
}
@Bean
public MetricsConfig metricsConfig(MeterRegistry meterRegistry) {
return new MetricsConfig(meterRegistry);
}
@Bean
public WebConfig webConfig() {
return new WebConfig();
}
}
REST API接口
/**
* 時(shí)間輪控制器
*/
@RestController
@RequestMapping("/api/timingwheel")
@CrossOrigin(origins = "*")
public class TimingWheelController {
@Autowired
private TimingWheelService timingWheelService;
/**
* 獲取時(shí)間輪統(tǒng)計(jì)信息
*/
@GetMapping("/stats")
public ResponseEntity<TimingWheel.TimingWheelStats> getStats() {
TimingWheel.TimingWheelStats stats = timingWheelService.getStats();
return ResponseEntity.ok(stats);
}
/**
* 創(chuàng)建示例任務(wù)
*/
@PostMapping("/tasks/sample")
public ResponseEntity<Map<String, Object>> createSampleTask(@RequestBody Map<String, Object> request) {
String type = (String) request.getOrDefault("type", "simple");
long delay = ((Number) request.getOrDefault("delay", 1000)).longValue();
String taskId = timingWheelService.createSampleTask(type, delay);
Map<String, Object> response = new HashMap<>();
response.put("taskId", taskId);
response.put("type", type);
response.put("delay", delay);
response.put("message", "Task created successfully");
return ResponseEntity.ok(response);
}
/**
* 批量創(chuàng)建任務(wù)
*/
@PostMapping("/tasks/batch")
public ResponseEntity<Map<String, Object>> createBatchTasks(@RequestBody Map<String, Object> request) {
int count = (Integer) request.getOrDefault("count", 10);
long minDelay = ((Number) request.getOrDefault("minDelay", 1000)).longValue();
long maxDelay = ((Number) request.getOrDefault("maxDelay", 10000)).longValue();
List<String> taskIds = timingWheelService.createBatchTasks(count, minDelay, maxDelay);
Map<String, Object> response = new HashMap<>();
response.put("taskIds", taskIds);
response.put("count", taskIds.size());
response.put("message", "Batch tasks created successfully");
return ResponseEntity.ok(response);
}
/**
* 壓力測(cè)試
*/
@PostMapping("/stress-test")
public ResponseEntity<Map<String, Object>> stressTest(@RequestBody Map<String, Object> request) {
int taskCount = (Integer) request.getOrDefault("taskCount", 1000);
long minDelay = ((Number) request.getOrDefault("minDelay", 100)).longValue();
long maxDelay = ((Number) request.getOrDefault("maxDelay", 5000)).longValue();
long startTime = System.currentTimeMillis();
List<String> taskIds = timingWheelService.createBatchTasks(taskCount, minDelay, maxDelay);
long endTime = System.currentTimeMillis();
Map<String, Object> response = new HashMap<>();
response.put("taskCount", taskIds.size());
response.put("creationTime", endTime - startTime);
response.put("throughput", taskIds.size() * 1000.0 / (endTime - startTime));
response.put("message", "Stress test completed successfully");
return ResponseEntity.ok(response);
}
}
應(yīng)用配置
# application.yml
server:
port: 8081
servlet:
context-path: /
spring:
application:
name: springboot-timingwheel
# Timing Wheel Configuration
timingwheel:
config:
slot-size: 512
tick-duration: 100
worker-threads: 4
enable-multi-wheel: true
enable-metrics: true
task-timeout: 30000
logging:
level:
com.example.timingwheel: DEBUG
org.springframework.web: INFO
pattern:
console: "%d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level %logger{36} - %msg%n"
總結(jié)
本文展示了時(shí)間輪的設(shè)計(jì)與實(shí)現(xiàn),從算法原理到可視化監(jiān)控。時(shí)間輪通過巧妙的時(shí)間維度空間化思想,用簡(jiǎn)單的指針移動(dòng)實(shí)現(xiàn)了高效的定時(shí)任務(wù)調(diào)度,在高并發(fā)場(chǎng)景下展現(xiàn)出卓越的性能優(yōu)勢(shì)。
以上就是SpringBoot時(shí)間輪實(shí)現(xiàn)延時(shí)任務(wù)的示例代碼的詳細(xì)內(nèi)容,更多關(guān)于SpringBoot時(shí)間輪延時(shí)任務(wù)的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
Springboot單元測(cè)試無法讀取配置文件的解決方案
這篇文章主要介紹了Springboot單元測(cè)試無法讀取配置文件的解決方案,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-01-01
maven子模塊相互依賴打包時(shí)報(bào)錯(cuò)找不到類的解決方案
本文主要介紹了maven子模塊相互依賴打包時(shí)報(bào)錯(cuò)找不到類的解決方案,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2023-06-06
關(guān)于Controller 層返回值的公共包裝類的問題
本文給大家介紹Controller 層返回值的公共包裝類-避免每次都包裝一次返回-InitializingBean增強(qiáng),本文通過實(shí)例代碼給大家介紹的非常詳細(xì),需要的朋友參考下吧2021-09-09
redis實(shí)現(xiàn)多進(jìn)程數(shù)據(jù)同步工具代碼分享
這篇文章主要介紹了使用redis實(shí)現(xiàn)多進(jìn)程數(shù)據(jù)同步工具的代碼,大家參考使用吧2014-01-01
詳解SpringBoot+SpringSecurity+jwt整合及初體驗(yàn)
這篇文章主要介紹了詳解SpringBoot+SpringSecurity+jwt整合及初體驗(yàn),小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧2019-06-06
深入分析Comparable與Comparator及Clonable三個(gè)Java接口
接口不是類,而是對(duì)類的一組需求描述,這些類要遵從接口描述的統(tǒng)一格式進(jìn)行定義,這篇文章主要為大家詳細(xì)介紹了Java的Comparable,Comparator和Cloneable的接口,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下,希望能夠給你帶來幫助2022-05-05
Java數(shù)組擴(kuò)容實(shí)現(xiàn)方法解析
這篇文章主要介紹了Java數(shù)組擴(kuò)容實(shí)現(xiàn)方法解析,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2019-11-11
springboot連接neo4j報(bào)錯(cuò)的解決方案
這篇文章主要介紹了springboot連接neo4j報(bào)錯(cuò)的解決方案,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2024-02-02
Java 5個(gè)人坐在一起(有關(guān)第五個(gè)人歲數(shù)的問題)
利用遞歸的方法,遞歸分為回推和遞推兩個(gè)階段。要想知道第五個(gè)人歲數(shù),需知道第四人的歲數(shù),依次類推,推到第一人(10歲),再往回推,需要的朋友可以參考下2017-02-02

