mirror of
https://github.com/charlienet/go-mixed.git
synced 2025-07-18 00:22:41 +08:00
cache
This commit is contained in:
47
cache/big_cache.go
vendored
Normal file
47
cache/big_cache.go
vendored
Normal file
@ -0,0 +1,47 @@
|
||||
package cache
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"time"
|
||||
|
||||
"github.com/allegro/bigcache"
|
||||
)
|
||||
|
||||
var _ MemCache = &bigCacheClient{}
|
||||
|
||||
type BigCacheConfig struct {
|
||||
}
|
||||
|
||||
type bigCacheClient struct {
|
||||
cache *bigcache.BigCache
|
||||
}
|
||||
|
||||
func NewBigCache(c *BigCacheConfig) (*bigCacheClient, error) {
|
||||
bigCache, err := bigcache.NewBigCache(bigcache.Config{})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &bigCacheClient{
|
||||
cache: bigCache,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (c *bigCacheClient) Get(key string) ([]byte, error) {
|
||||
return c.cache.Get(key)
|
||||
}
|
||||
|
||||
func (c *bigCacheClient) Set(key string, entry []byte, expire time.Duration) error {
|
||||
return c.cache.Set(key, entry)
|
||||
}
|
||||
|
||||
func (c *bigCacheClient) Delete(key string) error {
|
||||
return c.cache.Delete(key)
|
||||
}
|
||||
|
||||
func (c *bigCacheClient) Exist(key string) {
|
||||
}
|
||||
|
||||
func (c *bigCacheClient) IsNotFound(err error) bool {
|
||||
return errors.Is(err, bigcache.ErrEntryNotFound)
|
||||
}
|
113
cache/cache.go
vendored
Normal file
113
cache/cache.go
vendored
Normal file
@ -0,0 +1,113 @@
|
||||
package cache
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"time"
|
||||
|
||||
"github.com/charlienet/go-mixed/bytesconv"
|
||||
)
|
||||
|
||||
var ErrNotFound = errors.New("not found")
|
||||
|
||||
type LoadFunc func() (any, error)
|
||||
|
||||
type Cache struct {
|
||||
prefix string // 键前缀
|
||||
mem MemCache // 内存缓存
|
||||
distributdCache DistributdCache // 分布式缓存
|
||||
publishSubscribe PublishSubscribe // 发布订阅
|
||||
qps *qps
|
||||
}
|
||||
|
||||
func NewCache(opts ...option) (*Cache, error) {
|
||||
c := &Cache{
|
||||
qps: NewQps(),
|
||||
}
|
||||
|
||||
for _, f := range opts {
|
||||
f(c)
|
||||
}
|
||||
|
||||
go c.subscribe()
|
||||
|
||||
return c, nil
|
||||
}
|
||||
|
||||
func (c *Cache) Set(key string, value any, expiration time.Duration) error {
|
||||
if c.mem != nil {
|
||||
bytes, err := bytesconv.Encode(value)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
c.mem.Set(key, bytes, expiration)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *Cache) Get(key string, out any) error {
|
||||
if c.mem != nil {
|
||||
c.getFromMem(key, out)
|
||||
}
|
||||
|
||||
if c.distributdCache != nil {
|
||||
if err := c.distributdCache.Get(key, out); err != nil {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *Cache) GetFn(key string, out any, fn LoadFunc, expiration time.Duration) (bool, error) {
|
||||
ret, err := fn()
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
_ = ret
|
||||
|
||||
return false, nil
|
||||
}
|
||||
|
||||
func (c *Cache) Exist(key string) (bool, error) {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
func (c *Cache) Delete(key string) error {
|
||||
if c.mem != nil {
|
||||
c.mem.Delete(key)
|
||||
}
|
||||
|
||||
if c.distributdCache != nil {
|
||||
c.distributdCache.Delete(key)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *Cache) getFromMem(key string, out any) error {
|
||||
|
||||
bytes, err := c.mem.Get(key)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := bytesconv.Decode(bytes, out); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *Cache) subscribe() {
|
||||
}
|
||||
|
||||
func (c *Cache) genKey(key string) string {
|
||||
if len(c.prefix) == 0 {
|
||||
return key
|
||||
}
|
||||
|
||||
return c.prefix + "-" + key
|
||||
}
|
85
cache/cache_test.go
vendored
Normal file
85
cache/cache_test.go
vendored
Normal file
@ -0,0 +1,85 @@
|
||||
package cache
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestNewCache(t *testing.T) {
|
||||
r := NewRedis(&RedisConfig{})
|
||||
if err := r.Ping(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
c, err := NewCache(
|
||||
WithDistributdCache(r),
|
||||
WithPrefix("cache_test"))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
c.Set("abc", "value", time.Minute*10)
|
||||
}
|
||||
|
||||
type SimpleUser struct {
|
||||
FirstName string
|
||||
LastName string
|
||||
}
|
||||
|
||||
func TestMem(t *testing.T) {
|
||||
c, err := NewCache(WithFreeCache(10 * 1024 * 1024))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
key := "u-000"
|
||||
u := SimpleUser{FirstName: "Radomir", LastName: "Sohlich"}
|
||||
|
||||
c.Set(key, u, time.Second)
|
||||
|
||||
var u2 SimpleUser
|
||||
c.Get(key, &u2)
|
||||
|
||||
t.Logf("%+v", u2)
|
||||
}
|
||||
|
||||
func TestDistributedCache(t *testing.T) {
|
||||
key := "key-001"
|
||||
c := NewRedis(&RedisConfig{Addrs: []string{"192.168.2.222:6379"}, DB: 6, Password: "123456"})
|
||||
|
||||
if err := c.Ping(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
u := SimpleUser{FirstName: "redis client"}
|
||||
c.Set(key, u, time.Second)
|
||||
|
||||
var u2 SimpleUser
|
||||
if err := c.Get(key, &u2); err != nil {
|
||||
t.Fatal("err:", err)
|
||||
}
|
||||
t.Logf("%+v", u2)
|
||||
}
|
||||
|
||||
func TestGetFn(t *testing.T) {
|
||||
c, err := NewCache(WithBigCache(&BigCacheConfig{}))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
key := "u-000"
|
||||
|
||||
var u2 SimpleUser
|
||||
c.GetFn(key, &u2, func() (out any, err error) {
|
||||
v := &u2
|
||||
v.FirstName = "abc"
|
||||
|
||||
return nil, nil
|
||||
}, time.Second)
|
||||
|
||||
t.Logf("%+v", u2)
|
||||
}
|
||||
|
||||
func BenchmarkMemCache(b *testing.B) {
|
||||
}
|
||||
|
||||
func load() (any, error) {
|
||||
return nil, nil
|
||||
}
|
10
cache/distributd_cache.go
vendored
Normal file
10
cache/distributd_cache.go
vendored
Normal file
@ -0,0 +1,10 @@
|
||||
package cache
|
||||
|
||||
import "time"
|
||||
|
||||
type DistributdCache interface {
|
||||
Get(key string, out any) error
|
||||
Set(key string, value any, expiration time.Duration)
|
||||
Delete(key string) error
|
||||
Ping() error
|
||||
}
|
55
cache/free_cache.go
vendored
Normal file
55
cache/free_cache.go
vendored
Normal file
@ -0,0 +1,55 @@
|
||||
package cache
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"time"
|
||||
|
||||
"github.com/coocood/freecache"
|
||||
)
|
||||
|
||||
const defaultSize = 10 * 1024 * 1024 // 10M
|
||||
|
||||
var _ MemCache = &freeCache{}
|
||||
|
||||
type freeCache struct {
|
||||
cache *freecache.Cache
|
||||
}
|
||||
|
||||
func NewFreeCache(size int) *freeCache {
|
||||
if size < defaultSize {
|
||||
size = defaultSize
|
||||
}
|
||||
|
||||
// debug.SetGCPercent(20)
|
||||
|
||||
c := freecache.NewCache(size)
|
||||
return &freeCache{
|
||||
cache: c,
|
||||
}
|
||||
}
|
||||
|
||||
func (c *freeCache) Get(key string) ([]byte, error) {
|
||||
return c.cache.Get([]byte(key))
|
||||
}
|
||||
|
||||
func (c *freeCache) Set(key string, value []byte, d time.Duration) error {
|
||||
s := int(d.Seconds())
|
||||
return c.cache.Set([]byte(key), value, s)
|
||||
}
|
||||
|
||||
func (c *freeCache) Delete(key string) error {
|
||||
affected := c.cache.Del([]byte(key))
|
||||
if !affected {
|
||||
return errors.New("不存在")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *freeCache) Exist(key string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *freeCache) IsNotFound(err error) bool {
|
||||
return errors.Is(err, freecache.ErrNotFound)
|
||||
}
|
9
cache/mem.go
vendored
Normal file
9
cache/mem.go
vendored
Normal file
@ -0,0 +1,9 @@
|
||||
package cache
|
||||
|
||||
import "time"
|
||||
|
||||
type MemCache interface {
|
||||
Get(key string) ([]byte, error)
|
||||
Set(key string, entry []byte, expire time.Duration) error
|
||||
Delete(key string) error
|
||||
}
|
31
cache/options.go
vendored
Normal file
31
cache/options.go
vendored
Normal file
@ -0,0 +1,31 @@
|
||||
package cache
|
||||
|
||||
type option func(*Cache)
|
||||
|
||||
type options struct {
|
||||
Prefix string
|
||||
}
|
||||
|
||||
func WithPrefix(prefix string) option {
|
||||
return func(o *Cache) { o.prefix = prefix }
|
||||
}
|
||||
|
||||
func WithDistributdCache(d DistributdCache) option {
|
||||
return func(o *Cache) { o.distributdCache = d }
|
||||
}
|
||||
|
||||
func WithBigCache(config *BigCacheConfig) option {
|
||||
return func(o *Cache) {
|
||||
c, err := NewBigCache(config)
|
||||
_ = err
|
||||
o.mem = c
|
||||
}
|
||||
}
|
||||
|
||||
func WithFreeCache(size int) option {
|
||||
return func(o *Cache) { o.mem = NewFreeCache(size) }
|
||||
}
|
||||
|
||||
func WithPublishSubscribe(p PublishSubscribe) option {
|
||||
return func(o *Cache) {}
|
||||
}
|
4
cache/publish_subscribe.go
vendored
Normal file
4
cache/publish_subscribe.go
vendored
Normal file
@ -0,0 +1,4 @@
|
||||
package cache
|
||||
|
||||
type PublishSubscribe interface {
|
||||
}
|
43
cache/qps.go
vendored
Normal file
43
cache/qps.go
vendored
Normal file
@ -0,0 +1,43 @@
|
||||
package cache
|
||||
|
||||
import (
|
||||
"sync/atomic"
|
||||
"time"
|
||||
)
|
||||
|
||||
type qps struct {
|
||||
all qpsContent
|
||||
memoryTotal qpsContent
|
||||
memoryHit qpsContent
|
||||
redisTotal qpsContent
|
||||
redisHit qpsContent
|
||||
sourceTotal qpsContent
|
||||
}
|
||||
|
||||
type qpsContent struct {
|
||||
viewTotal int64
|
||||
total int64
|
||||
}
|
||||
|
||||
func NewQps() *qps {
|
||||
qps := &qps{}
|
||||
go qps.statisticsTotal()
|
||||
return qps
|
||||
}
|
||||
|
||||
func (q *qps) statisticsTotal() {
|
||||
defer func() {
|
||||
if e := recover(); e != nil {
|
||||
}
|
||||
}()
|
||||
|
||||
ticker := time.NewTicker(time.Second)
|
||||
for range ticker.C {
|
||||
q.all.viewTotal = atomic.SwapInt64(&q.all.total, 0)
|
||||
q.memoryTotal.viewTotal = atomic.SwapInt64(&q.memoryTotal.total, 0)
|
||||
q.memoryHit.viewTotal = atomic.SwapInt64(&q.memoryHit.total, 0)
|
||||
q.redisTotal.viewTotal = atomic.SwapInt64(&q.redisTotal.total, 0)
|
||||
q.redisHit.viewTotal = atomic.SwapInt64(&q.redisHit.total, 0)
|
||||
q.sourceTotal.viewTotal = atomic.SwapInt64(&q.sourceTotal.total, 0)
|
||||
}
|
||||
}
|
86
cache/redis.go
vendored
Normal file
86
cache/redis.go
vendored
Normal file
@ -0,0 +1,86 @@
|
||||
package cache
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/charlienet/go-mixed/bytesconv"
|
||||
"github.com/charlienet/go-mixed/json"
|
||||
"github.com/charlienet/go-mixed/rand"
|
||||
"github.com/go-redis/redis/v8"
|
||||
)
|
||||
|
||||
type RedisConfig struct {
|
||||
Perfix string // key perfix
|
||||
Addrs []string
|
||||
|
||||
// Database to be selected after connecting to the server.
|
||||
// Only single-node and failover clients.
|
||||
DB int
|
||||
|
||||
Username string
|
||||
Password string
|
||||
MaxRetries int
|
||||
MinRetryBackoff time.Duration
|
||||
MaxRetryBackoff time.Duration
|
||||
|
||||
DialTimeout time.Duration
|
||||
ReadTimeout time.Duration
|
||||
WriteTimeout time.Duration
|
||||
}
|
||||
|
||||
type redisClient struct {
|
||||
client redis.UniversalClient
|
||||
emptyStamp string // 空对象标识,每个实例隔离
|
||||
perfix string // 缓存键前缀
|
||||
}
|
||||
|
||||
func NewRedis(c *RedisConfig) *redisClient {
|
||||
client := redis.NewUniversalClient(&redis.UniversalOptions{
|
||||
Addrs: c.Addrs,
|
||||
DB: c.DB,
|
||||
Username: c.Username,
|
||||
Password: c.Password,
|
||||
})
|
||||
|
||||
return &redisClient{
|
||||
emptyStamp: fmt.Sprintf("redis-empty-%d-%s", time.Now().Unix(), rand.Hex.Generate(6)),
|
||||
perfix: c.Perfix,
|
||||
client: client,
|
||||
}
|
||||
}
|
||||
|
||||
func (c *redisClient) Get(key string, out any) error {
|
||||
cmd := c.client.Get(context.Background(), key)
|
||||
str, err := cmd.Result()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = json.Unmarshal(bytesconv.StringToBytes(str), out)
|
||||
return err
|
||||
}
|
||||
|
||||
func (c *redisClient) Set(key string, value any, expiration time.Duration) {
|
||||
j, _ := json.Marshal(value)
|
||||
c.client.Set(context.Background(), key, j, expiration)
|
||||
}
|
||||
|
||||
func (c *redisClient) Exist(key string) (bool, error) {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
func (c *redisClient) Delete(key string) error {
|
||||
cmd := c.client.Del(context.Background(), key)
|
||||
if cmd.Err() != nil {
|
||||
return cmd.Err()
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *redisClient) Ping() error {
|
||||
_, err := c.client.Ping(context.Background()).Result()
|
||||
return err
|
||||
}
|
Reference in New Issue
Block a user