这个 gofnext 提供以下函数扩展(go>=1.21)。
缓存装饰器(并发安全):类似于 Python 的 functools.cache
和 functools.lru_cache
。除了内存缓存,它也支持 Redis 缓存和自定义缓存。
函数 | 装饰器 |
---|---|
func f() res | gofnext.CacheFn0(f) |
func f(a) res | gofnext.CacheFn1(f) |
func f(a,b) res | gofnext.CacheFn2(f) |
func f() (res,err) | gofnext.CacheFn0Err(f) |
func f(a) (res,err) | gofnext.CacheFn1Err(f) |
func f(a,b) (res,err) | gofnext.CacheFn2Err(f) |
func f() (res,err) | gofnext.CacheFn0Err(f, &gofnext.Config{TTL: time.Hour}) // 带有 ttl 的内存缓存 |
func f() (res) | gofnext.CacheFn0(f, &gofnext.Config{CacheMap: gofnext.NewCacheLru(9999)}) // 缓存的最大大小为 9999 |
func f() (res) | gofnext.CacheFn0(f, &gofnext.Config{CacheMap: gofnext.NewCacheRedis("cacheKey")}) // 警告:redis 的序列化可能会导致数据丢失 |
- 缓存装饰器 (gofnext)
- 函数的装饰器缓存
- 并发协程安全
- 支持内存 CacheMap(默认)
- 支持内存-LRU CacheMap
- 支持 redis CacheMap
- 手动支持自定义 CacheMap
参考:示例
参考:装饰器斐波那契示例
package main
import "fmt"
import "github.com/ahuigo/gofnext"
func main() {
var fib func(int) int
fib = func(x int) int {
fmt.Printf("call arg:%d\n", x)
if x <= 1 {
return x
} else {
return fib(x-1) + fib(x-2)
}
}
fib = gofnext.CacheFn1(fib)
fmt.Println(fib(5))
fmt.Println(fib(6))
}
package examples
import "github.com/ahuigo/gofnext"
func getUserAnonymouse() (UserInfo, error) {
fmt.Println("select * from db limit 1", time.Now())
time.Sleep(10 * time.Millisecond)
return UserInfo{Name: "Anonymous", Age: 9}, errors.New("db error")
}
var (
// Cacheable Function
getUserInfoFromDbWithCache = gofnext.CacheFn0Err(getUserAnonymouse)
)
func TestCacheFuncWithNoParam(t *testing.T) {
// Execute the function multi times in parallel.
times := 10
parallelCall(func() {
userinfo, err := getUserInfoFromDbWithCache()
fmt.Println(userinfo, err)
}, times)
}
func getUserNoError(age int) (UserInfo) {
time.Sleep(10 * time.Millisecond)
return UserInfo{Name: "Alex", Age: age}
}
var (
// Cacheable Function with 1 param and no error
getUserInfoFromDbNil= gofnext.CacheFn1(getUserNoError)
)
func TestCacheFuncNil(t *testing.T) {
// Execute the function multi times in parallel.
times := 10
parallelCall(func() {
userinfo := getUserInfoFromDbNil(20)
fmt.Println(userinfo)
}, times)
}
func TestCacheFuncWith2Param(t *testing.T) {
// Original function
executeCount := 0
getUserScore := func(c context.Context, id int) (int, error) {
executeCount++
fmt.Println("select score from db where id=", id, time.Now())
time.Sleep(10 * time.Millisecond)
return 98 + id, errors.New("db error")
}
// Cacheable Function
getUserScoreFromDbWithCache := gofnext.CacheFn2Err(getUserScore, &gofnext.Config{
TTL: time.Hour,
}) // getFunc can only accept 2 parameter
// Execute the function multi times in parallel.
ctx := context.Background()
parallelCall(func() {
score, _ := getUserScoreFromDbWithCache(ctx, 1)
if score != 99 {
t.Errorf("score should be 99, but get %d", score)
}
getUserScoreFromDbWithCache(ctx, 2)
getUserScoreFromDbWithCache(ctx, 3)
}, 10)
if executeCount != 3 {
t.Errorf("executeCount should be 3, but get %d", executeCount)
}
}
executeCount := 0
type Stu struct {
name string
age int
gender int
}
// Original function
fn := func(name string, age, gender int) int {
executeCount++
// select score from db where name=name and age=age and gender=gender
switch name {
case "Alex":
return 10
default:
return 30
}
}
// Convert to extra parameters to a single parameter(2 prameters is ok)
fnWrap := func(arg Stu) int {
return fn(arg.name, arg.age, arg.gender)
}
// Cacheable Function
fnCachedInner := gofnext.CacheFn1(fnWrap)
fnCached := func(name string, age, gender int) int {
return fnCachedInner(Stu{name, age, gender})
}
// Execute the function multi times in parallel.
parallelCall(func() {
score := fnCached("Alex", 20, 1)
if score != 10 {
t.Errorf("score should be 10, but get %d", score)
}
fnCached("Jhon", 21, 0)
fnCached("Alex", 20, 1)
}, 10)
// Test Count
if executeCount != 2 {
t.Errorf("executeCount should be 2, but get %d", executeCount)
}
executeCount := 0
maxCacheSize := 2
var getUserScore = func(more int) (int, error) {
executeCount++
return 98 + more, errors.New("db error")
}
// Cacheable Function
var getUserScoreFromDbWithLruCache = gofnext.CacheFn1Err(getUserScore, &gofnext.Config{
TTL: time.Hour,
CacheMap: gofnext.NewCacheLru(maxCacheSize),
})
警告: 目前使用json序列化,可能会有私有属性丢失 后续序列化方法可能会有变化, 请不要用于生产
var (
// Cacheable Function
getUserScoreFromDbWithCache = gofnext.CacheFn1Err(getUserScore, &gofnext.Config{
TTL: time.Hour,
CacheMap: gofnext.NewCacheRedis("redis-cache-key"),
})
)
func TestRedisCacheFuncWithTTL(t *testing.T) {
// Execute the function multi times in parallel.
for i := 0; i < 10; i++ {
score, _ := getUserScoreFromDbWithCache(1)
if score != 99 {
t.Errorf("score should be 99, but get %d", score)
}
}
}
To avoid keys being too long, you can limit the length of Redis key:
cacheMap := gofnext.NewCacheRedis("redis-cache-key").SetMaxHashKeyLen(256);
Set redis config:
// method 1: by default: localhost:6379
cache := gofnext.NewCacheRedis("redis-cache-key")
// method 2: set redis addr
cache.SetRedisAddr("192.168.1.1:6379")
// method 3: set redis options
cache.SetRedisOpts(&redis.Options{
Addr: "localhost:6379",
})
// method 4: set redis universal options
cache.SetRedisUniversalOpts(&redis.UniversalOptions{
Addrs: []string{"localhost:6379"},
})
参考: https://github.com/ahuigo/gofnext/blob/main/cache-map-mem.go
gofnext.Config 清单:
| 键 | 描述 |默认 | |-----|------------------| | TTL | 缓存时间 | 0(不过期)| | CacheMap| 自定义缓存map |默认内存Map| | NeedCacheIfErr | 如果调用存在error,也要使用缓存 |有err就不缓存| | HashKeyPointerAddr | 哈希key时,使用指针本身地址(&p),而不是实际的值 |默认使用pointer指向实际值(*p)| | HashKeyFunc| 自定义哈希键函数 |内置hashFunc|
e.g.
gofnext.CacheFn1Err(getUserScore, &gofnext.Config{
TTL: time.Hour,
})
默认有函数返回error时, 就不会用缓存.
如果存在error时, 也需要缓存的话。 参考: https://github.com/ahuigo/gofnext/blob/main/examples/decorator-err_test.go
gofnext.CacheFn1Err(getUserScore, &gofnext.Config{
NeedCacheIfErr: true,
})
装饰器将函数的所有参数哈希成哈希键(hashkey)。 默认情况下,如果参数是指针,装饰器将哈希其实际值而不是指针地址。
如果您想要哈希指针地址,您应该打开 HashKeyPointerAddr
选项:
getUserScoreFromDbWithCache := gofnext.CacheFn1Err(getUserScore, &gofnext.Config{
HashKeyPointerAddr: true,
})
这种情况下,您需要保证不会有生成重复的key。
参考: example
// hash key function
hashKeyFunc := func(keys ...any) []byte{
user := keys[0].(*UserInfo)
flag := keys[1].(bool)
return []byte(fmt.Sprintf("user:%d,flag:%t", user.id, flag))
}
// Cacheable Function
getUserScoreFromDbWithCache := gofnext.CacheFn2Err(getUserScore, &gofnext.Config{
HashKeyFunc: hashKeyFunc,
})
- [] Redis CacheMap 支持序列化所有私有属性