@@ -7,11 +7,14 @@ import (
7
7
"fmt"
8
8
"io/ioutil"
9
9
"log"
10
+ "math"
11
+ "math/rand"
10
12
"net"
11
13
"os"
12
14
"reflect"
13
15
"strconv"
14
16
"strings"
17
+ "sync"
15
18
"time"
16
19
"unsafe"
17
20
)
@@ -27,6 +30,61 @@ type defaultsHandler interface {
27
30
SetDefaults (c * Config ) error
28
31
}
29
32
33
+ // BackoffStrategy defines a strategy for calculating the duration of time
34
+ // a consumer should backoff for a given attempt
35
+ type BackoffStrategy interface {
36
+ Calculate (attempt int ) time.Duration
37
+ }
38
+
39
+ // ExponentialStrategy implements an exponential backoff strategy (default)
40
+ type ExponentialStrategy struct {
41
+ cfg * Config
42
+ }
43
+
44
+ // Calculate returns a duration of time: 2 ^ attempt
45
+ func (s * ExponentialStrategy ) Calculate (attempt int ) time.Duration {
46
+ backoffDuration := s .cfg .BackoffMultiplier *
47
+ time .Duration (math .Pow (2 , float64 (attempt )))
48
+ if backoffDuration > s .cfg .MaxBackoffDuration {
49
+ backoffDuration = s .cfg .MaxBackoffDuration
50
+ }
51
+ return backoffDuration
52
+ }
53
+
54
+ func (s * ExponentialStrategy ) setConfig (cfg * Config ) {
55
+ s .cfg = cfg
56
+ }
57
+
58
+ // FullJitterStrategy implements http://www.awsarchitectureblog.com/2015/03/backoff.html
59
+ type FullJitterStrategy struct {
60
+ cfg * Config
61
+
62
+ rngOnce sync.Once
63
+ rng * rand.Rand
64
+ }
65
+
66
+ // Calculate returns a random duration of time [0, 2 ^ attempt]
67
+ func (s * FullJitterStrategy ) Calculate (attempt int ) time.Duration {
68
+ // lazily initialize the RNG
69
+ s .rngOnce .Do (func () {
70
+ if s .rng != nil {
71
+ return
72
+ }
73
+ s .rng = rand .New (rand .NewSource (time .Now ().UnixNano ()))
74
+ })
75
+
76
+ backoffDuration := s .cfg .BackoffMultiplier *
77
+ time .Duration (math .Pow (2 , float64 (attempt )))
78
+ if backoffDuration > s .cfg .MaxBackoffDuration {
79
+ backoffDuration = s .cfg .MaxBackoffDuration
80
+ }
81
+ return time .Duration (s .rng .Intn (int (backoffDuration )))
82
+ }
83
+
84
+ func (s * FullJitterStrategy ) setConfig (cfg * Config ) {
85
+ s .cfg = cfg
86
+ }
87
+
30
88
// Config is a struct of NSQ options
31
89
//
32
90
// The only valid way to create a Config is via NewConfig, using a struct literal will panic.
@@ -59,11 +117,17 @@ type Config struct {
59
117
// Maximum duration when REQueueing (for doubling of deferred requeue)
60
118
MaxRequeueDelay time.Duration `opt:"max_requeue_delay" min:"0" max:"60m" default:"15m"`
61
119
DefaultRequeueDelay time.Duration `opt:"default_requeue_delay" min:"0" max:"60m" default:"90s"`
120
+
121
+ // Backoff strategy, defaults to exponential backoff. Overwrite this to define alternative backoff algrithms.
122
+ BackoffStrategy BackoffStrategy
123
+ // Maximum amount of time to backoff when processing fails 0 == no backoff
124
+ MaxBackoffDuration time.Duration `opt:"max_backoff_duration" min:"0" max:"60m" default:"2m"`
62
125
// Unit of time for calculating consumer backoff
63
126
BackoffMultiplier time.Duration `opt:"backoff_multiplier" min:"0" max:"60m" default:"1s"`
64
127
65
128
// Maximum number of times this consumer will attempt to process a message before giving up
66
129
MaxAttempts uint16 `opt:"max_attempts" min:"0" max:"65535" default:"5"`
130
+
67
131
// Amount of time in seconds to wait for a message from a producer when in a state where RDY
68
132
// counts are re-distributed (ie. max_in_flight < num_producers)
69
133
LowRdyIdleTimeout time.Duration `opt:"low_rdy_idle_timeout" min:"1s" max:"5m" default:"10s"`
@@ -108,9 +172,6 @@ type Config struct {
108
172
// Maximum number of messages to allow in flight (concurrency knob)
109
173
MaxInFlight int `opt:"max_in_flight" min:"0" default:"1"`
110
174
111
- // Maximum amount of time to backoff when processing fails 0 == no backoff
112
- MaxBackoffDuration time.Duration `opt:"max_backoff_duration" min:"0" max:"60m" default:"2m"`
113
-
114
175
// The server-side message timeout for messages delivered to this client
115
176
MsgTimeout time.Duration `opt:"msg_timeout" min:"0"`
116
177
@@ -122,9 +183,10 @@ type Config struct {
122
183
//
123
184
// This must be used to initialize Config structs. Values can be set directly, or through Config.Set()
124
185
func NewConfig () * Config {
125
- c := & Config {}
126
- c .configHandlers = append (c .configHandlers , & structTagsConfig {}, & tlsConfig {})
127
- c .initialized = true
186
+ c := & Config {
187
+ configHandlers : []configHandler {& structTagsConfig {}, & tlsConfig {}},
188
+ initialized : true ,
189
+ }
128
190
if err := c .setDefaults (); err != nil {
129
191
panic (err .Error ())
130
192
}
@@ -170,7 +232,6 @@ func (c *Config) assertInitialized() {
170
232
// Validate checks that all values are within specified min/max ranges
171
233
func (c * Config ) Validate () error {
172
234
c .assertInitialized ()
173
-
174
235
for _ , h := range c .configHandlers {
175
236
if err := h .Validate (c ); err != nil {
176
237
return err
@@ -188,7 +249,6 @@ func (c *Config) setDefaults() error {
188
249
}
189
250
}
190
251
}
191
-
192
252
return nil
193
253
}
194
254
@@ -271,6 +331,7 @@ func (h *structTagsConfig) SetDefaults(c *Config) error {
271
331
log .Fatalf ("ERROR: unable to get hostname %s" , err .Error ())
272
332
}
273
333
334
+ c .BackoffStrategy = & ExponentialStrategy {}
274
335
c .ClientID = strings .Split (hostname , "." )[0 ]
275
336
c .Hostname = hostname
276
337
c .UserAgent = fmt .Sprintf ("go-nsq/%s" , VERSION )
@@ -311,6 +372,18 @@ func (h *structTagsConfig) Validate(c *Config) error {
311
372
if c .HeartbeatInterval > c .ReadTimeout {
312
373
return fmt .Errorf ("HeartbeatInterval %v must be less than ReadTimeout %v" , c .HeartbeatInterval , c .ReadTimeout )
313
374
}
375
+
376
+ if c .BackoffStrategy == nil {
377
+ return fmt .Errorf ("BackoffStrategy cannot be nil" )
378
+ }
379
+
380
+ // initialize internal backoff strategies that need access to config
381
+ if v , ok := c .BackoffStrategy .(interface {
382
+ setConfig (* Config )
383
+ }); ok {
384
+ v .setConfig (c )
385
+ }
386
+
314
387
return nil
315
388
}
316
389
0 commit comments