Go基于協程池的延遲任務調度器

原理

通過用一個goroutine以及堆來存儲要待調度的延遲任務,當達到調度時間后,將其添加到協程池中去執行。
主要是使用了chan、Mutex、atomic及ants協程池來實現。

用途

主要是用于高并發及大量定時任務要處理的情況,如果使用Go協程來實現每次延遲任務的調度,那么數量極大的goroutine將會占用內存,導致性能下降,使用協程池實現延遲任務的調度,會改善該情況。
如在物聯網設備中,當連接數量達到幾十萬時,如果使用goroutine來處理心跳或者活躍檢測,頻繁的創建銷毀goroutine會影響性能。

特色

在常見的cron等開源框架中使用的是數組存儲待調度的任務,每次循環時都要排序,并且要刪除某個任務則時間復雜度是O(n)。

本文通過使用堆及雙重Map優化存儲待調度的任務,使得添加任務時間復雜度為O(log n),獲取任務時間復雜度為O(1),刪除時間復雜度為O(1)。

調度器并不會真正的刪除取消任務,當取消任務達到執行時間時,會直接continue,是為了提高刪除效率,如果要刪除取消任務,那么刪除的時間復雜度為O(log n),當有極大量任務時,會占用一些內存,通過空間換時間來提高刪除效率,下文也提供了刪除取消任務的實現,根據不同的場景使用不同的定時任務。

API

創建

NewSchedule(workerNum int, options ...ants.Option) (*Schedule, error) //創建協程數是1的延遲任務調度器
s, _ := NewSchedule(1)

創建一個延遲調度任務器,workerNum是協程數量,options是ants協程池的配置,除了WithMaxBlockingTasks不能配置,別的都可以,具體參考:https://github.com/panjf2000/ants

調度一次

func (s *Schedule) ScheduleOne(job func(), duration time.Duration) (TaskId, error) //1秒后打印一次時間
taskId, _ := s.ScheduleOne(func() {fmt.Println(time.Now())
}, time.Second)

重復調度

func (s *Schedule) Schedule(job func(), duration time.Duration) (TaskId, error) //每隔一秒打印一次時間
taskId, _ := s.Schedule(func() {fmt.Println(time.Now())
}, time.Second)

取消調度

func (s *Schedule) Schedule(job func(), duration time.Duration) (TaskId, error) //每隔一秒打印一次時間
taskId, _ := s.Schedule(func() {fmt.Println(time.Now())
}, time.Second)
//休眠3秒后,取消調度
time.Sleep(3 * time.Second)
s.CancelTask(taskId)

停止調度

func (s *Schedule) Schedule(job func(), duration time.Duration) (TaskId, error) //每隔一秒打印一次時間
taskId, _ := s.Schedule(func() {fmt.Println(time.Now())
}, time.Second)
//休眠3秒后,停用延遲任務調度器
time.Sleep(3 * time.Second)
s.Shutdown()

代碼

