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,
Max: 10 * time.Second,
Factor: 2,
Jitter: false,
}
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
Ported from some JavaScript written by [@tj](https://github.com/tj)
@ -97,4 +139,4 @@ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

View File

@ -2,6 +2,7 @@ package backoff
import (
"math"
"math/rand"
"time"
)
@ -12,6 +13,8 @@ import (
type Backoff struct {
//Factor is the multiplying factor for each increment step
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, Max time.Duration
}
@ -32,6 +35,9 @@ func (b *Backoff) Duration() time.Duration {
}
//calculate this duration
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!
if dur > float64(b.Max) {
return b.Max

View File

@ -50,6 +50,30 @@ func Test3(t *testing.T) {
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) {
if d1 != d2 {
t.Fatalf("Got %s, Expecting %s", d1, d2)