Redis,一个完全免费开源的缓存软件,它是使用C语言编写,基于内存的高性能key-value数据库。Redis在互联网存储技术中得到非常广泛的应用,它作为缓存中间件,能够解决互联网应用中的一些技术瓶颈,且具有使用简单,性能强悍,功能应用场景丰富的特点。
本文讲述Redis的分片存储的架构。
什么是分片存储?
分片存储就是在Redis集群中,采用分布式存储数据的方案,让集群能够得到水平拓展。Redis中使用16384个虚拟哈希槽对集群进行分区,好比如在多个Redis集群中,把16384个槽的位置全部分到Redis集群的master中,每个Redis负责一些槽位,每个槽位都可以用来存储数据,这样就实现了分布式存储。每个Redis实例可以根据机器的性能分配到自定义数量的槽位用来存储数据,数据全部存在16384个槽中,但又分配在不同的Redis实例中,每个Redis实例至少有1个槽位。官网推荐最大Redis集群实例是1000个。
为什么要分片存储?
为了解决单机redis内存存储不足的瓶颈。
Redis是如何实现的?
Redis采用的是虚拟哈希槽分区,把所有的数据key值根据哈希函数映射到0~16384个槽中,每个key通过CRC16校验后对16384取模得到将数据分配到哪个槽中,每个Redis实例负责维护一部分槽。
计算公式:slot = CRC16(key)%16384
这样设计的好处:可以很方便的添加和删除redis集群中的节点。
为什么要这样设计?
想想,在通常的使用到哈希时,需要计算某个key对应的哈希位置时,都是hash(key)%容量来计算哈希的位置,但是这样的做法在集群中特别不适合。假如在集群中使用这种方式计算数据存储的位置,当向集群中加入或删除一个节点的时候,根据公式,原来存储的数据的位置也要相应的变化,或是下次来取数据的时候再计算一次,发现数据找不到了,就乱套了。
在客户端信息中是没有记录集群的16384个槽分配信息的,那么客户端计算出槽后又是如何知道发送给哪个节点的?
1、客户端在不知道向哪个节点发送时,会先发送到任何一台redis实例,让redis实例去计算槽,当redis实例发现这个槽不在当前实例时会向客户端发回数据,并告诉客户端这个数据是发向哪个节点的,客户端再重定向发到正确的redis实例上。
2、客户端在进行一次重定向后,会在本地缓存一份redis分布槽的信息,下次就会根据缓存信息发送到正确的槽位置。当槽的位置变化时,客户端也会进行定时的刷新。
3、重定向分为两个情况:ask和moved。当redis实例确定槽不在当前实例时,会发送moved命令告知客户端发送到哪个正确的redis实例;当redis实例发现当前槽不在当前实例,且集群的节点在发生迁移时,会发送ask命令来告知客户端重新发送给我(因为我还不确定槽在哪个实例上,重试几次或许就迁移完了)。
ps:频繁的重定向会导致redis性能下降。
Redis集群为什么要设计16384个槽呢?
作者官网也做出了回答https://github.com/antirez/redis/issues/2576
1、如果槽位是65536个,那么发送心跳信息的信息头达8k,发送的心跳包过于庞大。我们都知道在集群中各个节点之间都有通信的,可以通过发送心跳看看对方是否还存活着,如果槽数量太大的话,会ping心跳的时候消息头太大了,带来一定的压力,浪费带宽。
2、redis集群实例主节点数量基本不会超过1000个。如果redis实例超过1000个,会给集群带来网络阻塞,很明显不是不会这样做的,对于16384个槽位也是够的,没必要搞到65536个。
3、槽位越小,节点少的情况下,压缩率高。Redis主节点的配置信息中,它所负责的哈希槽是通过一张bitmap的形式来保存的,在传输过程中,会对bitmap进行压缩,但是如果bitmap的填充率slots / N很高的话(N表示节点数),bitmap的压缩率就很低。 如果节点数很少,而哈希槽数量很多的话,bitmap的压缩率就很低。
而16384÷8÷1024=2kb,怎么样,神奇不!
综上所述,作者决定取16384个槽,不多不少,刚刚好!
--注意的要点--
数据倾斜和访问倾斜问题
前期尽量对业务数据进行预测,避免造成部分节点的槽压力过大,数据分布不均匀。
后期可以对槽进行数据迁移,尽量把数据压力分摊开来。
集群节点之间是有通信的,尽量避免带宽的消耗
避免使用一个大的集群,可以分多个集群。
redis集群仍然存在数据不一致问题
笔者相关文章回顾:
为什么需要缓存?—— 互联网架构中缓存介绍
Redis雪崩、击穿、穿透——Redis系列之雪崩、击穿、穿透
Redis分片存储 —— Redis系列之分片存储
Redis高可用——Redis系列之高可用集群
(文章内容不定期补充,若有不恰当的地方,恳请指正。)