go.uber.org/yarpc@v1.72.1/peer/hashring32/list.go (about)

     1  // Copyright (c) 2022 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 hashring32
    22  
    23  // package harshing32yarpc provides peerlist bindings for hashring32 in YARPC.
    24  
    25  import (
    26  	"context"
    27  	"time"
    28  
    29  	"go.uber.org/yarpc/api/peer"
    30  	"go.uber.org/yarpc/api/transport"
    31  	"go.uber.org/yarpc/api/x/introspection"
    32  	"go.uber.org/yarpc/peer/abstractlist"
    33  	"go.uber.org/yarpc/peer/hashring32/internal/hashring32"
    34  	"go.uber.org/zap"
    35  )
    36  
    37  type options struct {
    38  	offsetHeader            string
    39  	offsetGeneratorValue    int
    40  	peerOverrideHeader      string
    41  	alternateShardKeyHeader string
    42  	peerRingOptions         []hashring32.Option
    43  	defaultChooseTimeout    *time.Duration
    44  	logger                  *zap.Logger
    45  }
    46  
    47  // Option customizes the behavior of hashring32 peer list.
    48  type Option interface {
    49  	apply(*options)
    50  }
    51  
    52  // OffsetHeader is the option function that allows a user to pass a header
    53  // key to the hashring to adjust to N value in the hashring choose function.
    54  func OffsetHeader(offsetHeader string) Option {
    55  	return offsetHeaderOption{offsetHeader: offsetHeader}
    56  }
    57  
    58  type offsetHeaderOption struct {
    59  	offsetHeader string
    60  }
    61  
    62  func (o offsetHeaderOption) apply(opts *options) {
    63  	opts.offsetHeader = o.offsetHeader
    64  }
    65  
    66  // OffsetGeneratorValue is the option function that client might give
    67  // if they want to generate an offset automatically when using hashring32
    68  //
    69  // For example, if this value is set to 4, the offset used by hashring32
    70  // will be between [0-4].
    71  //
    72  // It should be noted that this option will not be used if the option
    73  // OffsetHeader is being used.
    74  func OffsetGeneratorValue(offsetGenerator int) Option {
    75  	return offsetGeneratorValueOption{offsetGeneratorValue: offsetGenerator}
    76  }
    77  
    78  type offsetGeneratorValueOption struct {
    79  	offsetGeneratorValue int
    80  }
    81  
    82  func (o offsetGeneratorValueOption) apply(opts *options) {
    83  	opts.offsetGeneratorValue = o.offsetGeneratorValue
    84  }
    85  
    86  // PeerOverrideHeader allows clients to pass a header containing the shard
    87  // identifier for a specific peer to override the destination address for the
    88  // outgoing request.
    89  //
    90  // For example, if the peer list uses addresses to identify peers, the hash
    91  // ring will have retained a peer for every known address.
    92  // Specifying an address like "127.0.0.1" in the route override header will
    93  // deflect the request to that exact peer.
    94  // If that peer is not available, the request will continue on to the peer
    95  // implied by the shard key.
    96  func PeerOverrideHeader(peerOverrideHeader string) Option {
    97  	return peerOverrideHeaderOption{peerOverrideHeader: peerOverrideHeader}
    98  }
    99  
   100  type peerOverrideHeaderOption struct {
   101  	peerOverrideHeader string
   102  }
   103  
   104  func (o peerOverrideHeaderOption) apply(opts *options) {
   105  	opts.peerOverrideHeader = o.peerOverrideHeader
   106  }
   107  
   108  // AlternateShardKeyHeader allows clients to pass a header containing the shard
   109  // identifier for a specific peer to override the destination address for the
   110  // outgoing request.
   111  func AlternateShardKeyHeader(alternateShardKeyHeader string) Option {
   112  	return alternateShardKeyHeaderOption{alternateShardKeyHeader: alternateShardKeyHeader}
   113  }
   114  
   115  type alternateShardKeyHeaderOption struct {
   116  	alternateShardKeyHeader string
   117  }
   118  
   119  func (o alternateShardKeyHeaderOption) apply(opts *options) {
   120  	opts.alternateShardKeyHeader = o.alternateShardKeyHeader
   121  }
   122  
   123  // ReplicaDelimiter overrides the the delimiter the hash ring uses to construct
   124  // replica identifiers from peer identifiers and replica numbers.
   125  //
   126  // The default delimiter is an empty string.
   127  func ReplicaDelimiter(delimiter string) Option {
   128  	return replicaDelimiterOption{delimiter: delimiter}
   129  }
   130  
   131  type replicaDelimiterOption struct {
   132  	delimiter string
   133  }
   134  
   135  func (o replicaDelimiterOption) apply(opts *options) {
   136  	opts.peerRingOptions = append(
   137  		opts.peerRingOptions,
   138  		hashring32.ReplicaFormatter(
   139  			hashring32.DelimitedReplicaFormatter(o.delimiter),
   140  		),
   141  	)
   142  }
   143  
   144  // Logger threads a logger into the hash ring constructor.
   145  func Logger(logger *zap.Logger) Option {
   146  	return loggerOption{logger: logger}
   147  }
   148  
   149  type loggerOption struct {
   150  	logger *zap.Logger
   151  }
   152  
   153  func (o loggerOption) apply(opts *options) {
   154  	opts.logger = o.logger
   155  }
   156  
   157  // NumReplicas allos client to specify the number of replicas to use for each peer.
   158  //
   159  // More replicas produces a more even distribution of entities and slower
   160  // membership updates.
   161  func NumReplicas(n int) Option {
   162  	return numReplicasOption{numReplicas: n}
   163  }
   164  
   165  type numReplicasOption struct {
   166  	numReplicas int
   167  }
   168  
   169  func (n numReplicasOption) apply(opts *options) {
   170  	opts.peerRingOptions = append(
   171  		opts.peerRingOptions,
   172  		hashring32.NumReplicas(
   173  			n.numReplicas,
   174  		),
   175  	)
   176  }
   177  
   178  // NumPeersEstimate allows client to specifiy an estimate for the number of identified peers
   179  // the hashring will contain.
   180  func NumPeersEstimate(n int) Option {
   181  	return numPeersEstimateOption{numPeersEstimate: n}
   182  }
   183  
   184  type numPeersEstimateOption struct {
   185  	numPeersEstimate int
   186  }
   187  
   188  func (n numPeersEstimateOption) apply(opts *options) {
   189  	opts.peerRingOptions = append(
   190  		opts.peerRingOptions,
   191  		hashring32.NumPeersEstimate(
   192  			n.numPeersEstimate,
   193  		),
   194  	)
   195  }
   196  
   197  // DefaultChooseTimeout specifies the default timeout to add to 'Choose' calls
   198  // without context deadlines. This prevents long-lived streams from setting
   199  // calling deadlines.
   200  //
   201  // Defaults to 500ms.
   202  func DefaultChooseTimeout(timeout time.Duration) Option {
   203  	return optionFunc(func(options *options) {
   204  		options.defaultChooseTimeout = &timeout
   205  	})
   206  }
   207  
   208  type optionFunc func(*options)
   209  
   210  func (f optionFunc) apply(options *options) { f(options) }
   211  
   212  // New creates a new hashring32 peer list.
   213  func New(transport peer.Transport, hashFunc hashring32.HashFunc32, opts ...Option) *List {
   214  	var options options
   215  	for _, o := range opts {
   216  		o.apply(&options)
   217  	}
   218  
   219  	logger := options.logger
   220  	if logger == nil {
   221  		logger = zap.NewNop()
   222  	}
   223  
   224  	ring := newPeerRing(
   225  		hashFunc,
   226  		options.offsetHeader,
   227  		options.peerOverrideHeader,
   228  		options.alternateShardKeyHeader,
   229  		options.offsetGeneratorValue,
   230  		logger,
   231  		options.peerRingOptions...,
   232  	)
   233  
   234  	plOpts := []abstractlist.Option{abstractlist.Logger(logger)}
   235  
   236  	if options.defaultChooseTimeout != nil {
   237  		plOpts = append(plOpts, abstractlist.DefaultChooseTimeout(*options.defaultChooseTimeout))
   238  	}
   239  
   240  	return &List{
   241  		list: abstractlist.New("hashring32", transport, ring, plOpts...),
   242  	}
   243  }
   244  
   245  // List is a PeerList which chooses peers based on a hashing function.
   246  type List struct {
   247  	list *abstractlist.List
   248  }
   249  
   250  // Start causes the peer list to start.
   251  //
   252  // Starting will retain all peers that have been added but not removed
   253  // the first time it is called.
   254  //
   255  // Start may be called any number of times and in any order in relation to Stop
   256  // but will only cause the list to start the first time, and only if it has not
   257  // already been stopped.
   258  func (l *List) Start() error {
   259  	return l.list.Start()
   260  }
   261  
   262  // Stop causes the peer list to stop.
   263  //
   264  // Stopping will release all retained peers to the underlying transport.
   265  //
   266  // Stop may be called any number of times and in order in relation to Start but
   267  // will only cause the list to stop the first time, and only if it has
   268  // previously been started.
   269  func (l *List) Stop() error {
   270  	return l.list.Stop()
   271  }
   272  
   273  // IsRunning returns whether the list has started and not yet stopped.
   274  func (l *List) IsRunning() bool {
   275  	return l.list.IsRunning()
   276  }
   277  
   278  // Choose returns a peer, suitable for sending a request.
   279  //
   280  // The peer is not guaranteed to be connected and available, but the peer list
   281  // makes every attempt to ensure this and minimize the probability that a
   282  // chosen peer will fail to carry a request.
   283  func (l *List) Choose(ctx context.Context, req *transport.Request) (peer peer.Peer, onFinish func(error), err error) {
   284  	return l.list.Choose(ctx, req)
   285  }
   286  
   287  // Update may add and remove logical peers in the list.
   288  //
   289  // The peer list uses a transport to obtain a physical peer for each logical
   290  // peer.
   291  // The transport is responsible for informing the peer list whether the peer is
   292  // available or unavailable, but cannot guarantee that the peer will still be
   293  // available after it is chosen.
   294  func (l *List) Update(updates peer.ListUpdates) error {
   295  	return l.list.Update(updates)
   296  }
   297  
   298  // NotifyStatusChanged forwards a status change notification to an individual
   299  // peer in the list.
   300  //
   301  // This satisfies the peer.Subscriber interface and should only be used to
   302  // send notifications in tests.
   303  // The list's RetainPeer and ReleasePeer methods deal with an individual
   304  // peer.Subscriber instance for each peer in the list, avoiding a map lookup.
   305  func (l *List) NotifyStatusChanged(pid peer.Identifier) {
   306  	l.list.NotifyStatusChanged(pid)
   307  }
   308  
   309  // Introspect reveals information about the list to the internal YARPC
   310  // introspection system.
   311  func (l *List) Introspect() introspection.ChooserStatus {
   312  	return l.list.Introspect()
   313  }
   314  
   315  // Peers produces a slice of all retained peers.
   316  func (l *List) Peers() []peer.StatusPeer {
   317  	return l.list.Peers()
   318  }