From 446e80bd27d6ae24eddb4f46bb7986fa1e14d4c8 Mon Sep 17 00:00:00 2001 From: John Date: Tue, 27 Mar 2018 17:53:46 +0800 Subject: [PATCH] gcache lru --- g/os/gcache/gcache.go | 76 +++++++++++++++++++++++++++++++----- g/os/gcache/gcache_lru.go | 82 +++++++++++++++++++++++++++++++++++++++ geg/os/gcache.go | 7 +++- geg/other/test.go | 13 +++---- 4 files changed, 160 insertions(+), 18 deletions(-) create mode 100644 g/os/gcache/gcache_lru.go diff --git a/g/os/gcache/gcache.go b/g/os/gcache/gcache.go index 8faab1bd3..e6aeaa871 100644 --- a/g/os/gcache/gcache.go +++ b/g/os/gcache/gcache.go @@ -14,6 +14,8 @@ import ( "gitee.com/johng/gf/g/os/gtime" "gitee.com/johng/gf/g/container/gset" "gitee.com/johng/gf/g/container/gqueue" + "gitee.com/johng/gf/g/container/gtype" + "unsafe" ) // 缓存对象 @@ -21,8 +23,11 @@ type Cache struct { dmu sync.RWMutex // data锁 emu sync.RWMutex // ekmap锁 smu sync.RWMutex // eksets锁 + lru *_Lru // LRU缓存限制 + cap *gtype.Int // 缓存大小上限(byte,默认为0表示不做控制) + bytes *gtype.Int // 当前缓存所占内存大小(byte,注意如果缓存存放的是指针,那么所占大小=键名长度+8) data map[string]CacheItem // 缓存数据(所有的缓存数据存放哈希表) - ekmap map[string]int64 // 键名对应的分组过期时间(用于相同键名过期时间快速更新) + ekmap map[string]EkmapItem // 键名对应的分组过期时间及大小(用于相同键名过期时间快速更新) eksets map[int64]*gset.StringSet // 分组过期时间对应的键名列表(用于自动过期快速删除) eventQueue *gqueue.Queue // 异步处理队列 stopEvents chan struct{} // 关闭时间通知 @@ -34,10 +39,17 @@ type CacheItem struct { e int64 // 过期时间 } +// 过期处理数据项 +type EkmapItem struct { + e int64 // 过期时间 + s int // 数据项大小 +} + // 异步队列数据项 type EventItem struct { k string // 键名 e int64 // 过期时间 + s int // 数据项大小 } // 全局缓存管理对象 @@ -46,8 +58,11 @@ var cache *Cache = New() // Cache对象按照缓存键名首字母做了分组 func New() *Cache { c := &Cache { + lru : newLru(), + cap : gtype.NewInt(), + bytes : gtype.NewInt(), data : make(map[string]CacheItem), - ekmap : make(map[string]int64), + ekmap : make(map[string]EkmapItem), eksets : make(map[int64]*gset.StringSet), eventQueue : gqueue.New(), stopEvents : make(chan struct{}, 2), @@ -82,6 +97,27 @@ func BatchRemove(keys []string) { cache.BatchRemove(keys) } +// 获得所有的键名,组成字符串数组返回 +func Keys() []string { + return cache.Keys() +} + +// 获得所有的值,组成数组返回 +func Values() []interface{} { + return cache.Values() +} + +// 获得缓存对象的键值对数量 +func Size() int { + return cache.Size() +} + +// 获得缓存对象的内存占用大小(byte) +func Bytes() int { + return cache.Bytes() +} + + // 计算过期缓存的键名(将毫秒换算成秒的整数毫秒) func (c *Cache) makeExpireKey(expire int64) int64 { return int64(math.Ceil(float64(expire/1000) + 1)*1000) @@ -117,10 +153,11 @@ func (c *Cache) Set(key string, value interface{}, expire int64) { if expire > 0 { e = gtime.Millisecond() + int64(expire) } + item := CacheItem{v : value, e : e} c.dmu.Lock() - c.data[key] = CacheItem{v: value, e: e} + c.data[key] = item c.dmu.Unlock() - c.eventQueue.PushBack(EventItem{k: key, e:e}) + c.eventQueue.PushBack(EventItem{k : key, e : e, s : int(unsafe.Sizeof(item))}) } // 批量设置 @@ -195,10 +232,16 @@ func (c *Cache) Size() int { return length } +// 获得缓存对象的内存占用大小(byte) +func (c *Cache) Bytes() int { + return c.bytes.Get() +} + // 删除缓存对象 func (c *Cache) Close() { c.stopEvents <- struct{}{} c.eventQueue.Close() + c.lru.Close() } // 数据自动同步循环 @@ -206,22 +249,29 @@ 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() - olde, ok := c.ekmap[item.k]; + ekitem, ok := c.ekmap[item.k]; c.emu.RUnlock() // 是否需要删除旧的过期时间map中对应的键名 - if ok && newe != olde { - if ekset := c.getExpireSet(olde); ekset != nil { - ekset.Remove(item.k) + if ok { + if newe != ekitem.e { + if ekset := c.getExpireSet(ekitem.e); ekset != nil { + ekset.Remove(item.k) + } } + size -= ekitem.s } c.getOrNewExpireSet(newe).Add(item.k) // 重新设置对应键名的过期时间 c.emu.Lock() - c.ekmap[item.k] = newe + c.ekmap[item.k] = EkmapItem{newe, item.s} c.emu.Unlock() + // LRU操作 + c.lru.Push(item.k, item.s) + c.bytes.Add(size) } else { break } @@ -241,15 +291,21 @@ func (c *Cache) autoClearLoop() { 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() - delete(c.ekmap, key) + if ekitem, ok := c.ekmap[key]; ok { + c.bytes.Add(-ekitem.s) + delete(c.ekmap, key) + } c.emu.Unlock() }) } + // 删除异步处理键名set c.smu.Lock() delete(c.eksets, v) c.smu.Unlock() diff --git a/g/os/gcache/gcache_lru.go b/g/os/gcache/gcache_lru.go new file mode 100644 index 000000000..04cf52d59 --- /dev/null +++ b/g/os/gcache/gcache_lru.go @@ -0,0 +1,82 @@ +// Copyright 2018 gf Author(https://gitee.com/johng/gf). All Rights Reserved. +// +// This Source Code Form is subject to the terms of the MIT License. +// If a copy of the MIT was not distributed with this file, +// You can obtain one at https://gitee.com/johng/gf. + +package gcache + +import ( + "fmt" + "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 +type _Lru struct { + list *glist.List + data *gmap.StringInterfaceMap + queue *gqueue.Queue +} + +// 数据项结构 +type _LruItem struct { + key string + size int +} + +func newLru() *_Lru { + lru := &_Lru { + list : glist.New(), + data : gmap.NewStringInterfaceMap(), + queue : gqueue.New(), + } + go lru.StartAutoLoop() + return lru +} + +// 关闭LRU对象 +func (lru *_Lru) Close() { + lru.queue.Close() +} + +// 添加LRU数据项 +func (lru *_Lru) Push(key string, size int) { + lru.queue.PushBack(&_LruItem{key, size}) +} + +// 从链表尾删除LRU数据项,并返回对应数据 +func (lru *_Lru) Pop() *_LruItem { + if v := lru.list.PopBack(); v != nil { + item := v.(*_LruItem) + lru.data.Remove(item.key) + return item + } + return nil +} + +// 从链表头打印LRU链表值 +func (lru *_Lru) Print() { + for _, v := range lru.list.FrontAll() { + fmt.Printf("%s ", v.(*_LruItem).key) + } +} + +// 异步执行协程 +func (lru *_Lru) StartAutoLoop() { + for { + if v := lru.queue.PopFront(); v != nil { + item := v.(*_LruItem) + // 删除对应链表项 + if v := lru.data.Get(item.key); v != nil { + lru.list.Remove(v.(*list.Element)) + } + // 将数据插入到链表头,并生成新的链表项 + lru.data.Set(item.key, lru.list.PushFront(item)) + } else { + break + } + } +} \ No newline at end of file diff --git a/geg/os/gcache.go b/geg/os/gcache.go index 1f3015c0f..1813a5ef8 100644 --- a/geg/os/gcache.go +++ b/geg/os/gcache.go @@ -7,7 +7,12 @@ import ( ) func main() { - gcache.Set("k1", "v1", 1000) + gcache.Set("k1", "v111111111111111111111111111111111111111111", 1000) + //gcache.Set("k2", "v2", 2000) + time.Sleep(time.Second) + fmt.Println(gcache.Bytes()) + + return gcache.Set("k2", "v2", 2000) time.Sleep(500*time.Millisecond) fmt.Println(gcache.Get("k1")) diff --git a/geg/other/test.go b/geg/other/test.go index 78fdb6bb7..a9ec1e78f 100644 --- a/geg/other/test.go +++ b/geg/other/test.go @@ -1,14 +1,13 @@ package main +import ( + "fmt" + "unsafe" +) func main() { - defer func() { - recover() - }() - events1 := make(chan int, 100) - events1 <- 1 - close(events1) - events1 <- 2 + fmt.Println(unsafe.Sizeof("11111111111111111111111111111111")) + fmt.Println(unsafe.Sizeof("1")) //events2 := make(chan int, 100) //go func() { // for{