1. 程式人生 > >golang帶有key過期的執行緒安全的map->expiredMap

golang帶有key過期的執行緒安全的map->expiredMap

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
		}
	}
}