背景
上篇文章聊到了redis的哨兵机制,哨兵的作用是保证主从节点宕机或者故障的时候可以可以进行自愈,选举合适的master并且告知client。这个机制也就保证了redis集群的可用性。但是这次我们又遇到了新问题,那就是主从复制架构的情况下redis 的内存不够用了该怎么办。有人说那就不断的阔各个机器的内存,按照常理我们都知道一个人的力量是有限的。
当一个节点的内存过大,那么我们在进行同步的时候会通过RDB文件进行同步,而生成rdb文件是通过fork一个子进程进行的(下篇文章我们仔细聊下这个过程),在进行fork操作且在生成rdb文件的时候会阻塞主进程,导致redis 的性能就很慢。
所以我们得想办法,可以不再单台机器上扩容,而达到目的。那这个方案就是切片集群。
切片集群(cluster)
什么是切片集群?
就是多个集群/节点 组成的集群,存储数据的方式是分区存储,在这里为什么叫分区存储呢,就是说不通的节点/集群之间是不进行通信往来的,他们只需要存储客户端让存储的数据,也就是说他们存储的数据是不冗余的。
切片集群存储的问题?
1. 数据如何切分?
客户端会存储各种各样的数据,那怎么去将这个数据进行合理的分配到各个节点呢,其实这个我们应该想到一个知识点,那就是Java中的Hash表,他是如何存储的呢,在有限的hash slot 进行存储key,那就是通过key的hash值取模。然后会映射到对应的hash slot 如果有重复会进行新增加链表。那何尝和我们切片集群的场景不一致呢。每一个slot可以对应一个redis主从集群/redis节点。但是对与Java的hash表来讲它是可以进行一直扩容的。所以某些场景还是不符合的。所以我们得进行改进算法。
上面大概讲了下思路,接下啦看看redis cluster的官方方案:
- 根据redis的key适用CRC16的算法计算出一个16bit的值
- 将16bit的值对16384取模。得到一个0-16383之间的一个值。
- 在进行创建redis cluster的时候,每一个分片都会被分配到对应的slot
- 分配的规则是16383/n 也就是每一个分片会在16383/n个slot 上。
这个时候由于你手里的redis机器资源大小不太一样,所以你想让他存储的数据量不一样,这个时候你就可以进行手动分配这个slot。
2. 客户端如何去拿对应的数据呢?
我们通过上面的补助将数据存储到你了对应的一个redis节点/集群上,那么我们查询的时候如何拿呢?
- 首先客户端得必须知道数据是如何被分配到各个节点上去的。那就得知道CRC16算法,并且节点何hash slot对应的分配关系。
- redis 自身是有pubsub的能力的,所以会讲hash slot 和节点的映射关发送给客户端,客户端会缓存下来。
- 上文我们既然将了我们会进行一些扩容,那这如果扩容了那么这个hash slot的映射关系就会改变,那么我们怎么应对呢?
- redis cluster 的重定向方案,那就是redis在进行读数据的时候没有拿到对应的value值,会给客户端一个新的实例信息进行查找。
这个实例信息是通过redis 自己节点的数据存储的,因为我们这条请求的前提条件是key是存在的,但是hash slot 变了,就是在变的时候客户端还不知道,但是请求到redis node的时候才变了,于是redis node将这件事告诉了client,并且补救告诉他应该去哪里找。切更新了自己本地错误的映射关系缓存
返回的信息是:
GET hello:key(error) MOVED 13320 172.16.19.5:6379 - 但是还有一种情况是,redis 在重新分配了后还在进行迁移,这个时间访问一个已经存在的key对应的机器会讲:
GET hello:key(error) ASK 13320 172.16.19.5:6379
这个时候客户端会给对对应的机器请求asking 命令然后进行查询数据。
总结
- 我们聊了切片集群在保存大量数据方面的优势,以及基于哈希槽的数据分布机制和客户端定位键值对的方法。
- 在应对数据量扩容时,虽然增加内存这种纵向扩展的方法简单直接,但是会造成数据库的内存过大,导致性能变慢。
- Redis 切片集群提供了横向扩展的模式,也就是使用多个实例,并给每个实例配置一定数量的哈希槽,数据可以通过键的哈希值映射到哈希槽,再通过哈希槽分散保存到不同的实例上。
- 这样做的好处是扩展性好,不管有多少数据,切片集群都能应对。另外,集群的实例增减,或者是为了实现负载均衡而进行的数据重新分布,会导致哈希槽和实例的映射关系发生变化,客户端发送请求时,会收到命令执行报错信息。