Skip to content

Commit d3631d9

Browse files
authored
feat: add replica affinity option (#98)
1 parent 4e742a4 commit d3631d9

21 files changed

+890
-199
lines changed

README.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ gem 'redis-cluster-client'
1717
| --- | --- | --- | --- |
1818
| `:nodes` | String or Hash or Array<String, Hash> | `['redis://127.0.0.1:6379']` | node addresses for startup connection |
1919
| `:replica` | Boolean | `false` | `true` if client should use scale read feature |
20+
| `:replica_affinity` | Symbol or String | `:random` | scale reading strategy, `:random` or `:latency` are valid |
2021
| `:fixed_hostname` | String | `nil` | required if client should connect to single endpoint with SSL |
2122

2223
Also, [the other generic options](https://github.com/redis-rb/redis-client#configuration) can be passed.
@@ -36,6 +37,10 @@ RedisClient.cluster.new_client
3637
RedisClient.cluster(replica: true).new_client
3738
#=> #<RedisClient::Cluster 172.20.0.2:6379, 172.20.0.3:6379, 172.20.0.4:6379, 172.20.0.5:6379, 172.20.0.6:6379, 172.20.0.7:6379>
3839

40+
# To connect to all nodes to use scale reading feature prioritizing low-latency replicas
41+
RedisClient.cluster(replica: true, replica_affinity: :latency).new_client
42+
#=> #<RedisClient::Cluster 172.20.0.2:6379, 172.20.0.3:6379, 172.20.0.4:6379, 172.20.0.5:6379, 172.20.0.6:6379, 172.20.0.7:6379>
43+
3944
# With generic options for redis-client
4045
RedisClient.cluster(timeout: 3.0).new_client
4146
```

Rakefile

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,3 +45,20 @@ task :wait do
4545
**TEST_GENERIC_OPTIONS
4646
).wait_for_cluster_to_be_ready
4747
end
48+
49+
desc 'Build cluster'
50+
task :build_cluster, %i[addr1 addr2] do |_, args|
51+
$LOAD_PATH.unshift(File.expand_path('test', __dir__))
52+
require 'cluster_controller'
53+
hosts = args.values_at(:addr1, :addr2).compact
54+
ports = (6379..6384).to_a
55+
nodes = hosts.product(ports).map { |host, port| "redis://#{host}:#{port}" }
56+
shard_size = 3
57+
replica_size = (nodes.size / shard_size) - 1
58+
::ClusterController.new(
59+
nodes,
60+
shard_size: shard_size,
61+
replica_size: replica_size,
62+
timeout: 30.0
63+
).rebuild
64+
end

docker-compose.nat.yaml

Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
---
2+
# REDIS_HOST=192.168.11.9 docker compose -f docker-compose.nat.yaml ps
3+
services:
4+
node1: &node
5+
image: "redis:${REDIS_VERSION:-7}"
6+
command: >
7+
redis-server
8+
--maxmemory 64mb
9+
--maxmemory-policy allkeys-lru
10+
--appendonly yes
11+
--cluster-enabled yes
12+
--cluster-config-file nodes.conf
13+
--cluster-node-timeout 30000
14+
--cluster-announce-ip "${REDIS_HOST:-127.0.0.1}"
15+
--cluster-announce-port 6379
16+
--cluster-announce-bus-port 16379
17+
restart: "${RESTART_POLICY:-always}"
18+
healthcheck:
19+
test: ["CMD", "redis-cli", "ping"]
20+
interval: "7s"
21+
timeout: "5s"
22+
retries: 10
23+
ports:
24+
- "6379:6379"
25+
- "16379:16379"
26+
node2:
27+
<<: *node
28+
command: >
29+
redis-server
30+
--maxmemory 64mb
31+
--maxmemory-policy allkeys-lru
32+
--appendonly yes
33+
--cluster-enabled yes
34+
--cluster-config-file nodes.conf
35+
--cluster-node-timeout 30000
36+
--cluster-announce-ip "${REDIS_HOST:-127.0.0.1}"
37+
--cluster-announce-port 6380
38+
--cluster-announce-bus-port 16380
39+
ports:
40+
- "6380:6379"
41+
- "16380:16379"
42+
node3:
43+
<<: *node
44+
command: >
45+
redis-server
46+
--maxmemory 64mb
47+
--maxmemory-policy allkeys-lru
48+
--appendonly yes
49+
--cluster-enabled yes
50+
--cluster-config-file nodes.conf
51+
--cluster-node-timeout 30000
52+
--cluster-announce-ip "${REDIS_HOST:-127.0.0.1}"
53+
--cluster-announce-port 6381
54+
--cluster-announce-bus-port 16381
55+
ports:
56+
- "6381:6379"
57+
- "16381:16379"
58+
node4:
59+
<<: *node
60+
command: >
61+
redis-server
62+
--maxmemory 64mb
63+
--maxmemory-policy allkeys-lru
64+
--appendonly yes
65+
--cluster-enabled yes
66+
--cluster-config-file nodes.conf
67+
--cluster-node-timeout 30000
68+
--cluster-announce-ip "${REDIS_HOST:-127.0.0.1}"
69+
--cluster-announce-port 6382
70+
--cluster-announce-bus-port 16382
71+
ports:
72+
- "6382:6379"
73+
- "16382:16379"
74+
node5:
75+
<<: *node
76+
command: >
77+
redis-server
78+
--maxmemory 64mb
79+
--maxmemory-policy allkeys-lru
80+
--appendonly yes
81+
--cluster-enabled yes
82+
--cluster-config-file nodes.conf
83+
--cluster-node-timeout 30000
84+
--cluster-announce-ip "${REDIS_HOST:-127.0.0.1}"
85+
--cluster-announce-port 6383
86+
--cluster-announce-bus-port 16383
87+
ports:
88+
- "6383:6379"
89+
- "16383:16379"
90+
node6:
91+
<<: *node
92+
command: >
93+
redis-server
94+
--maxmemory 64mb
95+
--maxmemory-policy allkeys-lru
96+
--appendonly yes
97+
--cluster-enabled yes
98+
--cluster-config-file nodes.conf
99+
--cluster-node-timeout 30000
100+
--cluster-announce-ip "${REDIS_HOST:-127.0.0.1}"
101+
--cluster-announce-port 6384
102+
--cluster-announce-bus-port 16384
103+
ports:
104+
- "6384:6379"
105+
- "16384:16379"

docs/class_diagrams_redis_cluster_client.md

Lines changed: 39 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -44,18 +44,47 @@ classDiagram
4444
+each()
4545
+sample()
4646
+node_keys()
47-
+primary_node_keys()
48-
+replica_node_keys()
4947
+find_by()
5048
+call_all()
5149
+call_primaries()
5250
+call_replicas()
5351
+send_ping()
54-
+scale_reading_clients()
52+
+clients_for_scanning()
5553
+find_node_key_of_primary()
5654
+find_node_key_of_replica()
55+
+any_primary_node_key()
56+
+any_replica_node_key()
5757
+update_slot()
58-
+replicated?()
58+
}
59+
60+
class RedisClient_Cluster_Node_PrimaryOnly {
61+
+clients()
62+
+primary_clients()
63+
+replica_clients()
64+
+clients_for_scanning()
65+
+find_node_key_of_replica()
66+
+any_primary_node_key()
67+
+any_replica_node_key()
68+
}
69+
70+
class RedisClient_Cluster_Node_RandomReplica {
71+
+replica_clients()
72+
+clients_for_scanning()
73+
+find_node_key_of_replica()
74+
+any_replica_node_key()
75+
}
76+
77+
class RedisClient_Cluster_Node_LatencyReplica {
78+
+replica_clients()
79+
+clients_for_scanning()
80+
+find_node_key_of_replica()
81+
+any_replica_node_key()
82+
}
83+
84+
class module_RedisClient_Cluster_Node_ReplicaMixin {
85+
+clients()
86+
+primary_clients()
87+
+any_primary_node_key()
5988
}
6089
6190
class module_RedisClient_Cluster_NodeKey {
@@ -101,4 +130,10 @@ classDiagram
101130
RedisClient_Cluster_Router ..> RedisClient_Cluster_Command : new
102131
RedisClient_Cluster_Router ..> module_RedisClient_Cluster_KeySlotConverter : call
103132
RedisClient_Cluster_Router ..> module_RedisClient_Cluster_NodeKey : call
133+
134+
RedisClient_Cluster_Node_RandomReplica ..|> module_RedisClient_Cluster_Node_ReplicaMixin : include
135+
RedisClient_Cluster_Node_LatencyReplica ..|> module_RedisClient_Cluster_Node_ReplicaMixin : include
136+
RedisClient_Cluster_Node ..> RedisClient_Cluster_Node_PrimaryOnly : new
137+
RedisClient_Cluster_Node ..> RedisClient_Cluster_Node_RandomReplica : new
138+
RedisClient_Cluster_Node ..> RedisClient_Cluster_Node_LatencyReplica : new
104139
```

lib/redis_client/cluster.rb

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,9 +53,10 @@ def blocking_call_v(timeout, command, &block)
5353
def scan(*args, **kwargs, &block)
5454
raise ArgumentError, 'block required' unless block
5555

56+
seed = Random.new_seed
5657
cursor = ZERO_CURSOR_FOR_SCAN
5758
loop do
58-
cursor, keys = @router.scan('SCAN', cursor, *args, **kwargs)
59+
cursor, keys = @router.scan('SCAN', cursor, *args, seed: seed, **kwargs)
5960
keys.each(&block)
6061
break if cursor == ZERO_CURSOR_FOR_SCAN
6162
end

0 commit comments

Comments
 (0)