现在有一个场景:要用Redis保存5000万个键值对,每个键值对大约是512B,要怎么部署Redis服务呢?

第一个方案,也是最容易想到的,需要保存5000万个键值对,每个键值对约为512B,一共需要25GB空间,选择一台32GB内存的用品来部署Redis,还剩余7GB空间,可以采用RDB对数据做持续久。

但是Redis服务使用不久后出现Redis的响应有时会非常慢。原因是采用了RDB持久化,在前面介绍RDB原理时,我们知道fork子进程的瞬间会阻塞主线程,而且内存越大,阻塞越长。

第一个方案不太适合,那么有更好的方案吗?Redis提供切片集群机制,多个Redis实例组成一个集群,按照一定的规则,把收到数据划分成多份,每一份用一个实例来保存。这样一来,在生成RDB时,数据量就小了,fork就不会阻塞主线程太长时间。

这里就引出一个问题:该如何保存更多的数据?

如何保存更多数据

通常有两种方案,分别是纵向扩展和横向扩展。

纵向扩展,指通过增加硬件配置来扩展,采用更大的内存,更多的CPU。好处是实施简单,但缺点是受到硬件和成本的限制,不可能无限扩展。

横向扩展,指通过增加机器来组成更大的集群,这也是分布式方案常用的方式。好处是扩展性好,但缺点是管理复杂。

在面向百万、千万级别的用户规模时,横向扩展的Redis切片集群会是一个非常好的选择。

在使用单个实例时,数据保存在哪里,客户端访问哪里,都是非常明确的。但是切片集群不可避免要解决多个实例分布式管理的问题,需要解决两大问题:

  • 数据切片后,在多个实例之间如何分布?
  • 客户端怎么确定想要访问的数据在哪个实例上?

数据切片和实例的对应分布关系

在Redis 3.0之前,官方没有切片集群的方案,从3.0开始,官方提供了一个名为Redis Cluster的方案,用于实现切片集群。

Redis Cluster方案采用哈希槽来处理数据和实例之间的映射关系。这里有两个映射关系:键值对与哈希槽的映射关系和哈希槽与实例的映射关系。下面我们来介绍一下这两个映射关系的映射过程。

键值对与哈希槽的映射过程

根据键值对的key,按照CRC16算法计算一个16bit的值。

再用这个16bit值对16384取模,得到0~16383范围内的模数,每个模数代表一个相应编号的哈希槽。

说明:Redis切片集群最多提供16384个哈希槽。

哈希槽与实例的映射过程

哈希槽与实例的映射关系有两个方案设置,分为自动和手动。

自动映射:使用cluster create命令创建集群,Redis会自动把这些槽平均分布在集群实例上。

手动映射:使用cluster meet命令搬运建立实例间的连接,形成集群,再使用cluster addslots命令,指定每个实例上的哈希槽个数。

说明:在手动分配哈希槽时,需要把16384个槽都分配完,否则Redis集群无法正常工作。

客户端如何定位数据

客户端和集群实例建立连接后,实例就会把哈希槽的分配信息发给客户端。

集群刚创建时,实例如何互相知道哈希槽信息?Redis实例会扩展哈希槽信息,每个Redis实例都拥有完整的哈希槽信息。

另外,客户端收到哈希槽信息后,会缓存在本地,以便在客户端后续请求直接访问实例。

但在集群中,实例和哈希槽的对应关系不是一成不变的。最常见的变化:

  • 在集群中,实例有新增或删除,Redis需要重新分配哈希槽;
  • 为了负载均衡,Redis需要把哈希槽在所有实例上重新分布一遍。

Redis Cluster提供一种重定向机制,类似于HTTP协议的重定向。

客户端把一个键值对操作请求发给一个实例,如果这个实例没有这个键值对映射的哈希槽,这个实例就会给客户端返回MOVED命令的响应结果,包含新实例的访问地址。

GET hello:key (error)
MOVED 13320 172.16.19.5:6379

其中,MOVED命令表示,客户端请求的键值对所在的哈希槽13320,实际是在172.16.19.5这个实例上。

