golang帶有key過期的執行緒安全的map->expiredMap
阿新 • • 發佈:2018-12-26
github地址:https://github.com/hackssssss/ExpiredMap
偶然間看到一篇關於超期刪除key的map的文章,感覺很有意思,於是自己去實現了一下。
瞭解了一下redis中key的過期策略,發現主要有三種策略:一種是被動刪除,即key過期之後,先不將其刪除,當實際訪問到的時候,判斷其是否過期,再採取相應的措施。第二種是主動刪除,即當key過期之後,立即將這個key刪除掉。第三種是當記憶體超過某個限制,觸發刪除過期key的策略。
被動刪除即惰性刪除,在redis中資料量比較小時很好用,不需要實時去刪除key,只要在訪問時判定就可以,節省cpu效能,缺點是不能及時刪除過期key,記憶體用的稍多一些,但是資料量小時還是能接受的。
主動刪除需要後臺守護程序不斷去刪除過期的key,稍微耗費一些cpu資源,但是在資料量很大時,記憶體能很快降下來供其他資料的儲存。
這裡我採用主動刪除的方式,即後臺建立一個goroutine來不斷檢測是否有過期的key,檢測到了就刪除這個key,目前的過期單位能精確到秒。
package expired_map import ( "fmt" "sync" "time" ) type val struct { data interface{} expiredTime int64 } type ExpiredMap struct { m map[interface{}]*val timeMap map[int64][]interface{} lck *sync.Mutex stop chan bool } func NewExpiredMap() (*ExpiredMap) { e := ExpiredMap{ m : make(map[interface{}]*val), lck : new(sync.Mutex), timeMap: make(map[int64][]interface{}), stop : make(chan bool), } go e.run() return &e } //background goroutine 主動刪除過期的key //因為刪除需要花費時間,這裡啟動goroutine來刪除,但是啟動goroutine和now++也需要少量時間, //導致資料實際刪除時間比應該刪除的時間稍晚一些,這個誤差我們應該能接受。 func (e *ExpiredMap) run() { now := time.Now().Unix() t := time.NewTicker(time.Second) del := make(chan []interface{},10) go func() { for v:=range del{ e.MultiDelete(v) //todo 應該是list } }() for { select { case <- t.C: now++ //這裡用now++的形式,直接用time.Now().Unix()可能會導致時間跳過,導致key未刪除。 //fmt.Println("now: ", now, "realNow", time.Now().Unix()) if keys, found := e.timeMap[now]; found { //todo delete timeMap del <- keys } } select { //不放在同一個select中,防止同時收到兩個訊號後隨機選擇導致沒有return case <- e.stop: fmt.Println("=== STOP ===") return default: } } } func (e *ExpiredMap) Set(key, value interface{}) { e.lck.Lock() defer e.lck.Unlock() e.m[key] = &val{ data: value, expiredTime: -1, } } func (e *ExpiredMap) SetWithExpired(key, value interface{}, expiredSeconds int64){ if expiredSeconds <= 0 { return } e.lck.Lock() defer e.lck.Unlock() expiredTime := time.Now().Unix() + expiredSeconds e.m[key] = &val{ data: value, expiredTime: expiredTime, } //e.timeMapLck.Lock() //defer e.timeMapLck.Unlock() if keys, found := e.timeMap[expiredTime]; found { keys = append(keys, key) e.timeMap[expiredTime] = keys } else { keys = append(keys, key) e.timeMap[expiredTime] = keys } } // func (e *ExpiredMap) Get(key interface{}) (interface{}){ e.lck.Lock() defer e.lck.Unlock() if value, found := e.m[key]; found { return value.data } return nil } func (e *ExpiredMap) Delete(key interface{}) { e.lck.Lock() defer e.lck.Unlock() delete(e.m, key) } func (e *ExpiredMap) MultiDelete(keys []interface{}) { e.lck.Lock() defer e.lck.Unlock() var t int64 for _, key := range keys { if v, found := e.m[key]; found { t = v.expiredTime delete(e.m, key) } } delete(e.timeMap,t) } func (e *ExpiredMap) Length() int { e.lck.Lock() defer e.lck.Unlock() return len(e.m) } func (e *ExpiredMap) Size() int { return e.Length() } //返回key的剩餘生存時間 key不存在返回-2,key沒有設定生存時間返回-1 func (e *ExpiredMap) TTL (key interface{}) int64 { e.lck.Lock() defer e.lck.Unlock() if value, found := e.m[key]; found { if value.expiredTime == -1 { return -1 } now := time.Now().Unix() if value.expiredTime - now < 0 { go e.Delete(key) return -2 } return value.expiredTime - now } else { return -2 } } func (e *ExpiredMap) Clear() { e.lck.Lock() defer e.lck.Unlock() e.m = make(map[interface{}]*val) e.timeMap = make(map[int64][]interface{}) } func (e *ExpiredMap) Close () { e.lck.Lock() defer e.lck.Unlock() e.m = nil e.timeMap = nil e.stop <- true } func (e *ExpiredMap) Stop () { e.Close() } func (e *ExpiredMap) DoForEach(handler func (interface{}, interface{})) { e.lck.Lock() defer e.lck.Unlock() for k,v := range e.m { handler(k, v) } } func (e *ExpiredMap) DoForEachWithBreak (handler func (interface{}, interface{}) bool) { e.lck.Lock() defer e.lck.Unlock() for k,v := range e.m { if handler(k, v) { break } } }