gitee.com/sasukebo/go-micro/v4@v4.7.1/events/memory.go (about)

     1  package events
     2  
     3  import (
     4  	"encoding/json"
     5  	"fmt"
     6  	"sync"
     7  	"time"
     8  
     9  	"gitee.com/sasukebo/go-micro/v4/logger"
    10  	"gitee.com/sasukebo/go-micro/v4/store"
    11  	"github.com/google/uuid"
    12  	"github.com/pkg/errors"
    13  )
    14  
    15  // NewStream returns an initialized memory stream
    16  func NewStream(opts ...Option) (Stream, error) {
    17  	// parse the options
    18  	var options Options
    19  	for _, o := range opts {
    20  		o(&options)
    21  	}
    22  	return &mem{store: store.NewMemoryStore()}, nil
    23  }
    24  
    25  type subscriber struct {
    26  	Group   string
    27  	Topic   string
    28  	Channel chan Event
    29  
    30  	sync.RWMutex
    31  	retryMap   map[string]int
    32  	retryLimit int
    33  	autoAck    bool
    34  	ackWait    time.Duration
    35  }
    36  
    37  type mem struct {
    38  	store store.Store
    39  
    40  	subs []*subscriber
    41  	sync.RWMutex
    42  }
    43  
    44  func (m *mem) Publish(topic string, msg interface{}, opts ...PublishOption) error {
    45  	// validate the topic
    46  	if len(topic) == 0 {
    47  		return ErrMissingTopic
    48  	}
    49  
    50  	// parse the options
    51  	options := PublishOptions{
    52  		Timestamp: time.Now(),
    53  	}
    54  	for _, o := range opts {
    55  		o(&options)
    56  	}
    57  
    58  	// encode the message if it's not already encoded
    59  	var payload []byte
    60  	if p, ok := msg.([]byte); ok {
    61  		payload = p
    62  	} else {
    63  		p, err := json.Marshal(msg)
    64  		if err != nil {
    65  			return ErrEncodingMessage
    66  		}
    67  		payload = p
    68  	}
    69  
    70  	// construct the event
    71  	event := &Event{
    72  		ID:        uuid.New().String(),
    73  		Topic:     topic,
    74  		Timestamp: options.Timestamp,
    75  		Metadata:  options.Metadata,
    76  		Payload:   payload,
    77  	}
    78  
    79  	// serialize the event to bytes
    80  	bytes, err := json.Marshal(event)
    81  	if err != nil {
    82  		return errors.Wrap(err, "Error encoding event")
    83  	}
    84  
    85  	// write to the store
    86  	key := fmt.Sprintf("%v/%v", event.Topic, event.ID)
    87  	if err := m.store.Write(&store.Record{Key: key, Value: bytes}); err != nil {
    88  		return errors.Wrap(err, "Error writing event to store")
    89  	}
    90  
    91  	// send to the subscribers async
    92  	go m.handleEvent(event)
    93  
    94  	return nil
    95  }
    96  
    97  func (m *mem) Consume(topic string, opts ...ConsumeOption) (<-chan Event, error) {
    98  	// validate the topic
    99  	if len(topic) == 0 {
   100  		return nil, ErrMissingTopic
   101  	}
   102  
   103  	// parse the options
   104  	options := ConsumeOptions{
   105  		Group:   uuid.New().String(),
   106  		AutoAck: true,
   107  	}
   108  	for _, o := range opts {
   109  		o(&options)
   110  	}
   111  	// TODO RetryLimit
   112  
   113  	// setup the subscriber
   114  	sub := &subscriber{
   115  		Channel:    make(chan Event),
   116  		Topic:      topic,
   117  		Group:      options.Group,
   118  		retryMap:   map[string]int{},
   119  		autoAck:    true,
   120  		retryLimit: options.GetRetryLimit(),
   121  	}
   122  
   123  	if !options.AutoAck {
   124  		if options.AckWait == 0 {
   125  			return nil, fmt.Errorf("invalid AckWait passed, should be positive integer")
   126  		}
   127  		sub.autoAck = options.AutoAck
   128  		sub.ackWait = options.AckWait
   129  	}
   130  
   131  	// register the subscriber
   132  	m.Lock()
   133  	m.subs = append(m.subs, sub)
   134  	m.Unlock()
   135  
   136  	// lookup previous events if the start time option was passed
   137  	if options.Offset.Unix() > 0 {
   138  		go m.lookupPreviousEvents(sub, options.Offset)
   139  	}
   140  
   141  	// return the channel
   142  	return sub.Channel, nil
   143  }
   144  
   145  // lookupPreviousEvents finds events for a subscriber which occurred before a given time and sends
   146  // them into the subscribers channel
   147  func (m *mem) lookupPreviousEvents(sub *subscriber, startTime time.Time) {
   148  	// lookup all events which match the topic (a blank topic will return all results)
   149  	recs, err := m.store.Read(sub.Topic+"/", store.ReadPrefix())
   150  	if err != nil && logger.V(logger.ErrorLevel, logger.DefaultLogger) {
   151  		logger.Errorf("Error looking up previous events: %v", err)
   152  		return
   153  	} else if err != nil {
   154  		return
   155  	}
   156  
   157  	// loop through the records and send it to the channel if it matches
   158  	for _, r := range recs {
   159  		var ev Event
   160  		if err := json.Unmarshal(r.Value, &ev); err != nil {
   161  			continue
   162  		}
   163  		if ev.Timestamp.Unix() < startTime.Unix() {
   164  			continue
   165  		}
   166  		sendEvent(&ev, sub)
   167  	}
   168  }
   169  
   170  // handleEvents sends the event to any registered subscribers.
   171  func (m *mem) handleEvent(ev *Event) {
   172  	m.RLock()
   173  	subs := m.subs
   174  	m.RUnlock()
   175  
   176  	// filteredSubs is a KV map of the queue name and subscribers. This is used to prevent a message
   177  	// being sent to two subscribers with the same queue.
   178  	filteredSubs := map[string]*subscriber{}
   179  
   180  	// filter down to subscribers who are interested in this topic
   181  	for _, sub := range subs {
   182  		if len(sub.Topic) == 0 || sub.Topic == ev.Topic {
   183  			filteredSubs[sub.Group] = sub
   184  		}
   185  	}
   186  
   187  	// send the message to each channel async (since one channel might be blocked)
   188  	for _, sub := range filteredSubs {
   189  		sendEvent(ev, sub)
   190  	}
   191  }
   192  
   193  func sendEvent(ev *Event, sub *subscriber) {
   194  	go func(s *subscriber) {
   195  		evCopy := *ev
   196  		if s.autoAck {
   197  			s.Channel <- evCopy
   198  			return
   199  		}
   200  		evCopy.SetAckFunc(ackFunc(s, evCopy))
   201  		evCopy.SetNackFunc(nackFunc(s, evCopy))
   202  		s.retryMap[evCopy.ID] = 0
   203  		tick := time.NewTicker(s.ackWait)
   204  		defer tick.Stop()
   205  		for range tick.C {
   206  			s.Lock()
   207  			count, ok := s.retryMap[evCopy.ID]
   208  			s.Unlock()
   209  			if !ok {
   210  				// success
   211  				break
   212  			}
   213  
   214  			if s.retryLimit > -1 && count > s.retryLimit {
   215  				if logger.V(logger.ErrorLevel, logger.DefaultLogger) {
   216  					logger.Errorf("Message retry limit reached, discarding: %v %d %d", evCopy.ID, count, s.retryLimit)
   217  				}
   218  				s.Lock()
   219  				delete(s.retryMap, evCopy.ID)
   220  				s.Unlock()
   221  				return
   222  			}
   223  			s.Channel <- evCopy
   224  			s.Lock()
   225  			s.retryMap[evCopy.ID] = count + 1
   226  			s.Unlock()
   227  		}
   228  	}(sub)
   229  }
   230  
   231  func ackFunc(s *subscriber, evCopy Event) func() error {
   232  	return func() error {
   233  		s.Lock()
   234  		delete(s.retryMap, evCopy.ID)
   235  		s.Unlock()
   236  		return nil
   237  	}
   238  }
   239  
   240  func nackFunc(s *subscriber, evCopy Event) func() error {
   241  	return func() error {
   242  		return nil
   243  	}
   244  }