redis分区
为解决单机redis实例的容量、性能和网络带宽有限等问题,redis采取分区的概念,可将数据按照指定规则进行分区,存储在多个redis实例上,来拓展redis容量、扩宽网络和计算能力。
如何进行分区
分区有两种方式:
- 范围分区
- hash(哈希)分区
假设有4个Redis实例R0,R1,R2,R3
,和类似于user_1,user_2
这样表示用户标识
的多个key(<user_id>)
,对这些用户标识key 通过 范围分区
和 hash分区
来决定这个key应该存放到哪个实例中。
第一种:范围分区
范围查询相对简单,对玩家的id设置区间,比如
- id在[0,100000]的用户保存到redis实例R0中
- id在[100001,200000]的用户保存到redis实例R1中
- id在[200001,300000]的用户保存到redis实例R2中
- id在[300001,400000]的用户保存到redis实例R3中
这种方式在实际使用中,需要在代码中管理并维护一个玩家id范围到redis实例的映射表,能够通过玩家id获得要添加到的redis实例。
第二种:hash(哈希)分区
另一种方法是hash分区。通过hash函数将key转换成一个数字,比如用crc32 hash函数对 key 进行转换,获得一个long型数字。在用数字对拥有的redis实例数目进行取模,就得到了目标redis实例。例子如下:
java
public class CRC32HashExample {
public static long crc32Hash(String key) {
CRC32 crc32 = new CRC32();
crc32.update(key.getBytes());
return crc32.getValue();
}
public static void main(String[] args) {
// 示例:计算键 "foobar" 的 CRC32 哈希值
String key = "mykey";
long hashedValue = crc32Hash(key);
long unsignedValue = hashedValue & 0xFFFFFFFFL;
System.out.println("The unsigned hashed value is: " + unsignedValue);
System.out.println("The hashed value of key '" + key + "' is: " + hashedValue);
int index = (int) (unsignedValue % 4);
System.out.println("result redis index is :" + index);
}
}
/**
* The unsigned hashed value is: 3295074636
* The hashed value of key 'mykey' is: 3295074636
* result redis index is :0
*/
因为我们假设的是4个redis实例,所以对获得hash值对4进行取余,所以例子中的mykey
最终分配的实例是R0
分区一般在集群进行使用,如果不使用集群,在这种情况下,需要自己去维护管理,而不是由redis自身处理。
分区的不同实现方式
- 客户端分区:客户端直接选择正确的节点进行。通过redis客户算出要查询和操作的槽,在像该槽位于redis实例发送请求。
- 代理辅助分区:客户端通过redis协议将请求发送给代理,而不是直接发送给真正的redis实例服务器。这个代理会确保我们的请求根据配置分区策略发送到正确的Redis实例上,并返回给客户端。Redis和Memcached的代理都是用Twemproxy(twitter开源的一个代理框架)来实现代理服务分区。
- 查询路由:指可以将一个请求发送给一个随机的实例,这时实例会把该查询转发给正确的节点。通过客户端重定向(客户端的请求不用直接从一个实例转发给另一个实例,而是被重定向到正确的节点),Redis集群实现了一种混合查询路由
Redis集群混合使用了查询路由和客户端分区。
作为数据存储还是缓存使用?
使用redis存储数据或者缓存数据在概念上是相同的,但是Redis被用作数据存储时,有一个显著的限制。
当redis被用作redis存储服务器使用时意味对于同一个键值必须映射到同一个 实例(节点)上面。分区需要提供节点和键值的固定映射,还有节点数目必须是固定的,不能改变。对于给定的键,它将始终存储在集群的特定节点上,而不会重新分配其他节点。当需要增加或删除节点时,系统需要一个机制来重新分配键值到新的节点上(重新分片),2015年4月1号开始,redis集群提供了重新分配键值到新节点的特性。
当redis当作redis数据缓存器,可以使用一致性哈希来轻松映射键值到不同的实例上。一致性哈希允许根据某个哈希算法将键值映射到集群的不同节点。这种方式对于相同的键,不一定需要映射到相同的实例上,提高了系统的可用性。如果一个节点不可用,相同的键值对可以被应该映射到其他的节点上。redis作为数据缓存器,用来增减或删除节点,因为一致性哈希是非常容易的,节点的增减都只需重定位环空间中的一小部分数据,具有较好的容错性和可扩展性。。
上面的一致性hash都不是缓存机器自身的功能,而是集群前置的代理或客户端实现的。而redis官方的集群是集群本身通过slots实现了数据分片。
redis集群时3.0版本才出现的,出现的比较晚,在集群模式出现之前,很多公司都做了自己的redis集群了。这些自研的redis集群的实现方式有多种,比如在redis的jedis客户端jar包就是实现了一致性hash算法(客户端模式),我查了一下5.0之后似乎弃用了,没找到具体原因;或者在redis集群前面加上一层前置代理如Twemproxy也实现了hash一致性算法(代理模式)。Twemproxy,是 Twitter 开源出来的 Redis 和 Memcached 代理,使用这种代理模式搭建的集群,我们的客户端连接只需要连接代理服务器即可,不用连接代理后面具体的redis机器。Twemproxy具体使用哪一种hash算法也是可以通过配置文件指定的。
点击右边链接了解:一致性哈希
分区的使用场景
下面这些场景中,通过 Redis 集群的分区策略,可以更好地管理和处理大规模数据、高并发请求、分布式计算任务等。
-
大规模日志存储:一个系统产生了大量的日志,单个节点难以存储这些日志,需要将日志数据分散存储在多个节点上,以便更好地利用存储资源和提高读写性能。
-
高并发用户请求:一个应用有大量的玩家进行请求,单个节点无法满足请求的处理速度。需要将用户请求分散到多个节点上,来提高系统的处理速度
分区的优缺点
优势
通过在多台计算机上部署redis实例,进行分区、部署集群,有以下几点好处:
- 构建更大的内存数据库
- 扩展计算能力、网络带宽
缺点
- 不支持跨redis实例的事务,事务需要在一个实例上执行
bash
# 事务中的命令涉及不同的Redis实例
# 这是一个错误的示例,因为事务需要在同一个实例上执行
127.0.0.1:6379> MULTI
OK
127.0.0.1:6379> SET key1 "value1"
QUEUED
127.0.0.1:7100> SET key2 "value2"
QUEUED
127.0.0.1:6379> EXEC
- 不支持设计多个redis实例的多个key的操作,比如你不能操作映射在两个Redis实例上的两个集合的交叉集。
bash
# 两个SET分别存储在不同的Redis实例上
127.0.0.1:6379> SET key1 "value1"
127.0.0.1:6380> SET key2 "value2"
# 无法直接执行交集操作
# 这是一个错误的示例,因为交集操作需要在同一个实例上执行
127.0.0.1:6379> SINTER key1 key2
-
Redis是以键来分区,因此不能使用单个大键对数据集进行分片,例如一个非常大的有序集
-
数据处理复杂;需要处理多个rdb、aof文件,并且从多个实例主机上备份持久化文件。
-
增加和删除容器比较复杂;例如通过在运行时添加和删除节点,Redis集群(cluster)`通常支持透明地再均衡数据,但是其他系统像客户端分区或者代理分区的特性就不支持该特性。
redis分区应该选择什么系统?
- Redis集群
推荐使用Redis集群获得自动分片和高可用性。Redis集群是2015年4月1日版本发布的可用和生成就绪特性。可以从集群教程中获取更多信息。
一旦Redis集群是可用的,并且一个Redis集群兼容客户端支持您的编程语言,Redis集群就是Redis分区事实上标准。
Redis集群混合使用了查询路由和客户端分区。
Twemproxy是一个由Twitter开发的适合Memached ASCII和Redis协议的代理。它是单线程工作,使用C语言实现的,速度非常快。并且是基于Apache 2.0 协议的开源软件。
Twemproxy支持自动在多个redis节点分区,如果某个节点不可用,将会被自动屏蔽(这将改变键值和节点映射表,所以如果你把Redis当作缓存服务器使用,你应该使用这个功能)。
你可以启用多个代理,让你的客户端得到可用的连接,这样不会发生单点故障。
Twemproxy基本上是Redis和客户端的一个中间层,通过简化使用让我们使用可靠的分区。
- 客户端一致性哈希实现
替代Twemproxy的一种方案是使用客户端一致性哈西或者其他类似的算法。有很多Redis客户端支持一致性哈希,比如Redis-rb和Predis。
请检查Redis客户端全量列表,以确定是否有适合于你的编程语言、成熟的一致性哈希实现的客户端。
引申
redis为什么不用一致性hash
如果直接使用一致性哈希(非codis那种做过优化的),会导致循环雪崩。按照一致性哈希算法,会顺时针继续遍历,有可能A的流量会被打到B或C上,极端情况都打到了B,那大概率会导致B宕机;B宕机之后,集群只剩一台C,那这时候不用想,也会宕机;如果要恢复数据,则需要同时恢复A、B、C两台机器,因为这三台都挂了;真可谓一荣俱荣,一损俱损。
如果使用Redis官方的哈希槽方案,至少数据恢复相对来说简单一些。A宕机之后,由其slave(sentinel 模式从 slave 中选举一个新 master)服务器接管槽位,继续运行;如果新 master 也宕机,这个槽位的数据就无法获取了,直接返回:CLUSTERDOWN 的 err 信息,这种情况下集群就会下线,需要人工处理;其实这种方案更应该称为 人工强行高可用 方案,说白了就是:让问题提前暴露,人工干预;可见,hash方案,如果要恢复集群,只需要恢复A节点即可,逻辑简单易懂,恢复成本较小。
为什么redis cluster需要至少3个主节点
- 投票选举:Redis使用多数投票的方式进行故障切换,只有两个主节点A、B时,A节点不可用,B投票A不可用,A认为B下线,没有足够多的节点形成多数,可能无法做出正确的投票决策,影响集群的故障处理能力。当有三个或更多节点时,即使一个节点不可用,剩余的节点仍然可以形成多数,确保集群可以做出正确的决策。
- 数据分区:reids集群使用16384个哈希槽来分区数据。每个主节点负责一部分槽的数据。当只有两个主节点时,数据的分区可能不够均匀,导致性能和稳定性问题。有三个或更多的主节点时,可以更好地均衡数据的分布,提高集群性能。
综上所述,在集群中保持足够的节点数量,以确保系统的正常运行和故障处理的有效性。