Add new parameter to the RingOptions to override CommandInfo first key

There is problem with `eval` and `evalsha` commands.
`COMMAND INFO eval` returns first key position equals `0`.
After that, redis ring chooses `eval` as a value for sharding.
They, if you try to delete created value, ring may choose another shard
and delete won't work.

Eval command should be parsed, to be sharded properly, according to
redis specs: http://redis.io/commands/command .

I've introduced a new flag in the `RingOptions`, which will enable new
behavior: `EnableKeyLocationParsing`.

If it is enabled, `cmdFirstKey` will try to get key position using
`cmd.getFirstKeyPos()`. This function is defined for `eval` and
`evalsha` commands.
If it has parameters, it will return `3`, otherwise it will return `0`.
This commit is contained in:
Artem Chernyshev 2016-10-03 12:38:13 +03:00
parent 5a272d03b9
commit 03da66c18a
3 changed files with 39 additions and 0 deletions

View File

@ -138,6 +138,7 @@ func redisRingOptions() *redis.RingOptions {
PoolTimeout: 30 * time.Second,
IdleTimeout: 500 * time.Millisecond,
IdleCheckFrequency: 500 * time.Millisecond,
RouteByEvalKeys: true,
}
}

21
ring.go
View File

@ -40,6 +40,9 @@ type RingOptions struct {
PoolTimeout time.Duration
IdleTimeout time.Duration
IdleCheckFrequency time.Duration
// RouteByEvalKeys flag to enable eval and evalsha key position parsing for sharding
RouteByEvalKeys bool
}
func (opt *RingOptions) init() {
@ -132,6 +135,8 @@ type Ring struct {
cmdsInfoOnce *sync.Once
closed bool
routeByEvalKeys bool
}
var _ Cmdable = (*Ring)(nil)
@ -154,6 +159,7 @@ func NewRing(opt *RingOptions) *Ring {
clopt.Addr = addr
ring.addClient(name, NewClient(clopt))
}
ring.routeByEvalKeys = opt.RouteByEvalKeys
go ring.heartbeat()
return ring
}
@ -221,7 +227,22 @@ func (c *Ring) cmdInfo(name string) *CommandInfo {
return c.cmdsInfo[name]
}
func (c *Ring) getEvalFirstKey(cmd Cmder) string {
if c.routeByEvalKeys && cmd.arg(2) != "0" {
return cmd.arg(3)
} else {
return cmd.arg(0)
}
}
func (c *Ring) cmdFirstKey(cmd Cmder) string {
switch cmd.arg(0) {
case "eval":
return c.getEvalFirstKey(cmd)
case "evalsha":
return c.getEvalFirstKey(cmd)
}
cmdInfo := c.cmdInfo(cmd.arg(0))
if cmdInfo == nil {
internal.Logf("info for cmd=%s not found", cmd.arg(0))

View File

@ -89,6 +89,23 @@ var _ = Describe("Redis Ring", func() {
Expect(ringShard2.Info().Val()).To(ContainSubstring("keys=100"))
})
It("supports eval key search", func() {
script := redis.NewScript(`
local r = redis.call('SET', KEYS[1], ARGV[1])
return r
`)
var key string
for i := 0; i < 100; i++ {
key = fmt.Sprintf("key{%d}", i)
err := script.Run(ring, []string{key}, "value").Err()
Expect(err).NotTo(HaveOccurred())
}
Expect(ringShard1.Info().Val()).To(ContainSubstring("keys=52"))
Expect(ringShard2.Info().Val()).To(ContainSubstring("keys=48"))
})
Describe("pipelining", func() {
It("returns an error when all shards are down", func() {
ring := redis.NewRing(&redis.RingOptions{})