Skip to content

Commit

Permalink
Select random node when there are no keys.
Browse files Browse the repository at this point in the history
  • Loading branch information
vmihailenco committed Oct 9, 2016
1 parent 5aae583 commit eeba1d7
Show file tree
Hide file tree
Showing 4 changed files with 155 additions and 136 deletions.
162 changes: 80 additions & 82 deletions cluster.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,72 @@ import (
"gopkg.in/redis.v4/internal/pool"
)

// ClusterOptions are used to configure a cluster client and should be
// passed to NewClusterClient.
type ClusterOptions struct {
// A seed list of host:port addresses of cluster nodes.
Addrs []string

// The maximum number of retries before giving up. Command is retried
// on network errors and MOVED/ASK redirects.
// Default is 16.
MaxRedirects int

// Enables read queries for a connection to a Redis Cluster slave node.
ReadOnly bool

// Enables routing read-only queries to the closest master or slave node.
RouteByLatency bool

// Following options are copied from Options struct.

Password string

DialTimeout time.Duration
ReadTimeout time.Duration
WriteTimeout time.Duration

// PoolSize applies per cluster node and not for the whole cluster.
PoolSize int
PoolTimeout time.Duration
IdleTimeout time.Duration
IdleCheckFrequency time.Duration
}

func (opt *ClusterOptions) init() {
if opt.MaxRedirects == -1 {
opt.MaxRedirects = 0
} else if opt.MaxRedirects == 0 {
opt.MaxRedirects = 16
}

if opt.RouteByLatency {
opt.ReadOnly = true
}
}

func (opt *ClusterOptions) clientOptions() *Options {
const disableIdleCheck = -1

return &Options{
Password: opt.Password,
ReadOnly: opt.ReadOnly,

DialTimeout: opt.DialTimeout,
ReadTimeout: opt.ReadTimeout,
WriteTimeout: opt.WriteTimeout,

PoolSize: opt.PoolSize,
PoolTimeout: opt.PoolTimeout,
IdleTimeout: opt.IdleTimeout,

// IdleCheckFrequency is not copied to disable reaper
IdleCheckFrequency: disableIdleCheck,
}
}

//------------------------------------------------------------------------------

type clusterNode struct {
Client *Client
Latency time.Duration
Expand All @@ -36,8 +102,8 @@ type ClusterClient struct {
slots [][]*clusterNode
closed bool

cmdsInfo map[string]*CommandInfo
cmdsInfoOnce *sync.Once
cmdsInfo map[string]*CommandInfo

// Reports where slots reloading is in progress.
reloading uint32
Expand Down Expand Up @@ -81,19 +147,22 @@ func (c *ClusterClient) cmdInfo(name string) *CommandInfo {
}
c.cmdsInfoOnce = &sync.Once{}
})
if c.cmdsInfo == nil {
return nil
}
return c.cmdsInfo[name]
}

func (c *ClusterClient) getNodes() map[string]*clusterNode {
var nodes map[string]*clusterNode
c.mu.RLock()
if !c.closed {
nodes = make(map[string]*clusterNode, len(c.nodes))
c.mu.RLock()
for addr, node := range c.nodes {
nodes[addr] = node
}
c.mu.RUnlock()
}
c.mu.RUnlock()
return nodes
}

