redis 集群 Sharding 和 在线扩容 Pre-Sharding

发布于 2023-04-05 12:57:49 字数 3465 浏览 92 评论 0

redis 集群分为服务端集群和客户端分片,redis 3.0 以上版本实现了集群机制,即服务端集群,3.0 以下使用客户端分片(Sharding)。redis 3.0 服务端集群使用哈希槽,计算 key 的 CRC16 结果再模 16834。3.0 以下版本采用 Key 的一致性 hash 算法来区分 key 存储在哪个 Redis 实例上。

JedisPoolConfig config = new JedisPoolConfig();
config.setMaxTotal(500);
config.setTestOnBorrow(true);
List<JedisShardInfo> jdsInfoList = new ArrayList<>(2);
jdsInfoList.add(new JedisShardInfo("192.168.2.128", 6379));
jdsInfoList.add(new JedisShardInfo("192.168.2.108", 6379));
pool = new ShardedJedisPool(config, jdsInfoList, Hashing.MURMUR_HASH, Sharded.DEFAULT_KEY_TAG_PATTERN);
jds.set(key, value);
......
jds.close();
pool.close();

采用这种方式也存在两个问题

  1. 扩容问题:
    因为使用了一致性哈稀进行分片,那么不同的 key 分布到不同的 Redis-Server 上,当我们需要扩容时,需要增加机器到分片列表中,这时候会使得同样的key算出来落到跟原来不同的机器上,这样如果要取某一个值,会出现取不到的情况,之前的缓存相当于全部失效。对于扩容问题,Redis 的作者提出了一种名为 Pre-Sharding 的方式。即事先部署足够多的Redis服务。
  2. 单点故障问题:
    当集群中的某一台服务挂掉之后,客户端在根据一致性hash无法从这台服务器取数据。对于单点故障问题,我们可以使用 Redis 的 HA 高可用来实现。利用 Redis-Sentinal 来通知主从服务的切换。

通过 presharding 进行 Redis 在线扩容。

通过主动复制我们解决了 Redis 单点故障问题,那么还有一个重要的问题需要解决:容量规划与在线扩容问题。

我们前面分析过Redis的适用场景是全部数据存储在内存中,而内存容量有限,那么首先需要根据业务数据量进行初步的容量规划,比如你的业务数据需要100G存储空间,假设服务器内存是48G,那么根据上一篇我们讨论的Redis磁盘IO的问题,我们大约需要3~4台服务器来存储。这个实际是对现有业务情况所做的一个容量规划,假如业务增长很快,很快就会发现当前的容量已经不够了,Redis里面存储的数据很快就会超过物理内存大小,那么如何进行Redis的在线扩容呢?

Redis 的作者提出了一种叫做 presharding 的方案来解决动态扩容和数据分区的问题,实际就是在同一台机器上部署多个Redis实例的方式,当容量不够时将多个实例拆分到不同的机器上,这样实际就达到了扩容的效果。

Pre-Sharding 方法是将每一个台物理机上,运行多个不同端口的 Redis 实例,假如有三个物理机,每个物理机运行三个Redis实例,那么我们的分片列表中实际有9个 Redis 实例,当我们需要扩容时,增加一台物理机来代替9个中的一个 redis,有人说,这样不还是9个么,是的,但是以前服务器上面有三个 redis,压力很大的,这样做,相当于单独分离出来并且将数据一起copy给新的服务器。值得注意的是,还需要修改客户端被代替的 redis 的IP和端口为现在新的服务器,只要顺序不变,不会影响一致性哈希分片。

拆分过程如下:

  • 在新机器上启动好对应端口的 Redis 实例。
  • 配置新端口为待迁移端口的从库。
  • 待复制完成,与主库完成同步后,切换所有客户端配置到新的从库的端口。
  • 配置从库为新的主库。
  • 移除老的端口实例。
  • 重复上述过程迁移好所有的端口到指定服务器上。
    以上拆分流程是Redis作者提出的一个平滑迁移的过程,不过该拆分方法还是很依赖Redis本身的复制功能的,如果主库快照数据文件过大,这个复制的过程也会很久,同时会给主库带来压力。所以做这个拆分的过程最好选择为业务访问低峰时段进行。

单点故障问题

还是用到 Redis 主从复制的功能,两台物理主机上分别都运行有Redis-Server,其中一个Redis-Server是另一个的从库,采用双机热备技术,客户端通过虚拟IP访问主库的物理IP,当主库宕机时,切换到从库的物理IP。只是事后修复主库时,应该将之前的从库改为主库(使用命令 slaveof no one),主库变为其从库(使命令 slaveof IP PORT),这样才能保证修复期间新增数据的一致性。

Redis 集群中内置了 16384 个哈希槽,当需要在 Redis 集群中放置一个 key-value 时,redis 先对 key 使用 crc16 算法算出一个结果,然后把结果对 16384 求余数,这样每个 key 都会对应一个编号在 0-16383 之间的哈希槽,redis 会根据节点数量大致均等的将哈希槽映射到不同的节点。

Redis 集群没有使用一致性 hash 而是引入了哈希槽的概念。

Redis 集群有16384个哈希槽,每个key通过 CRC16 校验后对 16384 取模来决定放置哪个槽,集群的每个节点负责一部分 hash 槽。这种结构很容易添加或者删除节点,并且无论是添加删除或者修改某一个节点,都不会造成集群不可用的状态。

使用哈希槽的好处就在于可以方便的添加或移除节点。

  • 当需要增加节点时,只需要把其他节点的某些哈希槽挪到新节点就可以了;
  • 当需要移除节点时,只需要把移除节点上的哈希槽挪到其他节点就行了;

在这一点上,我们以后新增或移除节点的时候不用先停掉所有的 redis 服务。

  • 用了哈希槽的概念,而没有用一致性哈希算法,不都是哈希么?这样做的原因是为什么呢?
    Redis Cluster 是自己做的 crc16 的简单 hash 算法,没有用一致性 hash。Redis 的作者认为它的 crc16(key) mod 16384 的效果已经不错了,虽然没有一致性 hash 灵活,但实现很简单,节点增删时处理起来也很方便。

  • 为了动态增删节点的时候,不至于丢失数据么?
    节点增删时不丢失数据和hash算法没什么关系,不丢失数据要求的是一份数据有多个副本。

  • 还有集群总共有2的14次方,16384个哈希槽,那么每一个哈希槽中存的 key 和 value是什么?
    当你往 Redis Cluster 中加入一个 Key 时,会根据 crc16(key) mod 16384 计算这个key应该分布到哪个 hash slot 中,一个 hash slot 中会有很多 key 和 value。你可以理解成表的分区,使用单节点时的 redis 时只有一个表,所有的key都放在这个表里;改用 Redis Cluster 以后会自动为你生成16384个分区表,你 insert 数据时会根据上面的简单算法来决定你的key应该存在哪个分区,每个分区里有很多key。

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。
列表为空,暂无数据

关于作者

予囚

暂无简介

文章
评论
27 人气
更多

推荐作者

櫻之舞

文章 0 评论 0

弥枳

文章 0 评论 0

m2429

文章 0 评论 0

野却迷人

文章 0 评论 0

我怀念的。

文章 0 评论 0

    我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
    原文