gcache lru

This commit is contained in:
John 2018-03-27 17:53:46 +08:00
parent 6dfa35c533
commit 9eb01a395a
4 changed files with 160 additions and 18 deletions

View File

@ -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()

82
g/os/gcache/gcache_lru.go Normal file
View File

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

View File

@ -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"))

View File

@ -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{