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 }