如果哈希槽没有完成迁移,客户端请求的数据并不在哈希槽时,客户端就会收到一条ASK报错信息,如下所示:

GET hello:key (error)
ASK 13320 172.16.19.5:6379

这个结果中的ASK命令就表示,客户端请求的键值对所在的哈希槽13320,在172.16.19.5这个实例上,但是这个哈希槽正在迁移。

和MOVED命令不同,ASK命令并不会更新客户端缓存的哈希槽分配信息

Redis Cluster为什么不采用把key直接映射到实例的方式

整个集群存储key的数量是无法预估的,key的数量非常多时,直接记录每个key对应的实例映射关系,这个映射表会非常庞大,这个映射表无论是存储在服务端还是客户端都占用了非常大的内存空间。

Redis Cluster采用无中心化的模式(无proxy,客户端与服务端直连),客户端在某个节点访问一个key,如果这个key不在这个节点上,这个节点需要有纠正客户端路由到正确节点的能力(MOVED响应),这就需要节点之间互相交换路由表,每个节点拥有整个集群完整的路由关系。如果存储的都是key与实例的对应关系,节点之间交换信息也会变得非常庞大,消耗过多的网络资源,而且就算交换完成,相当于每个节点都需要额外存储其他节点的路由表,内存占用过大造成资源浪费。

当集群在扩容、缩容、数据均衡时,节点之间会发生数据迁移,迁移时需要修改每个key的映射关系,维护成本高。

而在中间增加一层哈希槽,可以把数据和节点解耦,key通过Hash计算,只需要关心映射到了哪个哈希槽,然后再通过哈希槽和节点的映射表找到节点,相当于消耗了很少的CPU资源,不但让数据分布更均匀,还可以让这个映射表变得很小,利于客户端和服务端保存,节点之间交换信息时也变得轻量。

当集群在扩容、缩容、数据均衡时,节点之间的操作例如数据迁移,都以哈希槽为基本单位进行操作,简化了节点扩容、缩容的难度,便于集群的维护和管理。

小结

  • 数据扩容有两种方式:纵向扩展和横向扩展。Redis切片集群提供了横向扩展的模式。
  • 集群的实例增减或者数据重新分布,会导致哈希槽和实例的映射关系发生变化。当客户端发送请求时,会收到命令执行报错信息。
  • 在Redis3.0之前,Redis官方并没有提供切片集群方案。业界提供了一些成熟的方案,例如基于客户端分区的ShardedJedis,基于代理的Codis、Twemproxy等。

参考资料

Redis基础篇(八)数据分片的更多相关文章

  1. 老司机带你玩转面试(1):缓存中间件 Redis 基础知识以及数据持久化

    引言 今天周末,我在家坐着掐指一算,马上又要到一年一度的金九银十招聘季了,国内今年上半年受到 YQ 冲击,金三银四泡汤了,这就直接导致很多今年毕业的同学会和明年毕业的同学一起参加今年下半年的秋招,这个 ...

  2. c# 扩展方法奇思妙用基础篇八:Distinct 扩展(转载)

    转载地址:http://www.cnblogs.com/ldp615/archive/2011/08/01/distinct-entension.html 刚看了篇文章 <Linq的Distin ...

  3. c# 扩展方法奇思妙用基础篇八:Distinct 扩展

    刚看了篇文章 <Linq的Distinct太不给力了>,文中给出了一个解决办法,略显复杂. 试想如果能写成下面的样子,是不是更简单优雅 var p1 = products.Distinct ...

  4. 在 Istio 中实现 Redis 集群的数据分片、读写分离和流量镜像

    Redis 是一个高性能的 key-value 存储系统,被广泛用于微服务架构中.如果我们想要使用 Redis 集群模式提供的高级特性,则需要对客户端代码进行改动,这带来了应用升级和维护的一些困难.利 ...

  5. redis基础篇

    1.redis常见的数据结构 redis是一种以键值对存储的高性能内存数据库,有五种常用的数据类型,string,list,hash,set,zset. 2.redis的过期时间 redis中的key ...

  6. Redis基础篇(四)持久化:内存快照(RDB)

    AOF好处是每次执行只需要记录操作命令,记录量不大.但在故障恢复时,需要逐一执行AOF的操作命令,如果日志很大,恢复就很慢. 今天学习另一种持久化方式:内存快照.内存快照,是Redis某一时刻的状态, ...

  7. Redis基础篇(六)数据同步:主从复制

    Redis具有高可靠性,体现在两方面: 一是数据尽量少丢失,通过前面介绍的持久化方式AOF和RDB,在宕机时可以恢复数据. 二是服务尽量少中断,通过副本冗余来实现. 今天我们学习的就是通过主从复制实现 ...

  8. Redis基础篇(二)高性能IO模型

    我们经常听到说Redis是单线程的,也会有疑问:为什么单线程的Redis能那么快? 这里要明白一点:Redis是单线程,主要是指Redis的网络IO和键值对读写是由一个线程来完成的,这也是Redis对 ...

  9. Redis基础篇(三)持久化:AOF日志

    Redis是内存数据库,但是一旦服务器宕机,内存中的数据将会全部丢失. 最简单的恢复方式是从后端数据库恢复,但这种方式有两个问题: 频繁访问数据库,会给数据库带来巨大的压力: 从数据库中读取相比从Re ...