Expand Down Expand Up @@ -257,18 +326,11 @@ func (c *ClusterClient) slotClosestNode(slot int) (*clusterNode, error) {

func (c *ClusterClient) cmdSlotAndNode(cmd Cmder) (int, *clusterNode, error) {
cmdInfo := c.cmdInfo(cmd.arg(0))
if cmdInfo == nil {
internal.Logf("info for cmd=%s not found", cmd.arg(0))
firstKey := cmd.arg(cmdFirstKeyPos(cmd, cmdInfo))
if firstKey == "" {
node, err := c.randomNode()
return 0, node, err
return -1, node, err
}

if cmdInfo.FirstKeyPos == -1 {
node, err := c.randomNode()
return 0, node, err
}

firstKey := cmd.arg(int(cmdInfo.FirstKeyPos))
slot := hashtag.Slot(firstKey)

if cmdInfo.ReadOnly && c.opt.ReadOnly {
Expand Down Expand Up @@ -330,9 +392,11 @@ func (c *ClusterClient) Process(cmd Cmder) error {
var addr string
moved, ask, addr = errors.IsMoved(err)
if moved || ask {
master, _ := c.slotMasterNode(slot)
if moved && (master == nil || master.Client.getAddr() != addr) {
c.lazyReloadSlots()
if slot >= 0 {
master, _ := c.slotMasterNode(slot)
if moved && (master == nil || master.Client.getAddr() != addr) {
c.lazyReloadSlots()
}
}

node, err = c.nodeByAddr(addr)
Expand Down Expand Up @@ -609,69 +673,3 @@ func (c *ClusterClient) execClusterCmds(

return failedCmds, retErr
}

//------------------------------------------------------------------------------

// ClusterOptions are used to configure a cluster client and should be
// passed to NewClusterClient.
type ClusterOptions struct {
// A seed list of host:port addresses of cluster nodes.
Addrs []string

// The maximum number of retries before giving up. Command is retried
// on network errors and MOVED/ASK redirects.
// Default is 16.
MaxRedirects int

// Enables read queries for a connection to a Redis Cluster slave node.
ReadOnly bool

// Enables routing read-only queries to the closest master or slave node.
RouteByLatency bool

// Following options are copied from Options struct.

Password string

DialTimeout time.Duration
ReadTimeout time.Duration
WriteTimeout time.Duration

// PoolSize applies per cluster node and not for the whole cluster.
PoolSize int
PoolTimeout time.Duration
IdleTimeout time.Duration
IdleCheckFrequency time.Duration
}

func (opt *ClusterOptions) init() {
if opt.MaxRedirects == -1 {
opt.MaxRedirects = 0
} else if opt.MaxRedirects == 0 {
opt.MaxRedirects = 16
}

if opt.RouteByLatency {
opt.ReadOnly = true
}
}

func (opt *ClusterOptions) clientOptions() *Options {
const disableIdleCheck = -1

return &Options{
Password: opt.Password,
ReadOnly: opt.ReadOnly,

DialTimeout: opt.DialTimeout,
ReadTimeout: opt.ReadTimeout,
WriteTimeout: opt.WriteTimeout,

PoolSize: opt.PoolSize,
PoolTimeout: opt.PoolTimeout,
IdleTimeout: opt.IdleTimeout,

// IdleCheckFrequency is not copied to disable reaper
IdleCheckFrequency: disableIdleCheck,
}
}
27 changes: 22 additions & 5 deletions command.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ import (
"strings"
"time"

"github.com/go-redis/redis/internal"

"gopkg.in/redis.v4/internal/pool"
"gopkg.in/redis.v4/internal/proto"
)
Expand Down Expand Up @@ -88,6 +90,22 @@ func cmdString(cmd Cmder, val interface{}) string {

}

func cmdFirstKeyPos(cmd Cmder, info *CommandInfo) int {
switch cmd.arg(0) {
case "eval", "evalsha":
if cmd.arg(2) != "0" {
return 3
} else {
return 0
}
}
if info == nil {
internal.Logf("info for cmd=%s not found", cmd.arg(0))
return -1
}
return int(info.FirstKeyPos)
}

//------------------------------------------------------------------------------

type baseCmd struct {
Expand All @@ -109,12 +127,11 @@ func (cmd *baseCmd) args() []interface{} {
}

func (cmd *baseCmd) arg(pos int) string {
if len(cmd._args) > pos {
if s, ok := cmd._args[pos].(string); ok {
return s
}
if pos < 0 || pos >= len(cmd._args) {
return ""
}
return ""
s, _ := cmd._args[pos].(string)
return s
}

func (cmd *baseCmd) readTimeout() *time.Duration {
Expand Down
1 change: 0 additions & 1 deletion main_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -138,7 +138,6 @@ func redisRingOptions() *redis.RingOptions {
PoolTimeout: 30 * time.Second,
IdleTimeout: 500 * time.Millisecond,
IdleCheckFrequency: 500 * time.Millisecond,
RouteByEvalKeys: true,
}
}

Expand Down
Loading

0 comments on commit eeba1d7

Please sign in to comment.