github.com/m3db/m3@v1.5.0/src/msg/producer/writer/consumer_service_writer.go (about)

     1  // Copyright (c) 2018 Uber Technologies, Inc.
     2  //
     3  // Permission is hereby granted, free of charge, to any person obtaining a copy
     4  // of this software and associated documentation files (the "Software"), to deal
     5  // in the Software without restriction, including without limitation the rights
     6  // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
     7  // copies of the Software, and to permit persons to whom the Software is
     8  // furnished to do so, subject to the following conditions:
     9  //
    10  // The above copyright notice and this permission notice shall be included in
    11  // all copies or substantial portions of the Software.
    12  //
    13  // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
    14  // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
    15  // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
    16  // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
    17  // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
    18  // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
    19  // THE SOFTWARE.
    20  
    21  package writer
    22  
    23  import (
    24  	"errors"
    25  	"fmt"
    26  	"sync"
    27  	"time"
    28  
    29  	"github.com/m3db/m3/src/cluster/placement"
    30  	"github.com/m3db/m3/src/msg/producer"
    31  	"github.com/m3db/m3/src/msg/topic"
    32  	"github.com/m3db/m3/src/x/watch"
    33  
    34  	"github.com/uber-go/tally"
    35  	"go.uber.org/zap"
    36  )
    37  
    38  var (
    39  	acceptAllFilter = producer.FilterFunc(
    40  		func(m producer.Message) bool {
    41  			return true
    42  		},
    43  	)
    44  
    45  	errUnknownConsumptionType = errors.New("unknown consumption type")
    46  )
    47  
    48  type initType int
    49  
    50  const (
    51  	// failOnError will fail the initialization when any error is encountered.
    52  	failOnError initType = iota
    53  
    54  	// allowInitValueError will not fail the initialization when the initial
    55  	// value could not be obtained within timeout.
    56  	// This could be used to initialize a new consumer service writer during
    57  	// runtime so it allows the consumer service writer to continue waiting
    58  	// for the placement update in the background.
    59  	allowInitValueError
    60  )
    61  
    62  type consumerServiceWriter interface {
    63  	// Write writes a message.
    64  	Write(rm *producer.RefCountedMessage)
    65  
    66  	// Init will initialize the consumer service writer.
    67  	Init(initType) error
    68  
    69  	// Close closes the writer and the background watch thread.
    70  	Close()
    71  
    72  	// SetMessageTTLNanos sets the message ttl nanoseconds.
    73  	SetMessageTTLNanos(value int64)
    74  
    75  	// RegisterFilter registers a filter for the consumer service.
    76  	RegisterFilter(fn producer.FilterFunc)
    77  
    78  	// UnregisterFilter unregisters the filter for the consumer service.
    79  	UnregisterFilter()
    80  }
    81  
    82  type consumerServiceWriterMetrics struct {
    83  	placementError    tally.Counter
    84  	placementUpdate   tally.Counter
    85  	filterAccepted    tally.Counter
    86  	filterNotAccepted tally.Counter
    87  	queueSize         tally.Gauge
    88  }
    89  
    90  func newConsumerServiceWriterMetrics(scope tally.Scope) consumerServiceWriterMetrics {
    91  	return consumerServiceWriterMetrics{
    92  		placementUpdate:   scope.Counter("placement-update"),
    93  		placementError:    scope.Counter("placement-error"),
    94  		filterAccepted:    scope.Counter("filter-accepted"),
    95  		filterNotAccepted: scope.Counter("filter-not-accepted"),
    96  		queueSize:         scope.Gauge("queue-size"),
    97  	}
    98  }
    99  
   100  type consumerServiceWriterImpl struct {
   101  	sync.Mutex
   102  
   103  	cs           topic.ConsumerService
   104  	ps           placement.Service
   105  	shardWriters []shardWriter
   106  	opts         Options
   107  	logger       *zap.Logger
   108  
   109  	value           watch.Value
   110  	dataFilter      producer.FilterFunc
   111  	router          ackRouter
   112  	consumerWriters map[string]consumerWriter
   113  	closed          bool
   114  	doneCh          chan struct{}
   115  	wg              sync.WaitGroup
   116  	m               consumerServiceWriterMetrics
   117  	cm              consumerWriterMetrics
   118  
   119  	processFn watch.ProcessFn
   120  }
   121  
   122  func newConsumerServiceWriter(
   123  	cs topic.ConsumerService,
   124  	numShards uint32,
   125  	opts Options,
   126  ) (consumerServiceWriter, error) {
   127  	ps, err := opts.ServiceDiscovery().
   128  		PlacementService(cs.ServiceID(), opts.PlacementOptions())
   129  	if err != nil {
   130  		return nil, err
   131  	}
   132  	ct := cs.ConsumptionType()
   133  	if ct == topic.Unknown {
   134  		return nil, errUnknownConsumptionType
   135  	}
   136  	router := newAckRouter(int(numShards))
   137  	w := &consumerServiceWriterImpl{
   138  		cs:              cs,
   139  		ps:              ps,
   140  		shardWriters:    initShardWriters(router, ct, numShards, opts),
   141  		opts:            opts,
   142  		logger:          opts.InstrumentOptions().Logger(),
   143  		dataFilter:      acceptAllFilter,
   144  		router:          router,
   145  		consumerWriters: make(map[string]consumerWriter),
   146  		closed:          false,
   147  		doneCh:          make(chan struct{}),
   148  		m:               newConsumerServiceWriterMetrics(opts.InstrumentOptions().MetricsScope()),
   149  		cm:              newConsumerWriterMetrics(opts.InstrumentOptions().MetricsScope()),
   150  	}
   151  	w.processFn = w.process
   152  	return w, nil
   153  }
   154  
   155  func initShardWriters(
   156  	router ackRouter,
   157  	ct topic.ConsumptionType,
   158  	numberOfShards uint32,
   159  	opts Options,
   160  ) []shardWriter {
   161  	var (
   162  		sws = make([]shardWriter, numberOfShards)
   163  		m   = newMessageWriterMetrics(
   164  			opts.InstrumentOptions().MetricsScope(),
   165  			opts.InstrumentOptions().TimerOptions(),
   166  			opts.WithoutConsumerScope(),
   167  		)
   168  		mPool messagePool
   169  	)
   170  	if opts.MessagePoolOptions() != nil {
   171  		mPool = newMessagePool(opts.MessagePoolOptions())
   172  		mPool.Init()
   173  	}
   174  	for i := range sws {
   175  		switch ct {
   176  		case topic.Shared:
   177  			sws[i] = newSharedShardWriter(uint32(i), router, mPool, opts, m)
   178  		case topic.Replicated:
   179  			sws[i] = newReplicatedShardWriter(uint32(i), numberOfShards, router, mPool, opts, m)
   180  		}
   181  	}
   182  	return sws
   183  }
   184  
   185  func (w *consumerServiceWriterImpl) Write(rm *producer.RefCountedMessage) {
   186  	if rm.Accept(w.dataFilter) {
   187  		w.shardWriters[rm.Shard()].Write(rm)
   188  		w.m.filterAccepted.Inc(1)
   189  		return
   190  	}
   191  	// It is not an error if the message does not pass the filter.
   192  	w.m.filterNotAccepted.Inc(1)
   193  }
   194  
   195  func (w *consumerServiceWriterImpl) Init(t initType) error {
   196  	w.wg.Add(1)
   197  	go func() {
   198  		w.reportMetrics()
   199  		w.wg.Done()
   200  	}()
   201  
   202  	updatableFn := func() (watch.Updatable, error) {
   203  		return w.ps.Watch()
   204  	}
   205  	getFn := func(updatable watch.Updatable) (interface{}, error) {
   206  		update, err := updatable.(placement.Watch).Get()
   207  		if err != nil {
   208  			w.m.placementError.Inc(1)
   209  			w.logger.Error("invalid placement update from kv", zap.Error(err))
   210  			return nil, err
   211  		}
   212  		w.m.placementUpdate.Inc(1)
   213  		return update, nil
   214  	}
   215  	vOptions := watch.NewOptions().
   216  		SetInitWatchTimeout(w.opts.PlacementWatchInitTimeout()).
   217  		SetInstrumentOptions(w.opts.InstrumentOptions()).
   218  		SetNewUpdatableFn(updatableFn).
   219  		SetGetUpdateFn(getFn).
   220  		SetProcessFn(w.processFn).
   221  		SetKey(w.opts.TopicName())
   222  	w.value = watch.NewValue(vOptions)
   223  	err := w.value.Watch()
   224  	if err == nil {
   225  		return nil
   226  	}
   227  	if t == allowInitValueError {
   228  		if _, ok := err.(watch.InitValueError); ok {
   229  			w.logger.Warn("invalid placement update, continue to watch for placement updates",
   230  				zap.Error(err))
   231  			return nil
   232  		}
   233  	}
   234  	return fmt.Errorf("consumer service writer init error: %v", err)
   235  }
   236  
   237  func (w *consumerServiceWriterImpl) process(update interface{}) error {
   238  	var (
   239  		p         = update.(placement.Placement)
   240  		isSharded = p.IsSharded()
   241  	)
   242  	// Non sharded placement is only allowed for Shared consumption type.
   243  	if w.cs.ConsumptionType() == topic.Replicated && !isSharded {
   244  		return fmt.Errorf("non-sharded placement for replicated consumer %s", w.cs.String())
   245  	}
   246  	// NB(cw): Lock can be removed as w.consumerWriters is only accessed in this thread.
   247  	w.Lock()
   248  	newConsumerWriters, tobeDeleted := w.diffPlacementWithLock(p)
   249  	for i, sw := range w.shardWriters {
   250  		if isSharded {
   251  			sw.UpdateInstances(p.InstancesForShard(uint32(i)), newConsumerWriters)
   252  			continue
   253  		}
   254  		sw.UpdateInstances(p.Instances(), newConsumerWriters)
   255  	}
   256  	oldConsumerWriters := w.consumerWriters
   257  	w.consumerWriters = newConsumerWriters
   258  	w.Unlock()
   259  	go func() {
   260  		for _, addr := range tobeDeleted {
   261  			cw, ok := oldConsumerWriters[addr]
   262  			if ok {
   263  				cw.Close()
   264  			}
   265  		}
   266  	}()
   267  	return nil
   268  }
   269  
   270  func (w *consumerServiceWriterImpl) diffPlacementWithLock(newPlacement placement.Placement) (map[string]consumerWriter, []string) {
   271  	var (
   272  		newInstances       = newPlacement.Instances()
   273  		newConsumerWriters = make(map[string]consumerWriter, len(newInstances))
   274  		toBeDeleted        []string
   275  	)
   276  	for _, instance := range newInstances {
   277  		id := instance.Endpoint()
   278  		cw, ok := w.consumerWriters[id]
   279  		if ok {
   280  			newConsumerWriters[id] = cw
   281  			continue
   282  		}
   283  		cw = newConsumerWriter(instance.Endpoint(), w.router, w.opts, w.cm)
   284  		cw.Init()
   285  		newConsumerWriters[id] = cw
   286  	}
   287  
   288  	for id := range w.consumerWriters {
   289  		if _, ok := newConsumerWriters[id]; !ok {
   290  			toBeDeleted = append(toBeDeleted, id)
   291  		}
   292  	}
   293  	return newConsumerWriters, toBeDeleted
   294  }
   295  
   296  func (w *consumerServiceWriterImpl) Close() {
   297  	w.Lock()
   298  	if w.closed {
   299  		w.Unlock()
   300  		return
   301  	}
   302  	w.closed = true
   303  	w.Unlock()
   304  
   305  	w.logger.Info("closing consumer service writer", zap.String("writer", w.cs.String()))
   306  	close(w.doneCh)
   307  	// Blocks until all messages consuemd.
   308  	var shardWriterWG sync.WaitGroup
   309  	for _, sw := range w.shardWriters {
   310  		sw := sw
   311  		shardWriterWG.Add(1)
   312  		go func() {
   313  			sw.Close()
   314  			shardWriterWG.Done()
   315  		}()
   316  	}
   317  	shardWriterWG.Wait()
   318  
   319  	w.value.Unwatch()
   320  	for _, cw := range w.consumerWriters {
   321  		cw.Close()
   322  	}
   323  	w.wg.Wait()
   324  	w.logger.Info("closed consumer service writer", zap.String("writer", w.cs.String()))
   325  }
   326  
   327  func (w *consumerServiceWriterImpl) SetMessageTTLNanos(value int64) {
   328  	for _, sw := range w.shardWriters {
   329  		sw.SetMessageTTLNanos(value)
   330  	}
   331  }
   332  
   333  func (w *consumerServiceWriterImpl) RegisterFilter(filter producer.FilterFunc) {
   334  	w.Lock()
   335  	w.dataFilter = filter
   336  	w.Unlock()
   337  }
   338  
   339  func (w *consumerServiceWriterImpl) UnregisterFilter() {
   340  	w.Lock()
   341  	w.dataFilter = acceptAllFilter
   342  	w.Unlock()
   343  }
   344  
   345  func (w *consumerServiceWriterImpl) reportMetrics() {
   346  	t := time.NewTicker(w.opts.InstrumentOptions().ReportInterval())
   347  	defer t.Stop()
   348  
   349  	for {
   350  		select {
   351  		case <-w.doneCh:
   352  			return
   353  		case <-t.C:
   354  			var l int
   355  			for _, sw := range w.shardWriters {
   356  				l += sw.QueueSize()
   357  			}
   358  			w.m.queueSize.Update(float64(l))
   359  		}
   360  	}
   361  }