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 }