From c7d2c02ac1f27c9cedca53378f31b6bd2af735ea Mon Sep 17 00:00:00 2001 From: wubin1989 <328454505@qq.com> Date: Tue, 12 Dec 2023 00:02:02 +0800 Subject: [PATCH] CacheManager --- framework/cache/cachemanager.go | 144 ++++++++++++++++++++++++++++++++ framework/config/config.go | 60 +++++++++---- framework/config/default.go | 6 +- framework/database/database.go | 109 +++++++----------------- toolkit/caches/caches.go | 44 ++++++---- 5 files changed, 251 insertions(+), 112 deletions(-) create mode 100644 framework/cache/cachemanager.go diff --git a/framework/cache/cachemanager.go b/framework/cache/cachemanager.go new file mode 100644 index 00000000..fede849c --- /dev/null +++ b/framework/cache/cachemanager.go @@ -0,0 +1,144 @@ +package cache + +import ( + "strings" + "time" + "unsafe" + + "github.com/dgraph-io/ristretto" + gocache "github.com/eko/gocache/lib/v4/cache" + "github.com/eko/gocache/lib/v4/metrics" + "github.com/eko/gocache/lib/v4/store" + redis_store "github.com/eko/gocache/store/redis/v4" + ristretto_store "github.com/eko/gocache/store/ristretto/v4" + "github.com/redis/go-redis/v9" + + "github.com/unionj-cloud/go-doudou/v2/framework/config" + "github.com/unionj-cloud/go-doudou/v2/toolkit/cast" + "github.com/unionj-cloud/go-doudou/v2/toolkit/sliceutils" + "github.com/unionj-cloud/go-doudou/v2/toolkit/stringutils" +) + +var CacheManager gocache.CacheInterface[any] + +const ( + CacheStoreRistretto = "ristretto" + CacheStoreRedis = "redis" +) + +func init() { + conf := config.Config{ + Cache: struct { + TTL int + Stores string + Redis struct { + Addr string + RouteByLatency bool "default:\"true\"" + RouteRandomly bool + } + Ristretto struct { + NumCounters int64 "default:\"1000\"" + MaxCost int64 "default:\"100\"" + BufferItems int64 "default:\"64\"" + } + }{ + TTL: cast.ToIntOrDefault(config.GddCacheTTL.Load(), config.DefaultGddCacheTTL), + Stores: config.GddCacheStores.LoadOrDefault(config.DefaultGddCacheStores), + Ristretto: struct { + NumCounters int64 "default:\"1000\"" + MaxCost int64 "default:\"100\"" + BufferItems int64 "default:\"64\"" + }{ + NumCounters: cast.ToInt64OrDefault(config.GddCacheRistrettoNumCounters.Load(), config.DefaultGddCacheRistrettoNumCounters), + MaxCost: cast.ToInt64OrDefault(config.GddCacheRistrettoMaxCost.Load(), config.DefaultGddCacheRistrettoMaxCost), + BufferItems: cast.ToInt64OrDefault(config.GddCacheRistrettoBufferItems.Load(), config.DefaultGddCacheRistrettoBufferItems), + }, + Redis: struct { + Addr string + RouteByLatency bool "default:\"true\"" + RouteRandomly bool + }{ + Addr: config.GddCacheRedisAddr.LoadOrDefault(config.DefaultGddCacheRedisAddr), + RouteByLatency: cast.ToBoolOrDefault(config.GddCacheRedisRouteByLatency.Load(), config.DefaultGddCacheRedisRouteByLatency), + RouteRandomly: cast.ToBoolOrDefault(config.GddCacheRedisRouteRandomly.Load(), config.DefaultGddCacheRedisRouteRandomly), + }, + }, + Service: struct{ Name string }{ + Name: config.GddServiceName.LoadOrDefault(config.DefaultGddServiceName), + }, + } + + CacheManager = NewCacheManager(conf) +} + +func NewCacheManager(conf config.Config) gocache.CacheInterface[any] { + storesStr := conf.Cache.Stores + if stringutils.IsEmpty(storesStr) { + return nil + } + stores := strings.Split(storesStr, ",") + + var setterCaches []gocache.SetterCacheInterface[any] + ttl := conf.Cache.TTL + + if sliceutils.StringContains(stores, CacheStoreRistretto) { + ristrettoCache, err := ristretto.NewCache(&ristretto.Config{ + NumCounters: conf.Cache.Ristretto.NumCounters, + MaxCost: conf.Cache.Ristretto.MaxCost, + BufferItems: conf.Cache.Ristretto.BufferItems, + Cost: func(value interface{}) int64 { + return int64(unsafe.Sizeof(value)) + }, + }) + if err != nil { + panic(err) + } + var ristrettoStore *ristretto_store.RistrettoStore + if ttl > 0 { + ristrettoStore = ristretto_store.NewRistretto(ristrettoCache, store.WithExpiration(time.Duration(ttl)*time.Second)) + } else { + ristrettoStore = ristretto_store.NewRistretto(ristrettoCache) + } + setterCaches = append(setterCaches, gocache.New[any](ristrettoStore)) + } + + if sliceutils.StringContains(stores, CacheStoreRedis) { + redisAddr := conf.Cache.Redis.Addr + if stringutils.IsNotEmpty(redisAddr) { + addrs := strings.Split(redisAddr, ",") + var redisClient redis_store.RedisClientInterface + if len(addrs) > 1 { + redisClient = redis.NewClusterClient(&redis.ClusterOptions{ + Addrs: addrs, + RouteByLatency: conf.Cache.Redis.RouteByLatency, + RouteRandomly: conf.Cache.Redis.RouteRandomly, + }) + } else { + redisClient = redis.NewClient(&redis.Options{Addr: addrs[0]}) + } + var redisStore *redis_store.RedisStore + if ttl > 0 { + redisStore = redis_store.NewRedis(redisClient, store.WithExpiration(time.Duration(ttl)*time.Second)) + } else { + redisStore = redis_store.NewRedis(redisClient) + } + setterCaches = append(setterCaches, gocache.New[any](redisStore)) + } + } + + var cacheManager gocache.CacheInterface[any] + + // Initialize chained cache + cacheManager = gocache.NewChain[any](setterCaches...) + + serviceName := conf.Service.Name + if stringutils.IsNotEmpty(serviceName) { + // Initializes Prometheus metrics service + promMetrics := metrics.NewPrometheus(serviceName) + + // Initialize chained cache + cacheManager = gocache.NewMetric[any](promMetrics, cacheManager) + } + + return cacheManager +} diff --git a/framework/config/config.go b/framework/config/config.go index 0b42f6a5..f5ac2064 100644 --- a/framework/config/config.go +++ b/framework/config/config.go @@ -2,11 +2,22 @@ package config import ( "fmt" + "net" + "net/url" + "os" + "strconv" + "strings" + "sync" + "github.com/apolloconfig/agollo/v4" "github.com/apolloconfig/agollo/v4/env/config" "github.com/pkg/errors" "github.com/rs/zerolog" "github.com/sirupsen/logrus" + "github.com/wubin1989/nacos-sdk-go/v2/common/constant" + "github.com/wubin1989/nacos-sdk-go/v2/vo" + _ "go.uber.org/automaxprocs" + "github.com/unionj-cloud/go-doudou/v2/framework" "github.com/unionj-cloud/go-doudou/v2/framework/configmgr" "github.com/unionj-cloud/go-doudou/v2/toolkit/cast" @@ -14,15 +25,6 @@ import ( "github.com/unionj-cloud/go-doudou/v2/toolkit/stringutils" "github.com/unionj-cloud/go-doudou/v2/toolkit/yaml" "github.com/unionj-cloud/go-doudou/v2/toolkit/zlogger" - "github.com/wubin1989/nacos-sdk-go/v2/common/constant" - "github.com/wubin1989/nacos-sdk-go/v2/vo" - _ "go.uber.org/automaxprocs" - "net" - "net/url" - "os" - "strconv" - "strings" - "sync" ) func LoadConfigFromLocal() { @@ -281,19 +283,20 @@ const ( GddDBPostgresWithoutReturning envVariable = "GDD_DB_POSTGRES_WITHOUTRETURNING" GddDbPrometheusEnable envVariable = "GDD_DB_PROMETHEUS_ENABLE" - GddDbPrometheusRefreshInterval envVariable = "GDD_DB_PROMETHEUS_REFRESH_INTERVAL" - GddDbPrometheusDBName envVariable = "GDD_DB_PROMETHEUS_DB_NAME" + GddDbPrometheusRefreshInterval envVariable = "GDD_DB_PROMETHEUS_REFRESHINTERVAL" + GddDbPrometheusDBName envVariable = "GDD_DB_PROMETHEUS_DBNAME" GddDbCacheEnable envVariable = "GDD_DB_CACHE_ENABLE" + GddCacheTTL envVariable = "GDD_CACHE_TTL" + GddCacheStores envVariable = "GDD_CACHE_STORES" GddCacheRedisAddr envVariable = "GDD_CACHE_REDIS_ADDR" - GddCacheRedisRouteByLatency envVariable = "GDD_CACHE_REDIS_ROUTE_BY_LATENCY" - GddCacheRedisRouteRandomly envVariable = "GDD_CACHE_REDIS_ROUTE_RANDOMLY" - GddCacheRedisTTL envVariable = "GDD_CACHE_REDIS_TTL" + GddCacheRedisRouteByLatency envVariable = "GDD_CACHE_REDIS_ROUTEBYLATENCY" + GddCacheRedisRouteRandomly envVariable = "GDD_CACHE_REDIS_ROUTERANDOMLY" GddCacheRistrettoNumCounters envVariable = "GDD_CACHE_RISTRETTO_NUMCOUNTERS" - GddCacheRistrettoMaxCost envVariable = "GDD_CACHE_RISTRETTO_MAX_COST" - GddCacheRistrettoBufferItems envVariable = "GDD_CACHE_RISTRETTO_BUFFER_ITEMS" + GddCacheRistrettoMaxCost envVariable = "GDD_CACHE_RISTRETTO_MAXCOST" + GddCacheRistrettoBufferItems envVariable = "GDD_CACHE_RISTRETTO_BUFFERITEMS" GddZkServers envVariable = "GDD_ZK_SERVERS" GddZkSequence envVariable = "GDD_ZK_SEQUENCE" @@ -471,5 +474,30 @@ type Config struct { ConnMaxLifetime string ConnMaxIdleTime string } + Cache struct { + Enable bool + } + Prometheus struct { + Enable bool + RefreshInterval int `default:"15"` + DBName string + } + } + Cache struct { + TTL int + Stores string + Redis struct { + Addr string + RouteByLatency bool `default:"true"` + RouteRandomly bool + } + Ristretto struct { + NumCounters int64 `default:"1000"` + MaxCost int64 `default:"100"` + BufferItems int64 `default:"64"` + } + } + Service struct { + Name string } } diff --git a/framework/config/default.go b/framework/config/default.go index c7cd31be..10e2c22a 100644 --- a/framework/config/default.go +++ b/framework/config/default.go @@ -1,8 +1,9 @@ package config import ( - "github.com/unionj-cloud/go-doudou/v2/framework/configmgr" "gorm.io/gorm/logger" + + "github.com/unionj-cloud/go-doudou/v2/framework/configmgr" ) const FrameworkName = "go-doudou" @@ -137,7 +138,8 @@ const ( DefaultGddCacheRedisAddr = "" DefaultGddCacheRedisRouteByLatency = true DefaultGddCacheRedisRouteRandomly = false - DefaultGddCacheRedisTTL = 0 + DefaultGddCacheTTL = 0 + DefaultGddCacheStores = "" DefaultGddCacheRistrettoNumCounters = 1000 DefaultGddCacheRistrettoMaxCost = 100 diff --git a/framework/database/database.go b/framework/database/database.go index bedc328c..e4c6e80a 100644 --- a/framework/database/database.go +++ b/framework/database/database.go @@ -1,19 +1,12 @@ package database import ( - "github.com/dgraph-io/ristretto" - "github.com/eko/gocache/lib/v4/cache" - "github.com/eko/gocache/lib/v4/metrics" - "github.com/eko/gocache/lib/v4/store" - redis_store "github.com/eko/gocache/store/redis/v4" - ristretto_store "github.com/eko/gocache/store/ristretto/v4" - "github.com/redis/go-redis/v9" - "github.com/unionj-cloud/go-doudou/v2/framework/config" - "github.com/unionj-cloud/go-doudou/v2/toolkit/caches" - "github.com/unionj-cloud/go-doudou/v2/toolkit/cast" - "github.com/unionj-cloud/go-doudou/v2/toolkit/errorx" - "github.com/unionj-cloud/go-doudou/v2/toolkit/stringutils" - "github.com/unionj-cloud/go-doudou/v2/toolkit/zlogger" + "log" + "os" + "strings" + "time" + + gocache "github.com/eko/gocache/lib/v4/cache" "gorm.io/driver/clickhouse" "gorm.io/driver/mysql" "gorm.io/driver/postgres" @@ -23,11 +16,14 @@ import ( "gorm.io/gorm/logger" "gorm.io/gorm/schema" "gorm.io/plugin/prometheus" - "log" - "os" - "strings" - "time" - "unsafe" + + "github.com/unionj-cloud/go-doudou/v2/framework/cache" + "github.com/unionj-cloud/go-doudou/v2/framework/config" + "github.com/unionj-cloud/go-doudou/v2/toolkit/caches" + "github.com/unionj-cloud/go-doudou/v2/toolkit/cast" + "github.com/unionj-cloud/go-doudou/v2/toolkit/errorx" + "github.com/unionj-cloud/go-doudou/v2/toolkit/stringutils" + "github.com/unionj-cloud/go-doudou/v2/toolkit/zlogger" ) const ( @@ -162,66 +158,8 @@ func init() { cast.ToUInt32OrDefault(config.GddDbPrometheusRefreshInterval.Load(), config.DefaultGddDbPrometheusRefreshInterval), nil, collectors...) } - if cast.ToBoolOrDefault(config.GddDbCacheEnable.Load(), config.DefaultGddDbCacheEnable) { - var cacheManager cache.CacheInterface[any] - var setterCaches []cache.SetterCacheInterface[any] - - // Initialize Ristretto cache and Redis client - ristrettoCache, err := ristretto.NewCache(&ristretto.Config{ - NumCounters: cast.ToInt64OrDefault(config.GddCacheRistrettoNumCounters.Load(), config.DefaultGddCacheRistrettoNumCounters), - MaxCost: cast.ToInt64OrDefault(config.GddCacheRistrettoMaxCost.Load(), config.DefaultGddCacheRistrettoMaxCost), - BufferItems: cast.ToInt64OrDefault(config.GddCacheRistrettoBufferItems.Load(), config.DefaultGddCacheRistrettoBufferItems), - Cost: func(value interface{}) int64 { - return int64(unsafe.Sizeof(value)) - }, - }) - if err != nil { - panic(err) - } - ttl := cast.ToIntOrDefault(config.GddCacheRedisTTL.Load(), config.DefaultGddCacheRedisTTL) - var ristrettoStore *ristretto_store.RistrettoStore - if ttl > 0 { - ristrettoStore = ristretto_store.NewRistretto(ristrettoCache, store.WithExpiration(time.Duration(ttl)*time.Second)) - } else { - ristrettoStore = ristretto_store.NewRistretto(ristrettoCache) - } - setterCaches = append(setterCaches, cache.New[any](ristrettoStore)) - - redisAddr := config.GddCacheRedisAddr.LoadOrDefault(config.DefaultGddCacheRedisAddr) - if stringutils.IsNotEmpty(redisAddr) { - addrs := strings.Split(redisAddr, ",") - var redisClient redis_store.RedisClientInterface - if len(addrs) > 1 { - redisClient = redis.NewClusterClient(&redis.ClusterOptions{ - Addrs: addrs, - RouteByLatency: cast.ToBoolOrDefault(config.GddCacheRedisRouteByLatency.Load(), config.DefaultGddCacheRedisRouteByLatency), - RouteRandomly: cast.ToBoolOrDefault(config.GddCacheRedisRouteRandomly.Load(), config.DefaultGddCacheRedisRouteRandomly), - }) - } else { - redisClient = redis.NewClient(&redis.Options{Addr: addrs[0]}) - } - var redisStore *redis_store.RedisStore - if ttl > 0 { - redisStore = redis_store.NewRedis(redisClient, store.WithExpiration(time.Duration(ttl)*time.Second)) - } else { - redisStore = redis_store.NewRedis(redisClient) - } - setterCaches = append(setterCaches, cache.New[any](redisStore)) - } - - // Initialize chained cache - cacheManager = cache.NewChain[any](setterCaches...) - - serviceName := config.GddServiceName.LoadOrDefault(config.DefaultGddServiceName) - if stringutils.IsNotEmpty(serviceName) { - // Initializes Prometheus metrics service - promMetrics := metrics.NewPrometheus(serviceName) - - // Initialize chained cache - cacheManager = cache.NewMetric[any](promMetrics, cacheManager) - } - - ConfigureDBCache(Db, cacheManager) + if cast.ToBoolOrDefault(config.GddDbCacheEnable.Load(), config.DefaultGddDbCacheEnable) && cache.CacheManager != nil { + ConfigureDBCache(Db, cache.CacheManager) } } @@ -335,6 +273,19 @@ func NewDb(conf config.Config) (db *gorm.DB) { } } sqlDB.SetConnMaxIdleTime(maxIdleTime) + + if conf.Db.Prometheus.Enable && + stringutils.IsNotEmpty(conf.Db.Prometheus.DBName) { + var collectors []prometheus.MetricsCollector + switch driver { + case DriverMysql, DriverTidb: + collectors = append(collectors, &prometheus.MySQL{}) + case DriverPostgres: + collectors = append(collectors, &prometheus.Postgres{}) + } + ConfigureMetrics(Db, conf.Db.Prometheus.DBName, uint32(conf.Db.Prometheus.RefreshInterval), + nil, collectors...) + } return } @@ -347,7 +298,7 @@ func ConfigureMetrics(db *gorm.DB, dbName string, refreshInterval uint32, labels })) } -func ConfigureDBCache(db *gorm.DB, cacheManager cache.CacheInterface[any]) { +func ConfigureDBCache(db *gorm.DB, cacheManager gocache.CacheInterface[any]) { cachesPlugin := &caches.Caches{Conf: &caches.Config{ Easer: true, Cacher: NewCacherAdapter(cacheManager), diff --git a/toolkit/caches/caches.go b/toolkit/caches/caches.go index d91706e3..fff4804d 100644 --- a/toolkit/caches/caches.go +++ b/toolkit/caches/caches.go @@ -1,11 +1,11 @@ package caches import ( - "gorm.io/gorm/callbacks" - "gorm.io/gorm/clause" "sync" "gorm.io/gorm" + "gorm.io/gorm/callbacks" + "gorm.io/gorm/clause" ) type Caches struct { @@ -140,30 +140,44 @@ func (c *Caches) checkCache(db *gorm.DB, identifier string) bool { return false } -func getFromClause(db *gorm.DB) *clause.From { +func getClause[T clause.Interface](db *gorm.DB) *T { if db == nil || db.Statement == nil { - return &clause.From{} + return new(T) } - c, ok := db.Statement.Clauses[clause.From{}.Name()] + c, ok := db.Statement.Clauses[(*(new(T))).Name()] if !ok || c.Expression == nil { - return &clause.From{} + return new(T) } - from, ok := c.Expression.(clause.From) + value, ok := c.Expression.(T) if !ok { - return &clause.From{} + return new(T) } - return &from + return &value } func getTables(db *gorm.DB) []string { - // Find all table names within the sql statement as cache tags - from := getFromClause(db) var tables []string - for _, item := range from.Tables { - tables = append(tables, item.Name) + // Find all table names within the sql statement as cache tags + from := getClause[clause.From](db) + if from != nil { + for _, item := range from.Tables { + tables = append(tables, item.Name) + } + for _, item := range from.Joins { + tables = append(tables, item.Table.Name) + } + } + insert := getClause[clause.Insert](db) + if insert != nil { + tables = append(tables, insert.Table.Name) + } + update := getClause[clause.Update](db) + if update != nil { + tables = append(tables, update.Table.Name) } - for _, item := range from.Joins { - tables = append(tables, item.Table.Name) + locking := getClause[clause.Locking](db) + if locking != nil { + tables = append(tables, locking.Table.Name) } return tables }