Golang實現(xiàn)Redis過期時間實例探究
引言
用11篇文章實現(xiàn)一個可用的Redis服務(wù),姑且叫EasyRedis吧,希望通過文章將Redis掰開撕碎了呈現(xiàn)給大家,而不是僅僅停留在八股文的層面,并且有非常爽的感覺,歡迎持續(xù)關(guān)注學(xué)習(xí)。
- [x] easyredis之TCP服務(wù)
- [x] easyredis之網(wǎng)絡(luò)請求序列化協(xié)議(RESP)
- [x] easyredis之內(nèi)存數(shù)據(jù)庫
- [x] easyredis之過期時間 (時間輪實現(xiàn))
- [x] easyredis之持久化 (AOF實現(xiàn))
- [ ] easyredis之發(fā)布訂閱功能
- [ ] easyredis之有序集合(跳表實現(xiàn))
- [ ] easyredis之 pipeline 客戶端實現(xiàn)
- [ ] easyredis之事務(wù)(原子性/回滾)
- [ ] easyredis之連接池
- [ ] easyredis之分布式集群存儲
【第四篇】EasyRedis之過期時間
在使用Redis
的時候經(jīng)常會對緩存設(shè)定過期時間,例如set key value ex 3
,設(shè)定過期時間3s
,等到過期以后,我們再執(zhí)行get key
正常情況下是得不到數(shù)據(jù)的。不同的key會設(shè)定不同的過期時間1s 5s 2s
等等。按照八股文我們知道key
過期的時候,有兩種刪除策略:
- 惰性刪除:不主動刪除過期key,當(dāng)訪問該key的時候,如果發(fā)現(xiàn)過期了再刪除 好處:對CPU友好,不用頻繁執(zhí)行刪除,但是對內(nèi)存不友好,都過期了還占用內(nèi)存
- 定時刪除:主動刪除key,到了key的過期時間,立即執(zhí)行刪除 好處:對內(nèi)存友好,可以緩解內(nèi)存壓力,對CPU不友好,需要頻繁的執(zhí)行刪除
所以redis就把兩種策略都實現(xiàn)了,我們看下代碼如何使下?
惰性刪除
本質(zhì)就是訪問的時候判斷下key是否過期,過期就刪除并返回空。 代碼路徑engine/database.go
在獲取key的值時候,我們會執(zhí)行一次 db.IsExpire(key)
判斷key是否過期
func (db *DB) GetEntity(key string) (*payload.DataEntity, bool) { // key 不存在 val, exist := db.dataDict.Get(key) if !exist { returnnil, false } // key是否過期(主動檢測一次) if db.IsExpire(key) { returnnil, false } // 返回內(nèi)存數(shù)據(jù) dataEntity, ok := val.(*payload.DataEntity) if !ok { returnnil, false } return dataEntity, true }
就是從過期字典ttlDict
中獲取key的過期時間
如果沒有獲取到,說明沒有設(shè)定過期時間(do nothing)
如果有過期時間,并且時間已經(jīng)過期,主動刪除之
// 判斷key是否已過期 func (db *DB) IsExpire(key string) bool { val, result := db.ttlDict.Get(key) if !result { returnfalse } expireTime, _ := val.(time.Time) isExpire := time.Now().After(expireTime) if isExpire { // 如果過期,主動刪除 db.Remove(key) } return isExpire }
定時刪除
本質(zhì)是對key設(shè)定一個過期時間,時間一到立即執(zhí)行刪除的任務(wù)。 正常的思路肯定是設(shè)定一個固定的定時器,例如3s
檢測一次,這種思路可以,但是存在一個問題,
如果key的過期時間為
1s
,那你3s
才檢測是否太不夠及時了?那就把檢測間隔設(shè)定為
1s
吧,那如果key
的過期時間都為3s
,到執(zhí)行時間檢測一遍發(fā)現(xiàn)任務(wù)都沒過期,那不就白白浪費CPU時間了嗎?
這就要推出我們的時間輪算法了,時間輪算法就是在模擬現(xiàn)實世界鐘表的原理
我想里面增加2個3s的任務(wù),那就將任務(wù)添加到距離當(dāng)前位置
pos + 3
的位置同時再加1個5s的任務(wù),那就將任務(wù)添加到距離當(dāng)前位置
pos + 5
的位置
當(dāng)鐘表的指針指向pos + 3
的位置,就執(zhí)行任務(wù)鏈表的任務(wù)即可。 因為鐘表是循環(huán)往復(fù)的運行,那如果我再添加11s的任務(wù),可以發(fā)現(xiàn)該任務(wù)也是放置到 pos+3
的位置,那任務(wù)就要區(qū)分下,到底是3s的任務(wù)還是11s的任務(wù)
所以里面又有了一個circle
的標(biāo)記,表示當(dāng)前任務(wù)是第幾圈的任務(wù)
代碼路徑tool/timewheel
代碼中通過切片模型環(huán),通過鏈表模擬任務(wù)鏈表
// 循環(huán)隊列 + 鏈表 type TimeWheel struct { // 間隔 interval time.Duration // 定時器 ticker *time.Ticker // 游標(biāo) curSlotPos int // 循環(huán)隊列大小 slotNum int // 底層存儲 slots []*list.List m map[string]*taskPos // 任務(wù)通道 addChannel chan *task cacelChannel chanstring // 停止 stopChannel chanstruct{} }
當(dāng)添加任務(wù)的時候,需要通過延遲時間計算當(dāng)前任務(wù)的圈數(shù)circle
func (tw *TimeWheel) posAndCircle(d time.Duration) (pos, circle int) { // 延遲(秒) delaySecond := int(d.Seconds()) // 間隔(秒) intervalSecond := int(tw.interval.Seconds()) // delaySecond/intervalSecond 表示從curSlotPos位置偏移 pos = (tw.curSlotPos + delaySecond/intervalSecond) % tw.slotNum circle = (delaySecond / intervalSecond) / tw.slotNum return } func (tw *TimeWheel) addTask(t *task) { // 定位任務(wù)應(yīng)該保存在循環(huán)隊列的位置 & 圈數(shù) pos, circle := tw.posAndCircle(t.delay) t.circle = circle // 將任務(wù)保存到循環(huán)隊列pos位置 ele := tw.slots[pos].PushBack(t) // 在map中記錄 key -> { pos, ele } 的映射 if t.key != "" { // 已經(jīng)存在重復(fù)的key if _, ok := tw.m[t.key]; ok { tw.cancelTask(t.key) } tw.m[t.key] = &taskPos{pos: pos, ele: ele} } }
代碼中注釋的很清晰,也就100多行建議看代碼結(jié)合上圖體會下(很簡單)
額外補充
我們在執(zhí)行set key value ex 3
的時候,先設(shè)定過期時間為3s,但是在1s的時候,我們又執(zhí)行了set key value
,請問key還會過期嗎??
答案:不會過期了。相當(dāng)于對key去掉了過期時間。所以在代碼處理中,我們需要考慮這種情況,重復(fù)設(shè)定的問題
代碼細(xì)節(jié)位于engine/string.go
set命令處理函數(shù)func cmdSet(db *DB, args [][]byte) protocal.Reply
的尾部位置
項目代碼地址: https://github.com/gofish2020/easyredis
以上就是Golang實現(xiàn)Redis過期時間實例探究的詳細(xì)內(nèi)容,更多關(guān)于Golang Redis過期時間的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
Go語言學(xué)習(xí)教程之goroutine和通道的示例詳解
這篇文章主要通過A?Tour?of?Go中的例子進行學(xué)習(xí),以此了解Go語言中的goroutine和通道,文中的示例代碼講解詳細(xì),感興趣的可以了解一下2022-09-09深入了解Golang網(wǎng)絡(luò)編程Net包的使用
net包主要是增加?context?控制,封裝了一些不同的連接類型以及DNS?查找等等,同時在有需要的地方引入?goroutine?提高處理效率。本文主要和大家分享下在Go中網(wǎng)絡(luò)編程的實現(xiàn),需要的可以參考一下2022-07-07