Skip to content

Commit

Permalink
feat: FetchWithCodec
Browse files Browse the repository at this point in the history
feat: redis prefix
  • Loading branch information
exuan committed Apr 27, 2024
1 parent a01baf8 commit 8d2ba8e
Show file tree
Hide file tree
Showing 5 changed files with 117 additions and 8 deletions.
11 changes: 11 additions & 0 deletions batch.go
Original file line number Diff line number Diff line change
Expand Up @@ -345,6 +345,7 @@ func (c *Client) FetchBatch(keys []string, expire time.Duration, fn func(idxs []

// FetchBatch2 is same with FetchBatch, except that a user defined context.Context can be provided.
func (c *Client) FetchBatch2(ctx context.Context, keys []string, expire time.Duration, fn func(idxs []int) (map[int]string, error)) (map[int]string, error) {
keys = c.formatKeys(keys)
if c.Options.DisableCacheRead {
return fn(c.keysIdx(keys))
} else if c.Options.StrongConsistency {
Expand All @@ -360,6 +361,7 @@ func (c *Client) TagAsDeletedBatch(keys []string) error {

// TagAsDeletedBatch2 a key list, the keys in list will expire after delay time.
func (c *Client) TagAsDeletedBatch2(ctx context.Context, keys []string) error {
keys = c.formatKeys(keys)
if c.Options.DisableCacheDelete {
return nil
}
Expand All @@ -385,3 +387,12 @@ func (c *Client) TagAsDeletedBatch2(ctx context.Context, keys []string) error {
}
return luaFn(c.rdb)
}

// formatKeys returns formatted keys
func (c *Client) formatKeys(keys []string) []string {
formattedKeys := make([]string, len(keys))
for i, k := range keys {
formattedKeys[i] = c.formatKey(k)
}
return formattedKeys
}
61 changes: 60 additions & 1 deletion client.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,10 @@ package rockscache
import (
"context"
"fmt"
"github.com/jinzhu/copier"
"math"
"math/rand"
"reflect"
"time"

"github.com/lithammer/shortuuid"
Expand Down Expand Up @@ -46,6 +48,10 @@ type Options struct {
StrongConsistency bool
// Context for redis command
Context context.Context
// Prefix for redis key
Prefix string
// codec
Codec Codec
}

// NewDefaultOptions return default options
Expand All @@ -58,6 +64,7 @@ func NewDefaultOptions() Options {
RandomExpireAdjustment: 0.1,
WaitReplicasTimeout: 3000 * time.Millisecond,
Context: context.Background(),
Codec: &codec{},
}
}

Expand Down Expand Up @@ -92,6 +99,7 @@ func (c *Client) TagAsDeleted2(ctx context.Context, key string) error {
if c.Options.DisableCacheDelete {
return nil
}
key = c.formatKey(key)
debugf("deleting: key=%s", key)
luaFn := func(con redis.Scripter) error {
_, err := callLua(ctx, con, deleteScript, []string{key}, []interface{}{int64(c.Options.Delay / time.Second)})
Expand Down Expand Up @@ -124,6 +132,7 @@ func (c *Client) Fetch(key string, expire time.Duration, fn func() (string, erro
// Fetch2 returns the value store in cache indexed by the key.
// If the key doest not exists, call fn to get result, store it in cache, then return.
func (c *Client) Fetch2(ctx context.Context, key string, expire time.Duration, fn func() (string, error)) (string, error) {
key = c.formatKey(key)
ex := expire - c.Options.Delay - time.Duration(rand.Float64()*c.Options.RandomExpireAdjustment*float64(expire))
v, err, _ := c.group.Do(key, func() (interface{}, error) {
if c.Options.DisableCacheRead {
Expand Down Expand Up @@ -221,11 +230,12 @@ func (c *Client) strongFetch(ctx context.Context, key string, expire time.Durati

// RawGet returns the value store in cache indexed by the key, no matter if the key locked or not
func (c *Client) RawGet(ctx context.Context, key string) (string, error) {
return c.rdb.HGet(ctx, key, "value").Result()
return c.rdb.HGet(ctx, c.formatKey(key), "value").Result()
}

// RawSet sets the value store in cache indexed by the key, no matter if the key locked or not
func (c *Client) RawSet(ctx context.Context, key string, value string, expire time.Duration) error {
key = c.formatKey(key)
err := c.rdb.HSet(ctx, key, "value", value).Err()
if err == nil {
err = c.rdb.Expire(ctx, key, expire).Err()
Expand All @@ -248,3 +258,52 @@ func (c *Client) UnlockForUpdate(ctx context.Context, key string, owner string)
_, err := callLua(ctx, c.rdb, unlockScript, []string{key}, []interface{}{owner, c.Options.LockExpire / time.Second})
return err
}

// formatKey returns the key with prefix
func (c *Client) formatKey(key string) string {
return c.Options.Prefix + key
}

// FetchWithCodec fetches the value from cache and encode the result using codec
func (c *Client) FetchWithCodec(ctx context.Context, key string, expire time.Duration, res any, fn func() (any, error)) error {
expirationAdjustment := time.Duration(rand.Float64() * c.Options.RandomExpireAdjustment * float64(expire))
ex := expire - c.Options.Delay - expirationAdjustment

fetch, err, _ := c.group.Do(key, func() (any, error) {
if c.Options.DisableCacheRead {
return fn()
}

fetchFunc := func() (string, error) {
val, err := fn()
if err != nil {
return "", err
}
bs, err := c.Options.Codec.Encode(val)
if err != nil {
return "", err
}
return string(bs), nil
}

if c.Options.StrongConsistency {
return c.strongFetch(ctx, key, ex, fetchFunc)
}
return c.weakFetch(ctx, key, ex, fetchFunc)
})

if err != nil {
return err
}

if reflect.TypeOf(res) == reflect.TypeOf(fetch) {
return copier.CopyWithOption(res, fetch, copier.Option{DeepCopy: true})
}

switch val := res.(type) {
case string:
return c.Options.Codec.Decode([]byte(val), res)
default:
return fmt.Errorf("invalid type returned from cache fetch: %T", res)
}
}
32 changes: 32 additions & 0 deletions codec.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package rockscache

import (
jsoniter "github.com/json-iterator/go"
)

type (
// Codec interface
Codec interface {
// Encode encodes the provided value into a byte slice.
Encode(interface{}) ([]byte, error)

// Decode decodes the provided byte slice into a value.
Decode([]byte, interface{}) error
}

// default codec
codec struct {
}
)

var _ Codec = (*codec)(nil)

// Encode encodes the given value into a JSON byte array.
func (c *codec) Encode(v any) ([]byte, error) {
return jsoniter.Marshal(v)
}

// Decode decodes binary data into a value pointed to by v using JSON encoding.
func (c *codec) Decode(data []byte, v any) error {
return jsoniter.Unmarshal(data, v)
}
4 changes: 4 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ module github.com/dtm-labs/rockscache
go 1.18

require (
github.com/jinzhu/copier v0.4.0
github.com/json-iterator/go v1.1.12
github.com/lithammer/shortuuid v3.0.0+incompatible
github.com/redis/go-redis/v9 v9.0.3
github.com/stretchr/testify v1.8.4
Expand All @@ -14,6 +16,8 @@ require (
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
github.com/google/uuid v1.3.0 // indirect
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 // indirect
github.com/modern-go/reflect2 v1.0.2 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)
17 changes: 10 additions & 7 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -7,27 +7,30 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78=
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/jinzhu/copier v0.4.0 h1:w3ciUoD19shMCRargcpm0cm91ytaBhDvuRpz1ODO/U8=
github.com/jinzhu/copier v0.4.0/go.mod h1:DfbEm0FYsaqBcKcFuvmOZb218JkPGtvSHsKg8S8hyyg=
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
github.com/lithammer/shortuuid v3.0.0+incompatible h1:NcD0xWW/MZYXEHa6ITy6kaXN5nwm/V115vj2YXfhS0w=
github.com/lithammer/shortuuid v3.0.0+incompatible/go.mod h1:FR74pbAuElzOUuenUHTK2Tciko1/vKuIKS9dSkDrA4w=
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 h1:ZqeYNhU3OHLH3mGKHDcjJRFFRrJa6eAM5H+CtDdOsPc=
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/redis/go-redis/v9 v9.0.3 h1:+7mmR26M0IvyLxGZUHxu4GiBkJkVDid0Un+j4ScYu4k=
github.com/redis/go-redis/v9 v9.0.3/go.mod h1:WqMKv5vnQbRuZstUwxQI195wHy+t4PuXDOjzMvcuQHk=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk=
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
golang.org/x/sync v0.1.0 h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o=
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

0 comments on commit 8d2ba8e

Please sign in to comment.