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  }