feature add singleflight cache (#5119)

This commit is contained in:
Stone-afk 2022-12-20 16:32:26 +08:00 committed by GitHub
parent 1c0cd9838a
commit f0a59fe984
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 149 additions and 10 deletions

View File

@ -17,6 +17,7 @@ Note: now we force the web admin service serving HTTP only.
- [Fix 5022: Miss assigning listener to graceful Server](https://github.com/beego/beego/pull/5028) - [Fix 5022: Miss assigning listener to graceful Server](https://github.com/beego/beego/pull/5028)
- [Fix 4955: Make commands and Docker compose for ORM unit tests](https://github.com/beego/beego/pull/5031) - [Fix 4955: Make commands and Docker compose for ORM unit tests](https://github.com/beego/beego/pull/5031)
- [add read through for cache module](https://github.com/beego/beego/pull/5116) - [add read through for cache module](https://github.com/beego/beego/pull/5116)
- [add singleflight cache for cache module](https://github.com/beego/beego/pull/5119)
# v2.0.4 # v2.0.4

View File

@ -123,6 +123,15 @@ var InvalidSsdbCacheValue = berror.DefineCode(4002022, moduleName, "InvalidSsdbC
SSDB cache only accept string value. Please check your input. SSDB cache only accept string value. Please check your input.
`) `)
var InvalidLoadFunc = berror.DefineCode(4002023, moduleName, "InvalidLoadFunc", `
Invalid load function for read-through pattern decorator.
You should pass a valid(non-nil) load function when initiate the decorator instance.
`)
var LoadFuncFailed = berror.DefineCode(4002024, moduleName, "LoadFuncFailed", `
Failed to load data, please check whether the loadfunc is correct
`)
var InvalidInitParameters = berror.DefineCode(4002025, moduleName, "InvalidInitParameters", ` var InvalidInitParameters = berror.DefineCode(4002025, moduleName, "InvalidInitParameters", `
Invalid init cache parameters. Invalid init cache parameters.
You can check the related function to confirm that if you pass correct parameters or configure to initiate a Cache instance. You can check the related function to confirm that if you pass correct parameters or configure to initiate a Cache instance.
@ -133,15 +142,6 @@ Failed to execute the StoreFunc.
Please check the log to make sure the StoreFunc works for the specific key and value. Please check the log to make sure the StoreFunc works for the specific key and value.
`) `)
var InvalidLoadFunc = berror.DefineCode(4002023, moduleName, "InvalidLoadFunc", `
Invalid load function for read-through pattern decorator.
You should pass a valid(non-nil) load function when initiate the decorator instance.
`)
var LoadFuncFailed = berror.DefineCode(4002024, moduleName, "InvalidLoadFunc", `
Failed to load data, please check whether the loadfunc is correct
`)
var DeleteFileCacheItemFailed = berror.DefineCode(5002001, moduleName, "DeleteFileCacheItemFailed", ` var DeleteFileCacheItemFailed = berror.DefineCode(5002001, moduleName, "DeleteFileCacheItemFailed", `
Beego try to delete file cache item failed. Beego try to delete file cache item failed.
Please check whether Beego generated file correctly. Please check whether Beego generated file correctly.

63
client/cache/singleflight.go vendored Normal file
View File

@ -0,0 +1,63 @@
// Copyright 2014 beego Author. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package cache
import (
"context"
"time"
"github.com/beego/beego/v2/core/berror"
"golang.org/x/sync/singleflight"
)
// SingleflightCache
// This is a very simple decorator mode
type SingleflightCache struct {
Cache
group *singleflight.Group
expiration time.Duration
loadFunc func(ctx context.Context, key string) (any, error)
}
// NewSingleflightCache create SingleflightCache
func NewSingleflightCache(c Cache, expiration time.Duration,
loadFunc func(ctx context.Context, key string) (any, error),
) (Cache, error) {
if loadFunc == nil {
return nil, berror.Error(InvalidLoadFunc, "loadFunc cannot be nil")
}
return &SingleflightCache{
Cache: c,
group: &singleflight.Group{},
expiration: expiration,
loadFunc: loadFunc,
}, nil
}
// Get In the Get method, single flight is used to load data and write back the cache.
func (s *SingleflightCache) Get(ctx context.Context, key string) (any, error) {
val, err := s.Cache.Get(ctx, key)
if val == nil || err != nil {
val, err, _ = s.group.Do(key, func() (interface{}, error) {
v, er := s.loadFunc(ctx, key)
if er != nil {
return nil, berror.Wrap(er, LoadFuncFailed, "cache unable to load data")
}
er = s.Cache.Put(ctx, key, v, s.expiration)
return v, er
})
}
return val, err
}

72
client/cache/singleflight_test.go vendored Normal file
View File

@ -0,0 +1,72 @@
// Copyright 2014 beego Author. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package cache
import (
"context"
"sync"
"testing"
"time"
"github.com/stretchr/testify/assert"
)
func TestSingleflight_Memory_Get(t *testing.T) {
bm, err := NewCache("memory", `{"interval":20}`)
assert.Nil(t, err)
testSingleflightCacheConcurrencyGet(t, bm)
}
func TestSingleflight_file_Get(t *testing.T) {
fc := NewFileCache().(*FileCache)
fc.CachePath = "////aaa"
err := fc.Init()
assert.NotNil(t, err)
fc.CachePath = getTestCacheFilePath()
err = fc.Init()
assert.Nil(t, err)
testSingleflightCacheConcurrencyGet(t, fc)
}
func testSingleflightCacheConcurrencyGet(t *testing.T, bm Cache) {
key, value := "key3", "value3"
db := &MockOrm{keysMap: map[string]int{key: 1}, kvs: map[string]any{key: value}}
c, err := NewSingleflightCache(bm, 10*time.Second,
func(ctx context.Context, key string) (any, error) {
val, er := db.Load(key)
if er != nil {
return nil, er
}
return val, nil
})
assert.Nil(t, err)
var wg sync.WaitGroup
wg.Add(10)
for i := 0; i < 10; i++ {
go func() {
defer wg.Done()
val, err := c.Get(context.Background(), key)
if err != nil {
t.Error(err)
}
assert.Equal(t, value, val)
}()
time.Sleep(1 * time.Millisecond)
}
wg.Wait()
}

View File

@ -27,7 +27,7 @@ import (
) )
func TestWriteThoughCache_Set(t *testing.T) { func TestWriteThoughCache_Set(t *testing.T) {
var mockDbStore = make(map[string]any) mockDbStore := make(map[string]any)
testCases := []struct { testCases := []struct {
name string name string

1
go.mod
View File

@ -35,6 +35,7 @@ require (
go.opentelemetry.io/otel/sdk v1.8.0 go.opentelemetry.io/otel/sdk v1.8.0
go.opentelemetry.io/otel/trace v1.8.0 go.opentelemetry.io/otel/trace v1.8.0
golang.org/x/crypto v0.0.0-20220315160706-3147a52a75dd golang.org/x/crypto v0.0.0-20220315160706-3147a52a75dd
golang.org/x/sync v0.0.0-20220601150217-0de741cfad7f
google.golang.org/grpc v1.40.0 google.golang.org/grpc v1.40.0
google.golang.org/protobuf v1.28.1 google.golang.org/protobuf v1.28.1
gopkg.in/yaml.v2 v2.4.0 gopkg.in/yaml.v2 v2.4.0

2
go.sum
View File

@ -446,6 +446,8 @@ golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJ
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20220601150217-0de741cfad7f h1:Ax0t5p6N38Ga0dThY21weqDEyz2oklo4IvDkpigvkD8=
golang.org/x/sync v0.0.0-20220601150217-0de741cfad7f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=