-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathbuff.go
More file actions
281 lines (243 loc) · 6.26 KB
/
Copy pathbuff.go
File metadata and controls
281 lines (243 loc) · 6.26 KB
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
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
package redisBuff
import (
"context"
"fmt"
"log"
"strconv"
"sync"
"time"
redis "github.com/redis/go-redis/v9"
)
var rdb redis.Cmdable
var rdbOnce sync.Once
var ctx = context.Background()
func InitRedisClient(client redis.Cmdable) {
rdbOnce.Do(func() {
rdb = client
})
if err := rdb.Ping(ctx).Err(); err != nil {
panic(fmt.Sprintf("[InitRedisClient fail] - %v", err))
}
}
func New(c *Config) *Buff {
if rdb == nil {
panic("redis client was nil, must call InitRedisClient() before New()")
}
b := new(Buff)
sendBuff := c.SendBuff
if sendBuff < 1 {
sendBuff = 100
}
b.send = make(chan interface{}, sendBuff)
msgBatch := c.MsgBatch
if msgBatch < 1 {
msgBatch = 5
}
b.msgBatch = msgBatch
runnerInterval := c.RunnerInterval
if runnerInterval < 1 {
runnerInterval = time.Millisecond * 5000
}
b.runnerInterval = runnerInterval
if c.CacheName == "" {
panic(fmt.Sprintf("[New fail] - %s", "c.CacheName wail empty"))
}
b.cacheName = c.CacheName
b.readLockName = fmt.Sprintf("redisBuff-read-lock-%s", b.cacheName)
b.writeLockName = fmt.Sprintf("redisBuff-write-lock-%s", b.cacheName)
lockDuration := c.LockDuration
if lockDuration < 1 {
lockDuration = time.Second * 3
}
b.lockDuration = lockDuration
rlockDuration := c.RLockDuration
if rlockDuration < 1 {
rlockDuration = time.Second * 3
}
b.rlockDuration = rlockDuration
clearFunc := c.ClearMsgFunc
if clearFunc == nil {
panic(fmt.Sprintf("[New fail] - %s", "c.ClearMsgFunc wail empty"))
}
b.clearMsgFunc = clearFunc
b.debug = c.Debug
return b
}
type Config struct {
SendBuff int // 决定发送讯息时使用的chan的buff大小
MsgBatch int64 // 讯息达到N则时发送
RunnerInterval time.Duration // 每N时间排程执行一次讯息处理
CacheName string // 缓存命名
LockDuration time.Duration // 分布式锁上锁时间,根据业务逻辑处理时间调整
RLockDuration time.Duration // 读锁的TTL
ClearMsgFunc func(msg []string) // 清除讯息时要执行的
Debug bool // debug model
}
type Buff struct {
send chan interface{}
msgBatch int64
runnerInterval time.Duration
cacheName string
lockDuration time.Duration
rlockDuration time.Duration
clearMsgFunc func(msg []string)
debug bool
readLockName string
writeLockName string
L sync.RWMutex
close bool
}
// execute runner
func (b *Buff) SendMsgRunner() chan<- bool {
done := make(chan bool)
ticker := time.NewTicker(b.runnerInterval)
go func() {
defer ticker.Stop()
for {
select {
case msg := <-b.send:
b.pushMsgWithLock(msg)
case <-done:
b.L.Lock()
close(b.send)
b.close = true
b.L.Unlock()
s := make(chan bool)
go func() { // 等chan上的讯息处理完后才能关闭
for len(b.send) > 0 {
b.debugMsg("[%s] - chan上还有讯息\n", b.cacheName)
time.Sleep(200 * time.Second)
}
s <- true
close(s)
}()
<-s
fmt.Printf("redisBuff SendMsgRunner close - %s\n", b.cacheName)
return
case <-ticker.C:
b.debugMsg("[%s] - 循环触发-start\n", b.cacheName)
if ok := b.getIntervalLock(); !ok {
b.debugMsg("[%s] - 循环触发-没拿到lock\n", b.cacheName)
continue
}
b.clearMsgWithLock()
b.debugMsg("[%s] - 循环触发-end\n", b.cacheName)
}
}
}()
return done
}
func (b *Buff) debugMsg(format string, a ...any) {
if !b.debug {
return
}
fmt.Printf(format, a...)
}
// 用来控制多server的情况下,保持interval逻辑单例模式运作
func (b *Buff) getIntervalLock() (ok bool) {
key := fmt.Sprintf("redisBuff-interval-lock-%s", b.cacheName)
ok = rdb.SetNX(ctx, key, 1, b.runnerInterval-200*time.Millisecond).Val()
return
}
// add msg
// sendBuff满时,将堵塞
func (b *Buff) Add(data interface{}) {
b.L.RLock()
defer b.L.RUnlock()
if !b.close {
b.send <- data
}
}
// 获取目前缓存上的资料
func (b *Buff) List() []string {
RUnlock := b.RLock()
defer func() {
b.debugMsg("[%s] - List end\n", b.cacheName)
RUnlock()
}()
b.debugMsg("[%s] - List start\n", b.cacheName)
result := rdb.LRange(ctx, b.cacheName, 0, -1).Val()
b.debugMsg("[%s] - List result: %v\n", b.cacheName, result)
return result
}
// 读锁
func (b *Buff) RLock() func() {
for {
if rdb.Exists(ctx, b.writeLockName).Val() < 1 {
break
}
// b.debugMsg("[%s RLock] - 等待写锁释放\n", b.cacheName)
time.Sleep(100 * time.Millisecond)
}
t := time.Now().Nanosecond()
rdb.Set(ctx, b.readLockName, t, b.rlockDuration)
// b.debugMsg("[%s RLock] - 获得读锁\n", b.cacheName)
return func() {
v, _ := strconv.Atoi(rdb.Get(ctx, b.readLockName).Val())
if v == t {
// b.debugMsg("[%s RLock] - 释放读锁\n", b.cacheName)
rdb.Del(ctx, b.readLockName)
}
}
}
// 清除/写入讯息要用的
func (b *Buff) lock() func() {
for {
if rdb.Exists(ctx, b.readLockName).Val() < 1 {
break
}
// b.debugMsg("[%s Lock] - 等待读锁释放\n", b.cacheName)
time.Sleep(100 * time.Millisecond)
}
for {
ok := rdb.SetNX(ctx, b.writeLockName, 1, b.lockDuration).Val()
if ok {
// b.debugMsg("[%s Lock] - 获得写锁\n", b.cacheName)
break
}
// b.debugMsg("[%s Lock] - 读锁抢占失败\n", b.cacheName)
time.Sleep(time.Millisecond * 50)
}
return func() {
// b.debugMsg("[%s Lock] - 释放写锁\n", b.cacheName)
rdb.Del(ctx, b.writeLockName)
}
}
func (b *Buff) clearMsgWithLock() {
unlock := b.lock()
defer unlock()
b.clearMsg()
}
func (b *Buff) clearMsg() {
key := b.cacheName
totalMsg := []string{}
var lenList int64
for {
lenList = rdb.LLen(ctx, key).Val()
if lenList <= 0 {
break
}
cursor := rdb.LPop(ctx, key).Val()
totalMsg = append(totalMsg, cursor)
}
if len(totalMsg) > 0 {
b.clearMsgFunc(totalMsg)
}
}
func (b *Buff) pushMsgWithLock(msg interface{}) {
unlock := b.lock()
defer unlock()
b.pushMsg(msg)
}
func (b *Buff) pushMsg(msg interface{}) {
key := b.cacheName
if err := rdb.RPush(ctx, key, msg).Err(); err != nil {
log.Fatalf("[push fail] - %v", err)
}
len := rdb.LLen(ctx, key).Val()
if len >= b.msgBatch { // 大于N则讯息则推送
b.debugMsg("[%s] - 大于N则讯息推送 - start\n", b.cacheName)
b.clearMsg()
b.debugMsg("[%s] - 大于N则讯息推送 - end\n", b.cacheName)
}
}