Redis 集群
使用 Redis Cluster 需要 Redis Server 3.0 或更高版本。更多信息请参见 Cluster Tutorial。
将 Redis Repositories 与 Redis Cluster 一起使用时,请先熟悉如何在 Cluster 上运行 Redis Repositories。
在使用 Redis Cluster 时不要依赖键空间事件(keyspace events),因为键空间事件不会在分片之间复制。发布/订阅订阅的是集群中的随机节点,而该节点只会收到来自单个分片的键空间事件。请使用单节点 Redis 以避免键空间事件丢失。
使用 Redis Cluster 连接
Redis Cluster 的行为与单节点 Redis,甚至与 Sentinel 监控的主-副本环境都有所不同。这是因为自动分片会将键映射到 16384 个槽位之一,而这些槽位分布在各个节点上。因此,涉及多个键的命令必须确保所有键都映射到完全相同的槽位,以避免跨槽位错误。单个集群节点只服务于一专用的键集合。针对某一特定服务器发出的命令只会返回由该服务器所服务的那些键的结果。举一个简单的例子,考虑 KEYS 命令。在集群环境中向某台服务器发出该命令时,它只会返回请求所发往的那个节点所服务的键,而不一定是集群中的所有键。因此,要在集群环境中获取所有键,你必须从所有已知的主节点读取键。
虽然将特定键重定向到相应服务槽位的节点由驱动库处理,但更高级别的功能(例如跨节点收集信息,或向集群中的所有节点发送命令)则由 RedisClusterConnection 负责。沿用前面键的例子,这意味着 keys(pattern) 方法会接管集群中的每个主节点,同时在每个主节点上运行 KEYS 命令,并收集结果、返回累积的键集合。若只想请求单个节点的键,RedisClusterConnection 为这些方法提供了重载形式(例如 keys(node, pattern))。
可以从 RedisClusterConnection.clusterGetNodes 获取 RedisClusterNode,也可以通过主机与端口,或者通过节点 Id 来构造它。
下例展示了在整个集群上运行的一组命令:
示例 1:跨集群运行命令示例
redis-cli@127.0.0.1:7379 > cluster nodes
6b38bb... 127.0.0.1:7379 master - 0 0 25 connected 0-5460 // <1>
7bb78c... 127.0.0.1:7380 master - 0 1449730618304 2 connected 5461-present2 // <2>
164888... 127.0.0.1:7381 master - 0 1449730618304 3 connected 10923-present3 // <3>
b8b5ee... 127.0.0.1:7382 slave 6b38bb... 0 1449730618304 25 connected // <4>
RedisClusterConnection connection = connectionFactory.getClusterConnection();
connection.set("thing1", value); 5
connection.set("thing2", value); 6
connection.keys("*"); 7
connection.keys(NODE_7379, "*"); 8
connection.keys(NODE_7380, "*"); 9
connection.keys(NODE_7381, "*"); 10
connection.keys(NODE_7382, "*"); 11
负责槽位 0 到 5460 的主节点,已复制到位于 7382 的副本
负责槽位 5461 到 10922 的主节点
负责槽位 10923 到 16383 的主节点
持有位于 7379 主节点副本数据的副本节点
请求路由到位于 7381、负责槽位 12182 的节点
请求路由到位于 7379、负责槽位 5061 的节点
请求路由到位于 7379、7380、7381 的节点 → [thing1, thing2]
请求路由到位于 7379 的节点 → [thing2]
请求路由到位于 7380 的节点 → []
请求路由到位于 7381 的节点 → [thing1]
请求路由到位于 7382 的节点 → [thing2]
当所有键都映射到同一槽位时,原生驱动库会自动处理跨槽位请求(例如 MGET)。但一旦情况并非如此,RedisClusterConnection 就会针对服务相应槽位的节点并行运行多个 GET 命令,并再次返回累积的结果。这比单槽位方式性能更低,因此应谨慎使用。如果不确定,可以通过在花括号中提供前缀来把键固定到同一槽位,例如 {my-prefix}.thing1 和 {my-prefix}.thing2,两者都会映射到相同的槽位编号。下例展示了跨槽位请求的处理:
示例 2:跨槽位请求处理示例
redis-cli@127.0.0.1:7379 > cluster nodes
6b38bb... 127.0.0.1:7379 master - 0 0 25 connected 0-5460 // <1>
7bb...
RedisClusterConnection connection = connectionFactory.getClusterConnection();
connection.set("thing1", value); // slot: 12182
connection.set("{thing1}.thing2", value); // slot: 12182
connection.set("thing2", value); // slot: 5461
connection.mGet("thing1", "{thing1}.thing2"); 2
connection.mGet("thing1", "thing2"); 3
与前一个示例中的配置相同。
键映射到同一槽位 → 127.0.0.1:7381 MGET thing1 {thing1}.thing2
键映射到不同槽位,被拆分为单槽请求并路由到相应节点
→ 127.0.0.1:7379 GET thing2
→ 127.0.0.1:7381 GET thing1
前面的示例展示了 Spring Data Redis 所遵循的通用策略。请注意,某些操作可能需要将大量数据加载到内存中才能计算出所需命令。此外,并非所有跨槽位请求都能安全地拆分为多个单槽请求,误用会报错(例如 PFCOUNT)。
使用 RedisTemplate 与 ClusterOperations
关于 RedisTemplate 的用途、配置和一般用法,请参见通过 RedisTemplate 操作对象一节。
使用任何 JSON RedisSerializer 设置 RedisTemplate#keySerializer 时要小心,因为更改 JSON 结构会立即影响哈希槽位的计算。
RedisTemplate 通过 ClusterOperations 接口提供对集群专用操作的访问,该接口可从 RedisTemplate.opsForCluster() 获取。这让你可以在集群中的单个节点上显式运行命令,同时保留为模板配置的序列化和反序列化特性。它还提供管理命令(例如 CLUSTER MEET)或更高级别的操作(例如重新分片)。
下例展示了如何通过 RedisTemplate 访问 RedisClusterConnection:
示例 3:通过 RedisTemplate 访问 RedisClusterConnection
ClusterOperations clusterOps = redisTemplate.opsForCluster();
clusterOps.shutdown(NODE_7379); 1
关闭位于 7379 的节点,并祈祷已有副本能够接管。
Redis Cluster 流水线操作目前只能通过 Lettuce 驱动支持,但使用跨槽位键时以下命令除外:rename、renameNX、sort、bLPop、bRPop、rPopLPush、bRPopLPush、info、sMove、sInter、sInterStore、sUnion、sUnionStore、sDiff、sDiffStore。同槽位键则完全支持。