Golang 实现缓存系统

这一生的挚爱 提交于 2020-10-25 11:27:35

缓存

缓存(Cache)在计算机硬件中普遍存在。比如在 CPU 中就有一级缓存,二级缓存,甚至三级缓存。缓存的工作原理一般是 CPU 需要读取数据时,会首先从缓存中查找需要的数据,如果找到了就直接进行处理,如果没有找到则从内存中读取数据。由于 CPU 中的缓存工作速度比内存还要快,所以缓存的使用能加快 CPU 处理速度。缓存不仅仅存在于硬件中,在各种软件系统中也处处可见。比如在 Web 系统中,缓存存在于服务器端,客户端或者代理服务器中。广泛使用的 CDN 网络,也可以看作一个巨大的缓存系统。缓存在 Web 系统中的使用有很多好处,不仅可以减少网络流量,降低客户访问延迟,还可以减轻服务器负载。

目前已经存在很多高性能的缓存系统,比如 Memcache,Redis 等,尤其是 Redis,现已经广泛用于各种 Web 服务中。既然有了这些功能完善的缓存系统,那为什么我们还需要自己实现一个缓存系统呢?这么做主要有两个原因,第一,通过动手实现我们可以了解缓存系统的工作原理,这也是老掉牙的理由了。第二,Redis 之类的缓存系统都是独立存在的,如果只是开发一个简单的应用还需要单独使用 Redis 服务器,难免会过于复杂。这时候如果有一个功能完善的软件包实现了这些功能,只需要引入这个软件包就能实现缓存功能,而不需要单独使用 Redis 服务器,就最好不过了。

缓存系统的设计

缓存系统中,缓存的数据一般都存储在内存中,所以我们设计的缓存系统要以某一种方式管理内存中的数据。既然缓存数据是存储在内存中的,那如果系统停机,那数据岂不就丢失了吗?其实一般情况下,缓存系统还支持将内存中的数据写入到文件中,在系统再次启动时,再将文件中的数据加载到内存中,这样一来就算系统停机,缓存数据也不会丢失。

同时缓存系统还提供过期数据清理机制,也就是说缓存的数据项是有生存时间的,如果数据项过期,则数据项会从内存中被删除,这样一来热数据会一直存在,而冷数据则会被删除,也没有必要进行缓存。

缓存系统还需要对外提供操作的接口,这样系统的其他组件才能使用缓存。一般情况下,缓存系统需要支持 CRUD 操作,也就算创建(添加),读取,更新和删除操作。

通过以上分析,可以总结出缓存系统需要有以下功能:

  1. 缓存数据的存储
  2. 过期数据项管理
  3. 内存数据导出,导入
  4. 提供 CRUD 接口

代码实现

package examples

import (
	"sync"
	"testing"
	"time"
)

type ICache interface {
	//size support: 1KB,100KB,1MB,2MB,1GB
	SetMaxMemory(size string) bool
	//key expire after expire time
	Set(key string, val interface{}, expire time.Duration)
	//get one key
	Get(key string) (interface{}, bool)
	//delete one key
	Del(key string) bool
	//exists one key
	Exists(key string) bool
	//delete all key
	Flush() bool
	//get all key
	Keys() []string
	//garbage collection every second
	GcLoop()
	//delete expired key
	DeleteExpired()
}

type Item struct {
	Object     interface{}
	Expiration int64
}

type cache struct {
	size     string
	items    map[string]Item
	mu       sync.RWMutex
	interval time.Duration
}

func (c *cache) SetMaxMemory(size string) bool {
	c.mu.RLock()
	defer c.mu.RUnlock()

	c.size = size
	return true
}

func (c *cache) Set(k string, x interface{}, d time.Duration) {
	c.mu.Lock()
	defer c.mu.Unlock()

	e := time.Now().Add(d * time.Second).Unix()

	c.items[k] = Item{
		Object:     x,
		Expiration: e,
	}
}

func (c *cache) Get(k string) (interface{}, bool) {
	c.mu.RLock()
	defer c.mu.RUnlock()

	item, found := c.items[k]
	if !found {
		return nil, false
	}

	if time.Now().Unix() > item.Expiration {
		return nil, false
	}

	return item.Object, true
}

func (c *cache) Del(k string) bool {
	c.mu.Lock()
	defer c.mu.Unlock()

	if _, found := c.items[k]; found {
		delete(c.items, k)
		return true
	}

	return false
}

func (c *cache) Exists(k string) bool {
	c.mu.RLock()
	defer c.mu.RUnlock()

	if _, found := c.items[k]; found {
		return true
	}

	return false
}

func (c *cache) Flush() bool {
	c.mu.Lock()
	defer c.mu.Unlock()

	c.items = map[string]Item{}
	return true
}

func (c *cache) Keys() []string {
	c.mu.RLock()
	defer c.mu.RUnlock()

	var keys []string
	for k := range c.items {
		keys = append(keys, k)
	}

	return keys
}

func (c *cache) GcLoop() {
	ticker := time.NewTicker(c.interval)

	for {
		select {
		case <-ticker.C:
			c.DeleteExpired()
		}
	}
}

func (c *cache) DeleteExpired() {
	now := time.Now().Unix()

	for k, v := range c.items {
		if now > v.Expiration {
			c.Del(k)
		}
	}
}

func NewCache() ICache {
	c := &cache{
		size:     "1024",
		items:    map[string]Item{},
		interval: time.Second,
	}

	go c.GcLoop()
	return c
}

func TestCacheExample(t *testing.T) {
	c := NewCache()

	c.Set("foo", "bar", 5)

	time.Sleep(10 * time.Second)
	r, b := c.Get("foo")

	t.Log(r, b)
	t.Log("test cache finished.")

}
标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!