一文搞懂什么是分区

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个哈希槽来分区数据。每个主节点负责一部分槽的数据。当只有两个主节点时,数据的分区可能不够均匀,导致性能和稳定性问题。有三个或更多的主节点时,可以更好地均衡数据的分布,提高集群性能。

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

相关推荐
monkey_meng2 分钟前
【遵守孤儿规则的External trait pattern】
开发语言·后端·rust
草莓base15 分钟前
【手写一个spring】spring源码的简单实现--bean对象的创建
java·spring·rpc
Estar.Lee17 分钟前
时间操作[计算时间差]免费API接口教程
android·网络·后端·网络协议·tcp/ip
drebander39 分钟前
使用 Java Stream 优雅实现List 转化为Map<key,Map<key,value>>
java·python·list
乌啼霜满天24943 分钟前
Spring 与 Spring MVC 与 Spring Boot三者之间的区别与联系
java·spring boot·spring·mvc
tangliang_cn1 小时前
java入门 自定义springboot starter
java·开发语言·spring boot
程序猿阿伟1 小时前
《智能指针频繁创建销毁:程序性能的“隐形杀手”》
java·开发语言·前端
Grey_fantasy1 小时前
高级编程之结构化代码
java·spring boot·spring cloud
新知图书1 小时前
Rust编程与项目实战-模块std::thread(之一)
开发语言·后端·rust
弗锐土豆1 小时前
工业生产安全-安全帽第二篇-用java语言看看opencv实现的目标检测使用过程
java·opencv·安全·检测·面部