引入
在实际生产中,只部署一个Redis时,就会造成单点问题:
- 可用性问题,单个节点部署的Redis,如果此时该服务器挂了,那么意味着Redis整体的服务也就断掉了。
- 性能/并发也是比较有限的。
为了避免单点问题的发生,自然而然就引入了分布式系统。在分布式系统中,采用多个服务器来部署Redis,从而构成一个Redis集群。并且,在Redis集群中引入一些机制,来保证Redis集群能够提供一个稳定且高效的数据存储功能,存在的模式有:
- 主从模式
- 哨兵模式
- 集群模式
主从模式
概述
主从模式,就是配置若干个Redis,其中存在一个主节点,多个从节点。主节点用来负责写数据和读数据,从节点只能用来负责读数据。主从关系建立之后,如果主节点中存在数据,那么先把这些数据同步给从节点。后续当主节点的数据被操作之后,就会同步给从节点。从节点则根据主节点同步过来的数据进行修改,从而保证主从节点数据的一致性。
配置主从模式
配置三个Redis服务
1. 先建立一个文件夹用来保存主从复制的各种内容。
2. 复制三份Redis的配置文件到该文件夹下。
3. 修改配置文件的内容(使用sudo vim 文件名的命令进入配置文件)。
a. 首先将端口号进行修改,分别修改成6380、6381、6382。
b. 其次将保护模式(protected-mode)和后台线程(daemonize)分别设置成no和yes。
c. 然后将rdb的文件名和文件地址改成/root/master和xxx.rdb。
上述图片是主节点修改的配置文件,从节点只需要把文件名和端口号修改一下就可以了,其他内容和上述图片中的保持一致即可。
d. 最后使用redis-server ./配置文件命令启动三个Redis服务。
e. 查看是否启动成功
配置主从模式
f. 配置主从模式:在6381和6382端口的服务中使用slaveof 127.0.0.1 6380建立主从关系。
在上述代码中,先对6381端口的服务创建了一个数据,然后再建立主从关系,发现数据没了,证明了建议主从关系之后,原先从节点的数据就会清零。
在上述代码中,先对6380端口的服务建立了几个数据,然后再进入6382端口的服务,建立两者的主从关系,通过代码发现6382中已经复制了主节点中的数据。
一共有三种配置主从模式的方法,不过他们使用的是同一个命令:slaveof IP PORT。如果想要断开主从关系,那么只需要输入命令slaveof no one即可。
对于三种配置主从模式的方法,分别是在配置文件中、Redis服务启动时以及直接在Redis中使用命令。
不论是连接还是断开,都是在从节点中进行配置。
上述全部操作完成后,就配置了一个6380端口的服务为主节点,6381和6382端口的服务为从节点的一主二从结构。
查看主从结构
使用info replication命令就可以查看主从结构。
从主节点中查看:
- role表示当前节点的角色(master或者slave)。
- connected_slaves表示从节点的数量。
- master_replid是主节点的唯一标识,当主节点启动时就会生成,并且重启之后生成的也不是相同的。当主从关系建立之后,从节点就会从主节点这里获取该标识。
- master_replid2是备胎,它的使用场景是:假设A节点是从节点,当他和主节点出现网络故障之后,A以为主节点挂了,就会把自己当成主节点来处理请求。此时就会有自己replid,并且她并不会把之前的主节点id给扔掉,而是记录在replid2中,当网络恢复之后,就可以按照replid2找到之前的从节点。
- master_repl_offset是偏移量,对于主节点来说,偏移量就表示自己写入命令的字节长度的累加值。
- master_repl_offset2是备胎,和上述的master_replid2是一起使用的。
- slave(数字)表示从节点的一些信息:ip就是从节点的ip地址、port就是从节点的端口号,state就是从节点的状态、offset是偏移量的意思(这个偏移量则是和主节点的偏移量对应,用来标识从节点复制数量的多少,当主从节点的offset相等时,表示同步完成,不等时则表示还未同步完成)、lag表示延迟,由于在生产环境中,分布式系统都是基于不同主机实现的,所以是通过网络交互,自热而然就有延迟。
- 下面四个字段是一起使用的,主要是在部分复制时使用。active表示开启复制缓冲区、size表示缓冲区最大长度、offset表示起始偏移量、histlen表示已保存数据的有效长度。具体使用在部分复制章节会详细介绍。
由于replid是唯一标识,offset是偏移量。所以当两个节点的replid和offset完全相同时,就证明两个节点中的数据也完全一致。
从从节点中查看:
从节点中的信息和主节点大致相同,就是排放顺序存在不同而已。
断开主从模式
配置主从模式时,在从节点中使用slaveof IP PORT就可以配置成功。并且,假设从节点中有数据,那么就会先清空;配置完成后,主节点中有数据,那么就会复制给从节点。
断开主从模式,就是在从节点中使用slaveof no one命令。主从关系断开之后,从节点并不会清空原有的数据,只是不会在接收主节点的数据了。不过,要是再次建立主从关系时,就会清空原有的所有数据。
当我们断开6381端口的程序时,如下图可以发现它标识id的第二个变成了原先主节点的标识id。因此我们还可以得出如果它连接的是原先的主节点,那么就要根据一定的标识来判断是否清空数据再复制了。
补充内容
安全性
对于一些数据比较重要的节点,通常会给其设置密码requirepass,此时客户端来进行访问时首先要就要通过密码的验证。而主从节点之间的连接是通过一个特殊标识的客户端来完成的,所以此时从节点就要配置masterauth参数,内容就是主节点的密码,这样从节点才能正确连接到主节点并成功复制主节点的数据内容。
只读
默认情况下,从节点都采用只读模式(slave-read-onely = yes)。原有是因为主节点的修改从节点可以进行复制,但是从节点修改之后主节点无法进行复制。因此从节点也修改数据,那么请求读到的数据可能是不同的,所以要求从节点只能读。
传输延迟
主从节点之间是使用TCP来传输数据的,而TCP内部支持了nagle算法。所谓的nagle算法,就是把数据包很小的几个合并传输,减少传输次数,从而减少网络带宽,但是传输延迟就会增加。
Redis为了避免这种情况,就要关闭TCP的nagle算法,从而使得从节点可以更快的同步主节点的内容数据,使用repl-disable-tcp-nodely来控制,默认是no,即开启了此功能。如果Redis是部署在同机房,那么主从之间的网络传输环境就非常好,可以关闭;不过如果是跨机房部署,那么网络环境复杂,关闭这种算法反而会使得主从之间出现一定的问题,所以可以开启,虽然会增加网络延迟,但是网络带宽会减少。
拓扑结构
一主一丛
一主一从结构如下图所示,主节点可以读数据和写数据,从节点用来写数据。所以读数据的性能肯定会得到提升,但是对于写数据来说,性能不会有太大的提升。
当写数据操作过多时,可以关闭主节点的AOF持久化方式,开启从节点的AOF。这样,既可以保证数据的实时持久化又可以减少持久化对主节点性能的影响。不过,假设主节点关掉AOF之后,是不能自动重启主节点的,因为它不存在AOF文件,如果自动重启就相当于主节点把所有的数据删除,进一步从节点也会复制导致删除自己的所有数据。所以,如果主节点关闭AOF持久化的话,那么重启时就需要先获取从节点的AOF文件,再启动。
。
一主多从
一主多从结构如下图所示,在实际生产中,读数据是要远远大于写数据的,因此就引入了一主多从的结构,从而支持更多的读并发。
当主节点数据发生改变之后,把改变的数据同步给从节点。但是,由于从节点过多,导致主节点要同步很多次,从而需要更大的网络带宽。
树状主从
树状主从结构如下图所示,由于一主多从结构导致需要极大的网络带宽才能支撑起对所有从节点数据的同步,因此引入了树状主从结构。
在树状主从结构中,从节点也有了复制数据的作用,从而使得真正的主节点不需要太大的网络带宽。这样把原先一台机器的压力给分配开,减少了主节点的压力。
不过,由于同步数据的链路拉长了,因此使得同步时间变长了。
主从复制的流程
- 在从节点中发起slaveof IP port的命令之后,就开始了主从复制的一系列流程。
- 从节点接收到命令之后,首先保存主节点的IP地址和端口号。
- 从节点开始和主节点建立连接,两者之间建立的是TCP连接,所以就要进行三次握手的操作
- 从节点向主节点发送ping命令,验证主节点是否能够正常工作。
- 如果主节点配置了密码,那么就要进行权限验证,只有验证通过的才能开启真正的复制。
- 主从节点之后所有都建立好连接之后,主节点就开始向从节点同步数据集,此时是全量复制,也就是把主节点的所有内容都复制给主节点(不过还有可能就是之间有过连接,不过后来网络等原因出现故障,这就情况就有可能是部分复制)。
- 第一次全量复制完成之后,后续就会持续建立连接,并且开启命令的持续复制,也就是实时复制。
在上述主从复制的流程中,比较重要的三个内容就是全量复制、部分复制和实时复制。接下来就针对这三种模式进行一个简单介绍。
数据同步
运行流程
首先,数据同步的命令是:psync replicationid offset。psync命令并不需要程序猿手动去执行,当主从连接建立好之后,从节点自动向主节点发送上述命令去开始复制数据。
在查看主从结构一节中,已经对replicationid和offset进行了一个简单概述,这里再来稍微介绍一下两者的含义。
replicationid是主从复制中主节点的唯一标识,主从关系建立之后,从节点主动找主节点获取该信息。数据同步命令中,从节点向主节点发送命令时,带上该标识id,主节点就可以判断该从节点是否是自己的从节点,进而判断是否要给其复制数据。
offset是偏移量,主节点中的偏移量表示已经操作了多少命令(主节点接收到命令之后,都会占几个字节,主节点就会记录下来,作为自己的偏移量),从节点中的偏移量则是用来表示自己同步主节点数据的位置,当主从的偏移量一致时,表示同步完成。
同步时,使用replicationid + offset的原因就是因为两者可以描述一个数据集合。当两者完全相同时,就表示两个节点的数据完全相同。
其次,发送命令时,如果replicationid和offset不填的话,那么默认就是?和-1。获取数据时,可以获取全部数据,也可以获取部分数据,这就和offset有一定的关系,当值为-1时,就表示获取全部数据,值为其他时,就表示获取部分数据;不过,不仅仅只是看这一个指标,同时还考虑主节点的情况,两者相结合决定是部分复制还是全量复制。
如上图,当从节点发送命令之后,主节点根据offset和自身的情况结合考虑,可能的回复结果有三种+FULLRESYNC、+CONTINUE、-ERR。
- +FULLRESYNC:表示从节点需要进行全量复制。
- +CONTINUE:表示从节点需要部分复制。
- -ERR:表示Redis主节点版本过低,不支持psync命令,从节点可以使用sync命令进行全量复制(psync命令不需要手动执行,Redis会在主从模式下自动调用执行;但是sync命令要手动调用,并且还会阻塞其他请求的执行)。
从节点和主节点是第一次连接或者主节点不方便进行部分复制时,两者时间使用的就是全量复制;当主节点已经复制过数据,由于某些原因之间发生了故障,再次连接时,使用的就是部分复制。虽然使用全量复制比较稳妥,但是如果数据量巨大的情况下,时间会比较长,因此才会出现部分复制。不过最后到底是怎么复制,还得看主节点给你发送的命令。
当从节点和主节点断开连接之后,发现会在replid2和offset2中存储上次主节点的数据和偏移量,因此我个人认为这就是当两者再次连接之后,用来进行部分复制的。并且,当我在曾经的从节点中写数据时,我发现replid1和offset1会变,但是replid2和offset2不会改变。
全量复制
- 从节点和主节点之间建立好关系之后,从节点就会自动向主节点发送psync replid offset命令,此时由于是第一次发送,因此replid为?,offset为-1。
- 主节点接收到从节点发送的命令之后,就会进行解析,发现是想要进行全量复制,就会返回给从节点+FULLRESYNC replid offset命令。
- 从节点接收到命令之后,就会保存replid和offset等信息。
- 主节点也会调用bgsave命令生成RDB文件。
- 生成RDB文件之后,主节点就会传输给从节点,从节点则是将命令保存在硬盘中。
- 在生成RDB文件到发送给从节点的过程中,主节点还会搞一个缓冲区,用来记录这段时间内主节点接收到的命令。等到从节点接收了RDB文件之后,主节点就会把缓冲区中的内容按照RDB的格式追加到RDB文件中,保持主从数据的一致性。
- 从节点删除自身的全部数据。
- 从节点解析RDB文件,此时从节点就会拥有主节点中的数据。
- 当从节点加载完RDB文件之后,发现还开启着AOF功能,此时就会执行重写命令,得到最新的AOF文件。
开发人员发现主节点生成RDB文件,然后从节点再解析RDB文件,这好像有点多此一举了,因此主节点直接把生成的RDB文件传输给从节点,而不是再传输文件了,这就叫做无硬盘模式(diskless)。即使引入了无硬盘模式,全量复制也是比较耗时的,毕竟他要进行网络传输,这是没办法节省的。
部分复制
部分复制是针对全量复制的一种特殊优化,依旧使用psync命令来进行部分复制。当从节点和主节点之间发生网络抖动、命令异常丢失等情况时,从节点此时就会发生psync命令,主节点判断是否可以进行部分复制,如果可以的话,那就会返回+CONTINUE命令进行部分复制。
从节点能否进行部分复制,主要参考的是主节点中的后续四个参数,他们能够影响主节点是否开启复制积压缓冲区,以及缓冲区的大小。如果没有开启,或者主节点修改的数量已经超过了缓冲区的大小,那么就不能进行复制。
上述四个内容分别表示是否开启复制积压缓冲区、缓冲区的大小、缓冲区的开始偏移量、缓冲区已经保持数据的长度。
本质上就是一个环形队列,当队列之中长度满了之后,起始偏移量就会改变,从而导致数据就会改变。所以当数据缺失太多时,就没法开启部分复制。
- 主节点和从节点之间由于网络故障等原因断开连接后,当主节点等待时间超过repl_timout之后,就会认为从节点故障并断开连接。
- 断开连接之后,此时的积压缓冲区就会起到它的作用,主节点把断开连接之后的命令填充到积压缓冲区中。
- 当网络恢复正常之后,主从之间再次建立连接关系。
- 从节点就会向主节点发送psync命令,不过此时的replid和offset不再是?和-1,而是尤其的特定值。
- 主节点接收到命令之后,首先判断replid是否和自己的一致,然后再根据积压缓冲区和传过来的offset判断是否满足部分复制的条件。如果满足,则将从节点发送+CONTINUE命令。
- 主节点将需要同步的数据发送给从节点,使得主从之间最后到达数据的一致性。
实时复制
全量复制和部分复制都是在主从节点建立关系时使用的,由于全量复制比较浪费时间,因此才引入了部分复制。两个复制都是用来初始化数据的。
实时复制则是在初始化数据之后,进行数据同步的一种手段。由于主节点会持续不断的接收从节点的命令,所以从节点也不能混吃等死,他也要接收主节点修改之后的数据进行同步。
主从节点之间会建立TCP的长连接,当主节点的数据修改之后,就会发送给从节点。从节点就会根据主节点同步过来的数据,修改自己的数据。
主从之间长连接的建立,使用心跳包的机制:
- 主节点会给从节点发送心跳包机制,从节点也会给主节点发送心跳包机制。
- 主节点每隔10秒向从节点发送ping命令,判断从节点的存活和连接状态。
- 从节点每隔1秒向主节点发送replconf ack offset的命令,给主节点上报自己的偏移量。
如果主节点发现从节点通信延迟超过repl_timeout(默认60秒),那么就会断开连接,等到从节点恢复连接之后,再开启心跳机制。
优点和缺点
优点
1. 从节点可以作为主节点的一个备份,后续如果主节点挂了,那么从节点就可以接替主节点继续工作。
2. 从节点可以分担主节点的读压力,让主节点增加写压力的承受。
缺点
1. 虽然从节点可以代替主节点进行工作,但是并没有办法自行提升为主节点,需要人工干预,因此运维人员就需要时刻注意运行情况。后续通过哨兵机制解决。
2. 虽然读压力但是有人承担了,但是写压力并没有人承担,依旧是主节点负重前行,因此当该业务是写操作多时,就会产生影响。后续通过集群机制解决。
3. 上述两个问题可以借助其他方法解决,不过如果机子太多,导致树状结构太大,进而引起同步延迟较长,这是不太好解决的。
哨兵模式
概述
哨兵模式的出现就是为了解决主从模式的问题。对于主从模式来说,当主节点宕机之后,如果人工干预来修复问题,这当然没问题,但是人工处理起来是相对慢的,假设此时大量的请求打到Redis上,那就会出现严重的问题。哨兵模式则是机器自动干预,不需要人工,当主节点挂掉之后,哨兵就可以找一个从节点自动将其提升为主节点,从而避免出现问题。
一个哨兵,就是一个单独的进程来实现的。
人工恢复主节点流程
首先,机器处于一个正常的工作状态。
当主节点发生问题宕机之后,首先监控平台会发现问题:
监控平台发现问题之后,就会通过某种机制通知工作人员。工作人员收到报警就去迅速查看日志信息。查看是否好修复,如果好修复的话,那就感觉修复,如果不好修复的话,那就赶快先找一个从节点代替主节点的工作,然后再查看详细问题。
找好从节点之后,就使用slaveof no one命令断开之前的主从关系,然后再使用slaveof IP port命令建立新的主从关系。
同时还要告诉客户端自己的信息发生改变了。
最后等到旧的主节点修复好之后,就再次充当从节点来继续工作。
这种方式最后也可以让集群恢复工作,但是整体操作流程较为复杂,并不符合分布式系统下的高可用,为了解决这种弊端,程序猿们研究了一套哨兵机制来自动实现主节点下线,新主节点上线的流程,接下来旧先大致描述一下此套流程。
自动恢复主节点流程
使用自动恢复流程,不仅有Redis提供服务的集群,还有监控Redis的集群,即哨兵集群。
首先,要明确的一点就是哨兵服务并不是单个,而是多个共同服务,原因是因为哨兵服务也是一个进程,那么就可能出现宕机的情况,并且,哨兵和Redis之间也是进行网络交互的,如果只有一个哨兵,那么出现网络抖动或其他情况时,哨兵服务就可能误判,从而进行一系列的无用操作。
其次,所谓的监控就是哨兵和Redis之间建立TCP长连接,定期发送心跳包。如果某个哨兵发现主节点好像挂了,那么他就会先和其他哨兵商量,如果其他哨兵也认为主节点挂了,那么就会采取错误,如果是从节点宕机了,哨兵将不会采取任何措施。
然后,当大多数哨兵认为主节点挂了,那么就会在哨兵中选取一个领导,领导就会去Redis的从节点中挑选一个节点,把他作为主节点(其实本质还是执行slavof no one以及slaveof IP port的命令)。
最后,哨兵在恢复完主节点之后,就会告诉客户端主节点是谁,这样客户端就能得知新的主节点,从而进行写数据。
功能
- 监控(监控节点是否出现故障)
- 自动故障转移(主节点宕机之后,自动更换主节点)
- 通知(更换完主节点之后,通知客户端)
配置哨兵模式
对于哨兵监控的主从集群来说,还是主从模式下搭建的集群,即一主而从。
1. 建立sentinel文件夹,用来放置sentinel的配置文件
2. 复制任意一份Redis的配置文件,我复制的是master的
3. 修改配置文件中的内容
端口号以及下述内容都要进行修改
sentinel monitor master 127.0.0.1 6380 2
sentinel down-after-milliseconds master 1000
sentinel monitor 主节点名 主节点ip 主节点端⼝ 法定票数
上述命令表示哨兵要监控的主节点信息(其中主节点名自己任意起一个,法定票数表示多少个哨兵认为主节点宕机时就可以认为主节点宕机)。
sentinel down-after-milliseconds 主节点名 时间
上述命令表示当心跳包的时间超过定义时间时,哨兵就认为主节点挂了。
4. 启动三个哨兵
5. 使用netstat -nlpt查看目录,出现对应端口号的服务就证明哨兵集群启动成功。
6. 测试
使用kill命令杀掉6380端口的程序之后,再进入6381端口的程序使用命令info replication查看:
从上述结果看到,当主节点下线之后,哨兵就会在剩下的两个节点中选取一个节点作为主节点。
我们再次启动6380端口的程序,再次查看主从架构:
从上述结构可以看到,原先是主节点的程序,将其下线再上线,就成了从节点。这都是来源于哨兵机制,它可以在主节点宕机之后,再在从节点之中找一个作为主节点,然后之前的主节点修复之后,他又可以将其重新拉到原先的主从集群中,不过此时就是作为从节点的身份。
哨兵选取主节点的流程
主观下线:当哨兵A在定义的时间内心跳包没有来,哨兵A就认为主节点挂了(如果不是主节点的话,哨兵根本不care。此时,还不能完全认为主节点挂了,也有可能是由于网络原因导致没有收到主节点的心跳包)。
客观下线:哨兵A认为挂了之后,就会去问其他哨兵,如果此时认为主节点挂了的哨兵数量超过了配置文件中定义的,那么就认为主节点确实是挂了。
挑选leader:一致认为主节点挂了之后,哨兵就会挑选出一个哨兵leader。至于这个哨兵是如何选出的,利用的是Raft共识算法。简单来说,就是当哨兵一致认为主节点挂了之后,就会开启投票,每个哨兵都有一票,并且谁先来要票就投给谁。假设此时A先说,投我一票,那么其他人就把票投给A。当A的票数过半时,就会告诉其他人说不用投了我是领导者,然后他就会去选择新的主节点。详细的Raft内容还请大家去看我另一篇文章即可。
挑选主节点:哨兵leader挑选出之后,它就作为代表去从节点中挑选一个合适的,让他来当主节点。至于如何挑选,首先看offset,谁的大证明谁同步的数据多。如果都相同那么就去看runid,不过这个东西是随机生成的,所以当offset相同之后,就是随机选一个了。
设置主节点:选好之后,leader就会执行slaveof no one命令,断掉之前的主从关系,然后再其他节点上执行slaveof ip port命令,建立新的主从关系。这样,新的主节点就选出来的。
总结
- 哨兵节点不能只有一个,否则哨兵宕机之后就是单纯的主从模式了。
- 哨兵节点的个数是奇数最好,这样方便推选出领导者(原因是因为成为领导者必须票数过半,偶数导致两个人票数相同的概率增大,从而使得要开启新一轮的选举)。
- 哨兵最好不要部署再同一台主机上。
- 哨兵模式只能解决高可用问题。
- 哨兵只是负责监督,不具备其他功能,所以只能解决主从模式中的主节点宕机问题,无法解决主节点压力过大的问题。想要解决该问题,就要使用集群模式。
集群模式
概述
在主从模式中,通过引入从节点从而避免了单点问题,增加了Redis服务的读并发能力。在哨兵模式中,通过引入哨兵机制从而避免了主节点宕机问题,增加了Redis服务的可用性。在集群模式中,则是为了解决存储空间不足的问题,假设数据有1TB需要存储在Redis中,那么使用三套内存为300多G的主从+哨兵集群就可以解决问题了。
需要注意的是,只要是多个集群构成的分布式系统,那么他就可以称为集群。不过,对于不同的集群来说,他解决的问题不同。主从模式下构建的一主多从结构可以称为集群,哨兵模式和主从模式的集合也可以称为集群。不过,对于Redis的集群来说,主要想解决的就是不同机子上放不同的数据,从而增加整体的容量这个问题。
分片
由于Redis的集群主要解决内存空间不足的问题,所以我们引入多个机子,那数据如何进行存储呢?这就成了一个关键的问题。下面就来介绍三种具体的分片方式:
哈希求余
哈希求余使用到了哈希的思想,使用hash函数,把key映射到整数,再使用数组的长度取余,就可以获得一个数组下标。
例如,现在有三个分片,所以数组的长度就是3,利用一个hash函数将key转换成一个数字,使用该数字取余数组长度,就能获取一个数组下标,此时把数据放到该下标对应的分片即可。如果是写操作的话,那就是直接写入;如果是读操作的话,那么进入对应分片去读数据。
对于哈希求余来说,存在的一个最大问题就是增加机子时,分片的个数就会变,从而每个key取余得到的结果就和以前不一致。因此,之前的所有数据都要重新计算,重新搬运,最严重的是,不同的数取余得到的结果变化是相对较大的,所以搬运的数据就会很多。
一致性哈希
首先,依旧是用哈希函数求出一个值,然后把哈希函数能求出值的范围类比成一个圆,并将这个圆进行分区处理,一个分片中存储的数据就是一个分区中的数据。如下图所示,当某个key取余求出的值是1024时,就会落在0到1这个区间之中,我们按照顺时针方法查找,找到的第一个分片就是这个key应该存储的分片,所以这个key就应该存在1号分片中。
当我们要加机子时,就可以对任意一个分区进行再分区处理,这样我们搬运的数据就不再是全部了,而是一小部分。但是,随之而来的一个问题就是数据倾斜,即分区之后两个片内的数据都变少了,但是其他没有分片的还是那么多。所以又想了一个办法就是,分片的时候都分片一下,开发人员倒是觉得没问题。不过到时候就要放着财务和运维人员半夜敲闷棍了。
哈希槽分区算法
哈希槽分区算法是结合了哈希求余和一致性哈希算法两种算法,解决了数据搬运和数据倾斜的问题,所以它就成了Reids使用的分片算法。
所谓的哈希槽分区算法,就是现有一个哈希函数获取一个整数值,然后再用整数值取余16384,从而得到一个槽位,然后再根据槽位把数据放到不同的片上。简单来说,就是先分了若干个槽位,然后又把槽位分到了若干个片上。
每一个分片都使用位图的数据结构来表示存放了哪几个槽位,16384个槽位,也就是2KB的大小。
举个例子,假设当前有三个分片,16384个槽位。现在0号分片就放0到5641槽位的数据;1号槽位就放5642带10923槽位的数据;2号分片就放10924到16383槽位的数据。当然,槽位并不是必须连续放置,它可以任意放置在不同的分片上。
对应的数据倾斜问题,只要每个分片上有着几乎对等的槽位,那对应的数据几乎也是差不多的。对应的数据搬运问题,我们搬运数据时,只需要看该分片中有没有改槽位,如果有我们才搬,如果没有的话,我们就不管这个槽位了。
问题一:Redis集群最多有16384个分片?
理论上,Redis集群中有多少个分片都是可以的,毕竟没有一个明确的规定。但是,如果有16384个分片,那运维老哥每天就忙死了,不是这个机子坏,就是那个机子坏,毕竟基数大了。并且,如果有这么多分片的话,也就是一个槽位对应一个机子,这很难保证数据的均匀。所以,可以有,但没有人这么多,官方给出的建议是不超过1000个分片。
问题二:为什么是16384个槽位?
集群之间也是需要进行通信的,并且也是基于心跳包的设置。通信时,都会带上对应分片中的槽位,虽然16384为也才2KB,但是这个数据是处于一个频繁的心跳网络包中,因此也是一笔不小的开销。并且,官方建议分片不超过1000个,所以16384个槽位完全够用了,同时对应的内存容量也不大。综上所述,设置了一个16384的槽位。