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) 靈感來(lái)源于我們?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í)間劃分成固定的槽位,通過(guò)指針的移動(dòng)來(lái)調(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ù)通過(guò)輪數(shù)計(jì)算
為什么時(shí)間輪如此高效?
時(shí)間輪的巧妙之處在于它徹底改變了任務(wù)調(diào)度的思路:
時(shí)間復(fù)雜度的革命 從O(log n)到O(1)
- 傳統(tǒng)方案:需要在堆中查找最小元素
- 時(shí)間輪:直接計(jì)算目標(biāo)槽位,一次到位
內(nèi)存訪問(wèn)的優(yōu)化
- 順序訪問(wèn)數(shù)組元素,CPU緩存命中率高
- 相比堆結(jié)構(gòu)的隨機(jī)訪問(wèn),性能提升明顯
批量處理的威力
- 同一槽位的多個(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)一格
- 我們可以通過(guò)指針的位置確定當(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ì)
- 記錄最后訪問(wèn)時(shí)間,用于監(jiān)控和清理
任務(wù)包裝器(TimerTaskWrapper)
- 包裝原始任務(wù)和相關(guān)信息
- 記錄任務(wù)的到期時(shí)間和需要經(jīng)過(guò)的輪數(shù)
- 維護(hù)任務(wù)狀態(tài)(等待、運(yùn)行、完成、失敗、取消)
- 提供任務(wù)進(jìn)度和剩余時(shí)間計(jì)算
算法關(guān)鍵步驟
任務(wù)調(diào)度算法:
- 計(jì)算任務(wù)需要經(jīng)過(guò)的tick數(shù)量:
ticks = delayMs / tickDuration - 計(jì)算目標(biāo)槽位:
targetSlot = (currentSlot + ticks) % slotSize - 計(jì)算需要經(jīng)過(guò)的輪數(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í)間超過(guò)一圈時(shí),需要記錄剩余輪數(shù)
- 每次指針經(jīng)過(guò)時(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ā)問(wèn)題
- 負(fù)責(zé)指針移動(dòng)和槽位處理
- 通過(guò)
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í)間輪通過(guò)巧妙的時(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)文章
Java同步鎖synchronized用法的最全總結(jié)
這篇文章主要介紹了Java同步鎖synchronized用法的最全總結(jié),需要的朋友可以參考下,文章詳細(xì)講解了Java同步鎖Synchronized的使用方法和需要注意的點(diǎn),希望對(duì)你有所幫助2023-03-03
Java 基礎(chǔ) byte[]與各種數(shù)據(jù)類型互相轉(zhuǎn)換的簡(jiǎn)單示例
這篇文章主要介紹了Java 基礎(chǔ) byte[]與各種數(shù)據(jù)類型互相轉(zhuǎn)換的簡(jiǎn)單示例的相關(guān)資料,這里對(duì)byte[]類型對(duì)long,int,double,float,short,cahr,object,string類型相互轉(zhuǎn)換的實(shí)例,需要的朋友可以參考下2017-01-01
Springboot中配置Mail和普通mail的實(shí)現(xiàn)方式
這篇文章主要介紹了Springboot中配置Mail和普通mail的實(shí)現(xiàn)方式,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2024-03-03
Mybatis動(dòng)態(tài)SQL之IF語(yǔ)句詳解
這篇文章主要給大家介紹了關(guān)于Mybatis動(dòng)態(tài)SQL之IF語(yǔ)句的相關(guān)資料,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2021-05-05
簡(jiǎn)單的用java實(shí)現(xiàn)讀/寫文本文件的示例
同時(shí)也展示了如果從輸入流中讀出來(lái)內(nèi)容寫入輸出流中(僅限文本流) 三個(gè)例子可以獨(dú)立存在,所以根據(jù)需要只看其中一個(gè)就行了。2008-07-07
mybatis-plus與mybatis共存的實(shí)現(xiàn)
本文主要介紹了mybatis-plus與mybatis共存的實(shí)現(xiàn),文中根據(jù)實(shí)例編碼詳細(xì)介紹的十分詳盡,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2022-03-03
JAVAEE中用Session簡(jiǎn)單實(shí)現(xiàn)購(gòu)物車功能示例代碼
本篇文章主要介紹了JAVAEE中用Session簡(jiǎn)單實(shí)現(xiàn)購(gòu)物車功能示例代碼,非常具有實(shí)用價(jià)值,需要的朋友可以參考下。2017-03-03
Eclipse項(xiàng)目出現(xiàn)紅色嘆號(hào)的解決方法
eclipse工程前面出現(xiàn)紅色嘆號(hào)都是由于eclipse項(xiàng)目、eclipse工程中,缺少了一些jar包等文件引起的,這篇文章主要給大家介紹了關(guān)于Eclipse項(xiàng)目出現(xiàn)紅色嘆號(hào)的解決方法,需要的朋友可以參考下2023-11-11

