go.uber.org/yarpc@v1.72.1/peer/hashring32/ring.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  import (
    24  	"math/rand"
    25  	"strconv"
    26  	"sync"
    27  	"time"
    28  
    29  	"go.uber.org/yarpc/api/peer"
    30  	"go.uber.org/yarpc/api/transport"
    31  	"go.uber.org/yarpc/peer/abstractlist"
    32  	"go.uber.org/yarpc/peer/hashring32/internal/farmhashring"
    33  	"go.uber.org/yarpc/peer/hashring32/internal/hashring32"
    34  	"go.uber.org/zap"
    35  )
    36  
    37  // NewImplementation creates a new hashring32 abstractlist.Implementation.
    38  //
    39  // Use this constructor instead of NewList, when wanting to do custom peer
    40  // connection management.
    41  func NewImplementation(opts ...Option) abstractlist.Implementation {
    42  	options := options{
    43  		logger: zap.NewNop(),
    44  	}
    45  	for _, o := range opts {
    46  		o.apply(&options)
    47  	}
    48  
    49  	return newPeerRing(
    50  		farmhashring.Fingerprint32,
    51  		options.offsetHeader,
    52  		options.peerOverrideHeader,
    53  		options.alternateShardKeyHeader,
    54  		options.offsetGeneratorValue,
    55  		options.logger,
    56  		options.peerRingOptions...,
    57  	)
    58  }
    59  
    60  // newPeerRing creates a new peerRing with an initial capacity
    61  func newPeerRing(hashFunc hashring32.HashFunc32, offsetHeader, peerOverrideHeader, alternateShardKeyHeader string, offsetGeneratorValue int, logger *zap.Logger, option ...hashring32.Option) *peerRing {
    62  	return &peerRing{
    63  		ring:                    hashring32.New(hashFunc, option...),
    64  		subscribers:             make(map[string]*subscriber),
    65  		offsetHeader:            offsetHeader,
    66  		offsetGeneratorValue:    offsetGeneratorValue,
    67  		peerOverrideHeader:      peerOverrideHeader,
    68  		logger:                  logger,
    69  		alternateShardKeyHeader: alternateShardKeyHeader,
    70  		random:                  rand.New(rand.NewSource(time.Now().UnixNano())),
    71  	}
    72  }
    73  
    74  type subscriber struct {
    75  	peer peer.StatusPeer
    76  }
    77  
    78  func (s *subscriber) UpdatePendingRequestCount(int) {}
    79  
    80  // peerRing provides a safe way to interact (Add/Remove/Get) with a potentially
    81  // changing list of peer objects
    82  type peerRing struct {
    83  	ring                    *hashring32.Hashring32
    84  	subscribers             map[string]*subscriber
    85  	offsetHeader            string
    86  	offsetGeneratorValue    int
    87  	peerOverrideHeader      string
    88  	alternateShardKeyHeader string
    89  	logger                  *zap.Logger
    90  	random                  *rand.Rand
    91  
    92  	m sync.RWMutex
    93  }
    94  
    95  var _ abstractlist.Implementation = (*peerRing)(nil)
    96  
    97  // shardIdentifier is the interface for an identifier that have a shard property
    98  type shardIdentifier interface {
    99  	Identifier() string
   100  	Shard() string
   101  }
   102  
   103  // Add a string to the end of the peerRing, if the ring is empty
   104  // it initializes the ring marker
   105  func (pr *peerRing) Add(p peer.StatusPeer, pid peer.Identifier) abstractlist.Subscriber {
   106  	pr.m.Lock()
   107  	defer pr.m.Unlock()
   108  
   109  	sub := &subscriber{peer: p}
   110  	shardID := getShardID(pid)
   111  	pr.ring.Add(shardID)
   112  	pr.subscribers[shardID] = sub
   113  
   114  	return sub
   115  }
   116  
   117  // Remove the peer from the ring. Use the subscriber to address the node of the
   118  // ring directly.
   119  func (pr *peerRing) Remove(p peer.StatusPeer, pid peer.Identifier, s abstractlist.Subscriber) {
   120  	pr.m.Lock()
   121  	defer pr.m.Unlock()
   122  
   123  	sub, ok := s.(*subscriber)
   124  	if !ok {
   125  		// Don't panic.
   126  		return
   127  	}
   128  	// Peerlist's responsibility to make sure this never happens.
   129  	if sub.peer.Identifier() != p.Identifier() {
   130  		return
   131  	}
   132  
   133  	shardID := getShardID(pid)
   134  	pr.ring.Add(shardID)
   135  	pr.subscribers[shardID] = sub
   136  
   137  	// validate that given peer is already in the subscriber
   138  	pr.ring.Remove(shardID)
   139  	// Peerlist's responsibility to make sure this is thread-safe.
   140  	delete(pr.subscribers, shardID)
   141  }
   142  
   143  func (pr *peerRing) getPeerOverride(req *transport.Request) peer.StatusPeer {
   144  	dest, ok := req.Headers.Get(pr.peerOverrideHeader)
   145  	if !ok {
   146  		return nil
   147  	}
   148  	sub, ok := pr.subscribers[dest]
   149  	if !ok {
   150  		return nil
   151  	}
   152  	return sub.peer
   153  }
   154  
   155  // Choose returns the assigned peer in the ring
   156  func (pr *peerRing) Choose(req *transport.Request) peer.StatusPeer {
   157  	pr.m.RLock()
   158  	defer pr.m.RUnlock()
   159  
   160  	// Client may want this request to go to a specific destination.
   161  	overridePeer := pr.getPeerOverride(req)
   162  	if overridePeer != nil {
   163  		return overridePeer
   164  	}
   165  
   166  	// Hashring choose can return an error eg if there are no peer available.
   167  	var n int
   168  	var err error
   169  	key, ok := req.Headers.Get(pr.offsetHeader)
   170  	if ok {
   171  		n, err = strconv.Atoi(key)
   172  		if err != nil {
   173  			pr.logger.Error("yarpc/hashring32: offset header is not a valid integer", zap.String("offsetHeader", key), zap.Error(err))
   174  		}
   175  	} else if pr.offsetGeneratorValue != 0 {
   176  		n = pr.random.Intn(pr.offsetGeneratorValue)
   177  	}
   178  
   179  	shardKey := req.ShardKey
   180  	if pr.alternateShardKeyHeader != "" {
   181  		shardKey, _ = req.Headers.Get(pr.alternateShardKeyHeader)
   182  	}
   183  
   184  	ids, err := pr.ring.Choose(hashring32.Shard{
   185  		Key: shardKey,
   186  		N:   n,
   187  	})
   188  	if err != nil {
   189  		return nil
   190  	}
   191  	if len(ids) <= n {
   192  		return nil
   193  	}
   194  	sub, ok := pr.subscribers[ids[n]]
   195  	if !ok {
   196  		return nil
   197  	}
   198  
   199  	return sub.peer
   200  }
   201  
   202  // getShardID returns the shardID from a StatusPeer.
   203  func getShardID(p peer.Identifier) string {
   204  	sp, ok := p.(shardIdentifier)
   205  	var id string
   206  	if !ok {
   207  		id = p.Identifier()
   208  	} else {
   209  		id = sp.Shard()
   210  	}
   211  	return id
   212  }