Golang使用ttl机制保存内存数据方法详解
作者:梦想画家
ttl(time-to-live) 数据存活时间,我们这里指数据在内存中保存一段时间,超过期限则不能被读取到,与Redis的ttl机制类似。本文仅实现ttl部分,不考虑序列化和反序列化。
获取当前时间
涉及时间计算,这里首先介绍如何获取当前时间,以及时间的精度,这里为了简化,精度到秒级。
使用time.Now可以获取当前时间,time.Unix 或 time.UnixNano可以获得时间戳。
now := time.Now() // current local time sec := now.Unix() // number of seconds since January 1, 1970 UTC nsec := now.UnixNano() // number of nanoseconds since January 1, 1970 UTC fmt.Println(now) // time.Time fmt.Println(sec) // int64 fmt.Println(nsec) // int64
输出结果:
2023-02-19 16:52:51.5894329 +0800 CST m=+0.004286801
1676796771
1676796771589432900
数据结构
首先定义数据结构,数据结构及存储数据容器的结构:
type Data struct { Key string Value interface{} Timestamp int64 } type Heap struct { dataMx *sync.RWMutex data map[string]Data }
Data 包括key和value以及ttl时间(单位秒),Heap容器包括map类型data以及RWMutex读写锁,读写锁是支持并发操作。
下面定义Heap结构一些方法。
Heap操作
主要方法包括New,Set,Del,Get三个方法。
func New() *Heap { return &Heap{ dataMx: &sync.RWMutex{}, data: map[string]Data{}, } } func (h *Heap) Set(key string, value interface{}, ttl int64) { if ttl == 0 { return } data := Data{ Key: key, Value: value, Timestamp: time.Now().Unix(), } if ttl > 0 { data.Timestamp += ttl } else if ttl < 0 { data.Timestamp = -1 } h.dataMx.Lock() h.data[key] = data h.dataMx.Unlock() } func (h *Heap) Get(key string) (val interface{}, ok bool) { var data Data h.dataMx.RLock() data, ok = h.data[key] h.dataMx.RUnlock() if ok { if data.Timestamp != -1 && data.Timestamp <= time.Now().Unix() { h.Del(key) ok = false } else { val = data.Value } } return } func (h *Heap) Del(key string) { h.dataMx.RLock() _, ok := h.data[key] h.dataMx.RUnlock() if !ok { return } h.dataMx.Lock() delete(h.data, key) h.dataMx.Unlock() }
New方法无需多解释,我们直接看Set方法。
Set方法实现逻辑:如果ttl为0则直接返回,反之先初始化Data数据,这里初始化当前时间为Data的时间戳;接着判断ttl,如果大于零则Data的时间戳加上ttl,反之为-1;下面开始通过读写锁存储Heap的data。
Del方法,首先通过读锁读取key对应数据,如果失败直接返回(可能已经过期,其他协程已经获取过),反之直接删除数据。
Get方法,读取逻辑与Del一样,如果正确读取,则判断时间戳,不等于-1且小于当前时间则表明已过期,调用Del方法进行删除,返回nil和false;反之返回value及true。
测试ttl容器Heap
首先定义heap,然后调用Set方法,增加数据key,value,ttl为2秒:
func main() { keyTag := "key" heap := New() defer func() { heap.Del(keyTag) }() heap.Set(keyTag, "value", 2) time.Sleep(1 * time.Second) val, flag := heap.Get(keyTag) fmt.Printf("%v, %v\n", val, flag) time.Sleep(1 * time.Second) val, flag = heap.Get(keyTag) fmt.Printf("%v, %v\n", val, flag) }
然后模拟等待1秒后调用Get方法,两次直接结果和预期一致:
value, true
<nil>, false
完整代码
下面给出完整代码:
package main import ( "fmt" "sync" "time" ) type Data struct { Key string Value interface{} Timestamp int64 } type Heap struct { dataMx *sync.RWMutex data map[string]Data } func New() *Heap { return &Heap{ dataMx: &sync.RWMutex{}, data: map[string]Data{}, } } func (h *Heap) Set(key string, value interface{}, ttl int64) { if ttl == 0 { return } data := Data{ Key: key, Value: value, Timestamp: time.Now().Unix(), } if ttl > 0 { data.Timestamp += ttl } else if ttl < 0 { data.Timestamp = -1 } h.dataMx.Lock() h.data[key] = data h.dataMx.Unlock() } func (h *Heap) Get(key string) (val interface{}, ok bool) { var data Data h.dataMx.RLock() data, ok = h.data[key] h.dataMx.RUnlock() if ok { if data.Timestamp != -1 && data.Timestamp <= time.Now().Unix() { h.Del(key) ok = false } else { val = data.Value } } return } func (h *Heap) Del(key string) { h.dataMx.RLock() _, ok := h.data[key] h.dataMx.RUnlock() if !ok { return } h.dataMx.Lock() delete(h.data, key) h.dataMx.Unlock() } func main() { keyTag := "key" heap := New() defer func() { heap.Del(keyTag) }() heap.Set(keyTag, "value", 2) time.Sleep(1 * time.Second) val, flag := heap.Get(keyTag) fmt.Printf("%v, %v\n", val, flag) time.Sleep(1 * time.Second) val, flag = heap.Get(keyTag) fmt.Printf("%v, %v\n", val, flag) }
总结
本文解释Golang如果实现ttl机制在内存存储自动失效数据。首先介绍时间戳原理,然后定义数据结构,并简单实现Set、Get、Del方法实现了ttl机制。未来再增加序列化功能:保存和恢复。参考实现:https://github.com/leprosus/golang-ttl-map。
到此这篇关于Golang使用ttl机制保存内存数据方法详解的文章就介绍到这了,更多相关Go保存内存数据内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!