@@ -14,7 +14,7 @@ package provide redis_cluster 0.1
1414
1515namespace eval redis_cluster {}
1616set ::redis_cluster::id 0
17- array set ::redis_cluster::start_nodes {}
17+ array set ::redis_cluster::startup_nodes {}
1818array set ::redis_cluster::nodes {}
1919array set ::redis_cluster::slots {}
2020
@@ -36,14 +36,58 @@ set ::redis_cluster::plain_commands {
3636
3737proc redis_cluster {nodes} {
3838 set id [incr ::redis_cluster::id]
39- set ::redis_cluster::start_nodes ($id ) $nodes
39+ set ::redis_cluster::startup_nodes ($id ) $nodes
4040 set ::redis_cluster::nodes($id ) {}
4141 set ::redis_cluster::slots($id ) {}
4242 set handle [interp alias {} ::redis_cluster::instance$id {} ::redis_cluster::__dispatch__ $id ]
4343 $handle refresh_nodes_map
4444 return $handle
4545}
4646
47+ # Totally reset the slots / nodes state for the client, calls
48+ # CLUSTER NODES in the first startup node available, populates the
49+ # list of nodes ::redis_cluster::nodes($id) with an hash mapping node
50+ # ip:port to a representation of the node (another hash), and finally
51+ # maps ::redis_cluster::slots($id) with an hash mapping slot numbers
52+ # to node IDs.
53+ #
54+ # This function is called when a new Redis Cluster client is initialized
55+ # and every time we get a -MOVED redirection error.
56+ proc ::redis_cluster::__method__refresh_nodes_map {id} {
57+ # Contact the first responding startup node.
58+ set idx 0; # Index of the node that will respond.
59+ foreach start_node $::redis_cluster::startup_nodes($id) {
60+ lassign [split $start_node :] host port
61+ if {[catch {
62+ set r [redis $host $port ]
63+ set nodes_descr [$r cluster nodes]
64+ $r close
65+ }]} {
66+ incr idx
67+ continue ; # Try next.
68+ } else {
69+ break ; # Good node found.
70+ }
71+ }
72+
73+ if {$idx == [llength $::redis_cluster::startup_nodes($id) ]} {
74+ error " No good startup node found."
75+ }
76+
77+ # Put the node that responded as first in the list if it is not
78+ # already the first.
79+ if {$idx != 0} {
80+ set l $::redis_cluster::startup_nodes($id)
81+ set left [lrange $l 0 [expr {$idx -1}]]
82+ set right [lrange $l [expr {$idx +1}] end]
83+ set l [concat [lindex $l $idx ] $left $right ]
84+ set :redis_cluster::startup_nodes($id ) $l
85+ }
86+
87+ puts $nodes_descr
88+ exit
89+ }
90+
4791proc ::redis_cluster::__dispatch__ {id method args} {
4892 if {[info command ::redis_cluster::__method__$method ] eq {}} {
4993 # Get the keys from the command.
@@ -59,20 +103,20 @@ proc ::redis_cluster::__dispatch__ {id method args} {
59103 }
60104
61105 # Get the node mapped to this slot.
62- set node_id [dict get $::redis_cluster::slots($id) $slot ]
63- if {$node_id eq {}} {
106+ set node_addr [dict get $::redis_cluster::slots($id) $slot ]
107+ if {$node_addr eq {}} {
64108 error " No mapped node for slot $slot ."
65109 }
66110
67111 # Execute the command in the node we think is the slot owner.
68- set node [dict get $::redis_cluster::nodes($id) $node_id ]
112+ set node [dict get $::redis_cluster::nodes($id) $node_addr ]
69113 set link [dict get $node link]
70114 if {[catch {$link $method {*}$args } e]} {
71115 # TODO: trap redirection error
72116 }
73117 return $e
74118 } else {
75- uplevel 1 [list ::redis_cluster::__method__$method $id $fd ] $args
119+ uplevel 1 [list ::redis_cluster::__method__$method $id ] $args
76120 }
77121}
78122
@@ -150,4 +194,14 @@ proc ::redis_cluster::hash {key} {
150194# If the keys hash to multiple slots, an empty string is returned to
151195# signal that the command can't be run in Redis Cluster.
152196proc ::redis_cluster::get_slot_from_keys {keys} {
197+ set slot {}
198+ foreach k $keys {
199+ set s [::redis_cluster::hash $k ]
200+ if {$slot eq {}} {
201+ set slot $s
202+ } elseif {$slot != $s } {
203+ return {} ; # Error
204+ }
205+ }
206+ return $slot
153207}
0 commit comments