一文搞懂什么是分区

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 集群的分区策略,可以更好地管理和处理大规模数据、高并发请求、分布式计算任务等。

  1. 大规模日志存储:一个系统产生了大量的日志,单个节点难以存储这些日志,需要将日志数据分散存储在多个节点上,以便更好地利用存储资源和提高读写性能。

  2. 高并发用户请求:一个应用有大量的玩家进行请求,单个节点无法满足请求的处理速度。需要将用户请求分散到多个节点上,来提高系统的处理速度

分区的优缺点

优势

通过在多台计算机上部署redis实例,进行分区、部署集群,有以下几点好处:

  1. 构建更大的内存数据库
  2. 扩展计算能力、网络带宽

缺点

  1. 不支持跨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
  1. 不支持设计多个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
  1. Redis是以键来分区,因此不能使用单个大键对数据集进行分片,例如一个非常大的有序集

  2. 数据处理复杂;需要处理多个rdb、aof文件,并且从多个实例主机上备份持久化文件。

  3. 增加和删除容器比较复杂;例如通过在运行时添加和删除节点,Redis集群(cluster)`通常支持透明地再均衡数据,但是其他系统像客户端分区或者代理分区的特性就不支持该特性。

redis分区应该选择什么系统?

  1. Redis集群
    推荐使用Redis集群获得自动分片和高可用性。Redis集群是2015年4月1日版本发布的可用和生成就绪特性。可以从集群教程中获取更多信息。

一旦Redis集群是可用的,并且一个Redis集群兼容客户端支持您的编程语言,Redis集群就是Redis分区事实上标准。

Redis集群混合使用了查询路由和客户端分区。

  1. Twemproxy框架

Twemproxy是一个由Twitter开发的适合Memached ASCII和Redis协议的代理。它是单线程工作,使用C语言实现的,速度非常快。并且是基于Apache 2.0 协议的开源软件。

Twemproxy支持自动在多个redis节点分区,如果某个节点不可用,将会被自动屏蔽(这将改变键值和节点映射表,所以如果你把Redis当作缓存服务器使用,你应该使用这个功能)。

你可以启用多个代理,让你的客户端得到可用的连接,这样不会发生单点故障。

Twemproxy基本上是Redis和客户端的一个中间层,通过简化使用让我们使用可靠的分区。

  1. 客户端一致性哈希实现

替代Twemproxy的一种方案是使用客户端一致性哈西或者其他类似的算法。有很多Redis客户端支持一致性哈希,比如Redis-rbPredis

请检查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个主节点

  1. 投票选举:Redis使用多数投票的方式进行故障切换,只有两个主节点A、B时,A节点不可用,B投票A不可用,A认为B下线,没有足够多的节点形成多数,可能无法做出正确的投票决策,影响集群的故障处理能力。当有三个或更多节点时,即使一个节点不可用,剩余的节点仍然可以形成多数,确保集群可以做出正确的决策。
  2. 数据分区:reids集群使用16384个哈希槽来分区数据。每个主节点负责一部分槽的数据。当只有两个主节点时,数据的分区可能不够均匀,导致性能和稳定性问题。有三个或更多的主节点时,可以更好地均衡数据的分布,提高集群性能。

综上所述,在集群中保持足够的节点数量,以确保系统的正常运行和故障处理的有效性。

相关推荐
Rust研习社16 小时前
组合真的优于继承吗?为什么 Rust 和 Go 都拥抱组合舍弃继承?
后端·rust·编程语言
IT_陈寒16 小时前
JavaScript的闭包把我坑惨了,说好的内存会自动回收呢?
前端·人工智能·后端
CaffeinePro17 小时前
Pydantic深度使用:数据校验、枚举、ORM映射
后端·fastapi
Chenyiax18 小时前
从 Chat 到 Responses:OpenAI API 抽象为什么变了?
后端
MariaH18 小时前
Koa和Express的区别
后端
MariaH18 小时前
Koa框架的使用
后端
luckdewei19 小时前
那个用 passlib 做认证的新同事,上线第一天就把用户密码写进了日志
后端
ping某20 小时前
为什么 Nginx 明明监听了 80,转发后端时却用了 4xxxx 端口?
后端·nginx
JustHappy20 小时前
我汇总了身边朋友的经历才发现,其实第一份实习是最难找的......
前端·后端·面试
uhakadotcom20 小时前
在python 的 工程化架构中 ,什么是 薄包装器层?
后端·面试·github