github地址:https://github.com/hackssssss/ExpiredMap
偶然間看到一篇關於超期刪除key的map的文章,感覺很有意思,於是自己去實現了一下。
瞭解了一下redis中key的過期策略,發現主要有三種策略:一種是被動刪除,即key過期之後,先不將其刪除,當實際訪問到的時候,判斷其是否過期,再採取相應的措施。第二種是主動刪除,即當key過期之後,立即將這個key刪除掉。第三種是當內存超過某個限制,觸發刪除過期key的策略。
被動刪除即惰性刪除,在redis中數據量比較小時很好用,不需要實時去刪除key,只要在訪問時判定就可以,節省cpu性能,缺點是不能及時刪除過期key,內存用的稍多一些,但是數據量小時還是能接受的。
主動刪除需要後臺守護進程不斷去刪除過期的key,稍微耗費一些cpu資源,但是在數據量很大時,內存能很快降下來供其他數據的存儲。
這裏採用主動刪除和被動刪除相結合的方式,後臺會創建一個goroutine來不斷檢測是否有過期的key,檢測到了就刪除這個key,目前的過期單位能精確到秒。
package ExpiredMap
import (
"fmt"
"sync"
"sync/atomic"
"time"
)
type val struct {
data interface{}
expiredTime int64
}
const delChannelCap = 100
type ExpiredMap struct {
m map[interface{}]*val
timeMap map[int64][]interface{}
lck *sync.Mutex
stop chan struct{}
needStop int32
}
func NewExpiredMap() *ExpiredMap {
e := ExpiredMap{
m: make(map[interface{}]*val),
lck: new(sync.Mutex),
timeMap: make(map[int64][]interface{}),
stop: make(chan struct{}),
}
atomic.StoreInt32(&e.needStop, 0)
go e.run(time.Now().Unix())
return &e
}
type delMsg struct {
keys []interface{}
t int64
}
//background goroutine 主動刪除過期的key
//數據實際刪除時間比應該刪除的時間稍晚一些,這個誤差我們應該能接受。
func (e *ExpiredMap) run(now int64) {
t := time.NewTicker(time.Second * 1)
delCh := make(chan *delMsg, delChannelCap)
go func() {
for v := range delCh {
if atomic.LoadInt32(&e.needStop) == 1 {
fmt.Println("---del stop---")
return
}
e.multiDelete(v.keys, v.t)
}
}()
for {
select {
case <-t.C:
now++ //這裏用now++的形式,直接用time.Now().Unix()可能會導致時間跳過1s,導致key未刪除。
if keys, found := e.timeMap[now]; found {
delCh <- &delMsg{keys:keys, t:now}
}
case <-e.stop:
fmt.Println("=== STOP ===")
atomic.StoreInt32(&e.needStop, 1)
delCh <- &delMsg{keys:[]interface{}{}, t:0}
return
}
}
}
func (e *ExpiredMap) Set(key, value interface{}, expireSeconds int64) {
if expireSeconds <= 0 {
return
}
e.lck.Lock()
defer e.lck.Unlock()
expiredTime := time.Now().Unix() + expireSeconds
e.m[key] = &val{
data: value,
expiredTime: expiredTime,
}
e.timeMap[expiredTime] = append(e.timeMap[expiredTime], key) //過期時間作爲key,放在map中
}
func (e *ExpiredMap) Get(key interface{}) (found bool, value interface{}) {
e.lck.Lock()
defer e.lck.Unlock()
if found = e.checkDeleteKey(key); !found {
return
}
value = e.m[key].data
return
}
func (e *ExpiredMap) Delete(key interface{}) {
e.lck.Lock()
delete(e.m, key)
e.lck.Unlock()
}
func (e *ExpiredMap) Remove(key interface{}) {
e.Delete(key)
}
func (e *ExpiredMap) multiDelete(keys []interface{}, t int64) {
e.lck.Lock()
defer e.lck.Unlock()
delete(e.timeMap, t)
for _, key := range keys {
delete(e.m, key)
}
}
func (e *ExpiredMap) Length() int { //結果是不準確的,因爲有未刪除的key
e.lck.Lock()
defer e.lck.Unlock()
return len(e.m)
}
func (e *ExpiredMap) Size() int {
return e.Length()
}
//返回key的剩餘生存時間 key不存在返回負數
func (e *ExpiredMap) TTL(key interface{}) int64 {
e.lck.Lock()
defer e.lck.Unlock()
if !e.checkDeleteKey(key) {
return -1
}
return e.m[key].expiredTime - time.Now().Unix()
}
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() {// todo 關閉後在使用怎麼處理
e.lck.Lock()
defer e.lck.Unlock()
e.stop <- struct{}{}
//e.m = nil
//e.timeMap = nil
}
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 {
if !e.checkDeleteKey(k) {
continue
}
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 !e.checkDeleteKey(k) {
continue
}
if handler(k, v) {
break
}
}
}
func (e *ExpiredMap) checkDeleteKey(key interface{}) bool {
if val, found := e.m[key]; found {
if val.expiredTime <= time.Now().Unix() {
delete(e.m, key)
//delete(e.timeMap, val.expiredTime)
return false
}
return true
}
return false
}