改进gcache设计

This commit is contained in:
John
2018-02-12 15:33:19 +08:00
parent 9333beaf26
commit ec4c23bb03
4 changed files with 161 additions and 208 deletions

View File

@ -4,33 +4,25 @@
// If a copy of the MIT was not distributed with this file,
// You can obtain one at https://gitee.com/johng/gf.
// 单进程缓存.
// @todo 需要新增一个MAP用于时间与过期键值对的快速处理。
// 单进程高速缓存.
package gcache
import (
"sync"
"time"
"math"
"sync/atomic"
"gitee.com/johng/gf/g/os/gtime"
"gitee.com/johng/gf/g/encoding/ghash"
)
const (
gDEFAULT_CACHE_GROUP_SIZE = 4 // 默认缓存分区大小不能超过uint8的最大值
"gitee.com/johng/gf/g/container/gmap"
"gitee.com/johng/gf/g/container/gset"
)
// 缓存对象
type Cache struct {
sync.RWMutex
g uint8 // 分区大小
m map[uint8]*CacheMap // 以分区大小数字作为索引
}
// 缓存分区对象
type CacheMap struct {
sync.RWMutex
closed bool // 对象是否已删除以便判断停止goroutine
m map[string]CacheItem // 键值对
mu sync.RWMutex
data *gmap.StringInterfaceMap // 存放真实的键值对数据
eksets *gmap.IntInterfaceMap // 存放过期的键名数据
closed int32 // 缓存对象是否关闭
}
// 缓存数据项
@ -40,208 +32,156 @@ type CacheItem struct {
}
// 全局缓存管理对象
var cache *Cache = New(gDEFAULT_CACHE_GROUP_SIZE)
var cache *Cache = New()
// Cache对象按照缓存键名首字母做了分组
func New(group uint8) *Cache {
func New() *Cache {
c := &Cache {
g : group,
m : make(map[uint8]*CacheMap),
}
// 初始化分区对象
var i uint8 = 0
for ; i < group; i++ {
m := &CacheMap {
m : make(map[string]CacheItem),
}
c.m[i] = m
go m.autoClearLoop()
data : gmap.NewStringInterfaceMap(),
eksets : gmap.NewIntInterfaceMap(),
}
c.autoClearLoop()
return c
}
// (使用全局KV缓存对象)设置kv缓存键值对过期时间单位为毫秒
func Set(k string, v interface{}, expired int64) {
cache.Set(k, v, expired)
func Set(key string, value interface{}, expired int64) {
cache.Set(key, value, expired)
}
// (使用全局KV缓存对象)批量设置kv缓存键值对过期时间单位为毫秒
func BatchSet(m map[string]interface{}, expired int64) {
cache.BatchSet(m, expired)
func BatchSet(data map[string]interface{}, expire int64) {
cache.BatchSet(data, expire)
}
// (使用全局KV缓存对象)获取指定键名的值
func Get(k string) interface{} {
return cache.Get(k)
func Get(key string) interface{} {
return cache.Get(key)
}
// (使用全局KV缓存对象)删除指定键值对
func Remove(k string) {
cache.Remove(k)
func Remove(key string) {
cache.Remove(key)
}
// (使用全局KV缓存对象)批量删除指定键值对
func BatchRemove(l []string) {
cache.BatchRemove(l)
func BatchRemove(keys []string) {
cache.BatchRemove(keys)
}
// 设置kv缓存键值对过期时间单位为毫秒
func (c *Cache) Set(k string, v interface{}, expired int64) {
c.RLock()
c.m[c.getIndex(k)].Set(k, v, expired)
c.RUnlock()
// 计算过期缓存的键名
func (c *Cache) getExpireKey(expire int64) int {
return int(math.Ceil(float64(expire/1000)) + 1)*1000
}
// 获取或者创建一个过期键名存放Set
func (c *Cache) getOrNewExpireSet(ek int) *gset.StringSet {
c.mu.RLock()
defer c.mu.RUnlock()
ekset := c.eksets.Get(ek)
if ekset == nil {
s := gset.NewStringSet()
c.eksets.Set(ek, s)
return s
} else {
return ekset.(*gset.StringSet)
}
}
// 设置kv缓存键值对过期时间单位为毫秒expire<=0表示不过期
func (c *Cache) Set(key string, value interface{}, expire int64) {
// 查找老的键值过期时间
olde := int64(0)
item := c.data.Get(key)
if item != nil {
olde = item.(CacheItem).e
}
// 保存新的键值对
newe := int64(math.MaxInt64)
if expire > 0 {
newe = gtime.Millisecond() + int64(expire)
}
// 删除旧的过期键名
if olde > 0 && newe != olde {
c.eksets.Get(c.getExpireKey(olde)).(*gset.StringSet).Remove(key)
}
// 保存新的过期键名
c.getOrNewExpireSet(c.getExpireKey(newe)).Add(key)
// 最后才真实保存数据
c.data.Set(key, CacheItem{v: value, e: newe})
}
// 批量设置
func (c *Cache) BatchSet(m map[string]interface{}, expired int64) {
c.RLock()
for k, v := range m {
c.m[c.getIndex(k)].Set(k, v, expired)
func (c *Cache) BatchSet(data map[string]interface{}, expire int64) {
l := make([]string, len(data))
m := make(map[string]interface{})
newe := int64(math.MaxInt64)
if expire > 0 {
newe = gtime.Millisecond() + int64(expire)
}
c.RUnlock()
for k, v := range m {
m[k] = CacheItem{v: v, e: newe}
l[len(l) - 1] = k
}
c.getOrNewExpireSet(c.getExpireKey(newe)).BatchAdd(l)
c.data.BatchSet(m)
}
// 获取指定键名的值
func (c *Cache) Get(k string) interface{} {
c.RLock()
r := c.m[c.getIndex(k)].Get(k)
c.RUnlock()
return r
func (c *Cache) Get(key string) interface{} {
r := c.data.Get(key)
if r != nil {
item := r.(CacheItem)
if item.e > gtime.Millisecond() {
return item.v
}
}
return nil
}
// 删除指定键值对
func (c *Cache) Remove(k string) {
c.RLock()
c.m[c.getIndex(k)].Remove(k)
c.RUnlock()
func (c *Cache) Remove(key string) {
c.data.Remove(key)
}
// 批量删除键值对
func (c *Cache) BatchRemove(l []string) {
c.RLock()
for _, k := range l {
c.m[c.getIndex(k)].Remove(k)
}
c.RUnlock()
func (c *Cache) BatchRemove(keys []string) {
c.data.BatchRemove(keys)
}
// 获得所有的键名,组成字符串数组返回
func (c *Cache) Keys() []string {
l := make([]string, 0)
c.RLock()
for _, cm := range c.m {
cm.RLock()
for k2, _ := range cm.m {
l = append(l, k2)
}
cm.RUnlock()
}
c.RUnlock()
return l
return c.data.Keys()
}
// 获得所有的值,组成数组返回
func (c *Cache) Values() []interface{} {
l := make([]interface{}, 0)
c.RLock()
for _, cm := range c.m {
cm.RLock()
for _, v2 := range cm.m {
l = append(l, v2.v)
}
cm.RUnlock()
}
c.RUnlock()
return l
return c.data.Values()
}
// 获得缓存对象的键值对数量
func (c *Cache) Size() int {
var size int
c.RLock()
for _, cm := range c.m {
cm.RLock()
size += len(cm.m)
cm.RUnlock()
}
c.RUnlock()
return size
return c.data.Size()
}
// 删除缓存对象
func (c *Cache) Close() {
c.RLock()
for _, cm := range c.m {
cm.Close()
}
c.RUnlock()
atomic.AddInt32(&c.closed, 1)
}
// 计算缓存的索引
func (c *Cache) getIndex(k string) uint8 {
return uint8(ghash.BKDRHash([]byte(k)) % uint32(c.g))
}
// 设置kv缓存键值对过期时间单位为毫秒
func (cm *CacheMap) Set(k string, v interface{}, expired int64) {
var e int64
if expired > 0 {
e = gtime.Millisecond() + int64(expired)
}
cm.Lock()
cm.m[k] = CacheItem{v: v, e: e}
cm.Unlock()
}
// 获取指定键名的值
func (cm *CacheMap) Get(k string) interface{} {
var v interface{}
cm.RLock()
if r, ok := cm.m[k]; ok {
if r.e > 0 && r.e < gtime.Millisecond() {
v = nil
} else {
v = r.v
}
}
cm.RUnlock()
return v
}
// 删除指定键值对
func (cm *CacheMap) Remove(k string) {
cm.Lock()
delete(cm.m, k)
cm.Unlock()
}
// 关闭缓存分区
func (cm *CacheMap) Close() {
cm.Lock()
cm.closed = true
cm.Unlock()
}
// 是否删除
func (cm *CacheMap) isClosed() bool {
cm.RLock()
r := cm.closed
cm.RUnlock()
return r
}
// 自动清理过期键值对(每间隔3秒执行)
func (cm *CacheMap) autoClearLoop() {
for !cm.isClosed() {
cm.Lock()
for k, v := range cm.m {
if v.e > 0 && v.e < gtime.Millisecond() {
delete(cm.m, k)
// 自动清理过期键值对
// 每隔1秒清除过去3秒的键值对数据
func (c *Cache) autoClearLoop() {
for atomic.LoadInt32(&c.closed) == 0 {
ek := c.getExpireKey(gtime.Millisecond())
eks := []int{ek - 2000, ek - 3000, ek - 4000}
for _, v := range eks {
if r := c.eksets.Get(v); r != nil {
c.data.BatchRemove(r.(*gset.StringSet).Slice())
}
}
cm.Unlock()
time.Sleep(3 * time.Second)
c.eksets.BatchRemove(eks)
time.Sleep(time.Second)
}
}

View File

@ -0,0 +1,34 @@
// Copyright 2017 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.
// go test *.go -bench=".*"
package gcache_test
import (
"testing"
"strconv"
"gitee.com/johng/gf/g/os/gcache"
)
func BenchmarkSet(b *testing.B) {
for i := 0; i < b.N; i++ {
gcache.Set(strconv.Itoa(i), strconv.Itoa(i), 0)
}
}
func BenchmarkGet(b *testing.B) {
for i := 0; i < b.N; i++ {
gcache.Get(strconv.Itoa(i))
}
}
func BenchmarkRemove(b *testing.B) {
for i := 0; i < b.N; i++ {
gcache.Remove(strconv.Itoa(i))
}
}

22
geg/os/gcache.go Normal file
View File

@ -0,0 +1,22 @@
package main
import (
"gitee.com/johng/gf/g/os/gcache"
"time"
"fmt"
)
func main() {
gcache.Set("k1", "v1", 1000)
gcache.Set("k2", "v2", 2000)
time.Sleep(500*time.Millisecond)
fmt.Println(gcache.Get("k1"))
fmt.Println(gcache.Get("k2"))
time.Sleep(400*time.Millisecond)
fmt.Println(gcache.Get("k1"))
fmt.Println(gcache.Get("k2"))
time.Sleep(3000*time.Millisecond)
fmt.Println(gcache.Get("k1"))
fmt.Println(gcache.Get("k2"))
time.Sleep(3000*time.Millisecond)
}

View File

@ -1,43 +0,0 @@
package main
import (
"testing"
"gitee.com/johng/gf/g/os/gcache"
)
var cache *gcache.Cache = gcache.New()
func BenchmarkSet(b *testing.B) {
b.N = 1000000
for i := 0; i < 1000000; i ++ {
cache.Set(string(i), i, 0)
}
}
func BenchmarkSetWithExpire(b *testing.B) {
b.N = 1000000
for i := 0; i < 1000000; i ++ {
cache.Set(string(i), i, 60)
}
}
func BenchmarkGet1(b *testing.B) {
b.N = 1000000
for i := 0; i < 1000000; i ++ {
cache.Get(string(i))
}
}
func BenchmarkGet2(b *testing.B) {
b.N = 1000000
for i := 0; i < 1000000; i ++ {
cache.Get(string(i))
}
}
func BenchmarkRemove(b *testing.B) {
b.N = 1000000
for i := 0; i < 1000000; i ++ {
cache.Remove(string(i))
}
}