面对海量数据Redis如何应对

        如果要用Redis保存1亿个键值对,每个键值对大小为256B,该如何选择集群呢?先估算一下,这些键值对需要多少空间。1亿 * 256B 大约需要25G左右的空间来保存这些数据。

        我们可以使用单台机器来保存,但是数据量如果继续增加呢?只能纵向扩展,增加内存,但这种方式是由问题的

  1. 单台机器数据量如此巨大,Redis进行RDB持久化时,如果全量数据做持久化,可想而知这个过程是多么的漫长;
  2. 单台机器不可能不限制的纵向扩展,总会遇到天花板,那后期就无法使用了。

        Redis的高可靠主要体现在两方面,一是数据尽量不丢失;二是服务尽量少的中断。AOF和RDB持久化机制保证了数据不丢失,对于后者主要通过冗余设计来保证。

        Redis提供了多种分布式架构设计。主要包括:主从同步、哨兵机制、分片集群方式。

主从同步

        Redis提供了主从模式,以保证数据副本的一致性,主从库之间采用的是读写分离的方式。读操作在主从库上都能完成,写操作只在主库上完成,然后再将写操作从主库同步到从库。

        采用读写分离后,写操作只在主库上进行,而读操作可以在任意服务上进行,这样可以分担主库的压力。如果主从库都能进行写操作,那势必会增加复杂度,如果不做一致性处理,那最终的数据就不知道应该是什么了。

        主从库数据同步

        当第一次启动Redis集群时,可以通过命令来确定主从库的关系,这时就要进行第一次数据同步了,同步流程如下:​​​​​​​

  1. 建立链接:从库给主库发送psync命令,表示要进行数据同步,主库根据这个命令的参数来启动复制。psync命令包含了主库的runID和复制进度offset两个参数。runId是每个Redis实例的唯一标识,启动时会自动生成一个runID,第一次复制时,从库不知道主库的runId,所以设置为?,offset为-1,表示第一次复制。
  2. 主库响应从库:主库收到psync命令后,会用FULLRESYNC响应命令带上两个参数:主库ID和主库目前的复制进度offset,返回给从库。从库收到响应后,会记录下这两个参数。
  3. 主库将数据同步给从库:主库执行bgsave命令,生成RDB文件,在将文件发给从库。从库接收到RDB文件后,会先清空当前数据库,然后加载RDB文件。
  4. 主库将新写入数据进行同步:主库同步RDB给从库的过程,主库不会阻塞,因为采用了bgsave命令,所以主库还能接受客户端的写入请求,但新写入的数据并没有在刚才的RDB文件中,为了实现数据一致,Redis采用了replication buffer。

        客户端和从库对于Redis主库来说都一样的,Redis会为每个客户端分配一个buffer,所有的数据交互都在这个buffer中进行。写入数据时先写入buffer,在把buffer中的数据发送给网络IO,在发送出去,这样就实现了数据交互。

        数据同步时为何使用RDB

        数据同步时为何使用RDB而不是AOF呢,主要有这几点原因

  1. RDB文件是Redis对当前所有数据的二进制序列化形式,经过压缩存储,文件相对较小。
  2. RDB速度快,文件小时一方面,AOF记录的是操作命令,文件较大,需要执行一条条的命令才能恢复数据,而RDB记录的直接就是数据,速度较快
  3. 由于RDB文件较小,所以网络代考消耗低

        如果从库数量较多,每个从库都从主库同步数据,那主库的压力势必较大,影响性能。这时可以将一个从库和其他从库相连,专门用于数据同步,这样主库只需要和一个从库进行交互即可。

