引言
- Cache实际上在项目很常见、常见的redis、memcached等等
- 为了减少代码修改,灵活切换缓存存储源
- 今天开始为期3天交大家,如何打造一款高效扩展性强的的Cache管理器
- 主要用来学习啊、共同进步
- 案例代码地址
1、gocache设计理念
- 全局缓存管理器
- 支持多个缓存源
- interface定义接口,完美实现适配器设计模式
2、定义
- 定义全局存储源
- 定义全局Cache管理器
var Stores = make(map[string]StoreInterface)
type Cache struct {
name string
store StoreInterface
}
- 定义store接口
type StoreInterface interface {
GetStoreName() string
// 获取缓存
Get(key string) (interface{}, error)
// 设置缓存带过期时间
Set(key string, value interface{}, time int) error
// 设置永久缓存无过期时间
Forever(key string, value interface{}) error
// 删除key
Delete(key string) error
// 判断key是否存在
Has(key string) error
// 全部清空
Clear() error
// 获取缓存key的数量
Size() int
// 获取expire
GetTTl(key string) (time.Duration, error)
// 随机删除已过期key
GC()
}
- 定义实例化Cache方法
func New(name string) (*Cache, error) {
name = strings.ToLower(name)
if store, ok := Stores[name]; ok {
go store.GC()
return &Cache{
name: name, //有点多余
store: store,
}, nil
} else {
return nil, fmt.Errorf("Cache:unknown %s store please import", name)
}
}
- 定义注册缓存源的方法 (忽略store名称大小写,都转成小写存储)
func Register(name string, store StoreInterface) {
if store == nil {
log.Panic("Cache: Register store is nil")
}
name = strings.ToLower(name)
if _, ok := Stores[name]; ok {
log.Panic("Cache: Register store is exist")
}
Stores[name] = store
}
- 定义Cache对外方法
// 获取store名称
func (c *Cache) GetStoreName() string {
return c.store.GetStoreName()
}
// 获取键值
func (c *Cache) Get(key string) (interface{}, error) {
return c.store.Get(key)
}
// 设置键值以及过期时间
func (c *Cache) Set(key string, value interface{}, time int) error {
return c.store.Set(key, value, time)
}
// 永久设置键值不过期
func (c *Cache) Forever(key string, value interface{}) error {
return c.store.Forever(key, value)
}
// 删除键值
func (c *Cache) Delete(key string) error {
return c.store.Delete(key)
}
// 判断键是否存在
func (c *Cache) Has(key string) error {
return c.store.Has(key)
}
// 清除所有键值
func (c *Cache) Clear() error {
return c.store.Clear()
}
func (c *Cache) Size() int {
return c.store.Size()
}
func (c *Cache) GetTTl(key string) (time.Duration, error) {
return c.store.GetTTl(key)
}
3、实现一个内存存储源 github地址
-
新建一个store目录,新建memory.go
-
定义 MemoryStore
type MemoryStore struct {
list map[string]*list.Element //真实存储
expireList *list.List //要过期的键(双向链表)
mu sync.RWMutex 读写锁保证并发读写
}
type Memory struct {
key string
value interface{}
expirat time.Time
}
var MemoryName = "Memory" //store的名称
- 实例化store以及初始化注册cache
func init() {
gocache.Register(MemoryName, NewStore())
}
func NewStore() *MemoryStore {
return &MemoryStore{
list: make(map[string]*list.Element),
expireList: list.New(),
}
}
- Memory实现的方法
func (m *Memory) Get() interface{} {
return m.value
}
func (m *Memory) Set(key string, value interface{}, d int) {
m.key = key
m.value = value
m.expirat = time.Now().Add(time.Duration(d) * time.Second)
}
func (m *Memory) Forever(key string, value interface{}) {
m.key = key
m.value = value
}
func (m *Memory) TTL(key string) time.Time {
return m.expirat
}
- store实现的方法
func (ms *MemoryStore) GetStoreName() string {
return MemoryName
}
func (ms *MemoryStore) Get(key string) (interface{}, error) {
defer ms.mu.RUnlock()
ms.mu.RLock()
isExpre, err := ms.IsExpire(key)
if err != nil {
return nil, err
} else {
if isExpre {
ms.expireList.Remove(ms.list[key])
delete(ms.list, key)
return nil, fmt.Errorf("Cache :%s not found", key)
} else {
return ms.list[key].Value.(*Memory).Get(), nil
}
}
}
func (ms *MemoryStore) Set(key string, value interface{}, time int) error {
if value == nil {
return fmt.Errorf("Cache : %s set value not is nil", "Memory")
}
ms.mu.Lock()
defer ms.mu.Unlock()
m := &Memory{}
m.Set(key, value, time)
element := ms.expireList.PushBack(m)
ms.list[key] = element
return nil
}
func (ms *MemoryStore) Delete(key string) error {
defer ms.mu.Unlock()
ms.mu.Lock()
if _, ok := ms.list[key]; !ok {
return fmt.Errorf("Cache :%s value not found", key)
} else {
ms.expireList.Remove(ms.list[key])
delete(ms.list, key)
return nil
}
}
func (ms *MemoryStore) Has(key string) error {
defer ms.mu.RUnlock()
ms.mu.RLock()
if _, ok := ms.list[key]; ok {
return nil
} else {
return fmt.Errorf("Cache :%s value not found", key)
}
}
func (ms *MemoryStore) Forever(key string, value interface{}) error {
defer ms.mu.Unlock()
ms.mu.Lock()
if value == nil {
return fmt.Errorf("Cache : %s set value not is nil", "Memory")
}
m := &Memory{}
m.Forever(key, value)
//无过期时间的只存在list map
list := list.New()
element := list.PushBack(m)
ms.list[key] = element
return nil
}
func (ms *MemoryStore) Clear() error {
defer ms.mu.Unlock()
ms.mu.Lock()
ms.list = make(map[string]*list.Element)
ms.expireList = list.New()
return nil
}
func (ms *MemoryStore) Size() int {
defer ms.mu.RUnlock()
ms.mu.RLock()
return len(ms.list)
}
func (ms *MemoryStore) GetTTl(key string) (time.Duration, error) {
defer ms.mu.RUnlock()
ms.mu.RLock()
if err := ms.Has(key); err != nil {
return time.Duration(0), err
} else {
return time.Now().Sub(ms.list[key].Value.(*Memory).TTL(key)), nil
}
}
func (ms *MemoryStore) IsExpire(key string) (bool, error) {
if err := ms.Has(key); err != nil {
return false, err
}
expire := ms.list[key].Value.(*Memory).TTL(key)
return !expire.IsZero() && time.Now().After(expire), nil
}
- 重点说下过期策略
参考redis实现了这个惰性删除和定时删除 定时删除主要是用到了go 的定时器 每隔2秒去扫expireList (更优的方案,可以考虑用堆实现,堆顶是过期时间戳最小的)
func (ms *MemoryStore) GC() {
ticker := time.NewTicker(2 * time.Second)
for {
fmt.Println("GC Start")
select {
case <-ticker.C:
// 触发定时器
elememt := ms.expireList.Back()
if elememt == nil {
break
}
key := elememt.Value.(*Memory).key
if isbool, _ := ms.IsExpire(key); isbool {
ms.expireList.Remove(elememt)
delete(ms.list, key)
fmt.Println("缓存删除成功:", key)
}
}
}
}
4、demo走起
package main
import (
"fmt"
"log"
"time"
"github.com/18211167516/gocache"
_ "github.com/18211167516/gocache/store"
)
func main() {
cache, err := gocache.New("memory")
if err != nil {
log.Fatalf("初始化缓存管理器 失败 %s", err)
}
fmt.Println("获取store type:", cache.GetStoreName())
fmt.Println(time.Now())
fmt.Println("删除全部:", fmt.Sprintln(cache.Clear()))
if err := cache.Set("aaaa", "123123", 100); err != nil {
log.Fatalf("设置键 %s 的value 失败 %s", "aaaa", err)
}
fmt.Println("判断键值:", fmt.Sprintln(cache.Has("aaaa")))
fmt.Println("获取键值:", fmt.Sprintln(cache.Get("aaaa")))
fmt.Println("获取ttl:", fmt.Sprintln(cache.GetTTl("aaaa")))
/* fmt.Println("设置永久键值:", cache.Forever("bbb", "bbbb"))
fmt.Println("获取ttl:", fmt.Sprintln(cache.GetTTl("bbb")))
fmt.Println("获取键值:", fmt.Sprintln(cache.Get("bbb"))) */
_ = cache.Set("cccc", "cccc", 5)
_ = cache.Set("dddd", "dddd", 20)
fmt.Println("获取键值:", fmt.Sprintln(cache.Get("cccc")))
time.Sleep(time.Duration(5) * time.Second)
fmt.Println("延迟5秒获取键值:", fmt.Sprintln(cache.Get("cccc")))
fmt.Println("获取键值:", fmt.Sprintln(cache.Get("dddd")))
fmt.Println("获取ttl:", fmt.Sprintln(cache.GetTTl("dddd")))
//fmt.Println("删除全部:", fmt.Sprintln(cache.Clear()))
fmt.Println("缓存个数:", cache.Size())
//select {}
ticker := time.NewTicker(5 * time.Second)
exit:
for {
select {
case <-ticker.C:
size := cache.Size()
fmt.Println("缓存个数:", size)
if size == 0 {
ticker.Stop()
break exit
}
}
}
}
5、结尾
定义interface是golang扩展性支持的关键 实际上就内存存储源也可以inteface话来用多个数据结构实现
6、其它
与诸君共勉之,写的不好的地址给指正,希望对您有帮助,思路很重要