diff --git a/g/os/gcache/gcache.go b/g/os/gcache/gcache.go index e6aeaa871..88442ecb6 100644 --- a/g/os/gcache/gcache.go +++ b/g/os/gcache/gcache.go @@ -15,7 +15,10 @@ import ( "gitee.com/johng/gf/g/container/gset" "gitee.com/johng/gf/g/container/gqueue" "gitee.com/johng/gf/g/container/gtype" - "unsafe" +) + +const ( + gDEFAULT_MAX_EXPIRE = 9223372036854 // 当数据不过期时,默认设置的过期属性值,相当于:math.MaxInt64/1000000 ) // 缓存对象 @@ -24,10 +27,9 @@ type Cache struct { emu sync.RWMutex // ekmap锁 smu sync.RWMutex // eksets锁 lru *_Lru // LRU缓存限制 - cap *gtype.Int // 缓存大小上限(byte,默认为0表示不做控制) - bytes *gtype.Int // 当前缓存所占内存大小(byte,注意如果缓存存放的是指针,那么所占大小=键名长度+8) + cap *gtype.Int // 控制缓存池大小,超过大小则按照LRU算法进行缓存过期处理(默认为0表示不进行限制) data map[string]CacheItem // 缓存数据(所有的缓存数据存放哈希表) - ekmap map[string]EkmapItem // 键名对应的分组过期时间及大小(用于相同键名过期时间快速更新) + ekmap map[string]int64 // 键名对应的分组过期时间(用于相同键名过期时间快速更新) eksets map[int64]*gset.StringSet // 分组过期时间对应的键名列表(用于自动过期快速删除) eventQueue *gqueue.Queue // 异步处理队列 stopEvents chan struct{} // 关闭时间通知 @@ -39,17 +41,10 @@ type CacheItem struct { e int64 // 过期时间 } -// 过期处理数据项 -type EkmapItem struct { - e int64 // 过期时间 - s int // 数据项大小 -} - // 异步队列数据项 type EventItem struct { k string // 键名 e int64 // 过期时间 - s int // 数据项大小 } // 全局缓存管理对象 @@ -60,9 +55,8 @@ func New() *Cache { c := &Cache { lru : newLru(), cap : gtype.NewInt(), - bytes : gtype.NewInt(), data : make(map[string]CacheItem), - ekmap : make(map[string]EkmapItem), + ekmap : make(map[string]int64), eksets : make(map[int64]*gset.StringSet), eventQueue : gqueue.New(), stopEvents : make(chan struct{}, 2), @@ -72,6 +66,11 @@ func New() *Cache { return c } +// 设置缓存池大小,内部依靠LRU算法进行缓存淘汰处理 +func SetCap(cap int) { + cache.cap.Set(cap) +} + // (使用全局KV缓存对象)设置kv缓存键值对,过期时间单位为毫秒 func Set(key string, value interface{}, expire int64) { cache.Set(key, value, expire) @@ -112,12 +111,11 @@ func Size() int { return cache.Size() } -// 获得缓存对象的内存占用大小(byte) -func Bytes() int { - return cache.Bytes() +// 设置缓存池大小,内部依靠LRU算法进行缓存淘汰处理 +func (c *Cache) SetCap(cap int) { + c.cap.Set(cap) } - // 计算过期缓存的键名(将毫秒换算成秒的整数毫秒) func (c *Cache) makeExpireKey(expire int64) int64 { return int64(math.Ceil(float64(expire/1000) + 1)*1000) @@ -149,22 +147,25 @@ func (c *Cache) getOrNewExpireSet(expire int64) *gset.StringSet { // 设置kv缓存键值对,过期时间单位为毫秒,expire<=0表示不过期 func (c *Cache) Set(key string, value interface{}, expire int64) { - e := int64(math.MaxInt64) - if expire > 0 { + var e int64 + if expire != 0 { e = gtime.Millisecond() + int64(expire) + } else { + e = gDEFAULT_MAX_EXPIRE } - item := CacheItem{v : value, e : e} c.dmu.Lock() - c.data[key] = item + c.data[key] = CacheItem{v : value, e : e} c.dmu.Unlock() - c.eventQueue.PushBack(EventItem{k : key, e : e, s : int(unsafe.Sizeof(item))}) + c.eventQueue.PushBack(EventItem{k : key, e : e}) } // 批量设置 func (c *Cache) BatchSet(data map[string]interface{}, expire int64) { - e := int64(math.MaxInt64) - if expire > 0 { + var e int64 + if expire != 0 { e = gtime.Millisecond() + int64(expire) + } else { + e = gDEFAULT_MAX_EXPIRE } for k, v := range data { c.dmu.Lock() @@ -189,16 +190,16 @@ func (c *Cache) Get(key string) interface{} { // 删除指定键值对 func (c *Cache) Remove(key string) { - c.Set(key, nil, -1) + c.Set(key, nil, -1000) } // 批量删除键值对 func (c *Cache) BatchRemove(keys []string) { for _, key := range keys { c.dmu.Lock() - c.data[key] = CacheItem{v: nil, e: -1} + c.data[key] = CacheItem{v: nil, e: -1000} c.dmu.Unlock() - c.eventQueue.PushBack(EventItem{k: key, e: -1}) + c.eventQueue.PushBack(EventItem{k: key, e: -1000}) } } @@ -232,11 +233,6 @@ func (c *Cache) Size() int { return length } -// 获得缓存对象的内存占用大小(byte) -func (c *Cache) Bytes() int { - return c.bytes.Get() -} - // 删除缓存对象 func (c *Cache) Close() { c.stopEvents <- struct{}{} @@ -249,36 +245,33 @@ func (c *Cache) autoSyncLoop() { for { if r := c.eventQueue.PopFront(); r != nil { item := r.(EventItem) - size := item.s newe := c.makeExpireKey(item.e) // 查询键名是否已经存在过期时间 c.emu.RLock() - ekitem, ok := c.ekmap[item.k]; + olde, ok := c.ekmap[item.k]; c.emu.RUnlock() // 是否需要删除旧的过期时间map中对应的键名 - if ok { - if newe != ekitem.e { - if ekset := c.getExpireSet(ekitem.e); ekset != nil { - ekset.Remove(item.k) - } + if ok && newe != olde { + if ekset := c.getExpireSet(olde); ekset != nil { + ekset.Remove(item.k) } - size -= ekitem.s } c.getOrNewExpireSet(newe).Add(item.k) // 重新设置对应键名的过期时间 c.emu.Lock() - c.ekmap[item.k] = EkmapItem{newe, item.s} + c.ekmap[item.k] = newe c.emu.Unlock() - // LRU操作 - c.lru.Push(item.k, item.s) - c.bytes.Add(size) + // LRU操作记录(只有新增和修改操作才会记录到LRU管理对象中,删除不会) + if newe >= olde { + c.lru.Push(item.k) + } } else { break } } } -// 自动清理过期键值对 +// LRU缓存淘汰处理+自动清理过期键值对 // 每隔1秒清除过去3秒的键值对数据 func (c *Cache) autoClearLoop() { for { @@ -286,31 +279,43 @@ func (c *Cache) autoClearLoop() { case <- c.stopEvents: return default: + // 缓存过期处理 ek := c.makeExpireKey(gtime.Millisecond()) eks := []int64{ek - 2000, ek - 3000, ek - 4000} for _, v := range eks { if ekset := c.getExpireSet(v); ekset != nil { - ekset.Iterator(func(key string) { - // 删除缓存数据 - c.dmu.Lock() - delete(c.data, key) - c.dmu.Unlock() - - // 删除异步处理数据项,并更新缓存的内存使用大小记录值 - c.emu.Lock() - if ekitem, ok := c.ekmap[key]; ok { - c.bytes.Add(-ekitem.s) - delete(c.ekmap, key) - } - c.emu.Unlock() - }) + ekset.Iterator(c.clearByKey) } // 删除异步处理键名set c.smu.Lock() delete(c.eksets, v) c.smu.Unlock() } + // LRU缓存淘汰处理 + if c.cap.Get() > 0 { + for i := c.Size() - c.cap.Get(); i > 0; i-- { + if s := c.lru.Pop(); s != "" { + c.clearByKey(s) + } + } + } time.Sleep(time.Second) } } } + +// 删除对应键名的缓存数据 +func (c *Cache) clearByKey(key string) { + // 删除缓存数据 + c.dmu.Lock() + delete(c.data, key) + c.dmu.Unlock() + + // 删除异步处理数据项,并更新缓存的内存使用大小记录值 + c.emu.Lock() + delete(c.ekmap, key) + c.emu.Unlock() + + // 删除LRU管理对象中指定键名 + c.lru.Remove(key) +} diff --git a/g/os/gcache/gcache_lru.go b/g/os/gcache/gcache_lru.go index 04cf52d59..dd3883e4e 100644 --- a/g/os/gcache/gcache_lru.go +++ b/g/os/gcache/gcache_lru.go @@ -8,10 +8,11 @@ package gcache import ( "fmt" + "container/list" "gitee.com/johng/gf/g/container/glist" "gitee.com/johng/gf/g/container/gqueue" "gitee.com/johng/gf/g/container/gmap" - "container/list" + ) // LRU算法实现对象,底层双向链表使用了标准库的list.List @@ -21,12 +22,6 @@ type _Lru struct { queue *gqueue.Queue } -// 数据项结构 -type _LruItem struct { - key string - size int -} - func newLru() *_Lru { lru := &_Lru { list : glist.New(), @@ -42,25 +37,32 @@ func (lru *_Lru) Close() { lru.queue.Close() } +// 删除指定数据项 +func (lru *_Lru) Remove(key string) { + if v := lru.data.Get(key); v != nil { + lru.list.Remove(v.(*list.Element)) + } +} + // 添加LRU数据项 -func (lru *_Lru) Push(key string, size int) { - lru.queue.PushBack(&_LruItem{key, size}) +func (lru *_Lru) Push(key string) { + lru.queue.PushBack(key) } // 从链表尾删除LRU数据项,并返回对应数据 -func (lru *_Lru) Pop() *_LruItem { +func (lru *_Lru) Pop() string { if v := lru.list.PopBack(); v != nil { - item := v.(*_LruItem) - lru.data.Remove(item.key) - return item + s := v.(string) + lru.data.Remove(s) + return s } - return nil + return "" } // 从链表头打印LRU链表值 func (lru *_Lru) Print() { for _, v := range lru.list.FrontAll() { - fmt.Printf("%s ", v.(*_LruItem).key) + fmt.Printf("%s ", v.(string)) } } @@ -68,13 +70,13 @@ func (lru *_Lru) Print() { func (lru *_Lru) StartAutoLoop() { for { if v := lru.queue.PopFront(); v != nil { - item := v.(*_LruItem) + s := v.(string) // 删除对应链表项 - if v := lru.data.Get(item.key); v != nil { + if v := lru.data.Get(s); v != nil { lru.list.Remove(v.(*list.Element)) } // 将数据插入到链表头,并生成新的链表项 - lru.data.Set(item.key, lru.list.PushFront(item)) + lru.data.Set(s, lru.list.PushFront(s)) } else { break } diff --git a/geg/os/gcache.go b/geg/os/gcache.go index 1813a5ef8..e5c6bc7dc 100644 --- a/geg/os/gcache.go +++ b/geg/os/gcache.go @@ -4,16 +4,26 @@ import ( "gitee.com/johng/gf/g/os/gcache" "time" "fmt" + "strconv" ) func main() { - gcache.Set("k1", "v111111111111111111111111111111111111111111", 1000) - //gcache.Set("k2", "v2", 2000) - time.Sleep(time.Second) - fmt.Println(gcache.Bytes()) + for i := 0; i < 10; i++ { + gcache.Set(strconv.Itoa(i), strconv.Itoa(i), 0) + } + fmt.Println(gcache.Size()) + fmt.Println(gcache.Keys()) + gcache.SetCap(2) + time.Sleep(3*time.Second) + fmt.Println(gcache.Size()) + fmt.Println(gcache.Keys()) return + gcache.Set("k1", "v1", 1000) gcache.Set("k2", "v2", 2000) + fmt.Println(gcache.Keys()) + fmt.Println(gcache.Values()) + fmt.Println(gcache.Size()) time.Sleep(500*time.Millisecond) fmt.Println(gcache.Get("k1")) fmt.Println(gcache.Get("k2")) diff --git a/geg/other/test.go b/geg/other/test.go index a9ec1e78f..e6738c575 100644 --- a/geg/other/test.go +++ b/geg/other/test.go @@ -2,12 +2,13 @@ package main import ( "fmt" - "unsafe" + "gitee.com/johng/gf/g/os/gtime" + "math" ) func main() { - fmt.Println(unsafe.Sizeof("11111111111111111111111111111111")) - fmt.Println(unsafe.Sizeof("1")) + fmt.Println(gtime.Millisecond()) + fmt.Println(math.MaxInt64) //events2 := make(chan int, 100) //go func() { // for{