Skip to content

Latest commit

 

History

History
340 lines (276 loc) · 11.5 KB

readme.zh.md

File metadata and controls

340 lines (276 loc) · 11.5 KB

🛠️ Go function extended

标签 Go 版本 GoDoc 构建状态 Go 报告 覆盖率 贡献者 许可证

这个 gofnext 提供以下函数扩展(go>=1.21)。

缓存装饰器(并发安全):类似于 Python 的 functools.cachefunctools.lru_cache。除了内存缓存,它也支持 Redis 缓存和自定义缓存。

Egnlish/中文

装饰器cases

函数 装饰器
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

装饰器示例

参考:示例

缓存斐波那契函数

参考:装饰器斐波那契示例

Play: https://go.dev/play/p/7BCINKENJzA

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))
}

带有0个参数的缓存函数

参考: decorator example

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)
}

带有1个参数的缓存函数

参考: decorator example

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)
}

带有2个参数的缓存函数

参考: decorator example

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)
    }
}

带有2个以上参数的缓存函数

参考: decorator example

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)
}

带LRU 缓存的函数

参考: decorator lru example

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),
})

带redis缓存的函数(unstable)

警告: 目前使用json序列化,可能会有私有属性丢失 后续序列化方法可能会有变化, 请不要用于生产

参考: decorator redis example

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)

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时, 就不会用缓存.

如果存在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,
})

Roadmap

  • [] Redis CacheMap 支持序列化所有私有属性