-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathhscache.go
134 lines (115 loc) · 2.77 KB
/
hscache.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
package hscache
import (
"context"
"encoding/json"
"errors"
"time"
"github.com/redis/go-redis/v9"
)
const (
DefaultName = "hscache"
DefaultSleep = 60 * time.Second
DefaultFetchCount = 100
)
var (
ErrKeyExpired = errors.New("key expired")
)
type RedisCompatible interface {
HGet(ctx context.Context, name string, key string) *redis.StringCmd
HDel(ctx context.Context, key string, fields ...string) *redis.IntCmd
HSet(ctx context.Context, key string, values ...interface{}) *redis.IntCmd
HScan(background context.Context, name string, cursor uint64, s string, count int64) *redis.ScanCmd
}
type Container struct {
ExpiresTS int64 `json:"expiresTs"`
Value interface{} `json:"value"`
}
type HSCache struct {
client RedisCompatible
name string
sleep time.Duration
fetchCount int64
}
func (c *HSCache) Get(ctx context.Context, key string) (interface{}, error) {
var (
container Container
)
data, err := c.client.HGet(ctx, c.name, key).Result()
if err != nil {
return nil, err
}
err = json.Unmarshal([]byte(data), &container)
if err != nil {
return nil, err
}
if container.ExpiresTS < time.Now().Unix() {
// Remove expired key
if _, err := c.client.HDel(ctx, c.name, key).Result(); err != nil {
return nil, err
}
return nil, ErrKeyExpired
}
return container.Value, nil
}
func (c *HSCache) SetEx(ctx context.Context, key string, value interface{}, expiresSeconds int64) error {
container := Container{
ExpiresTS: time.Now().Unix() + expiresSeconds,
Value: value,
}
data, err := json.Marshal(container)
if err != nil {
return err
}
return c.client.HSet(ctx, c.name, key, string(data)).Err()
}
func (c *HSCache) SetSleep(duration time.Duration) {
c.sleep = duration
}
func (c *HSCache) SetFetchCount(count int64) {
c.fetchCount = count
}
func (c *HSCache) Evictor() {
var (
container Container
)
cursor := uint64(0)
for {
keys, newCursor, err := c.client.HScan(context.Background(), c.name, cursor, "*", c.fetchCount).Result()
if err != nil {
time.Sleep(c.sleep)
continue
}
if newCursor == 0 {
time.Sleep(c.sleep)
continue
}
for _, key := range keys {
data, err := c.client.HGet(context.Background(), c.name, key).Result()
if err != nil {
continue
}
err = json.Unmarshal([]byte(data), &container)
if err != nil {
continue
}
if container.ExpiresTS < time.Now().Unix() {
// Remove expired key
if _, err := c.client.HDel(context.Background(), c.name, key).Result(); err != nil {
continue
}
}
}
cursor = newCursor
}
}
func New(client RedisCompatible, name string) *HSCache {
if name == "" {
name = DefaultName
}
return &HSCache{
client: client,
name: name,
sleep: DefaultSleep,
fetchCount: DefaultFetchCount,
}
}