raft: use a singleton global rand

rand.NewSource creates a 4872 byte object. With a small number of raft
groups in a process this isn't a problem. With 10k raft groups we'd use
46MB for these random sources. The only usage is in
raft.resetRandomizedElectionTimeout which isn't performance critical.

Fixes #6347.
This commit is contained in:
Peter Mattis 2016-09-05 09:03:18 -04:00
parent 1ebeef5cbf
commit 4a33aa3917

View File

@ -22,6 +22,8 @@ import (
"math/rand"
"sort"
"strings"
"sync"
"time"
pb "github.com/coreos/etcd/raft/raftpb"
)
@ -45,6 +47,25 @@ const (
campaignTransfer CampaignType = "CampaignTransfer"
)
// lockedRand is a small wrapper around rand.Rand to provide
// synchronization. Only the methods needed by the code are exposed
// (e.g. Intn).
type lockedRand struct {
mu sync.Mutex
rand *rand.Rand
}
func (r *lockedRand) Intn(n int) int {
r.mu.Lock()
v := r.rand.Intn(n)
r.mu.Unlock()
return v
}
var globalRand = &lockedRand{
rand: rand.New(rand.NewSource(time.Now().UnixNano())),
}
// CampaignType represents the type of campaigning
// the reason we use the type of string instead of uint64
// is because it's simpler to compare and fill in raft entries
@ -205,7 +226,6 @@ type raft struct {
// when raft changes its state to follower or candidate.
randomizedElectionTimeout int
rand *rand.Rand
tick func()
step stepFunc
@ -244,7 +264,6 @@ func newRaft(c *Config) *raft {
logger: c.Logger,
checkQuorum: c.CheckQuorum,
}
r.rand = rand.New(rand.NewSource(int64(c.ID)))
for _, p := range peers {
r.prs[p] = &Progress{Next: 1, ins: newInflights(r.maxInflight)}
}
@ -1024,7 +1043,7 @@ func (r *raft) pastElectionTimeout() bool {
}
func (r *raft) resetRandomizedElectionTimeout() {
r.randomizedElectionTimeout = r.electionTimeout + r.rand.Intn(r.electionTimeout)
r.randomizedElectionTimeout = r.electionTimeout + globalRand.Intn(r.electionTimeout)
}
// checkQuorumActive returns true if the quorum is active from