github.com/kaydxh/golang@v0.0.131/pkg/pool/taskqueue/queue/redis/queue.redis.go (about) 1 package redisq 2 3 import ( 4 "context" 5 "encoding/json" 6 "fmt" 7 "strings" 8 "time" 9 10 "github.com/go-redis/redis/v8" 11 "github.com/google/uuid" 12 os_ "github.com/kaydxh/golang/go/os" 13 queue_ "github.com/kaydxh/golang/pkg/pool/taskqueue/queue" 14 "github.com/sirupsen/logrus" 15 ) 16 17 type Queue struct { 18 opts queue_.QueueOptions 19 20 stream string 21 streamGroup string 22 consumerName string 23 db *redis.Client 24 } 25 26 func NewQueue(db *redis.Client, opts queue_.QueueOptions) *Queue { 27 q := &Queue{ 28 db: db, 29 stream: fmt.Sprintf("taskq-%s-stream", opts.Name), 30 streamGroup: "taskq", 31 consumerName: os_.GetProcId(), 32 } 33 34 return q 35 } 36 37 // https://redis.io/commands/xadd/ 38 // * 为自动生成Id 39 // XADD taskq-redis-stream * taskq "123" 40 func (q *Queue) Add(ctx context.Context, msg *queue_.Message) (string, error) { 41 if msg.Name == "" { 42 return "", fmt.Errorf("messgae name is empty") 43 } 44 if msg.Id == "" { 45 msg.Id = uuid.NewString() 46 } 47 48 //todo check message name if exists 49 data, err := json.Marshal(msg) 50 if err != nil { 51 return "", err 52 } 53 54 // xadd msg to redis 55 cmd := q.db.XAdd(ctx, &redis.XAddArgs{ 56 Stream: q.stream, 57 Values: map[string]interface{}{ 58 "message": string(data), 59 }, 60 }) 61 err = cmd.Err() 62 if err != nil { 63 logrus.WithError(err).Errorf("failed to xadd msg [name: %v, id: %v]", msg.Name, msg.Id) 64 return "", err 65 } 66 msg.InnerId = cmd.Val() 67 return cmd.Val(), nil 68 } 69 70 // get all 71 // XRANGE taskq-redis-stream - + 72 73 func (q *Queue) fetchN(ctx context.Context, n int64, waitTimeout time.Duration) ([]*queue_.Message, error) { 74 streams, err := q.db.XReadGroup(ctx, &redis.XReadGroupArgs{ 75 Streams: []string{q.stream, ">"}, 76 Group: q.streamGroup, 77 Consumer: q.consumerName, 78 Count: n, 79 Block: waitTimeout, 80 }).Result() 81 if err != nil { 82 if err == redis.Nil { 83 return nil, nil 84 } 85 return nil, err 86 } 87 88 stream := &streams[0] 89 msgs := make([]*queue_.Message, len(stream.Messages)) 90 for i := range stream.Messages { 91 xmsg := &stream.Messages[i] 92 msgs[i] = &queue_.Message{} 93 msg := msgs[i] 94 // msg.Ctx = ctx 95 data, ok := xmsg.Values["message"].(string) 96 if !ok { 97 logrus.Errorf("message type is not string") 98 continue 99 } 100 err = json.Unmarshal([]byte(data), msg) 101 if err != nil { 102 //msg.Err = err 103 logrus.WithError(err).Errorf("failed to unmarshal msg %v", string(data)) 104 return nil, err 105 } 106 msg.InnerId = xmsg.ID 107 } 108 109 return msgs, nil 110 } 111 112 func (q *Queue) FetchN(ctx context.Context, n int64, waitTimeout time.Duration) ([]*queue_.Message, error) { 113 msgs, err := q.fetchN(ctx, n, waitTimeout) 114 if err != nil { 115 if strings.HasPrefix(err.Error(), "NOGROUP") { 116 q.createMkStreamGroup(ctx) 117 return q.fetchN(ctx, n, waitTimeout) 118 } 119 } 120 return msgs, err 121 } 122 123 func (q *Queue) FetchOne(ctx context.Context, waitTimeout time.Duration) (*queue_.Message, error) { 124 msgs, err := q.FetchN(ctx, 1, waitTimeout) 125 if err != nil { 126 return nil, err 127 } 128 129 if len(msgs) == 0 { 130 //return nil, fmt.Errorf("fetch %v number messages", len(msgs)) 131 return nil, nil 132 } 133 return msgs[0], nil 134 } 135 136 func (q *Queue) createMkStreamGroup(ctx context.Context) { 137 logrus.Infof("createMkStreamGroup stream: %v, streamGroup: %v", q.stream, q.streamGroup) 138 _ = q.db.XGroupCreateMkStream(ctx, q.stream, q.streamGroup, "0").Err() 139 } 140 141 // XLEN taskq-redis-stream 142 func (q *Queue) Len() (int64, error) { 143 return q.db.XLen(context.Background(), q.stream).Result() 144 } 145 146 // XACK taskq-redis-stream taskq 1671960330664-0 147 // XDEL taskq-redis-stream 1671960330664-0 148 func (q *Queue) Delete(ctx context.Context, msg *queue_.Message) error { 149 err := q.db.XAck(ctx, q.stream, q.streamGroup, msg.InnerId).Err() 150 if err != nil { 151 logrus.WithError(err).Errorf("failed to XAck msg %v, stream[%v], stremaGroup[%v], id[%v], innerId[%v]", msg, q.stream, q.streamGroup, msg.Id, msg.InnerId) 152 return err 153 } 154 return q.db.XDel(ctx, q.stream, msg.InnerId).Err() 155 } 156 157 func (q *Queue) AddResult(ctx context.Context, result *queue_.MessageResult, expired time.Duration) (string, error) { 158 159 if result.InnerId == "" { 160 return "", fmt.Errorf("innerId is empty") 161 } 162 163 data, err := json.Marshal(result) 164 if err != nil { 165 return "", err 166 } 167 168 _, err = q.db.Set(ctx, result.InnerId, string(data), expired).Result() 169 if err != nil { 170 logrus.WithError(err).Errorf("failed to set msg result[name: %v, id: %v]", result.Name, result.Id) 171 return "", err 172 } 173 174 return result.InnerId, nil 175 } 176 177 func (q *Queue) FetchResult(ctx context.Context, key string) (*queue_.MessageResult, error) { 178 179 if key == "" { 180 return nil, fmt.Errorf("key is empty") 181 } 182 183 val, err := q.db.Get(ctx, key).Result() 184 if err != nil { 185 // redis has't the key 186 if err == redis.Nil { 187 return nil, nil 188 } 189 logrus.WithError(err).Errorf("failed to get msg result of %v", key) 190 return nil, err 191 } 192 193 result := &queue_.MessageResult{} 194 err = json.Unmarshal([]byte(val), result) 195 if err != nil { 196 return nil, err 197 } 198 199 return result, nil 200 }