@@ -19,18 +19,20 @@ var ErrNoSlot = errors.New("the slot has no redis node")
19
19
var ErrReplicaOnlyConflict = errors .New ("ReplicaOnly conflicts with SendToReplicas option" )
20
20
var ErrInvalidShardsRefreshInterval = errors .New ("ShardsRefreshInterval must be greater than or equal to 0" )
21
21
var ErrReplicaOnlyConflictWithReplicaSelector = errors .New ("ReplicaOnly conflicts with ReplicaSelector option" )
22
+ var ErrReplicaOnlyConflictWithReadNodeSelector = errors .New ("ReplicaOnly conflicts with ReadNodeSelector option" )
23
+ var ErrReplicaSelectorConflictWithReadNodeSelector = errors .New ("either set ReplicaSelector or ReadNodeSelector, not both" )
22
24
var ErrSendToReplicasNotSet = errors .New ("SendToReplicas must be set when ReplicaSelector is set" )
23
25
24
26
type clusterClient struct {
25
- pslots [16384 ]conn
27
+ wslots [16384 ]conn
26
28
retryHandler retryHandler
27
29
opt * ClientOption
28
30
rOpt * ClientOption
29
31
conns map [string ]connrole
30
32
connFn connFn
31
33
stopCh chan struct {}
32
34
sc call
33
- rslots []conn
35
+ rslots [][] NodeInfo
34
36
mu sync.RWMutex
35
37
stop uint32
36
38
cmd Builder
@@ -45,7 +47,7 @@ type connrole struct {
45
47
//replica bool <- this field is removed because a server may have mixed roles at the same time in the future. https://github.com/valkey-io/valkey/issues/1372
46
48
}
47
49
48
- var replicaOnlySelector = func (_ uint16 , replicas []ReplicaInfo ) int {
50
+ var replicaOnlySelector = func (_ uint16 , replicas []NodeInfo ) int {
49
51
return util .FastRand (len (replicas ))
50
52
}
51
53
@@ -67,11 +69,16 @@ func newClusterClient(opt *ClientOption, connFn connFn, retryer retryHandler) (*
67
69
if opt .ReplicaOnly && opt .ReplicaSelector != nil {
68
70
return nil , ErrReplicaOnlyConflictWithReplicaSelector
69
71
}
72
+ if opt .ReplicaOnly && opt .ReadNodeSelector != nil {
73
+ return nil , ErrReplicaOnlyConflictWithReadNodeSelector
74
+ }
75
+ if opt .ReplicaSelector != nil && opt .ReadNodeSelector != nil {
76
+ return nil , ErrReplicaSelectorConflictWithReadNodeSelector
77
+ }
70
78
if opt .ReplicaSelector != nil && opt .SendToReplicas == nil {
71
79
return nil , ErrSendToReplicasNotSet
72
80
}
73
-
74
- if opt .SendToReplicas != nil && opt .ReplicaSelector == nil {
81
+ if opt .SendToReplicas != nil && opt .ReplicaSelector == nil && opt .ReadNodeSelector == nil {
75
82
opt .ReplicaSelector = replicaOnlySelector
76
83
}
77
84
@@ -244,68 +251,77 @@ func (c *clusterClient) _refresh() (err error) {
244
251
}
245
252
c .mu .RUnlock ()
246
253
247
- pslots := [16384 ]conn {}
248
- var rslots []conn
249
- for master , g := range groups {
254
+ wslots := [16384 ]conn {}
255
+ var rslots [][]NodeInfo
256
+ for _ , g := range groups {
257
+
258
+ for i , nodeInfo := range g .nodes {
259
+ g .nodes [i ].conn = conns [nodeInfo .Addr ].conn
260
+ }
261
+
250
262
switch {
251
263
case c .opt .ReplicaOnly && len (g .nodes ) > 1 :
252
264
nodesCount := len (g .nodes )
253
265
for _ , slot := range g .slots {
254
266
for i := slot [0 ]; i <= slot [1 ] && i >= 0 && i < 16384 ; i ++ {
255
- pslots [i ] = conns [ g .nodes [1 + util .FastRand (nodesCount - 1 )]. Addr ].conn
267
+ wslots [i ] = g .nodes [1 + util .FastRand (nodesCount - 1 )].conn
256
268
}
257
269
}
258
270
case c .rOpt != nil :
259
271
if len (rslots ) == 0 { // lazy init
260
- rslots = make ([]conn , 16384 )
272
+ rslots = make ([][] NodeInfo , 16384 )
261
273
}
262
274
if len (g .nodes ) > 1 {
263
- n := len (g .nodes ) - 1
264
-
265
275
if c .opt .EnableReplicaAZInfo {
266
276
var wg sync.WaitGroup
267
- for i := 1 ; i <= n ; i += 4 { // batch AZ() for every 4 connections
268
- for j := i ; j <= i + 4 && j <= n ; j ++ {
277
+ for i := 0 ; i < len ( g . nodes ) ; i += 4 { // batch AZ() for every 4 connections
278
+ for j := i ; j < i + 4 && j < len ( g . nodes ) ; j ++ {
269
279
wg .Add (1 )
270
- go func (wg * sync.WaitGroup , conn conn , info * ReplicaInfo ) {
271
- info .AZ = conn .AZ ()
280
+ go func (wg * sync.WaitGroup , info * NodeInfo ) {
281
+ info .AZ = info . conn .AZ ()
272
282
wg .Done ()
273
- }(& wg , conns [ g . nodes [ j ]. Addr ]. conn , & g .nodes [j ])
283
+ }(& wg , & g .nodes [j ])
274
284
}
275
285
wg .Wait ()
276
286
}
277
287
}
278
-
279
288
for _ , slot := range g .slots {
280
289
for i := slot [0 ]; i <= slot [1 ] && i >= 0 && i < 16384 ; i ++ {
281
- pslots [i ] = conns [master ].conn
282
- rIndex := c .opt .ReplicaSelector (uint16 (i ), g .nodes [1 :])
283
- if rIndex >= 0 && rIndex < n {
284
- rslots [i ] = conns [g .nodes [1 + rIndex ].Addr ].conn
290
+ wslots [i ] = g .nodes [0 ].conn
291
+ if c .opt .ReadNodeSelector != nil {
292
+ rslots [i ] = g .nodes
285
293
} else {
286
- rslots [i ] = conns [master ].conn
294
+ rIndex := c .opt .ReplicaSelector (uint16 (i ), g .nodes [1 :]) // exclude master node
295
+ if rIndex >= 0 && rIndex < len (g .nodes )- 1 {
296
+ node := g .nodes [1 + rIndex ]
297
+ rslots [i ] = nodes {node }
298
+ } else {
299
+ node := g .nodes [0 ] // fallback to master
300
+ rslots [i ] = nodes {node }
301
+ }
287
302
}
288
303
}
289
304
}
290
305
} else {
291
306
for _ , slot := range g .slots {
292
307
for i := slot [0 ]; i <= slot [1 ] && i >= 0 && i < 16384 ; i ++ {
293
- pslots [i ] = conns [master ].conn
294
- rslots [i ] = conns [master ].conn
308
+ node := g .nodes [0 ]
309
+ wslots [i ] = node .conn
310
+ rslots [i ] = nodes {node }
295
311
}
296
312
}
297
313
}
298
314
default :
299
315
for _ , slot := range g .slots {
300
316
for i := slot [0 ]; i <= slot [1 ] && i >= 0 && i < 16384 ; i ++ {
301
- pslots [i ] = conns [ master ].conn
317
+ wslots [i ] = g . nodes [ 0 ].conn
302
318
}
303
319
}
304
320
}
305
321
}
306
322
307
323
c .mu .Lock ()
308
- c .pslots = pslots
324
+ c .wslots = wslots
309
325
c .rslots = rslots
310
326
c .conns = conns
311
327
c .mu .Unlock ()
@@ -336,7 +352,7 @@ func (c *clusterClient) nodes() []string {
336
352
return nodes
337
353
}
338
354
339
- type nodes []ReplicaInfo
355
+ type nodes []NodeInfo
340
356
341
357
type group struct {
342
358
nodes nodes
@@ -368,7 +384,7 @@ func parseSlots(slots RedisMessage, defaultAddr string) map[string]group {
368
384
g .nodes = make (nodes , 0 , len (v .values ())- 2 )
369
385
for i := 2 ; i < len (v .values ()); i ++ {
370
386
if dst := parseEndpoint (defaultAddr , v .values ()[i ].values ()[0 ].string (), v .values ()[i ].values ()[1 ].intlen ); dst != "" {
371
- g .nodes = append (g .nodes , ReplicaInfo {Addr : dst })
387
+ g .nodes = append (g .nodes , NodeInfo {Addr : dst })
372
388
}
373
389
}
374
390
}
@@ -411,7 +427,7 @@ func parseShards(shards RedisMessage, defaultAddr string, tls bool) map[string]g
411
427
if dictRole := dict ["role" ]; dictRole .string () == "master" {
412
428
m = len (g .nodes )
413
429
}
414
- g .nodes = append (g .nodes , ReplicaInfo {Addr : dst })
430
+ g .nodes = append (g .nodes , NodeInfo {Addr : dst })
415
431
}
416
432
}
417
433
if m >= 0 {
@@ -443,9 +459,19 @@ func (c *clusterClient) _pick(slot uint16, toReplica bool) (p conn) {
443
459
break
444
460
}
445
461
} else if toReplica && c .rslots != nil {
446
- p = c .rslots [slot ]
462
+ if c .opt .ReadNodeSelector != nil {
463
+ nodes := c .rslots [slot ]
464
+ rIndex := c .opt .ReadNodeSelector (slot , nodes )
465
+ if rIndex >= 0 && rIndex < len (nodes ) {
466
+ p = c.rslots [slot ][rIndex ].conn
467
+ } else {
468
+ p = c .wslots [slot ]
469
+ }
470
+ } else {
471
+ p = c .rslots [slot ][0 ].conn
472
+ }
447
473
} else {
448
- p = c .pslots [slot ]
474
+ p = c .wslots [slot ]
449
475
}
450
476
c .mu .RUnlock ()
451
477
return p
@@ -476,7 +502,7 @@ func (c *clusterClient) redirectOrNew(addr string, prev conn, slot uint16, mode
476
502
cc = connrole {conn : p }
477
503
c .conns [addr ] = cc
478
504
if mode == RedirectMove {
479
- c .pslots [slot ] = p
505
+ c .wslots [slot ] = p
480
506
}
481
507
} else if prev == cc .conn {
482
508
// try reconnection if the MOVED redirects to the same host,
@@ -490,7 +516,7 @@ func (c *clusterClient) redirectOrNew(addr string, prev conn, slot uint16, mode
490
516
cc = connrole {conn : p }
491
517
c .conns [addr ] = cc
492
518
if mode == RedirectMove { // MOVED should always point to the primary.
493
- c .pslots [slot ] = p
519
+ c .wslots [slot ] = p
494
520
}
495
521
}
496
522
c .mu .Unlock ()
@@ -575,14 +601,27 @@ func (c *clusterClient) _pickMulti(multi []Completed) (retries *connretry, init
575
601
576
602
if ! init && c .rslots != nil && c .opt .SendToReplicas != nil {
577
603
var bm bitmap
604
+ itor := make (map [int ]int )
578
605
bm .Init (len (multi ))
579
606
for i , cmd := range multi {
580
607
var cc conn
608
+ slot := cmd .Slot ()
581
609
if c .opt .SendToReplicas (cmd ) {
582
610
bm .Set (i )
583
- cc = c .rslots [cmd .Slot ()]
611
+ if c .opt .ReadNodeSelector != nil {
612
+ nodes := c .rslots [slot ]
613
+ rIndex := c .opt .ReadNodeSelector (slot , nodes )
614
+ if rIndex > 0 && rIndex < len (nodes ) {
615
+ itor [i ] = rIndex
616
+ } else {
617
+ rIndex = 0 // default itor[i] = 0
618
+ }
619
+ cc = nodes [rIndex ].conn
620
+ } else {
621
+ cc = c .rslots [slot ][0 ].conn
622
+ }
584
623
} else {
585
- cc = c .pslots [ cmd . Slot () ]
624
+ cc = c .wslots [ slot ]
586
625
}
587
626
if cc == nil {
588
627
return nil , false
@@ -599,9 +638,9 @@ func (c *clusterClient) _pickMulti(multi []Completed) (retries *connretry, init
599
638
for i , cmd := range multi {
600
639
var cc conn
601
640
if bm .Get (i ) {
602
- cc = c .rslots [cmd .Slot ()]
641
+ cc = c .rslots [cmd .Slot ()][ itor [ i ]]. conn
603
642
} else {
604
- cc = c .pslots [cmd .Slot ()]
643
+ cc = c .wslots [cmd .Slot ()]
605
644
}
606
645
re := retries .m [cc ]
607
646
re .commands = append (re .commands , cmd )
@@ -621,7 +660,7 @@ func (c *clusterClient) _pickMulti(multi []Completed) (retries *connretry, init
621
660
} else if init && last != cmd .Slot () {
622
661
panic (panicMixCxSlot )
623
662
}
624
- cc := c .pslots [cmd .Slot ()]
663
+ cc := c .wslots [cmd .Slot ()]
625
664
if cc == nil {
626
665
return nil , false
627
666
}
@@ -630,7 +669,7 @@ func (c *clusterClient) _pickMulti(multi []Completed) (retries *connretry, init
630
669
631
670
if last == cmds .InitSlot {
632
671
// if all commands have no slots, such as INFO, we pick a non-nil slot.
633
- for i , cc := range c .pslots {
672
+ for i , cc := range c .wslots {
634
673
if cc != nil {
635
674
last = uint16 (i )
636
675
count .m [cc ] = inits
@@ -641,7 +680,7 @@ func (c *clusterClient) _pickMulti(multi []Completed) (retries *connretry, init
641
680
return nil , false
642
681
}
643
682
} else if init {
644
- cc := c .pslots [last ]
683
+ cc := c .wslots [last ]
645
684
count .m [cc ] += inits
646
685
}
647
686
@@ -654,9 +693,9 @@ func (c *clusterClient) _pickMulti(multi []Completed) (retries *connretry, init
654
693
for i , cmd := range multi {
655
694
var cc conn
656
695
if cmd .Slot () != cmds .InitSlot {
657
- cc = c .pslots [cmd .Slot ()]
696
+ cc = c .wslots [cmd .Slot ()]
658
697
} else {
659
- cc = c .pslots [last ]
698
+ cc = c .wslots [last ]
660
699
}
661
700
re := retries .m [cc ]
662
701
re .commands = append (re .commands , cmd )
@@ -1013,7 +1052,7 @@ func (c *clusterClient) _pickMultiCache(multi []CacheableTTL) *connretrycache {
1013
1052
count := conncountp .Get (len (c .conns ), len (c .conns ))
1014
1053
if c .opt .SendToReplicas == nil || c .rslots == nil {
1015
1054
for _ , cmd := range multi {
1016
- p := c .pslots [cmd .Cmd .Slot ()]
1055
+ p := c .wslots [cmd .Cmd .Slot ()]
1017
1056
if p == nil {
1018
1057
return nil
1019
1058
}
@@ -1027,7 +1066,7 @@ func (c *clusterClient) _pickMultiCache(multi []CacheableTTL) *connretrycache {
1027
1066
conncountp .Put (count )
1028
1067
1029
1068
for i , cmd := range multi {
1030
- cc := c .pslots [cmd .Cmd .Slot ()]
1069
+ cc := c .wslots [cmd .Cmd .Slot ()]
1031
1070
re := retries .m [cc ]
1032
1071
re .commands = append (re .commands , cmd )
1033
1072
re .cIndexes = append (re .cIndexes , i )
@@ -1044,10 +1083,20 @@ func (c *clusterClient) _pickMultiCache(multi []CacheableTTL) *connretrycache {
1044
1083
}
1045
1084
for i , cmd := range multi {
1046
1085
var p conn
1086
+ slot := cmd .Cmd .Slot ()
1047
1087
if c .opt .SendToReplicas (Completed (cmd .Cmd )) {
1048
- p = c .rslots [cmd .Cmd .Slot ()]
1088
+ if c .opt .ReadNodeSelector != nil {
1089
+ rIndex := c .opt .ReadNodeSelector (slot , c .rslots [slot ])
1090
+ if rIndex >= 0 && rIndex < len (c .rslots [slot ]) {
1091
+ p = c.rslots [slot ][rIndex ].conn
1092
+ } else {
1093
+ p = c .wslots [slot ]
1094
+ }
1095
+ } else {
1096
+ p = c .rslots [slot ][0 ].conn
1097
+ }
1049
1098
} else {
1050
- p = c .pslots [ cmd . Cmd . Slot () ]
1099
+ p = c .wslots [ slot ]
1051
1100
}
1052
1101
if p == nil {
1053
1102
return nil
0 commit comments