gin框架实践连载番外篇 | 3天打造专属Cache(First day)

1,560 阅读4分钟

引言

  • 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、其它

与诸君共勉之,写的不好的地址给指正,希望对您有帮助,思路很重要

7、系列文章