mirror of
https://github.com/charlienet/go-mixed.git
synced 2025-07-17 16:12:42 +08:00
delay
This commit is contained in:
75
concurrent/delay_queue/delay_queue.go
Normal file
75
concurrent/delay_queue/delay_queue.go
Normal file
@ -0,0 +1,75 @@
|
|||||||
|
package delayqueue
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/charlienet/go-mixed/locker"
|
||||||
|
)
|
||||||
|
|
||||||
|
type store[T Delayed] interface {
|
||||||
|
Push(context.Context, T) error
|
||||||
|
Pop() (T, error)
|
||||||
|
Peek() (T, bool)
|
||||||
|
IsEmpty() bool // 队列是否为空
|
||||||
|
}
|
||||||
|
|
||||||
|
type delayQueue[T Delayed] struct {
|
||||||
|
mu locker.RWLocker
|
||||||
|
store store[T]
|
||||||
|
}
|
||||||
|
|
||||||
|
type Delayed interface {
|
||||||
|
Delay() time.Time
|
||||||
|
}
|
||||||
|
|
||||||
|
func New[T Delayed]() *delayQueue[T] {
|
||||||
|
return &delayQueue[T]{
|
||||||
|
mu: locker.NewRWLocker(),
|
||||||
|
store: newMemStore[T](),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (q *delayQueue[T]) UseStore(s store[T]) *delayQueue[T] {
|
||||||
|
q.store = s
|
||||||
|
return q
|
||||||
|
}
|
||||||
|
|
||||||
|
func (q *delayQueue[T]) Push(task T) error {
|
||||||
|
q.mu.Lock()
|
||||||
|
defer q.mu.Unlock()
|
||||||
|
|
||||||
|
return q.store.Push(context.Background(), task)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (q *delayQueue[T]) Peek() (T, bool) {
|
||||||
|
q.mu.RLock()
|
||||||
|
defer q.mu.RUnlock()
|
||||||
|
|
||||||
|
return q.store.Peek()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (q *delayQueue[T]) Pop() (T, error) {
|
||||||
|
q.mu.Lock()
|
||||||
|
defer q.mu.Unlock()
|
||||||
|
|
||||||
|
return q.store.Pop()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (q *delayQueue[T]) Channel(size int) <-chan T {
|
||||||
|
out := make(chan T, size)
|
||||||
|
go func() {
|
||||||
|
for {
|
||||||
|
entry, _ := q.Pop()
|
||||||
|
out <- entry
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
||||||
|
func (q *delayQueue[T]) IsEmpty() bool {
|
||||||
|
q.mu.RLock()
|
||||||
|
defer q.mu.RUnlock()
|
||||||
|
|
||||||
|
return q.store.IsEmpty()
|
||||||
|
}
|
48
concurrent/delay_queue/delay_queue_test.go
Normal file
48
concurrent/delay_queue/delay_queue_test.go
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
package delayqueue_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
delayqueue "github.com/charlienet/go-mixed/concurrent/delay_queue"
|
||||||
|
)
|
||||||
|
|
||||||
|
type delayTask struct {
|
||||||
|
message string
|
||||||
|
delay time.Time
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t delayTask) Delay() time.Time {
|
||||||
|
return t.delay
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDelayQueue(t *testing.T) {
|
||||||
|
queue := delayqueue.New[delayTask]()
|
||||||
|
queue.Push(delayTask{})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDelayedFunc(t *testing.T) {
|
||||||
|
q := delayqueue.New[delayTask]()
|
||||||
|
q.Push(delayTask{})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDelayedChannel(t *testing.T) {
|
||||||
|
q := delayqueue.New[delayTask]()
|
||||||
|
c := q.Channel(10)
|
||||||
|
|
||||||
|
q.Push(delayTask{message: "abc", delay: time.Now().Add(time.Second)})
|
||||||
|
q.Push(delayTask{message: "abcaaa", delay: time.Now().Add(time.Second * 3)})
|
||||||
|
|
||||||
|
for {
|
||||||
|
if q.IsEmpty() {
|
||||||
|
t.Log("队列为空,退出")
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
select {
|
||||||
|
case task := <-c:
|
||||||
|
t.Log(task)
|
||||||
|
case <-time.After(time.Second * 2):
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
37
concurrent/delay_queue/kafka_store.go
Normal file
37
concurrent/delay_queue/kafka_store.go
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
package delayqueue
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"github.com/charlienet/go-mixed/errors"
|
||||||
|
)
|
||||||
|
|
||||||
|
type kafkaStore[T Delayed] struct {
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *delayQueue[T]) UseKafka() *delayQueue[T] {
|
||||||
|
s.UseStore(newKafka[T]())
|
||||||
|
|
||||||
|
panic(errors.NotImplemented)
|
||||||
|
// return s.UseStore(newKafka[T]())
|
||||||
|
}
|
||||||
|
|
||||||
|
func newKafka[T Delayed]() *kafkaStore[T] {
|
||||||
|
return &kafkaStore[T]{}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (*kafkaStore[T]) Push(context.Context, T) error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (*kafkaStore[T]) Pop() (T, error) {
|
||||||
|
return *new(T), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (*kafkaStore[T]) Peek() (T, bool) {
|
||||||
|
return *new(T), false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (*kafkaStore[T]) IsEmpty() bool {
|
||||||
|
return false
|
||||||
|
}
|
88
concurrent/delay_queue/mem_store.go
Normal file
88
concurrent/delay_queue/mem_store.go
Normal file
@ -0,0 +1,88 @@
|
|||||||
|
package delayqueue
|
||||||
|
|
||||||
|
import (
|
||||||
|
"container/heap"
|
||||||
|
"context"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
type delayedQueue []Delayed
|
||||||
|
|
||||||
|
type memStore[T Delayed] struct {
|
||||||
|
mu sync.Mutex
|
||||||
|
h *delayedQueue
|
||||||
|
wakeup chan struct{}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (q delayedQueue) Len() int {
|
||||||
|
return len(q)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (q delayedQueue) Less(i, j int) bool {
|
||||||
|
return q[i].Delay().Before(q[j].Delay())
|
||||||
|
}
|
||||||
|
|
||||||
|
func (q delayedQueue) Swap(i, j int) {
|
||||||
|
q[i], q[j] = q[j], q[i]
|
||||||
|
}
|
||||||
|
|
||||||
|
func (q *delayedQueue) Push(x any) {
|
||||||
|
*q = append(*q, x.(Delayed))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *delayedQueue) Pop() interface{} {
|
||||||
|
old := *h
|
||||||
|
n := len(old)
|
||||||
|
x := old[n-1]
|
||||||
|
*h = old[0 : n-1]
|
||||||
|
return x
|
||||||
|
}
|
||||||
|
|
||||||
|
func newMemStore[T Delayed]() *memStore[T] {
|
||||||
|
store := &memStore[T]{
|
||||||
|
h: new(delayedQueue),
|
||||||
|
wakeup: make(chan struct{}, 1),
|
||||||
|
}
|
||||||
|
|
||||||
|
heap.Init(store.h)
|
||||||
|
return store
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *memStore[T]) Push(ctx context.Context, v T) error {
|
||||||
|
s.mu.Lock()
|
||||||
|
defer s.mu.Unlock()
|
||||||
|
|
||||||
|
heap.Push(s.h, v)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *memStore[T]) Pop() (T, error) {
|
||||||
|
for {
|
||||||
|
_, exist := s.Peek()
|
||||||
|
if exist {
|
||||||
|
return s.h.Pop().(T), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
time.Sleep(time.Millisecond * 10)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *memStore[T]) Peek() (T, bool) {
|
||||||
|
s.mu.Lock()
|
||||||
|
defer s.mu.Unlock()
|
||||||
|
|
||||||
|
if s.h.Len() > 0 {
|
||||||
|
return (*s.h)[0].(T), true
|
||||||
|
}
|
||||||
|
|
||||||
|
return *new(T), false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *memStore[T]) Len() int {
|
||||||
|
return s.h.Len()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *memStore[T]) IsEmpty() bool {
|
||||||
|
return s.Len() == 0
|
||||||
|
}
|
119
concurrent/delay_queue/mem_store_test.go
Normal file
119
concurrent/delay_queue/mem_store_test.go
Normal file
@ -0,0 +1,119 @@
|
|||||||
|
package delayqueue
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/charlienet/go-mixed/calendar"
|
||||||
|
"github.com/charlienet/go-mixed/rand"
|
||||||
|
)
|
||||||
|
|
||||||
|
type delayTask struct {
|
||||||
|
Message string
|
||||||
|
At time.Time
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t delayTask) Delay() time.Time {
|
||||||
|
return t.At
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t delayTask) execute() {
|
||||||
|
println(t.Message)
|
||||||
|
}
|
||||||
|
|
||||||
|
// var _ encoding.BinaryMarshaler = new(myStruct)
|
||||||
|
// var _ encoding.BinaryUnmarshaler = new(myStruct)
|
||||||
|
|
||||||
|
func (t delayTask) BinaryUnmarshaler(data []byte, v any) {
|
||||||
|
json.Unmarshal(data, v)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t delayTask) MarshalBinary() (data []byte, err error) {
|
||||||
|
return json.Marshal(t)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMemStore(t *testing.T) {
|
||||||
|
s := newMemStore[delayTask]()
|
||||||
|
|
||||||
|
for i := 0; i < 10; i++ {
|
||||||
|
s.Push(
|
||||||
|
context.Background(),
|
||||||
|
delayTask{
|
||||||
|
Message: "tesss",
|
||||||
|
At: time.Now().Add(-time.Minute * time.Duration(rand.Intn(20))),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Log("count:", s.Len())
|
||||||
|
|
||||||
|
v, exists := s.Peek()
|
||||||
|
t.Logf("Peek %v:%v %v", exists, v.Message, calendar.Create(v.Delay()).ToDateTimeString())
|
||||||
|
|
||||||
|
for i := 0; i < 10; i++ {
|
||||||
|
v, _ := s.Pop()
|
||||||
|
t.Logf("POP:%v %v", v.Message, calendar.Create(v.Delay()).ToDateTimeString())
|
||||||
|
}
|
||||||
|
|
||||||
|
v, exists = s.Peek()
|
||||||
|
t.Logf("Peek %v:%v %v", exists, v.Message, calendar.Create(v.At).ToDateTimeString())
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMemPush(t *testing.T) {
|
||||||
|
s := newMemStore[delayTask]()
|
||||||
|
|
||||||
|
for i := 0; i < 10; i++ {
|
||||||
|
s.Push(
|
||||||
|
context.Background(),
|
||||||
|
delayTask{
|
||||||
|
Message: fmt.Sprintf("abc:%d", i),
|
||||||
|
At: time.Now().Add(time.Second * time.Duration(rand.IntRange(5, 30))),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
now := time.Now()
|
||||||
|
|
||||||
|
delay, _ := s.Pop()
|
||||||
|
after := delay.Delay().Sub(now)
|
||||||
|
|
||||||
|
t.Log("after:", calendar.String(now), calendar.String(delay.Delay()), after)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestExecute(t *testing.T) {
|
||||||
|
s := newMemStore[delayTask]()
|
||||||
|
|
||||||
|
s.Push(context.Background(),
|
||||||
|
delayTask{
|
||||||
|
Message: "这是消息",
|
||||||
|
At: time.Now().Add(time.Second * 2),
|
||||||
|
})
|
||||||
|
|
||||||
|
s.Push(context.Background(),
|
||||||
|
delayTask{
|
||||||
|
Message: "这是消息",
|
||||||
|
At: time.Now().Add(time.Second * 4),
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Log("start:", calendar.String(time.Now()))
|
||||||
|
|
||||||
|
for {
|
||||||
|
if s.IsEmpty() {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
task, _ := s.Pop()
|
||||||
|
|
||||||
|
for {
|
||||||
|
if task.Delay().Before(time.Now()) {
|
||||||
|
task.execute()
|
||||||
|
t.Log("end:", calendar.String(time.Now()))
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
time.Sleep(time.Millisecond * 20)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
154
concurrent/delay_queue/redis_store.go
Normal file
154
concurrent/delay_queue/redis_store.go
Normal file
@ -0,0 +1,154 @@
|
|||||||
|
package delayqueue
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"encoding"
|
||||||
|
"encoding/json"
|
||||||
|
"strconv"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
goredis "github.com/redis/go-redis/v9"
|
||||||
|
|
||||||
|
"github.com/charlienet/go-mixed/hash"
|
||||||
|
"github.com/charlienet/go-mixed/redis"
|
||||||
|
)
|
||||||
|
|
||||||
|
// 使用Redis存储队列
|
||||||
|
|
||||||
|
type redisStore[T Delayed] struct {
|
||||||
|
rdb redis.Client
|
||||||
|
delayQueue string
|
||||||
|
executeQueue string
|
||||||
|
delayTaskSet string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (q *delayQueue[T]) UseRedis(delayQueueName, executeQueueName, delayTaskName string, rdb redis.Client) *delayQueue[T] {
|
||||||
|
q.store = newRedisStroe[T](delayQueueName, executeQueueName, delayTaskName, rdb)
|
||||||
|
return q
|
||||||
|
}
|
||||||
|
func newRedisStroe[T Delayed](delayQueueName, executeQueueName, delayTaskName string, rdb redis.Client) *redisStore[T] {
|
||||||
|
|
||||||
|
store := &redisStore[T]{
|
||||||
|
delayQueue: delayQueueName,
|
||||||
|
executeQueue: executeQueueName,
|
||||||
|
delayTaskSet: delayTaskName,
|
||||||
|
|
||||||
|
rdb: rdb,
|
||||||
|
}
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
for {
|
||||||
|
store.pushToExecute()
|
||||||
|
time.Sleep(time.Millisecond * 100)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
return store
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *redisStore[T]) Push(ctx context.Context, v T) error {
|
||||||
|
o := any(v).(encoding.BinaryMarshaler)
|
||||||
|
bytes, err := o.MarshalBinary()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
tx := s.rdb.TxPipeline()
|
||||||
|
tx.HSet(context.Background(), s.delayTaskSet, hash.Sha1(bytes).Hex(), bytes)
|
||||||
|
tx.Exec(context.Background())
|
||||||
|
|
||||||
|
tx.HSet(context.Background(), s.delayTaskSet)
|
||||||
|
|
||||||
|
// tx.Exec()
|
||||||
|
ret := s.rdb.ZAdd(ctx, s.delayQueue, goredis.Z{
|
||||||
|
Score: float64(v.Delay().Unix()),
|
||||||
|
Member: v,
|
||||||
|
})
|
||||||
|
|
||||||
|
return ret.Err()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *redisStore[T]) pushToExecute() error {
|
||||||
|
now := time.Now().Unix()
|
||||||
|
|
||||||
|
ret, err := s.rdb.ZRangeByScore(
|
||||||
|
context.Background(),
|
||||||
|
s.delayQueue,
|
||||||
|
&goredis.ZRangeBy{
|
||||||
|
Min: "-inf",
|
||||||
|
Max: strconv.FormatInt(now, 10),
|
||||||
|
}).Result()
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(ret) > 0 {
|
||||||
|
pipe := s.rdb.TxPipeline()
|
||||||
|
ctx, cancel := context.WithTimeout(context.Background(), time.Second*3)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
pipe.LPush(ctx, s.executeQueue, ret)
|
||||||
|
pipe.ZRem(ctx, s.delayQueue, ret)
|
||||||
|
|
||||||
|
if _, err := pipe.Exec(ctx); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *redisStore[T]) Pop() (T, error) {
|
||||||
|
for {
|
||||||
|
v, err := s.rdb.RPop(context.Background(), s.executeQueue).Result()
|
||||||
|
if err != nil {
|
||||||
|
if err == redis.Nil {
|
||||||
|
time.Sleep(time.Millisecond * 10)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
return *new(T), err
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(v) > 0 {
|
||||||
|
var task T
|
||||||
|
if err := json.Unmarshal([]byte(v), &task); err != nil {
|
||||||
|
return *new(T), err
|
||||||
|
}
|
||||||
|
|
||||||
|
return task, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *redisStore[T]) Peek() (t T, r bool) {
|
||||||
|
m, err := s.rdb.ZRange(context.Background(), s.delayQueue, 0, 0).Result()
|
||||||
|
if err != nil {
|
||||||
|
return *new(T), false
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(m) == 1 {
|
||||||
|
var t T
|
||||||
|
|
||||||
|
s := m[0]
|
||||||
|
if err := json.Unmarshal([]byte(s), &t); err != nil {
|
||||||
|
return *new(T), false
|
||||||
|
}
|
||||||
|
return t, true
|
||||||
|
}
|
||||||
|
|
||||||
|
return *new(T), false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *redisStore[T]) Clear() {
|
||||||
|
s.rdb.Del(context.Background(), s.delayQueue)
|
||||||
|
s.rdb.Del(context.Background(), s.executeQueue)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *redisStore[T]) IsEmpty() bool {
|
||||||
|
n, _ := s.rdb.LLen(context.Background(), s.executeQueue).Result()
|
||||||
|
m, _ := s.rdb.ZCard(context.Background(), s.delayQueue).Result()
|
||||||
|
|
||||||
|
return (m + n) == 0
|
||||||
|
}
|
89
concurrent/delay_queue/redis_store_test.go
Normal file
89
concurrent/delay_queue/redis_store_test.go
Normal file
@ -0,0 +1,89 @@
|
|||||||
|
package delayqueue
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/charlienet/go-mixed/redis"
|
||||||
|
"github.com/charlienet/go-mixed/tests"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
redisAddr = "192.168.123.100:6379"
|
||||||
|
delay_queue = "delay_queue"
|
||||||
|
execute_queue = "execute_queue"
|
||||||
|
delay_task_set = "task_set"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestRedis(t *testing.T) {
|
||||||
|
tests.RunOnRedis(t, func(client redis.Client) {
|
||||||
|
defer client.Close()
|
||||||
|
|
||||||
|
q := New[delayTask]().UseRedis(delay_queue, execute_queue, delay_task_set, client)
|
||||||
|
|
||||||
|
err := q.Push(delayTask{
|
||||||
|
Message: "abc1111111111111",
|
||||||
|
At: time.Now().Add(time.Second * 2)})
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Log(time.Now())
|
||||||
|
|
||||||
|
task, _ := q.Pop()
|
||||||
|
t.Logf("%+v", task)
|
||||||
|
|
||||||
|
t.Log(time.Now())
|
||||||
|
|
||||||
|
task.execute()
|
||||||
|
|
||||||
|
}, redis.RedisOption{Addr: redisAddr, Prefix: "redis_test"})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMutiTask(t *testing.T) {
|
||||||
|
tests.RunOnRedis(t, func(client redis.Client) {
|
||||||
|
defer client.Close()
|
||||||
|
|
||||||
|
timer := time.NewTimer(time.Second)
|
||||||
|
ticker := time.NewTicker(time.Second)
|
||||||
|
|
||||||
|
timer.Reset(time.Microsecond)
|
||||||
|
ticker.Reset(time.Millisecond)
|
||||||
|
|
||||||
|
store := newRedisStroe[delayTask](delay_queue, execute_queue, delay_task_set, client)
|
||||||
|
|
||||||
|
for i := 1; i <= 5; i++ {
|
||||||
|
store.Push(context.Background(), delayTask{
|
||||||
|
Message: fmt.Sprintf("abc:%d", i),
|
||||||
|
At: time.Now().Add(time.Second * time.Duration(i)),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
for !store.IsEmpty() {
|
||||||
|
v, err := store.Pop()
|
||||||
|
assert.Nil(t, err)
|
||||||
|
t.Log(time.Now(), v)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestIsEmpty(t *testing.T) {
|
||||||
|
tests.RunOnRedis(t, func(client redis.Client) {
|
||||||
|
defer client.Close()
|
||||||
|
|
||||||
|
store := newRedisStroe[delayTask](delay_queue, execute_queue, delay_task_set, client)
|
||||||
|
store.Clear()
|
||||||
|
|
||||||
|
assert.True(t, store.IsEmpty())
|
||||||
|
|
||||||
|
store.Push(context.Background(), delayTask{Message: "bbb", At: time.Now().Add(time.Second)})
|
||||||
|
assert.False(t, store.IsEmpty())
|
||||||
|
}, redis.RedisOption{
|
||||||
|
Addrs: []string{"redis-10448.c90.us-east-1-3.ec2.cloud.redislabs.com:10448"},
|
||||||
|
Password: "E7HFwvENEqimiB1EG4IjJSa2IUi0B22o",
|
||||||
|
})
|
||||||
|
}
|
20
concurrent/readme.md
Normal file
20
concurrent/readme.md
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
延迟队列
|
||||||
|
|
||||||
|
包含以下实现模型
|
||||||
|
|
||||||
|
1. 内存模式。
|
||||||
|
2. Redis模式
|
||||||
|
3. MQ模式
|
||||||
|
|
||||||
|
|
||||||
|
内存模式,在队列中放入。定时检查过期时间,过期时间到达时取出并使用协程执行。
|
||||||
|
|
||||||
|
Redis模式,使用ZSET存储任务队列。定时取出规则内的任务。取出后使用ZREM删除,并放入执行队列LPUSH。放入成功后在任务执行通道发送消息,执行通道使用LPOP取出并执行。
|
||||||
|
|
||||||
|
|
||||||
|
```
|
||||||
|
queue := delayqueue.New()
|
||||||
|
|
||||||
|
|
||||||
|
```
|
||||||
|
|
Reference in New Issue
Block a user