cache: fix typo and optimize the naming

This commit is contained in:
Deng Ming 2023-05-26 22:13:03 +08:00 committed by Ming Deng
parent d8ffaffb78
commit d216cb76fa
9 changed files with 142 additions and 45 deletions

View File

@ -10,6 +10,7 @@
- [Fix 4435: Controller SaveToFile remove all temp file](https://github.com/beego/beego/pull/5138)
- [Fix 5079: Split signalChan into flushChan and closeChan](https://github.com/beego/beego/pull/5139)
- [Fix 5172: protect field access with lock to avoid possible data race](https://github.com/beego/beego/pull/5210)
- [cache: fix typo and optimize the naming]()
# v2.0.7
- [Upgrade github.com/go-kit/kit, CVE-2022-24450](https://github.com/beego/beego/pull/5121)

View File

@ -24,7 +24,7 @@ import (
type BloomFilterCache struct {
Cache
BloomFilter
blm BloomFilter
loadFunc func(ctx context.Context, key string) (any, error)
expiration time.Duration // set cache expiration, default never expire
}
@ -34,7 +34,7 @@ type BloomFilter interface {
Add(data string)
}
func NewBloomFilterCache(cache Cache, ln func(context.Context, string) (any, error), blm BloomFilter,
func NewBloomFilterCache(cache Cache, ln func(ctx context.Context, key string) (any, error), blm BloomFilter,
expiration time.Duration,
) (*BloomFilterCache, error) {
if cache == nil || ln == nil || blm == nil {
@ -43,7 +43,7 @@ func NewBloomFilterCache(cache Cache, ln func(context.Context, string) (any, err
return &BloomFilterCache{
Cache: cache,
BloomFilter: blm,
blm: blm,
loadFunc: ln,
expiration: expiration,
}, nil
@ -55,7 +55,7 @@ func (bfc *BloomFilterCache) Get(ctx context.Context, key string) (any, error) {
return nil, err
}
if errors.Is(err, ErrKeyNotExist) {
exist := bfc.BloomFilter.Test(key)
exist := bfc.blm.Test(key)
if exist {
val, err = bfc.loadFunc(ctx, key)
if err != nil {

View File

@ -18,6 +18,7 @@ package cache
import (
"context"
"errors"
"fmt"
"sync"
"testing"
"time"
@ -175,29 +176,31 @@ func TestBloomFilterCache_Get(t *testing.T) {
}
}
// This implementation of Bloom filters cache is NOT safe for concurrent use.
// Uncomment the following method.
// func TestBloomFilterCache_Get_Concurrency(t *testing.T) {
// bfc, err := NewBloomFilterCache(cacheUnderlying, loadFunc, mockBloom, time.Minute)
// assert.Nil(t, err)
//
// _ = mockDB.Db.ClearAll(context.Background())
// _ = mockDB.Db.Put(context.Background(), "key_11", "value_11", 0)
// mockBloom.AddString("key_11")
//
// var wg sync.WaitGroup
// wg.Add(100000)
// for i := 0; i < 100000; i++ {
// key := fmt.Sprintf("key_%d", i)
// go func(key string) {
// defer wg.Done()
// val, _ := bfc.Get(context.Background(), key)
//
// if val != nil {
// assert.Equal(t, "value_11", val)
// }
// }(key)
// }
// wg.Wait()
// assert.Equal(t, int64(1), mockDB.loadCnt)
// }
func ExampleNewBloomFilterCache() {
c := NewMemoryCache()
c, err := NewBloomFilterCache(c, func(ctx context.Context, key string) (any, error) {
return fmt.Sprintf("hello, %s", key), nil
}, &AlwaysExist{}, time.Minute)
if err != nil {
panic(err)
}
val, err := c.Get(context.Background(), "Beego")
if err != nil {
panic(err)
}
fmt.Println(val)
// Output:
// hello, Beego
}
type AlwaysExist struct {
}
func (*AlwaysExist) Test(string) bool {
return true
}
func (*AlwaysExist) Add(string) {
}

View File

@ -24,8 +24,8 @@ import (
// RandomExpireCacheOption implement genreate random time offset expired option
type RandomExpireCacheOption func(*RandomExpireCache)
// WithOffsetFunc returns a RandomExpireCacheOption that configures the offset function
func WithOffsetFunc(fn func() time.Duration) RandomExpireCacheOption {
// WithRandomExpireOffsetFunc returns a RandomExpireCacheOption that configures the offset function
func WithRandomExpireOffsetFunc(fn func() time.Duration) RandomExpireCacheOption {
return func(cache *RandomExpireCache) {
cache.offset = fn
}

View File

@ -16,6 +16,7 @@ package cache
import (
"context"
"fmt"
"math/rand"
"strings"
"testing"
@ -86,14 +87,42 @@ func TestRandomExpireCache(t *testing.T) {
assert.True(t, strings.Contains(err.Error(), "key isn't exist"))
}
func TestWithOffsetFunc(t *testing.T) {
func TestWithRandomExpireOffsetFunc(t *testing.T) {
bm, err := NewCache("memory", `{"interval":20}`)
assert.Nil(t, err)
magic := -time.Duration(rand.Int())
cache := NewRandomExpireCache(bm, WithOffsetFunc(func() time.Duration {
cache := NewRandomExpireCache(bm, WithRandomExpireOffsetFunc(func() time.Duration {
return magic
}))
// offset should return the magic value
assert.Equal(t, magic, cache.(*RandomExpireCache).offset())
}
func ExampleNewRandomExpireCache() {
mc := NewMemoryCache()
// use the default strategy which will generate random time offset (range: [3s,8s)) expired
c := NewRandomExpireCache(mc)
// so the expiration will be [1m3s, 1m8s)
err := c.Put(context.Background(), "hello", "world", time.Minute)
if err != nil {
panic(err)
}
c = NewRandomExpireCache(mc,
// based on the expiration
WithRandomExpireOffsetFunc(func() time.Duration {
val := rand.Int31n(100)
fmt.Printf("calculate offset")
return time.Duration(val) * time.Second
}))
// so the expiration will be [1m0s, 1m100s)
err = c.Put(context.Background(), "hello", "world", time.Minute)
if err != nil {
panic(err)
}
// Output:
// calculate offset
}

View File

@ -17,6 +17,7 @@ package cache
import (
"context"
"errors"
"fmt"
"testing"
"time"
@ -143,3 +144,28 @@ func (m *MockOrm) Load(key string) (any, error) {
}
return m.kvs[key], nil
}
func ExampleNewReadThroughCache() {
c := NewMemoryCache()
var err error
c, err = NewReadThroughCache(c,
// expiration, same as the expiration of key
time.Minute,
// load func, how to load data if the key is absent.
// in general, you should load data from database.
func(ctx context.Context, key string) (any, error) {
return fmt.Sprintf("hello, %s", key), nil
})
if err != nil {
panic(err)
}
val, err := c.Get(context.Background(), "Beego")
if err != nil {
panic(err)
}
fmt.Print(val)
// Output:
// hello, Beego
}

View File

@ -16,6 +16,7 @@ package cache
import (
"context"
"fmt"
"sync"
"testing"
"time"
@ -70,3 +71,20 @@ func testSingleflightCacheConcurrencyGet(t *testing.T, bm Cache) {
}
wg.Wait()
}
func ExampleNewSingleflightCache() {
c := NewMemoryCache()
c, err := NewSingleflightCache(c, time.Minute, func(ctx context.Context, key string) (any, error) {
return fmt.Sprintf("hello, %s", key), nil
})
if err != nil {
panic(err)
}
val, err := c.Get(context.Background(), "Beego")
if err != nil {
panic(err)
}
fmt.Print(val)
// Output:
// hello, Beego
}

View File

@ -22,24 +22,26 @@ import (
"github.com/beego/beego/v2/core/berror"
)
type WriteThoughCache struct {
type WriteThroughCache struct {
Cache
storeFunc func(ctx context.Context, key string, val any) error
}
func NewWriteThoughCache(cache Cache, fn func(ctx context.Context, key string, val any) error) (*WriteThoughCache, error) {
// NewWriteThroughCache creates a write through cache pattern decorator.
// The fn is the function that persistent the key and val.
func NewWriteThroughCache(cache Cache, fn func(ctx context.Context, key string, val any) error) (*WriteThroughCache, error) {
if fn == nil || cache == nil {
return nil, berror.Error(InvalidInitParameters, "cache or storeFunc can not be nil")
}
w := &WriteThoughCache{
w := &WriteThroughCache{
Cache: cache,
storeFunc: fn,
}
return w, nil
}
func (w *WriteThoughCache) Set(ctx context.Context, key string, val any, expiration time.Duration) error {
func (w *WriteThroughCache) Set(ctx context.Context, key string, val any, expiration time.Duration) error {
err := w.storeFunc(ctx, key, val)
if err != nil {
return berror.Wrap(err, PersistCacheFailed, fmt.Sprintf("key: %s, val: %v", key, val))

View File

@ -60,7 +60,7 @@ func TestWriteThoughCache_Set(t *testing.T) {
}
for _, tt := range testCases {
t.Run(tt.name, func(t *testing.T) {
w, err := NewWriteThoughCache(tt.cache, tt.storeFunc)
w, err := NewWriteThroughCache(tt.cache, tt.storeFunc)
if err != nil {
assert.EqualError(t, tt.wantErr, err.Error())
return
@ -94,7 +94,7 @@ func TestNewWriteThoughCache(t *testing.T) {
tests := []struct {
name string
args args
wantRes *WriteThoughCache
wantRes *WriteThroughCache
wantErr error
}{
{
@ -119,7 +119,7 @@ func TestNewWriteThoughCache(t *testing.T) {
cache: underlyingCache,
fn: storeFunc,
},
wantRes: &WriteThoughCache{
wantRes: &WriteThroughCache{
Cache: underlyingCache,
storeFunc: storeFunc,
},
@ -127,7 +127,7 @@ func TestNewWriteThoughCache(t *testing.T) {
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
_, err := NewWriteThoughCache(tt.args.cache, tt.args.fn)
_, err := NewWriteThroughCache(tt.args.cache, tt.args.fn)
assert.Equal(t, tt.wantErr, err)
if err != nil {
return
@ -135,3 +135,21 @@ func TestNewWriteThoughCache(t *testing.T) {
})
}
}
func ExampleNewWriteThroughCache() {
c := NewMemoryCache()
wtc, err := NewWriteThroughCache(c, func(ctx context.Context, key string, val any) error {
fmt.Printf("write data to somewhere key %s, val %v \n", key, val)
return nil
})
if err != nil {
panic(err)
}
err = wtc.Set(context.Background(),
"/biz/user/id=1", "I am user 1", time.Minute)
if err != nil {
panic(err)
}
// Output:
// write data to somewhere key /biz/user/id=1, val I am user 1
}