哨兵机制

        主从同步从一定程度上提高了Redis集群的可用性,但还是存在一些问题,如果主库出现故障挂了,此时如果只要读操作,集群还能提供服务,但在正常情况下这是不现实的,写操作不能进行,对业务影响很大,处于停摆状态,这时不能接受的,这时需要人工介入,这个过程比较好使,而且也不可靠。这时需要一个自动切换主库的功能,哨兵就是专门进行这个工作的。

        哨兵的功能

        哨兵其实就是一个运行在特殊模式下的Redis进程,主从库运行的同时,它也在运行。哨兵主要负责的就是三个任务:监控、选主和通知。

        监控:指哨兵进程在运行时,周期性的给所有主从库发送ping命令,检测它们是否仍然在线运行。如果从库没有在规定时间内响应哨兵的PING命令,哨兵就会把它标记为“下线状态”;同样主库没有在规定时间内响应哨兵的PING命令,哨兵就会判定主库下线,然后开始自动切换主库的流程。

        选主:主库挂了以后,哨兵就需要从很多从库里选择一个实例,把它作为新的主库。这一步完成后,现在的集群里就有了新主库。

        通知:哨兵把新主库的连接信息发送给其他从库,让它们执行replicaof命令,和新主库建立连接,并进行数据复制,同时哨兵会把新主库的连接信息通知给客户端,让它们把请求操作发送到新主库上。

        那怎么判断主库真的下线了呢?如果是由于哨兵自己的原因判断主库下线了,实际上主库并没有下线,如果将其下线并执行了选主,那这个开销太大了,也不合理,这时需要引入哨兵集群,即哨兵不是单台服务器,而是多台哨兵组成一个集群,共同来保证主库的运行状态和选举出新主库。

        哨兵集群中只要超过半数认为主库下线了,才认为是客观下线,否则不会执行下线操作。然后再由这些哨兵进行选主,通过给每个从库打分,得分高的为新主库。

  1. 优先级高的从库得分高:用户可以通过slave-prority配置项,给不同的从库设置不同的优先级,比如,有两个从库,它们的内存大小不一样,可以手动给内存大的实例设置一个高优先级。在选主时,哨兵会给优先级高的从库打高分,如果只有一个从库优先级高,那么它就是新主库了。如果优先级都一样,那么进行第二轮打分。
  2. 和旧主库同步程度最近的得分高如果选择和旧主库同步最近的那个从库作为主库,那么这个新主库就有最新的数据。如何判断从库和旧主库的同步进度呢?主从库同步时有个命令传播的过程。在这个过程中,主库会用master_repl_offset记录当前最新的写操作在repl_backlog_buffer中的位置,而从库会用slave_repl_offset这个值记录当前的复制进度。此时我们想要找的从库,它的slave_repl_offset需要最接近master_repl_offset,如果在所有从库中,有从库的slave_repl_offset最接近master_repl_offset,那么它的得分就高,可以作为新主库。如果值都一样的话,就进行第三轮打分。
  3. ID号小的得分高:每个实例都有一个ID,这个ID就是一个编号,在优先级和复制进度相同的情况下,ID小的得分高。

        通过以上打分,就可以从从库中选出新的主库,继续对外服务。

分片集群

        回到开头的问题,保存大量数据,使用单台机器做不到,使用一主多从同样存在问题,那就得使用分片集群。就是指将多个Redis实例组成一个集群,然后按照一定的规则,把收到的数据划分成多份,每一份用一个实例来保存。回到刚提到的场景,如果把25GB数据均分成5份,用5个实例来保存,每个实例只需要保存5GB数据。​​​​​​​

        在实际应用中,随着业务发展,保存大量的数据是无法避免的,而分片集群是一个非常好的解决方案。

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

        在分片集群中,数据需要分布在多个实例上,Redis提供了Redis Cluster方案。分片集群是一种保存大量数据的通用机制。在Redis3.0之前,官方没有提供分片集群的具体方案。从3.0开始,官方提供了Redis Cluster方案,用于实现分片集群。

        Redis Cluster方案采用哈希槽(Hash Slot),来处理数据和实例之间的映射关系。在Redis Cluster方案中,一个分片集群共有16384个哈希槽,这些哈希槽类似于数据分区,每个键值对都会根据它的key,被映射到一个哈希槽中。

        为什么哈希槽选择了16384呢?

        在Redis节点发送心跳包时需要把所有的槽放到这个心跳包里,以便让节点知道当前集群信息,16384=16k,在发送心跳包时使用char进行bitmap压缩后是2k(2 * 8 (8 bit) * 1024(1k) = 16K),也就是说使用2k的空间创建了16k的槽数。

        虽然使用CRC16算法最多可以分配65535(2^16-1)个槽位,65535=65k,压缩后就是8k(8 * 8 (8 bit) * 1024(1k) =65K),也就是说需要需要8k的心跳包,作者认为这样做不太值得;并且一般情况下一个Redis集群不会有超过1000个master节点,所以16k的槽位是个比较合适的选择。

        键值对和实例的映射过程如下。

  1. 首先根据键值对的key,按照CRC16计算一个16bit的值;
  2. 然后再用这个16bit值对16384取模,得到0-16383范围内的模数,每个模数代表一个相应编号的哈希槽。

        客户端如何定位数据

        在定位键值对数据时,它所处的哈希槽是可以计算得到的,这个计算可以在客户端发送请求时来执行。但是,要进一步定位到实例,还需要知道哈希槽分布在哪个实例上。

        一般来说,客户端和集群建立连接后,实例就会把哈希槽分配信息发给客户端。但在集群刚刚创建的时候,每个实例只知道自己被分配了哪些哈希槽,是不知道其他实例拥有的哈希槽信息的。客户端为什么可以在访问任何一个实例时,都能获得所有哈希槽的信息呢?因为Redis实例会把自己的哈希槽信息发给和他相连的其他实例,来完成哈希槽分配信息的扩散,当实例之间互相连接后,每个实例就有所有哈希槽的映射关系了。

        客户端收到哈希槽信息后,会把哈希槽信息缓存在本地。当客户端请求键值对时,会先计算键值对的哈希槽,然后就可以给相应的实例发送请求了。

        

  • 26
    点赞
  • 21
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

超越不平凡

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值