随机推荐

  1. PyQt(Python+Qt)学习随笔:containers容器类部件QStackedWidget堆叠窗口属性

    老猿Python博文目录 专栏:使用PyQt开发图形界面Python应用 老猿Python博客地址 一.堆叠窗口简介 StackedWidget堆叠窗口部件为一系列窗口部件的堆叠,对应类为QStack ...

  2. 第11.5节 Python正则表达式搜索任意字符匹配及元字符“.”(点)功能介绍

    在re模块中,任意字符匹配使用"."(点)来表示, 在默认模式下,点匹配除了换行的任意字符.如果指定了搜索标记re.DOTALL ,它将匹配包括换行符的任意字符.关于搜索标记的含义 ...

  3. PyQt Designer中带参数的信号为什么匹配不到带参数的槽函数?

    老猿在学习ListView组件时,想实现一个在ListView组件中选中一个选择项后触发消息给主窗口,通过主窗口显示当前选中的项的内容. 进入QtDesigner后,设计一个图形界面,其中窗口界面使用 ...

  4. PyQt(Python+Qt)学习随笔:Qt Designer中QAbstractButton派生按钮部件的shortcut 属性

    shortcut 属性保存与按钮关联的快捷键.可以使用shortcut()和setShortcut(QKeySequence)访问和设置该属性. 关于这个属性官网介绍的不多,经老猿实际验证,它与tex ...

  5. 小程序view的显示与隐藏

    需要在全局数据块中,设定一个控制键. data: { ......//省略其他代码 showView: true }, 然后是在wxml中,view的class中设置2个class,并用三目表达式来进 ...

  6. jquery 执行a 标签 点击事件 跳转href 路径

    <a href="./export.pdf" id="pdfdown" download="文件名.pdf">下载</a& ...

  7. CF850F Rainbow Balls 题解

    考虑最后变成哪一种颜色. 设 \(s = \sum\limits_{i=1}^n a_i\) 设现在有 \(k\) 种当前颜色, 需要全部变成该种颜色, 期望步数为 \(f_k\). 考虑状态转移.设 ...

  8. OpenResty&Canal

    OpenResty&Canal OpenResty 提供缓存功能 封装了Nginx,并且提供了Lua扩展,大大提升了Nginx的并发处理能力10k~1000k Nginx限流 1.控制速率 2 ...

  9. Docker部署Mysql8.0.20并配置主从复制

    1. Linux安装Mysql8.0.20并配置主从复制(一主一从,双主双从)   Linux安装Mysql8.0.20并配置主从复制(一主一从,双主双从) 2. 前提准备 # 创建主从数据库文件夹 ...

  10. ssh-copy-id三步实现SSH免密登录

    背景 在日常工作中,不希望每次登录都输入密码,这里主要介绍一种简单的配置Linux主机间免密登录的方式 先了解两个核心命令: ssh-keygen :产生公钥和私钥对 ssh-copy-id:将北极的 ...