246 lines
5.6 KiB
Go
246 lines
5.6 KiB
Go
package cache
|
|
|
|
import (
|
|
"context"
|
|
"sync"
|
|
"time"
|
|
)
|
|
|
|
// memoryCache 内存缓存实现
|
|
type memoryCache struct {
|
|
data sync.Map
|
|
ttlMap sync.Map
|
|
maxSize int
|
|
stats CacheStats
|
|
}
|
|
|
|
// NewMemoryCache 创建内存缓存
|
|
func NewMemoryCache(maxSize int) Cache {
|
|
cache := &memoryCache{
|
|
data: sync.Map{},
|
|
ttlMap: sync.Map{},
|
|
maxSize: maxSize,
|
|
}
|
|
|
|
// 启动清理协程
|
|
go cache.cleanupRoutine()
|
|
|
|
return cache
|
|
}
|
|
|
|
// cleanupRoutine 定期清理过期数据
|
|
func (m *memoryCache) cleanupRoutine() {
|
|
ticker := time.NewTicker(1 * time.Minute)
|
|
defer ticker.Stop()
|
|
|
|
for range ticker.C {
|
|
m.cleanupExpired()
|
|
}
|
|
}
|
|
|
|
// cleanupExpired 清理过期数据
|
|
func (m *memoryCache) cleanupExpired() {
|
|
now := time.Now()
|
|
m.ttlMap.Range(func(key, value interface{}) bool {
|
|
if expireTime, ok := value.(time.Time); ok && now.After(expireTime) {
|
|
m.data.Delete(key)
|
|
m.ttlMap.Delete(key)
|
|
}
|
|
return true
|
|
})
|
|
}
|
|
|
|
func (m *memoryCache) Get(ctx context.Context, key string) (string, error) {
|
|
defer func(start time.Time) {
|
|
m.stats.AvgRespTime = (m.stats.AvgRespTime + time.Since(start).Nanoseconds()) / 2
|
|
}(time.Now())
|
|
|
|
value, ok := m.data.Load(key)
|
|
if !ok {
|
|
m.stats.RecordMiss()
|
|
return "", ErrCacheMiss
|
|
}
|
|
|
|
// 检查是否过期
|
|
if ttl, exists := m.ttlMap.Load(key); exists {
|
|
if expireTime, ok := ttl.(time.Time); ok && time.Now().After(expireTime) {
|
|
m.data.Delete(key)
|
|
m.ttlMap.Delete(key)
|
|
m.stats.RecordMiss()
|
|
return "", ErrCacheMiss
|
|
}
|
|
}
|
|
|
|
m.stats.RecordHit()
|
|
if str, ok := value.(string); ok {
|
|
return str, nil
|
|
}
|
|
|
|
m.stats.RecordError()
|
|
return "", NewCacheError("invalid cache value type")
|
|
}
|
|
|
|
func (m *memoryCache) Set(ctx context.Context, key string, value string, ttl time.Duration) error {
|
|
defer func(start time.Time) {
|
|
m.stats.AvgRespTime = (m.stats.AvgRespTime + time.Since(start).Nanoseconds()) / 2
|
|
}(time.Now())
|
|
|
|
// 简单的容量控制(生产环境建议使用LRU库)
|
|
if m.maxSize > 0 {
|
|
// 简单的清理策略:当存储量接近最大值时,清理一些过期数据
|
|
size := 0
|
|
m.data.Range(func(key, value interface{}) bool {
|
|
size++
|
|
return size < m.maxSize
|
|
})
|
|
|
|
if size >= m.maxSize {
|
|
m.cleanupExpired()
|
|
}
|
|
}
|
|
|
|
m.data.Store(key, value)
|
|
if ttl > 0 {
|
|
m.ttlMap.Store(key, time.Now().Add(ttl))
|
|
} else {
|
|
m.ttlMap.Delete(key) // 永不过期
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (m *memoryCache) Del(ctx context.Context, keys ...string) error {
|
|
defer func(start time.Time) {
|
|
m.stats.AvgRespTime = (m.stats.AvgRespTime + time.Since(start).Nanoseconds()) / 2
|
|
}(time.Now())
|
|
|
|
for _, key := range keys {
|
|
m.data.Delete(key)
|
|
m.ttlMap.Delete(key)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (m *memoryCache) Exists(ctx context.Context, key string) (bool, error) {
|
|
defer func(start time.Time) {
|
|
m.stats.AvgRespTime = (m.stats.AvgRespTime + time.Since(start).Nanoseconds()) / 2
|
|
}(time.Now())
|
|
|
|
_, exists := m.data.Load(key)
|
|
if !exists {
|
|
return false, nil
|
|
}
|
|
|
|
// 检查是否过期
|
|
if ttl, exists := m.ttlMap.Load(key); exists {
|
|
if expireTime, ok := ttl.(time.Time); ok && time.Now().After(expireTime) {
|
|
m.data.Delete(key)
|
|
m.ttlMap.Delete(key)
|
|
return false, nil
|
|
}
|
|
}
|
|
|
|
return true, nil
|
|
}
|
|
|
|
func (m *memoryCache) TTL(ctx context.Context, key string) (time.Duration, error) {
|
|
defer func(start time.Time) {
|
|
m.stats.AvgRespTime = (m.stats.AvgRespTime + time.Since(start).Nanoseconds()) / 2
|
|
}(time.Now())
|
|
|
|
ttl, exists := m.ttlMap.Load(key)
|
|
if !exists {
|
|
return -1, nil // 没有设置过期时间
|
|
}
|
|
|
|
if expireTime, ok := ttl.(time.Time); ok {
|
|
remaining := time.Until(expireTime)
|
|
if remaining <= 0 {
|
|
m.data.Delete(key)
|
|
m.ttlMap.Delete(key)
|
|
return -2, nil // 已过期
|
|
}
|
|
return remaining, nil
|
|
}
|
|
|
|
return -1, nil
|
|
}
|
|
|
|
func (m *memoryCache) Expire(ctx context.Context, key string, ttl time.Duration) error {
|
|
defer func(start time.Time) {
|
|
m.stats.AvgRespTime = (m.stats.AvgRespTime + time.Since(start).Nanoseconds()) / 2
|
|
}(time.Now())
|
|
|
|
if ttl <= 0 {
|
|
m.ttlMap.Delete(key)
|
|
return nil
|
|
}
|
|
|
|
// 检查key是否存在
|
|
if _, exists := m.data.Load(key); !exists {
|
|
return NewCacheError("key not found")
|
|
}
|
|
|
|
m.ttlMap.Store(key, time.Now().Add(ttl))
|
|
return nil
|
|
}
|
|
|
|
func (m *memoryCache) Incr(ctx context.Context, key string) (int64, error) {
|
|
defer func(start time.Time) {
|
|
m.stats.AvgRespTime = (m.stats.AvgRespTime + time.Since(start).Nanoseconds()) / 2
|
|
}(time.Now())
|
|
|
|
// 内存缓存的数值操作实现比较简单
|
|
return 0, NewCacheError("Incr not implemented for memory cache")
|
|
}
|
|
|
|
func (m *memoryCache) Decr(ctx context.Context, key string) (int64, error) {
|
|
defer func(start time.Time) {
|
|
m.stats.AvgRespTime = (m.stats.AvgRespTime + time.Since(start).Nanoseconds()) / 2
|
|
}(time.Now())
|
|
|
|
return 0, NewCacheError("Decr not implemented for memory cache")
|
|
}
|
|
|
|
func (m *memoryCache) MGet(ctx context.Context, keys ...string) ([]string, error) {
|
|
defer func(start time.Time) {
|
|
m.stats.AvgRespTime = (m.stats.AvgRespTime + time.Since(start).Nanoseconds()) / 2
|
|
}(time.Now())
|
|
|
|
results := make([]string, len(keys))
|
|
for i, key := range keys {
|
|
if value, err := m.Get(ctx, key); err == nil {
|
|
results[i] = value
|
|
} else {
|
|
results[i] = ""
|
|
}
|
|
}
|
|
return results, nil
|
|
}
|
|
|
|
func (m *memoryCache) MSet(ctx context.Context, pairs map[string]string, ttl time.Duration) error {
|
|
defer func(start time.Time) {
|
|
m.stats.AvgRespTime = (m.stats.AvgRespTime + time.Since(start).Nanoseconds()) / 2
|
|
}(time.Now())
|
|
|
|
for key, value := range pairs {
|
|
if err := m.Set(ctx, key, value, ttl); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (m *memoryCache) Ping(ctx context.Context) error {
|
|
return nil
|
|
}
|
|
|
|
func (m *memoryCache) Close() error {
|
|
return nil
|
|
}
|
|
|
|
// GetStats 获取缓存统计信息
|
|
func (m *memoryCache) GetStats() CacheStats {
|
|
return m.stats
|
|
}
|