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