package scheduleimport ("container/heap""errors""github.com/panjf2000/ants/v2""math""sync/atomic""time"
)var (// ErrScheduleShutdown 延遲任務調度器已關閉錯誤ErrScheduleShutdown = errors.New("schedule: schedule is already in shutdown")
)const invalidTaskId = 0type TaskId uint32
type OriginalTaskId uint32// Schedule 延遲調度的結構體,提供延遲調度任務的全部方法
// 通過NewSchedule方法創建Schedule,通過Schedule、ScheduleOne方法添加延遲調度任務,通過CancelTask方法取消任務,通過Shutdown停止延遲任務
type Schedule struct {//任務堆,按時間排序taskHeap taskHeap//可執行的任務Map,key是當前的任務id,value是任務的第一次原始id,用于優化取消任務時需要遍歷堆去刪除executeTaskIdMap map[TaskId]OriginalTaskId//任務id的Map,key是任務的第一次原始id,value是當前的任務id,用于優化取消任務時需要遍歷堆去刪除originalTaskIdMap map[OriginalTaskId]TaskId//調度器是否運行中running atomic.Bool//下一個任務idnextTaskId atomic.Uint32//任務運行池pool *ants.Pool//添加任務ChanaddTaskChan chan *Task//刪除任務ChanstopTaskChan chan struct{}//取消任務ChancancelTaskChan chan OriginalTaskId
}// NewSchedule 構建一個Schedule
// workerNum 工作的協程數量,options ants協程池的配置,除了WithMaxBlockingTasks不能配置,別的都可以,具體參考:https://github.com/panjf2000/ants
func NewSchedule(workerNum int, options ...ants.Option) (*Schedule, error) {//延遲任務的最大任務數量必須不限制options = append(options, ants.WithMaxBlockingTasks(0))//創建一個協程池pool, err := ants.NewPool(workerNum)if err != nil {return nil, err}//創建一個延遲調度結構體s := &Schedule{taskHeap:          make(taskHeap, 0),executeTaskIdMap:  make(map[TaskId]OriginalTaskId),originalTaskIdMap: make(map[OriginalTaskId]TaskId),running:           atomic.Bool{},nextTaskId:        atomic.Uint32{},pool:              pool,addTaskChan:       make(chan *Task),stopTaskChan:      make(chan struct{}),cancelTaskChan:    make(chan OriginalTaskId),}//啟動調度 會開啟一個協程去將即將要調度的任務添加到協程池中運行s.start()return s, nil
}// ScheduleOne 添加延遲調度任務,只調度一次
// job 執行的方法 duration 周期間隔,如果是負數立馬執行,如果是負數立馬且只執行一次
func (s *Schedule) ScheduleOne(job func(), duration time.Duration) (uint32, error) {return s.doSchedule(job, duration, true)
}// Schedule 添加延遲調度任務,重復調度
// job 執行的方法 duration 周期間隔,如果是負數立馬且只執行一次
func (s *Schedule) Schedule(job func(), duration time.Duration) (uint32, error) {return s.doSchedule(job, duration, false)
}// doSchedule 添加延遲調度任務的具體實現
func (s *Schedule) doSchedule(job func(), duration time.Duration, onlyOne bool) (uint32, error) {if s.running.Load() {//如果是負數 只執行一次if duration <= 0 {onlyOne = true}nextTaskId := s.getNextTaskId()task := new(Task)task.job = jobtask.executeTime = time.Now().Add(duration)task.onlyOne = onlyOnetask.duration = durationtask.originalId = OriginalTaskId(nextTaskId)task.id = TaskId(nextTaskId)s.addTaskChan <- taskreturn uint32(task.originalId), nil} else {return invalidTaskId, ErrScheduleShutdown}
}// CancelTask 取消延遲調度任務
// taskId 任務id
func (s *Schedule) CancelTask(taskId uint32) {if s.running.Load() {if taskId != invalidTaskId {s.cancelTaskChan <- OriginalTaskId(taskId)}}
}// Shutdown 結束延遲任務調度
func (s *Schedule) Shutdown() {//通過cas設值if s.running.CompareAndSwap(true, false) {s.stopTaskChan <- struct{}{}}
}// IsShutdown 延遲任務調度是否關閉
func (s *Schedule) IsShutdown() bool {return !s.running.Load()
}// start 啟動延遲任務調度
func (s *Schedule) start() {s.running.Store(true)go func() {for {now := time.Now()var timer *time.Timer//如果沒有任務提交,睡眠等待任務if s.taskHeap.Len() == 0 {timer = time.NewTimer(math.MaxUint16 * time.Hour)} else {//查看第一個要執行的任務是否是被取消的task := s.taskHeap.Peek()_, ok := s.executeTaskIdMap[task.id]if !ok {//是被取消的任務,移除后continueheap.Pop(&s.taskHeap)continue} else {//設置執行間隔timer = time.NewTimer(task.executeTime.Sub(now))}}select {case <-timer.C://到達第一個任務執行時間task := heap.Pop(&s.taskHeap).(*Task)//提交到線程池執行,返回的error不需要處理,因為任務池是無限大_ = s.pool.Submit(task.job)//單次執行則刪除,多次執行,則更新if task.onlyOne {s.removeTask(task.originalId, task.id)} else {s.updateTask(task)}case originalTaskId := <-s.cancelTaskChan:timer.Stop()//如果取消的任務id在待執行任務列表中,則刪除任務if taskId, ok := s.originalTaskIdMap[originalTaskId]; ok {s.removeTask(originalTaskId, taskId)}case task := <-s.addTaskChan:timer.Stop()//添加任務s.addTask(task)case <-s.stopTaskChan:timer.Stop()//關閉資源s.close()return}}}()
}// updateTask 更新延遲調度任務
func (s *Schedule) updateTask(executedTask *Task) {//拷貝 并設置新的執行時間和IDtask := *executedTasktask.executeTime = time.Now().Add(task.duration)nextTaskId := s.getNextTaskId()task.id = TaskId(nextTaskId)//把已執行的任務刪除s.removeTask(invalidTaskId, executedTask.id)//添加新的任務s.addTask(&task)
}// removeTask 移除任務
func (s *Schedule) removeTask(originalTaskId OriginalTaskId, taskId TaskId) {//如果原始的任務ID不為空,則為使用者取消的,從任務Map中也刪除if originalTaskId != invalidTaskId {delete(s.originalTaskIdMap, originalTaskId)}delete(s.executeTaskIdMap, taskId)
}// addTask 添加任務
func (s *Schedule) addTask(task *Task) {s.originalTaskIdMap[task.originalId] = task.ids.executeTaskIdMap[task.id] = task.originalIdheap.Push(&s.taskHeap, task)
}// getNextTaskId 獲取下一個任務id
func (s *Schedule) getNextTaskId() uint32 {taskId := s.nextTaskId.Add(1)if taskId == invalidTaskId {taskId = s.nextTaskId.Add(1)}return taskId
}// close 關閉Schedule資源和協程池的資源
func (s *Schedule) close() {//關閉所有資源并設置為 nil help gcs.taskHeap = nils.executeTaskIdMap = nils.originalTaskIdMap = nils.pool.Release()s.pool = nilclose(s.addTaskChan)close(s.cancelTaskChan)close(s.stopTaskChan)s.addTaskChan = nils.cancelTaskChan = nils.stopTaskChan = nil
}// Task 調度任務結構體,是一個調度任務的實體信息
type Task struct {// 原始id,用于Schedule本身的刪除使用,用兩層Map的方式優化數組刪除的O(n)時間復雜度originalId OriginalTaskId// 任務idid TaskId// 執行的時間,每次執行完,如果重復調度就重新計算executeTime time.Time// 周期間隔duration time.Duration// 執行的任務job func()// 是否只執行一次onlyOne bool
}// 任務的堆,使用隊只需要在添加的時候進行排序,堆頂是最先要執行的任務
type taskHeap []*Task// 下面都是堆接口的實現func (t *taskHeap) Len() int {return len(*t)
}
func (t *taskHeap) Less(i, j int) bool {return (*t)[i].executeTime.Before((*t)[j].executeTime)
}func (t *taskHeap) Swap(i, j int) {(*t)[i], (*t)[j] = (*t)[j], (*t)[i]
}func (t *taskHeap) Push(x interface{}) {*t = append(*t, x.(*Task))
}func (t *taskHeap) Pop() interface{} {old := *tn := len(old)x := old[n-1]old[n-1] = nil*t = old[:n-1]return x
}// Peek 查看堆頂元素,非堆接口的實現
func (t *taskHeap) Peek() *Task {return (*t)[0]
}

代碼加上詳細的中文注解,大約300行。
github地址:
https://github.com/xzc-coder/go-schedule

另一個版本的實現,刪除時間復雜度為:O(log n),相對上文中的實現,占用的內存會少,但是刪除效率會變低。

package scheduleimport ("container/heap""errors""github.com/panjf2000/ants/v2""math""sync/atomic""time"
)var (// ErrScheduleShutdown 延遲任務調度器已關閉錯誤ErrScheduleShutdown = errors.New("schedule: schedule is already in shutdown")
)const invalidTaskId = 0type TaskId uint32// Schedule 延遲調度的結構體,提供延遲調度任務的全部方法
// 通過NewSchedule方法創建Schedule,通過Schedule、ScheduleOne方法添加延遲調度任務,通過CancelTask方法取消任務,通過Shutdown停止延遲任務
type Schedule struct {//任務堆,按時間排序taskHeap taskHeaptaskMap  map[TaskId]*Task//調度器是否運行中running atomic.Bool//下一個任務idnextTaskId atomic.Uint32//任務運行池pool *ants.Pool//添加任務ChanaddTaskChan chan *Task//刪除任務ChanstopTaskChan chan struct{}//取消任務ChancancelTaskChan chan TaskId
}// NewSchedule 構建一個Schedule
// workerNum 工作的協程數量,options ants協程池的配置,除了WithMaxBlockingTasks不能配置,別的都可以,具體參考:https://github.com/panjf2000/ants
func NewSchedule(workerNum int, options ...ants.Option) (*Schedule, error) {//延遲任務的最大任務數量必須不限制options = append(options, ants.WithMaxBlockingTasks(0))//創建一個協程池pool, err := ants.NewPool(workerNum)if err != nil {return nil, err}//創建一個延遲調度結構體s := &Schedule{taskHeap:       make(taskHeap, 0),taskMap:        make(map[TaskId]*Task),running:        atomic.Bool{},nextTaskId:     atomic.Uint32{},pool:           pool,addTaskChan:    make(chan *Task),stopTaskChan:   make(chan struct{}),cancelTaskChan: make(chan TaskId),}//啟動調度 會開啟一個協程去將即將要調度的任務添加到協程池中運行s.start()return s, nil
}// ScheduleOne 添加延遲調度任務,只調度一次
// job 執行的方法 duration 周期間隔,如果是負數立馬執行,如果是負數立馬且只執行一次
func (s *Schedule) ScheduleOne(job func(), duration time.Duration) (uint32, error) {return s.doSchedule(job, duration, true)
}// Schedule 添加延遲調度任務,重復調度
// job 執行的方法 duration 周期間隔,如果是負數立馬且只執行一次
func (s *Schedule) Schedule(job func(), duration time.Duration) (uint32, error) {return s.doSchedule(job, duration, false)
}// doSchedule 添加延遲調度任務的具體實現
func (s *Schedule) doSchedule(job func(), duration time.Duration, onlyOne bool) (uint32, error) {if s.running.Load() {//如果是負數 只執行一次if duration <= 0 {onlyOne = true}nextTaskId := s.getNextTaskId()task := new(Task)task.job = jobtask.executeTime = time.Now().Add(duration)task.onlyOne = onlyOnetask.duration = durationtask.id = TaskId(nextTaskId)task.index = 0s.addTaskChan <- taskreturn uint32(task.id), nil} else {return invalidTaskId, ErrScheduleShutdown}
}// CancelTask 取消延遲調度任務
// taskId 任務id
func (s *Schedule) CancelTask(taskId uint32) {if s.running.Load() {if taskId != invalidTaskId {s.cancelTaskChan <- TaskId(taskId)}}
}// Shutdown 結束延遲任務調度
func (s *Schedule) Shutdown() {//通過cas設值if s.running.CompareAndSwap(true, false) {s.stopTaskChan <- struct{}{}}
}// IsShutdown 延遲任務調度是否關閉
func (s *Schedule) IsShutdown() bool {return !s.running.Load()
}// start 啟動延遲任務調度
func (s *Schedule) start() {s.running.Store(true)go func() {for {now := time.Now()var timer *time.Timer//如果沒有任務提交,睡眠等待任務if s.taskHeap.Len() == 0 {timer = time.NewTimer(math.MaxUint16 * time.Hour)} else {task := s.taskHeap.Peek()//設置執行間隔timer = time.NewTimer(task.executeTime.Sub(now))}select {case <-timer.C://到達第一個任務執行時間task := heap.Pop(&s.taskHeap).(*Task)//提交到線程池執行,返回的error不需要處理,因為任務池是無限大_ = s.pool.Submit(task.job)//單次執行則刪除,多次執行,則更新if task.onlyOne {s.removeTask(false, task)} else {s.updateTask(task)}case taskId := <-s.cancelTaskChan:timer.Stop()//如果取消的任務id在待執行任務列表中,則刪除任務if task, ok := s.taskMap[taskId]; ok {s.removeTask(true, task)}case task := <-s.addTaskChan:timer.Stop()//添加任務s.addTask(task)case <-s.stopTaskChan:timer.Stop()//關閉資源s.close()return}}}()
}// updateTask 更新延遲調度任務
func (s *Schedule) updateTask(executedTask *Task) {//拷貝 并設置新的執行時間和IDtask := *executedTasktask.executeTime = time.Now().Add(task.duration)//把已執行的任務刪除s.removeTask(false, executedTask)//添加新的任務s.addTask(&task)
}// removeTask 移除任務
func (s *Schedule) removeTask(removeHeap bool, task *Task) {//從Map和堆中delete(s.taskMap, task.id)if removeHeap {heap.Remove(&s.taskHeap, task.index)}
}// addTask 添加任務
func (s *Schedule) addTask(task *Task) {heap.Push(&s.taskHeap, task)s.taskMap[task.id] = task
}// getNextTaskId 獲取下一個任務id
func (s *Schedule) getNextTaskId() uint32 {taskId := s.nextTaskId.Add(1)if taskId == invalidTaskId {taskId = s.nextTaskId.Add(1)}return taskId
}// close 關閉Schedule資源和協程池的資源
func (s *Schedule) close() {//關閉所有資源并設置為 nil help gcs.taskHeap = nils.taskMap = nils.pool.Release()s.pool = nilclose(s.addTaskChan)close(s.cancelTaskChan)close(s.stopTaskChan)s.addTaskChan = nils.cancelTaskChan = nils.stopTaskChan = nil
}// Task 調度任務結構體,是一個調度任務的實體信息
type Task struct {// 任務idid TaskId// 執行的時間,每次執行完,如果重復調度就重新計算executeTime time.Time// 周期間隔duration time.Duration// 執行的任務job func()// 是否只執行一次onlyOne bool//所在堆數組的下標位置index int
}// 任務的堆,使用隊只需要在添加的時候進行排序,堆頂是最先要執行的任務
type taskHeap []*Task// 下面都是堆接口的實現func (t *taskHeap) Len() int {return len(*t)
}
func (t *taskHeap) Less(i, j int) bool {return (*t)[i].executeTime.Before((*t)[j].executeTime)
}func (t *taskHeap) Swap(i, j int) {(*t)[i], (*t)[j] = (*t)[j], (*t)[i](*t)[i].index = i(*t)[j].index = j
}func (t *taskHeap) Push(x interface{}) {*t = append(*t, x.(*Task))
}func (t *taskHeap) Pop() interface{} {old := *tn := len(old)x := old[n-1]old[n-1] = nil*t = old[:n-1]return x
}// Peek 查看堆頂元素,非堆接口的實現
func (t *taskHeap) Peek() *Task {return (*t)[0]
}

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

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

相關文章

杰發科技AC7801——滴答定時器獲取時間戳

1. 滴答定時器 杰發科技7801內部有一個滴答定時器&#xff0c;該定時器是M0核自帶的&#xff0c;因此可以直接用該定時器來獲取時間戳。 同樣&#xff0c;7803也可以使用該方式獲取時間戳。 2. 滴答定時器原理 SysTick是一個24位的遞減計數器&#xff0c;它從預設的重裝載值…

湖倉一體概述

湖倉一體之前&#xff0c;數據分析經歷了數據庫、數據倉庫和數據湖分析三個時代。 首先是數據庫&#xff0c;它是一個最基礎的概念&#xff0c;主要負責聯機事務處理&#xff0c;也提供基本的數據分析能力。 隨著數據量的增長&#xff0c;出現了數據倉庫&#xff0c;它存儲的是…

第十五屆藍橋杯單片機組4T模擬賽三(第二套)

本套試題在4T平臺中的名字為第15屆藍橋杯單片機組模擬考試三&#xff0c;不知道哪套是4T的模擬賽&#xff0c;所以兩套都敲一遍練練手感。 為了代碼呈現美觀&#xff0c;本文章前面的各個模塊在main函數中的處理函數均未添加退出處理&#xff0c;在最后給出的完整代碼中體現。 …

CT技術變遷史——CT是如何誕生的?

第一代CT(平移-旋轉) X線球管為固定陽極,發射X線為直線筆形束,一個探測器,采用直線和旋轉掃描相結合,即直線掃描后,旋轉1次,再行直線掃描,旋轉180完成一層面掃描,掃描時間3~6分鐘。矩陣象素256256或320320。僅用于顱腦檢查。 第二代CT (平移-旋轉) 與第一代無質…

Virtual Box虛擬機安裝蘋果Monterey和big sur版本實踐

虛擬機安裝蘋果實踐&#xff0c;在Windows10系統&#xff0c;安裝Virtual Box7.1.6&#xff0c;安裝虛擬蘋果Monterey版本Monterey (macOS 12) 。碰到的主要問題是安裝光盤不像Windows那么容易拿到&#xff0c;而且根據網上很多文章制作的光盤&#xff0c;在viritualBox里都無法…

dify基礎之prompts

摘要&#xff1a;在大型語言模型&#xff08;LLM&#xff09;應用中&#xff0c;Prompt&#xff08;提示詞&#xff09;是連接用戶意圖與模型輸出的核心工具。本文從概念、組成、設計原則到實踐案例&#xff0c;系統講解如何通過Prompt解鎖LLM的潛能&#xff0c;提升生成內容的…

【學寫LibreCAD】0 仿寫LibreCAD簡介

一、LibreCAD 核心模塊&#xff1a; 核心模塊&#xff08;Core&#xff09; 功能&#xff1a;處理 CAD 的核心邏輯&#xff0c;如幾何計算、圖形對象管理、坐標系轉換等。關鍵組件&#xff1a; 圖形對象&#xff1a;如直線、圓、圓弧、多段線等。數學工具&#xff1a;向量、矩…

HTML元素,標簽到底指的哪塊部分?單雙標簽何時使用?

1. 標簽&#xff08;Tag&#xff09; vs 元素&#xff08;Element&#xff09; 標簽&#xff08;Tag&#xff09; 標簽是 HTML 中用于定義元素的符號&#xff0c;用尖括號 < > 包裹。例如 <img> 是標簽。元素&#xff08;Element&#xff09; 元素是由 標簽 內容…

Android APK組成編譯打包流程詳解

Android APK&#xff08;Android Package&#xff09;是 Android 應用的安裝包文件&#xff0c;其組成和打包流程涉及多個步驟和文件結構。以下是詳細的說明&#xff1a; 一、APK 的組成 APK 是一個 ZIP 格式的壓縮包&#xff0c;包含應用運行所需的所有文件。解壓后主要包含以…

Token相關設計

文章目錄 1. 雙Token 機制概述1.1 訪問令牌&#xff08;Access Token&#xff09;1.2 刷新令牌&#xff08;Refresh Token&#xff09; 2. 雙Token 認證流程3. Spring Boot 具體實現3.1 生成 Token&#xff08;使用 JWT&#xff09;3.2 解析 Token3.3 登錄接口&#xff08;返回…

HTTP 請求時傳遞多部分表單數據

HTTP 請求時傳遞多部分表單數據&#xff08;multipart/form-data&#xff09; --data-raw $------demo11111\r\nContent-Disposition: form-data; name"Filedata"; filename"截屏2025-02-27 15.45.46.png"\r\nContent-Type: image/png\r\n\r\n\r\n------d…

Java基礎關鍵_013_日期處理

目 錄 一、傳統 API 1.System.currentTimeMillis() &#xff08;1&#xff09;說明 &#xff08;2&#xff09;實例 2.構造方法 &#xff08;1&#xff09;說明 &#xff08;2&#xff09;無參構造 &#xff08;3&#xff09;有參構造 3.日期格式化 &#xff08;1&am…

51單片機中reg52.h與regx52.h在進行位操作時的不同

reg52.h中不能使用例如 P2_0;這樣的定義 而只能使用 P2^0;這樣的定義 但是都不可以對位進行直接賦值操作&#xff1b; 而 regx52.h中可以使用 P2_0和P2^0&#xff1b;但是只有使用下劃線的才可以對位進行賦值操作 例如P2_0 1; 但不可以是P2^0 1; 在 C 語言中&#xff0c;…

基于Rook的Ceph云原生存儲部署與實踐指南(上)

#作者&#xff1a;任少近 文章目錄 1 Ceph環境準備2 rook部署ceph群集2.1 Rook 幫助地址2.2 安裝ceph2.3 獲取csi鏡像2.4 Master參加到osd2.5 設置默認存儲 3 Rook部署云原生RBD塊存儲3.1 部署storageclass資源3.2 部署WordPress使用RBD3.3 WordPress訪問 4 Rook部署云原生RGW…

FastExcel與Reactor響應式編程深度集成技術解析

一、技術融合背景與核心價值 在2025年企業級應用開發中&#xff0c;大規模異步Excel處理與響應式系統架構的結合已成為技術剛需。FastExcel與Reactor的整合方案&#xff0c;通過以下技術協同實現突破性性能&#xff1a; 內存效率革命&#xff1a;FastExcel的流式字節操作與Re…

DeepSeek R1/V3滿血版——在線體驗與API調用

前言&#xff1a;在人工智能的大模型發展進程中&#xff0c;每一次新模型的亮相都宛如一顆投入湖面的石子&#xff0c;激起層層波瀾。如今&#xff0c;DeepSeek R1/V3 滿血版強勢登場&#xff0c;為大模型應用領域帶來了全新的活力與變革。 本文不但介紹在線體驗 DeepSeek R1/…

Spring Data JPA 中的分頁實現:從 BasePage 到 Pageable

文章目錄 Spring Data JPA 中的分頁實現&#xff1a;從 BasePage 到 Pageable背景&#xff1a;為什么需要分頁&#xff1f;認識 BasePage 類深入 toPageable() 方法1. 處理頁碼和頁面大小2. 處理排序方向3. 處理排序字段4. 生成 Pageable 對象 實戰&#xff1a;如何使用 BasePa…

Android Studio 新版本Gradle發布本地Maven倉庫示例

發布代碼到JitPack示例&#xff1a;https://blog.csdn.net/loutengyuan/article/details/145938967 以下是基于 Android Studio 24.2.2&#xff08;Gradle 8.10.2 AGP 8.8.0 JDK17&#xff09; 的本地 Maven 倉庫發布示例&#xff0c;包含aar和jar的不同配置&#xff1a; 1.…

python量化交易——金融數據管理最佳實踐——qteasy創建本地數據源

文章目錄 qteasy金融歷史數據管理總體介紹本地數據源——DataSource對象默認數據源查看數據表查看數據源的整體信息最重要的數據表其他的數據表 從數據表中獲取數據向數據表中添加數據刪除數據表 —— 請盡量小心&#xff0c;刪除后無法恢復&#xff01;&#xff01;總結 qteas…

Android中使用Robolectric測試點擊事件(不需要手機)

文章目錄 一、前言二、簡單示例三、參考文檔 一、前言 Robolectric 是一個由 Google 維護的開源 Android 測試框架&#xff0c;它允許你以 Android 運行時環境運行單元測試。 Robolectric 提供了一個模擬 Android 運行時環境&#xff0c;允許你測試你的代碼是否正確地使用 And…