-
Notifications
You must be signed in to change notification settings - Fork 0
/
cache.go
166 lines (134 loc) · 4.62 KB
/
cache.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
package sync_cache
import (
"fmt"
"github.com/go-redis/redis"
uuidgen "github.com/google/uuid"
"sync"
"time"
)
const SyncCacheGroupCapacity = 10
type SyncCacheOpts struct {
Address string
Password string
Db int
Redis Redis
Expiration time.Duration
}
type SyncCacheClient struct {
redis Redis
cacheGroupManager *CacheGroupsManager
expiration time.Duration
}
type CacheGroupsManager struct {
cacheGroups map[string]*CacheGroup
sync.RWMutex
}
func NewSyncCacheClient(opts SyncCacheOpts) *SyncCacheClient {
c := &SyncCacheClient{
cacheGroupManager: &CacheGroupsManager{
cacheGroups: make(map[string]*CacheGroup, SyncCacheGroupCapacity),
},
expiration: opts.Expiration,
}
if opts.Redis != nil {
c.redis = opts.Redis
return c
}
c.redis = redis.NewClient(&redis.Options{
Addr: opts.Address,
Password: opts.Password,
DB: opts.Db,
})
return c
}
func (c *SyncCacheClient) AddCacheGroup(cacheGroupName string, getterFunc GetterFunc) {
c.cacheGroupManager.Lock()
c.cacheGroupManager.cacheGroups[cacheGroupName] = NewCacheGroup(cacheGroupName, getterFunc)
c.cacheGroupManager.Unlock()
}
func (c *SyncCacheClient) GetCacheGroup(cacheGroupName string) *CacheGroup {
c.cacheGroupManager.RLock()
g := c.cacheGroupManager.cacheGroups[cacheGroupName]
c.cacheGroupManager.RUnlock()
return g
}
func (c *SyncCacheClient) GetCacheGroups() map[string]*CacheGroup {
c.cacheGroupManager.RLock()
g := c.cacheGroupManager.cacheGroups
c.cacheGroupManager.RUnlock()
return g
}
func (c *SyncCacheClient) RemoveCacheGroup(cacheGroupName string) {
c.cacheGroupManager.Lock()
delete(c.cacheGroupManager.cacheGroups, cacheGroupName)
c.cacheGroupManager.Unlock()
}
func (c *SyncCacheClient) Update(cacheGroupName, key string) {
newUuid := uuidgen.New().String()
c.redis.Set(cacheGroupName+"_"+key, newUuid, c.expiration)
}
func (c *SyncCacheClient) RedisFlushDb() {
c.redis.FlushDB()
}
func (c *SyncCacheClient) Get(cacheGroupName, key string) (interface{}, error) {
// Try to find an record with a given ID object in Redis
uuid, err := c.redis.Get(cacheGroupName + "_" + key).Result()
// Record not found in Redis
if err == redis.Nil {
// If object exists in cache - remove it
k := c.cacheGroupManager.cacheGroups[cacheGroupName].get(key)
if k.object != nil {
c.cacheGroupManager.cacheGroups[cacheGroupName].delete(key)
}
// Fetch object from db and add to cache
// To avoid the situation when updating / deleting a record is faster,
// than adding to the cache (andreyverbin comment) we add an object with a zero UUID to the cache.
// Then the first time you access the cache, the difference in UUID with Redis will be revealed,
// and the data from the database will be requested again.
setCacheFunc := func(i interface{}) {
c.cacheGroupManager.cacheGroups[cacheGroupName].set(key, i, "")
}
// and to Redis
newUuid := uuidgen.New().String()
c.redis.Set(cacheGroupName+"_"+key, newUuid, c.expiration)
if err := c.cacheGroupManager.cacheGroups[cacheGroupName].getterFunc(key, setCacheFunc); err != nil {
return nil, err
}
} else if err != nil {
return nil, err
}
// If object exists in Redis
c.cacheGroupManager.RLock()
if c.cacheGroupManager.cacheGroups[cacheGroupName] == nil {
panic(fmt.Sprintf("group %s does not exist", cacheGroupName))
}
k := c.cacheGroupManager.cacheGroups[cacheGroupName].get(key)
c.cacheGroupManager.RUnlock()
setCacheFunc := func(i interface{}) {
c.cacheGroupManager.cacheGroups[cacheGroupName].set(key, i, uuid)
}
// If object not exists in memory cache than fetch it from db and add to memory cache with Redis uuid
if k.object == nil {
if err := c.cacheGroupManager.cacheGroups[cacheGroupName].getterFunc(key, setCacheFunc); err != nil {
return nil, err
}
}
// If object exists in memory cache, than take it from cache, compare uuid in cache with Redis uuid
// if not, delete object from memory cache, and add object from db to cache with Redis uuid
if k.uuid != uuid {
c.cacheGroupManager.cacheGroups[cacheGroupName].delete(key)
if err := c.cacheGroupManager.cacheGroups[cacheGroupName].getterFunc(key, setCacheFunc); err != nil {
return nil, err
}
}
return c.cacheGroupManager.cacheGroups[cacheGroupName].get(key).object, nil
}
func (c *SyncCacheClient) Delete(cacheGroupName, key string) (int64, error) {
c.cacheGroupManager.RLock()
if c.cacheGroupManager.cacheGroups[cacheGroupName] == nil {
panic(fmt.Sprintf("group %s does not exist", cacheGroupName))
}
c.cacheGroupManager.cacheGroups[cacheGroupName].delete(key)
c.cacheGroupManager.RUnlock()
return c.redis.Del(cacheGroupName + "_" + key).Result()
}