golang 实现缓存切面

2,803 阅读2分钟

内存缓存

从java转到golang,一个很不方便的点就是golang没有切面。最近项目里需要做一个内存缓存,对于数据库中的一些配置信息,避免每次都从数据库查询,以减轻数据库的压力,也降低数据库查询导致的接口失败问题。

最开始实现很简单,最简单的业务实现:

    if cache 存在&&未过期
        返回cache中的数据
    else 
        从数据库查询
        放入cache
        返回查询的数据

但是写多了之后,就会想每次重复的一块逻辑能不能抽出来呢?java里有一个很方便也很通用的做法是,通过注解实现一个切面,将从cache中取数据以及放入数据到cache的部分抽离出公共逻辑,但是golang作为一个还不成熟的baby,目前还处于全靠堆业务逻辑代码实现功能的阶段,所以有没有一个比较好的实现呢?

我期望的一个实现是对业务代码(从数据库查询)的侵入越小越好

下面是一个实现示例:

import (
	"fmt"
	"time"
)

var CacheMap = make(map[string]Cache)

type Cache struct {
	Value      string
	ExpireTime time.Time
}

func withithCache(cacheKey string, expireTime int64, function func() (string, error)) (string, error) {
	if value, ok := CacheMap[cacheKey]; ok {
		if value.ExpireTime.After(time.Now()) {
			fmt.Println("get from cache")
			return value.Value, nil
		}
	}
	value, err := function()
	if err != nil {
		return "", err
	}
	//todo 此处应该加锁
	CacheMap[cacheKey] = Cache{
		Value:      value,
		ExpireTime: time.Now().Add(time.Duration(expireTime) * time.Second),
	}
	//
	return value, nil
}

下边写一个调用示例:

func queryDbData(param string) (string, error) {
	return withithCache("queryDbData", 4, func() (s string, err error) {
		fmt.Println("get from db")
		return param, nil
	})

}

写一个测试方法测试一下:

func TestCacheProxy(t *testing.T) {
	for i := 0; i < 10; i++ {
		data, err := queryDbData("abc")
		fmt.Println(data, err)
		time.Sleep(1 * time.Second)
	}
}

其中queryDbData是一个简单的数据库查询操作,在TestCacheProxy中可以直接调用

queryDbData的实现也很简单

不使用cache的实现方式:

func queryDbDataDirectly(param string) (string, error) {
	fmt.Println("get from db")
	return param, nil
}

可以看到,相比queryDbData来说,只是多了withithCache一个调用,以设置缓存的key和缓存时间。而withithCache也可以同时被其他希望做内存缓存的方法调用。

进阶

以上示例代码只是一个最简单的实现,目前github上有很多localcache的实现,对内存缓存做了更多丰富的实现,包括:

  1. 内存缓存的过期策略配置,参考redis的过期策略,包括:定时清理惰性清理定期清理
  2. 设置最大使用内存
  3. 在达到了最大使用内存后,内存的淘汰策略:FIFO,LRU,LFU

想做工程使用的,可以将示例代码中的map使用更完善的内存缓存替换下