Add Jitter to backoff.

Amazon recently wrote about performance gains of using jitter in
concurrent systems. http://www.awsarchitectureblog.com/2015/03/backoff.html

Seemed simple enough to add and wont change any code that may already be
using Backoff.
This commit is contained in:
Daniel Wakefield 2015-03-06 14:42:27 +00:00
parent ff2ca325ba
commit 491389a93e
3 changed files with 73 additions and 1 deletions

View File

@ -25,6 +25,7 @@ b := &backoff.Backoff{
Min: 100 * time.Millisecond, Min: 100 * time.Millisecond,
Max: 10 * time.Second, Max: 10 * time.Second,
Factor: 2, Factor: 2,
Jitter: false,
} }
fmt.Printf("%s\n", b.Duration()) fmt.Printf("%s\n", b.Duration())
@ -72,6 +73,47 @@ for {
``` ```
**Exmaple using `Jitter`**
Setting `Jitter` adds some randomization to the backoff durations.
[See amazon's writeup of performance gains using jitter](http://www.awsarchitectureblog.com/2015/03/backoff.html).
Seeding is not necessary but doing so gives repeatable results.
```go
import "math/rand"
b := &backoff.Backoff{
//These are the defaults
Min: 100 * time.Millisecond,
Max: 10 * time.Second,
Factor: 2,
Jitter: true,
}
rand.Seed(42)
fmt.Printf("%s\n", b.Duration())
fmt.Printf("%s\n", b.Duration())
fmt.Printf("%s\n", b.Duration())
fmt.Printf("Reset!\n")
b.Reset()
fmt.Printf("%s\n", b.Duration())
fmt.Printf("%s\n", b.Duration())
fmt.Printf("%s\n", b.Duration())
```
```
100ms
106.600049ms
281.228155ms
Reset!
100ms
104.381845ms
214.957989ms
```
#### Credits #### Credits
Ported from some JavaScript written by [@tj](https://github.com/tj) Ported from some JavaScript written by [@tj](https://github.com/tj)

View File

@ -2,6 +2,7 @@ package backoff
import ( import (
"math" "math"
"math/rand"
"time" "time"
) )
@ -12,6 +13,8 @@ import (
type Backoff struct { type Backoff struct {
//Factor is the multiplying factor for each increment step //Factor is the multiplying factor for each increment step
attempts, Factor float64 attempts, Factor float64
//Jitter eases contention by randomizing backoff steps
Jitter bool
//Min and Max are the minimum and maximum values of the counter //Min and Max are the minimum and maximum values of the counter
Min, Max time.Duration Min, Max time.Duration
} }
@ -32,6 +35,9 @@ func (b *Backoff) Duration() time.Duration {
} }
//calculate this duration //calculate this duration
dur := float64(b.Min) * math.Pow(b.Factor, b.attempts) dur := float64(b.Min) * math.Pow(b.Factor, b.attempts)
if b.Jitter == true {
dur = rand.Float64()*(dur-float64(b.Min)) + float64(b.Min)
}
//cap! //cap!
if dur > float64(b.Max) { if dur > float64(b.Max) {
return b.Max return b.Max

View File

@ -50,6 +50,30 @@ func Test3(t *testing.T) {
equals(t, b.Duration(), 100*time.Nanosecond) equals(t, b.Duration(), 100*time.Nanosecond)
} }
func TestJitter(t *testing.T) {
b := &Backoff{
Min: 100 * time.Millisecond,
Max: 10 * time.Second,
Factor: 2,
Jitter: true,
}
equals(t, b.Duration(), 100*time.Millisecond)
between(t, b.Duration(), 100*time.Millisecond, 200*time.Millisecond)
between(t, b.Duration(), 100*time.Millisecond, 400*time.Millisecond)
b.Reset()
equals(t, b.Duration(), 100*time.Millisecond)
}
func between(t *testing.T, actual, low, high time.Duration) {
if actual < low {
t.Fatalf("Got %s, Expecting >= %s", actual, low)
}
if actual > high {
t.Fatalf("Got %s, Expecting <= %s", actual, high)
}
}
func equals(t *testing.T, d1, d2 time.Duration) { func equals(t *testing.T, d1, d2 time.Duration) {
if d1 != d2 { if d1 != d2 {
t.Fatalf("Got %s, Expecting %s", d1, d2) t.Fatalf("Got %s, Expecting %s", d1, d2)