mirror of
https://github.com/andwati/telecom-platform.git
synced 2026-10-23 13:12:16 +00:00
feat: Add cache package with in-memory implementation, LRU eviction, and multi-cache management
- Add CacheItem struct with key/value/expiration/createdAt/accessCount/lastAccess fields and IsExpired method - Add Cache interface with Get/Set/Delete/Clear/Keys/Size/Stats methods - Add CacheStats struct with hits/misses/hitRate/size/evictions/totalItems metrics - Add MemoryCache with sync.RWMutex, maxSize limit, automatic cleanup goroutine, LRU eviction, and access tracking - Add RedisCache placeholder
This commit is contained in:
+412
@@ -0,0 +1,412 @@
|
||||
package cache
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
// CacheItem represents an item in the cache
|
||||
type CacheItem struct {
|
||||
Key string `json:"key"`
|
||||
Value any `json:"value"`
|
||||
Expiration time.Time `json:"expiration"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
AccessCount int64 `json:"access_count"`
|
||||
LastAccess time.Time `json:"last_access"`
|
||||
}
|
||||
|
||||
// IsExpired checks if the cache item has expired
|
||||
func (ci *CacheItem) IsExpired() bool {
|
||||
return time.Now().After(ci.Expiration)
|
||||
}
|
||||
|
||||
// Cache interface defines the cache operations
|
||||
type Cache interface {
|
||||
Get(key string) (any, bool)
|
||||
Set(key string, value any, ttl time.Duration)
|
||||
Delete(key string)
|
||||
Clear()
|
||||
Keys() []string
|
||||
Size() int
|
||||
Stats() CacheStats
|
||||
}
|
||||
|
||||
// CacheStats provides cache statistics
|
||||
type CacheStats struct {
|
||||
Hits int64 `json:"hits"`
|
||||
Misses int64 `json:"misses"`
|
||||
HitRate float64 `json:"hit_rate"`
|
||||
Size int `json:"size"`
|
||||
Evictions int64 `json:"evictions"`
|
||||
TotalItems int64 `json:"total_items"`
|
||||
}
|
||||
|
||||
// MemoryCache is an in-memory cache implementation
|
||||
type MemoryCache struct {
|
||||
items map[string]*CacheItem
|
||||
mu sync.RWMutex
|
||||
stats CacheStats
|
||||
maxSize int
|
||||
cleanupTick *time.Ticker
|
||||
stopCleanup chan bool
|
||||
}
|
||||
|
||||
// NewMemoryCache creates a new memory cache
|
||||
func NewMemoryCache(maxSize int, cleanupInterval time.Duration) *MemoryCache {
|
||||
cache := &MemoryCache{
|
||||
items: make(map[string]*CacheItem),
|
||||
maxSize: maxSize,
|
||||
cleanupTick: time.NewTicker(cleanupInterval),
|
||||
stopCleanup: make(chan bool),
|
||||
}
|
||||
|
||||
// Start cleanup goroutine
|
||||
go cache.cleanup()
|
||||
|
||||
return cache
|
||||
}
|
||||
|
||||
// cleanup removes expired items from cache
|
||||
func (mc *MemoryCache) cleanup() {
|
||||
for {
|
||||
select {
|
||||
case <-mc.cleanupTick.C:
|
||||
mc.removeExpired()
|
||||
case <-mc.stopCleanup:
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// removeExpired removes expired items from cache
|
||||
func (mc *MemoryCache) removeExpired() {
|
||||
mc.mu.Lock()
|
||||
defer mc.mu.Unlock()
|
||||
|
||||
now := time.Now()
|
||||
for key, item := range mc.items {
|
||||
if now.After(item.Expiration) {
|
||||
delete(mc.items, key)
|
||||
mc.stats.Evictions++
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Get retrieves an item from cache
|
||||
func (mc *MemoryCache) Get(key string) (any, bool) {
|
||||
mc.mu.RLock()
|
||||
defer mc.mu.RUnlock()
|
||||
|
||||
item, exists := mc.items[key]
|
||||
if !exists {
|
||||
mc.stats.Misses++
|
||||
return nil, false
|
||||
}
|
||||
|
||||
if item.IsExpired() {
|
||||
delete(mc.items, key)
|
||||
mc.stats.Misses++
|
||||
mc.stats.Evictions++
|
||||
return nil, false
|
||||
}
|
||||
|
||||
// Update access statistics
|
||||
item.AccessCount++
|
||||
item.LastAccess = time.Now()
|
||||
mc.stats.Hits++
|
||||
|
||||
return item.Value, true
|
||||
}
|
||||
|
||||
// Set stores an item in cache
|
||||
func (mc *MemoryCache) Set(key string, value any, ttl time.Duration) {
|
||||
mc.mu.Lock()
|
||||
defer mc.mu.Unlock()
|
||||
|
||||
// Check if we need to evict items to make space
|
||||
if len(mc.items) >= mc.maxSize {
|
||||
mc.evictLRU()
|
||||
}
|
||||
|
||||
expiration := time.Now().Add(ttl)
|
||||
item := &CacheItem{
|
||||
Key: key,
|
||||
Value: value,
|
||||
Expiration: expiration,
|
||||
CreatedAt: time.Now(),
|
||||
AccessCount: 1,
|
||||
LastAccess: time.Now(),
|
||||
}
|
||||
|
||||
mc.items[key] = item
|
||||
mc.stats.TotalItems++
|
||||
}
|
||||
|
||||
// evictLRU removes the least recently used item
|
||||
func (mc *MemoryCache) evictLRU() {
|
||||
var lruKey string
|
||||
var lruTime time.Time
|
||||
|
||||
for key, item := range mc.items {
|
||||
if lruKey == "" || item.LastAccess.Before(lruTime) {
|
||||
lruKey = key
|
||||
lruTime = item.LastAccess
|
||||
}
|
||||
}
|
||||
|
||||
if lruKey != "" {
|
||||
delete(mc.items, lruKey)
|
||||
mc.stats.Evictions++
|
||||
}
|
||||
}
|
||||
|
||||
// Delete removes an item from cache
|
||||
func (mc *MemoryCache) Delete(key string) {
|
||||
mc.mu.Lock()
|
||||
defer mc.mu.Unlock()
|
||||
|
||||
delete(mc.items, key)
|
||||
}
|
||||
|
||||
// Clear removes all items from cache
|
||||
func (mc *MemoryCache) Clear() {
|
||||
mc.mu.Lock()
|
||||
defer mc.mu.Unlock()
|
||||
|
||||
mc.items = make(map[string]*CacheItem)
|
||||
mc.stats.Evictions += int64(len(mc.items))
|
||||
}
|
||||
|
||||
// Keys returns all keys in cache
|
||||
func (mc *MemoryCache) Keys() []string {
|
||||
mc.mu.RLock()
|
||||
defer mc.mu.RUnlock()
|
||||
|
||||
keys := make([]string, 0, len(mc.items))
|
||||
for key := range mc.items {
|
||||
keys = append(keys, key)
|
||||
}
|
||||
return keys
|
||||
}
|
||||
|
||||
// Size returns the current cache size
|
||||
func (mc *MemoryCache) Size() int {
|
||||
mc.mu.RLock()
|
||||
defer mc.mu.RUnlock()
|
||||
|
||||
return len(mc.items)
|
||||
}
|
||||
|
||||
// Stats returns cache statistics
|
||||
func (mc *MemoryCache) Stats() CacheStats {
|
||||
mc.mu.RLock()
|
||||
defer mc.mu.RUnlock()
|
||||
|
||||
total := mc.stats.Hits + mc.stats.Misses
|
||||
hitRate := 0.0
|
||||
if total > 0 {
|
||||
hitRate = float64(mc.stats.Hits) / float64(total) * 100
|
||||
}
|
||||
|
||||
return CacheStats{
|
||||
Hits: mc.stats.Hits,
|
||||
Misses: mc.stats.Misses,
|
||||
HitRate: hitRate,
|
||||
Size: len(mc.items),
|
||||
Evictions: mc.stats.Evictions,
|
||||
TotalItems: mc.stats.TotalItems,
|
||||
}
|
||||
}
|
||||
|
||||
// Close stops the cache cleanup goroutine
|
||||
func (mc *MemoryCache) Close() {
|
||||
mc.cleanupTick.Stop()
|
||||
close(mc.stopCleanup)
|
||||
}
|
||||
|
||||
// RedisCache is a Redis-based cache implementation (placeholder)
|
||||
type RedisCache struct {
|
||||
// Redis client would be here
|
||||
}
|
||||
|
||||
// NewRedisCache creates a new Redis cache
|
||||
func NewRedisCache(addr string, password string, db int) *RedisCache {
|
||||
// Implementation would connect to Redis
|
||||
return &RedisCache{}
|
||||
}
|
||||
|
||||
// Get retrieves an item from Redis cache
|
||||
func (rc *RedisCache) Get(key string) (any, bool) {
|
||||
// Redis GET implementation
|
||||
return nil, false
|
||||
}
|
||||
|
||||
// Set stores an item in Redis cache
|
||||
func (rc *RedisCache) Set(key string, value any, ttl time.Duration) {
|
||||
// Redis SET implementation
|
||||
}
|
||||
|
||||
// Delete removes an item from Redis cache
|
||||
func (rc *RedisCache) Delete(key string) {
|
||||
// Redis DEL implementation
|
||||
}
|
||||
|
||||
// Clear removes all items from Redis cache
|
||||
func (rc *RedisCache) Clear() {
|
||||
// Redis FLUSH implementation
|
||||
}
|
||||
|
||||
// Keys returns all keys in Redis cache
|
||||
func (rc *RedisCache) Keys() []string {
|
||||
// Redis KEYS implementation
|
||||
return nil
|
||||
}
|
||||
|
||||
// Size returns the current cache size
|
||||
func (rc *RedisCache) Size() int {
|
||||
// Redis DBSIZE implementation
|
||||
return 0
|
||||
}
|
||||
|
||||
// Stats returns cache statistics
|
||||
func (rc *RedisCache) Stats() CacheStats {
|
||||
// Redis INFO implementation
|
||||
return CacheStats{}
|
||||
}
|
||||
|
||||
// CacheManager manages multiple cache instances
|
||||
type CacheManager struct {
|
||||
caches map[string]Cache
|
||||
mu sync.RWMutex
|
||||
}
|
||||
|
||||
// NewCacheManager creates a new cache manager
|
||||
func NewCacheManager() *CacheManager {
|
||||
return &CacheManager{
|
||||
caches: make(map[string]Cache),
|
||||
}
|
||||
}
|
||||
|
||||
// AddCache adds a cache instance
|
||||
func (cm *CacheManager) AddCache(name string, cache Cache) {
|
||||
cm.mu.Lock()
|
||||
defer cm.mu.Unlock()
|
||||
cm.caches[name] = cache
|
||||
}
|
||||
|
||||
// GetCache retrieves a cache instance
|
||||
func (cm *CacheManager) GetCache(name string) (Cache, bool) {
|
||||
cm.mu.RLock()
|
||||
defer cm.mu.RUnlock()
|
||||
cache, exists := cm.caches[name]
|
||||
return cache, exists
|
||||
}
|
||||
|
||||
// RemoveCache removes a cache instance
|
||||
func (cm *CacheManager) RemoveCache(name string) {
|
||||
cm.mu.Lock()
|
||||
defer cm.mu.Unlock()
|
||||
delete(cm.caches, name)
|
||||
}
|
||||
|
||||
// GetAllStats returns statistics for all caches
|
||||
func (cm *CacheManager) GetAllStats() map[string]CacheStats {
|
||||
cm.mu.RLock()
|
||||
defer cm.mu.RUnlock()
|
||||
|
||||
stats := make(map[string]CacheStats)
|
||||
for name, cache := range cm.caches {
|
||||
stats[name] = cache.Stats()
|
||||
}
|
||||
return stats
|
||||
}
|
||||
|
||||
// ClearAll clears all caches
|
||||
func (cm *CacheManager) ClearAll() {
|
||||
cm.mu.RLock()
|
||||
defer cm.mu.RUnlock()
|
||||
|
||||
for _, cache := range cm.caches {
|
||||
cache.Clear()
|
||||
}
|
||||
}
|
||||
|
||||
// Global cache manager instance
|
||||
var globalCacheManager *CacheManager
|
||||
|
||||
// InitializeCacheManager initializes the global cache manager
|
||||
func InitializeCacheManager() {
|
||||
globalCacheManager = NewCacheManager()
|
||||
|
||||
// Add default memory cache
|
||||
memoryCache := NewMemoryCache(1000, 5*time.Minute)
|
||||
globalCacheManager.AddCache("default", memoryCache)
|
||||
|
||||
// Add specialized caches
|
||||
userCache := NewMemoryCache(500, 10*time.Minute)
|
||||
globalCacheManager.AddCache("users", userCache)
|
||||
|
||||
serviceCache := NewMemoryCache(200, 2*time.Minute)
|
||||
globalCacheManager.AddCache("services", serviceCache)
|
||||
|
||||
metricsCache := NewMemoryCache(1000, 1*time.Minute)
|
||||
globalCacheManager.AddCache("metrics", metricsCache)
|
||||
}
|
||||
|
||||
// GetCacheManager returns the global cache manager
|
||||
func GetCacheManager() *CacheManager {
|
||||
return globalCacheManager
|
||||
}
|
||||
|
||||
// GetDefaultCache returns the default cache
|
||||
func GetDefaultCache() Cache {
|
||||
if globalCacheManager == nil {
|
||||
InitializeCacheManager()
|
||||
}
|
||||
cache, _ := globalCacheManager.GetCache("default")
|
||||
return cache
|
||||
}
|
||||
|
||||
// GetUserCache returns the user cache
|
||||
func GetUserCache() Cache {
|
||||
if globalCacheManager == nil {
|
||||
InitializeCacheManager()
|
||||
}
|
||||
cache, _ := globalCacheManager.GetCache("users")
|
||||
return cache
|
||||
}
|
||||
|
||||
// GetServiceCache returns the service cache
|
||||
func GetServiceCache() Cache {
|
||||
if globalCacheManager == nil {
|
||||
InitializeCacheManager()
|
||||
}
|
||||
cache, _ := globalCacheManager.GetCache("services")
|
||||
return cache
|
||||
}
|
||||
|
||||
// GetMetricsCache returns the metrics cache
|
||||
func GetMetricsCache() Cache {
|
||||
if globalCacheManager == nil {
|
||||
InitializeCacheManager()
|
||||
}
|
||||
cache, _ := globalCacheManager.GetCache("metrics")
|
||||
return cache
|
||||
}
|
||||
|
||||
// CacheKey generates a cache key with namespace
|
||||
func CacheKey(namespace, key string) string {
|
||||
return fmt.Sprintf("%s:%s", namespace, key)
|
||||
}
|
||||
|
||||
// SerializeValue serializes a value for caching
|
||||
func SerializeValue(value any) ([]byte, error) {
|
||||
return json.Marshal(value)
|
||||
}
|
||||
|
||||
// DeserializeValue deserializes a value from cache
|
||||
func DeserializeValue(data []byte, target any) error {
|
||||
return json.Unmarshal(data, target)
|
||||
}
|
||||
Reference in New Issue
Block a user