1
0
mirror of https://github.com/charlienet/go-mixed.git synced 2025-07-17 16:12:42 +08:00
This commit is contained in:
2022-04-26 17:11:45 +08:00
parent 78c957c98e
commit fb9f70d150
10 changed files with 483 additions and 0 deletions

47
cache/big_cache.go vendored Normal file
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View File

@ -0,0 +1,4 @@
package cache
type PublishSubscribe interface {
}

43
cache/qps.go vendored Normal file
View 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
View 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
}