Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(freecache): v2 finish #702

Merged
merged 14 commits into from
Feb 17, 2023
30 changes: 11 additions & 19 deletions pkg/cache/xfreecache/cache_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,39 +19,33 @@ func TestLocalCache(t *testing.T) {
missCount := 0

tests := []struct {
name string
stu Student
stu Student
}{
{
name: "Student 1",
stu: Student{
Age: 1,
Name: "Student 1",
},
},
{
name: "Student 2",
stu: Student{
Age: 2,
Name: "Student 2",
},
},
{
name: "Student 1",
stu: Student{
Age: 1,
Name: "Student 1",
},
},
{
name: "Student 3",
stu: Student{
Age: 1,
Name: "Student 3",
},
},
{
name: "Student 2",
stu: Student{
Age: 2,
Name: "Student 2",
Expand All @@ -60,19 +54,17 @@ func TestLocalCache(t *testing.T) {
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
key := fmt.Sprintf("%d-%s", tt.stu.Age, tt.stu.Name)
result, _ := oneCache.GetAndSetCacheData(key, func() ([]byte, error) {
missCount++
fmt.Println("local cache miss hit")
ret, _ := json.Marshal(tt.stu)
return ret, nil
})
ret := Student{}
_ = json.Unmarshal(result, &ret)
fmt.Println(ret)
assert.Equalf(t, tt.stu, ret, "GetAndSetCacheData(%v) cache value error", key)
key := fmt.Sprintf("%d-%s", tt.stu.Age, tt.stu.Name)
result, _ := oneCache.GetAndSetCacheData(key, func() ([]byte, error) {
missCount++
fmt.Println("local cache miss hit")
ret, _ := json.Marshal(tt.stu)
return ret, nil
})
ret := Student{}
_ = json.Unmarshal(result, &ret)
fmt.Println(ret)
assert.Equalf(t, tt.stu, ret, "GetAndSetCacheData(%v) cache value error", key)
}
assert.Equalf(t, missCount, 3, "GetAndSetCacheData miss count error")
}
Expand Down
96 changes: 96 additions & 0 deletions pkg/cache/xfreecache/v2/cache.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
package xfreecache

import (
"encoding/json"
"fmt"
"github.com/douyu/jupiter/pkg/xlog"
"github.com/samber/lo"
"go.uber.org/zap"
)

type storage interface {
// SetCacheData 设置缓存数据 key:缓存key data:缓存数据
SetCacheData(key string, data []byte) (err error)
// GetCacheData 存储缓存数据 key:缓存key data:缓存数据
GetCacheData(key string) (data []byte, err error)
}

type cache[K comparable, V any] struct {
storage
}

// GetAndSetCacheData 获取缓存后数据
func (c *cache[K, V]) GetAndSetCacheData(key string, id K, fn func() (V, error)) (value V, err error) {
resMap, err := c.GetAndSetCacheMap(key, []K{id}, func([]K) (map[K]V, error) {
innerVal, innerErr := fn()
return map[K]V{id: innerVal}, innerErr
})
value = resMap[id]
return
}

// GetAndSetCacheMap 获取缓存后数据 map形式
func (c *cache[K, V]) GetAndSetCacheMap(key string, ids []K, fn func([]K) (map[K]V, error)) (v map[K]V, err error) {
args := []zap.Field{zap.Any("key", key), zap.Any("ids", ids)}

v = make(map[K]V)

// id去重
ids = lo.Uniq(ids)
idsNone := make([]K, 0)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

这里可以考虑预分配len(ids)的内存

for _, id := range ids {
cacheKey := c.getKey(key, id)
if resT, innerErr := c.GetCacheData(cacheKey); innerErr == nil && resT != nil {
var value V
err = json.Unmarshal(resT, &value)
if err != nil {
return
}
v[id] = value
continue
}
idsNone = append(idsNone, id)
}

if len(idsNone) == 0 {
return
}

// 执行函数
resMap, err := fn(idsNone)
if err != nil {
xlog.Jupiter().Error("GetAndSetCacheMap doMap", append(args, zap.Error(err))...)
return
}

// 填入返回中
for key, value := range resMap {
v[key] = value
}

// 写入缓存
for _, id := range idsNone {
var cacheData V
if val, ok := v[id]; ok {
cacheData = val
}
data, innerErr := json.Marshal(cacheData)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

这个是否判断下,如果是proto.Message,就用proto来编解码

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

可以加个benchmark对比下proto和json两种对象的性能

if innerErr != nil {
err = innerErr
xlog.Jupiter().Error("GetAndSetCacheMap Marshal", append(args, zap.Error(err))...)
return
}

cacheKey := c.getKey(key, id)
err = c.SetCacheData(cacheKey, data)
if err != nil {
xlog.Jupiter().Error("GetAndSetCacheMap setCacheData", append(args, zap.Error(err))...)
return
}
}
return
}

func (c *cache[K, V]) getKey(key string, id K) string {
return fmt.Sprintf("%s:%v", key, id)
}
65 changes: 65 additions & 0 deletions pkg/cache/xfreecache/v2/cache_bench_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
package xfreecache

import (
"strconv"
"testing"
)

func BenchmarkLocalCache_GetCacheData(b *testing.B) {
localCache := New[string, Student](DefaultConfig(0))

b.Run("read", func(b *testing.B) {
student := Student{10, "student1"}
for i := 0; i < b.N; i++ {
_, _ = localCache.GetAndSetCacheData("mytest", student.Name, func() (Student, error) {
res := student
return res, nil
})
}
})

b.Run("read & write", func(b *testing.B) {
for i := 0; i < b.N; i++ {
student := Student{10, "student" + strconv.Itoa(i)}
_, _ = localCache.GetAndSetCacheData("mytest", student.Name, func() (Student, error) {
res := student
return res, nil
})
}
})
}

func BenchmarkLocalCache_GetCacheMap(b *testing.B) {

localCache := New[int64, int64](DefaultConfig(0))

b.Run("read", func(b *testing.B) {
uidList := []int64{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
for i := 0; i < b.N; i++ {
_, _ = localCache.GetAndSetCacheMap("mytest2", uidList, func(in []int64) (map[int64]int64, error) {
res := make(map[int64]int64)
for _, uid := range in {
res[uid] = uid
}
return res, nil
})
}
})

b.Run("read & write", func(b *testing.B) {
uidList := []int64{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
for i := 0; i < b.N; i++ {
uidListT := make([]int64, 0, 10)
for _, uid := range uidList {
uidListT = append(uidListT, uid+int64(i))
}
_, _ = localCache.GetAndSetCacheMap("mytest2", uidListT, func(in []int64) (map[int64]int64, error) {
res := make(map[int64]int64)
for _, uid := range in {
res[uid] = uid
}
return res, nil
})
}
})
}
151 changes: 151 additions & 0 deletions pkg/cache/xfreecache/v2/cache_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,151 @@
package xfreecache

import (
"bytes"
"fmt"
"github.com/BurntSushi/toml"
"github.com/douyu/jupiter/pkg/conf"
"github.com/stretchr/testify/assert"
"testing"
"time"
)

type Student struct {
Age int
Name string
}

func Test_cache_GetAndSetCacheData(t *testing.T) {
var configStr = `
[jupiter.cache]
[jupiter.cache.test]
expire = "60s"

`
assert.Nil(t, conf.LoadFromReader(bytes.NewBufferString(configStr), toml.Unmarshal))
oneCache := StdNew[string, Student]("test")
missCount := 0

tests := []struct {
stu Student
}{
{
stu: Student{
Age: 1,
Name: "Student 1",
},
},
{
stu: Student{
Age: 2,
Name: "Student 2",
},
},
{
stu: Student{
Age: 1,
Name: "Student 1",
},
},
{
stu: Student{
Age: 1,
Name: "Student 3",
},
},
{
stu: Student{
Age: 2,
Name: "Student 2",
},
},
}

for _, tt := range tests {
key := fmt.Sprintf("%d-%s", tt.stu.Age, tt.stu.Name)
result, _ := oneCache.GetAndSetCacheData(key, tt.stu.Name, func() (Student, error) {
missCount++
fmt.Println("local cache miss hit")
return tt.stu, nil
})
fmt.Println(result)
assert.Equalf(t, tt.stu, result, "GetAndSetCacheData(%v) cache value error", key)
}
assert.Equalf(t, missCount, 3, "GetAndSetCacheData miss count error")
}

func Test_cache_GetAndSetCacheMap(t *testing.T) {
type args struct {
ids []int64
}
tests := []struct {
args args
wantV map[int64]int64
}{
{
args: args{
ids: []int64{1, 2, 1, 3},
},
wantV: map[int64]int64{1: 1, 2: 2, 3: 3},
},
{
args: args{
ids: []int64{2, 3, 4},
},
wantV: map[int64]int64{2: 2, 3: 3, 4: 4},
},
{
args: args{
ids: []int64{9, 6},
},
wantV: map[int64]int64{9: 9, 6: 6},
},
{
args: args{
ids: []int64{1, 2, 3},
},
wantV: map[int64]int64{1: 1, 2: 2, 3: 3},
},
}

missCount := 0
for _, tt := range tests {
c := New[int64, int64](DefaultConfig(0))
gotV, err := c.GetAndSetCacheMap("mytest2", tt.args.ids, func(in []int64) (map[int64]int64, error) {
missCount++
res := make(map[int64]int64)
for _, uid := range in {
res[uid] = uid
}
fmt.Println("======== in =========")
fmt.Println(res)
return res, nil
})
fmt.Println("======== out =========")
fmt.Println(gotV)
assert.Nil(t, err, fmt.Sprintf("GetAndSetCacheMap(%v)", tt.args.ids))
assert.Equalf(t, tt.wantV, gotV, "GetAndSetCacheMap(%v)", tt.args.ids)
}
assert.Equalf(t, missCount, 3, "GetAndSetCacheMap miss count error")
}

func TestStdConfig(t *testing.T) {
var configStr = `
[jupiter.cache]
size = "100MB"
[jupiter.cache.test]
expire = "1m"
disableMetric = true
`
assert.Nil(t, conf.LoadFromReader(bytes.NewBufferString(configStr), toml.Unmarshal))
t.Run("std config on addr nil", func(t *testing.T) {
var config *Config
name := "test"
config = StdConfig(name)
assert.NotNil(t, config)
assert.Equalf(t, config.Name, name, "StdConfig Name")
assert.Equalf(t, config.Expire, 1*time.Minute, "StdConfig Expire")
assert.Equalf(t, config.DisableMetric, true, "StdConfig DisableMetric")
})

}
Loading