mirror of
https://github.com/redis/go-redis.git
synced 2025-07-19 11:43:14 +03:00
Set correct cluster slot for scan commands, similarly to Java's Jedis client (#2623)
- At present, the `scan` command is dispatched to a random slot. - As far as I can tell, the scanX family of commands are not cluster aware (e.g. don't redirect the client to the correct slot). - You can see [here](869dc0bb66/src/main/java/redis/clients/jedis/ShardedCommandObjects.java (L101)
), the Jedis client calling `processKey` on the match argument, and this is what this PR also does. We've had this patch running in production, and it seems to work well for us. For further thought: - Continuing looking at other Redis clients (e.g. Jedis), they outright [reject as invalid](869dc0bb66/src/main/java/redis/clients/jedis/ShardedCommandObjects.java (L98)
) any scan command that does not include a hash-tag. Presumably this has the advantage of users not being surprised when their scan produces no results when a random server is picked. - Perhaps it would be sensible for go-redis to do the same also? Co-authored-by: Nedyalko Dyakov <1547186+ndyakov@users.noreply.github.com>
This commit is contained in:
@ -3,6 +3,8 @@ package redis
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/redis/go-redis/v9/internal/hashtag"
|
||||||
)
|
)
|
||||||
|
|
||||||
type GenericCmdable interface {
|
type GenericCmdable interface {
|
||||||
@ -363,6 +365,9 @@ func (c cmdable) Scan(ctx context.Context, cursor uint64, match string, count in
|
|||||||
args = append(args, "count", count)
|
args = append(args, "count", count)
|
||||||
}
|
}
|
||||||
cmd := NewScanCmd(ctx, c, args...)
|
cmd := NewScanCmd(ctx, c, args...)
|
||||||
|
if hashtag.Present(match) {
|
||||||
|
cmd.SetFirstKeyPos(3)
|
||||||
|
}
|
||||||
_ = c(ctx, cmd)
|
_ = c(ctx, cmd)
|
||||||
return cmd
|
return cmd
|
||||||
}
|
}
|
||||||
@ -379,6 +384,9 @@ func (c cmdable) ScanType(ctx context.Context, cursor uint64, match string, coun
|
|||||||
args = append(args, "type", keyType)
|
args = append(args, "type", keyType)
|
||||||
}
|
}
|
||||||
cmd := NewScanCmd(ctx, c, args...)
|
cmd := NewScanCmd(ctx, c, args...)
|
||||||
|
if hashtag.Present(match) {
|
||||||
|
cmd.SetFirstKeyPos(3)
|
||||||
|
}
|
||||||
_ = c(ctx, cmd)
|
_ = c(ctx, cmd)
|
||||||
return cmd
|
return cmd
|
||||||
}
|
}
|
||||||
|
@ -3,6 +3,8 @@ package redis
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/redis/go-redis/v9/internal/hashtag"
|
||||||
)
|
)
|
||||||
|
|
||||||
type HashCmdable interface {
|
type HashCmdable interface {
|
||||||
@ -192,6 +194,9 @@ func (c cmdable) HScan(ctx context.Context, key string, cursor uint64, match str
|
|||||||
args = append(args, "count", count)
|
args = append(args, "count", count)
|
||||||
}
|
}
|
||||||
cmd := NewScanCmd(ctx, c, args...)
|
cmd := NewScanCmd(ctx, c, args...)
|
||||||
|
if hashtag.Present(match) {
|
||||||
|
cmd.SetFirstKeyPos(4)
|
||||||
|
}
|
||||||
_ = c(ctx, cmd)
|
_ = c(ctx, cmd)
|
||||||
return cmd
|
return cmd
|
||||||
}
|
}
|
||||||
@ -211,6 +216,9 @@ func (c cmdable) HScanNoValues(ctx context.Context, key string, cursor uint64, m
|
|||||||
}
|
}
|
||||||
args = append(args, "novalues")
|
args = append(args, "novalues")
|
||||||
cmd := NewScanCmd(ctx, c, args...)
|
cmd := NewScanCmd(ctx, c, args...)
|
||||||
|
if hashtag.Present(match) {
|
||||||
|
cmd.SetFirstKeyPos(4)
|
||||||
|
}
|
||||||
_ = c(ctx, cmd)
|
_ = c(ctx, cmd)
|
||||||
return cmd
|
return cmd
|
||||||
}
|
}
|
||||||
|
@ -56,6 +56,18 @@ func Key(key string) string {
|
|||||||
return key
|
return key
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func Present(key string) bool {
|
||||||
|
if key == "" {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if s := strings.IndexByte(key, '{'); s > -1 {
|
||||||
|
if e := strings.IndexByte(key[s+1:], '}'); e > 0 {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
func RandomSlot() int {
|
func RandomSlot() int {
|
||||||
return rand.Intn(slotNumber)
|
return rand.Intn(slotNumber)
|
||||||
}
|
}
|
||||||
|
@ -69,3 +69,28 @@ var _ = Describe("HashSlot", func() {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
var _ = Describe("Present", func() {
|
||||||
|
It("should calculate hash slots", func() {
|
||||||
|
tests := []struct {
|
||||||
|
key string
|
||||||
|
present bool
|
||||||
|
}{
|
||||||
|
{"123456789", false},
|
||||||
|
{"{}foo", false},
|
||||||
|
{"foo{}", false},
|
||||||
|
{"foo{}{bar}", false},
|
||||||
|
{"", false},
|
||||||
|
{string([]byte{83, 153, 134, 118, 229, 214, 244, 75, 140, 37, 215, 215}), false},
|
||||||
|
{"foo{bar}", true},
|
||||||
|
{"{foo}bar", true},
|
||||||
|
{"{user1000}.following", true},
|
||||||
|
{"foo{{bar}}zap", true},
|
||||||
|
{"foo{bar}{zap}", true},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, test := range tests {
|
||||||
|
Expect(Present(test.key)).To(Equal(test.present), "for %s", test.key)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
@ -1,6 +1,10 @@
|
|||||||
package redis
|
package redis
|
||||||
|
|
||||||
import "context"
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"github.com/redis/go-redis/v9/internal/hashtag"
|
||||||
|
)
|
||||||
|
|
||||||
type SetCmdable interface {
|
type SetCmdable interface {
|
||||||
SAdd(ctx context.Context, key string, members ...interface{}) *IntCmd
|
SAdd(ctx context.Context, key string, members ...interface{}) *IntCmd
|
||||||
@ -211,6 +215,9 @@ func (c cmdable) SScan(ctx context.Context, key string, cursor uint64, match str
|
|||||||
args = append(args, "count", count)
|
args = append(args, "count", count)
|
||||||
}
|
}
|
||||||
cmd := NewScanCmd(ctx, c, args...)
|
cmd := NewScanCmd(ctx, c, args...)
|
||||||
|
if hashtag.Present(match) {
|
||||||
|
cmd.SetFirstKeyPos(4)
|
||||||
|
}
|
||||||
_ = c(ctx, cmd)
|
_ = c(ctx, cmd)
|
||||||
return cmd
|
return cmd
|
||||||
}
|
}
|
||||||
|
@ -4,6 +4,8 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/redis/go-redis/v9/internal/hashtag"
|
||||||
)
|
)
|
||||||
|
|
||||||
type SortedSetCmdable interface {
|
type SortedSetCmdable interface {
|
||||||
@ -719,6 +721,9 @@ func (c cmdable) ZScan(ctx context.Context, key string, cursor uint64, match str
|
|||||||
args = append(args, "count", count)
|
args = append(args, "count", count)
|
||||||
}
|
}
|
||||||
cmd := NewScanCmd(ctx, c, args...)
|
cmd := NewScanCmd(ctx, c, args...)
|
||||||
|
if hashtag.Present(match) {
|
||||||
|
cmd.SetFirstKeyPos(4)
|
||||||
|
}
|
||||||
_ = c(ctx, cmd)
|
_ = c(ctx, cmd)
|
||||||
return cmd
|
return cmd
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user