上一篇写到了Redis哨兵模式,这种模式虽然能解决一些并发情况下的数据请求压力问题和可用性问题,主从替换以及故障恢复。哨兵模式只是redis架构在历史演变过程中一个的转折点,哨兵这种模式主从模式的数据都是全量同步的,也就是每个从节点上的数据都是主节点上的数据副本,大家想一想有木有问题?
是不是太浪费内存了?或者说可不可以把全量的数据拆解成部分数据,然后分布在不同节点上?那么这种有什么好处吗???
如果数据全部存储在一台机子上,全量同步的时候会时间过长,有时候出现了big key情况更是一个问题,网络一波动,本来传输正在进行中,一半断掉了,会增加同步失败的概率。分片的存储好处,是不是可以减低这些风险的,每个节点我存储一部分数据,实质我们可以交叉存储数据块,a节点存储b节点一个副本,b节点存储a节点一个副本,这样就算如果a当掉了,我们是不是可以去b里面拿取副本继续使用,当然这个我是自己的一些看法,我看hdfs就是这么设计的,当然redis没有这个交叉存储策略,但是笔者认为如果在key下功夫也可以达到的,把key以特定格式的命名的格式二次存储也能达到,只是说浪费一点内存吧了。
存储模型有木有想过为什么redis为什么是k:v这种存储模型,而不是直接设计成关系型数据库那样的??熟悉redis应该都了解一点历史,使用key-value是有历史原因的,早期的时候,redis作者起初是为提高自己网站的读写并发能力而创建redis,而key-value字典结构的常规时间复杂度是O(1),刚好满足作者的性能要求和业务数据结构需求。
redis大部分作用都是缓存数据的,大部分数据都是从db读取的,而且这些数据也不是db中全量的数据,只是一部分而已,没有高度组织化结构化数据,用一部分数据,怎么组成一个完整的关系模型?所以不需要结构化查询语言。
Codis分片方案正是redis作者这些历史原因,本来在设计之初可能也就是单机就可以满足作者自己的需求,但是没想到它写的这个软件被这么多人使用,影响力这么大,然后出现了主从模式,哨兵模式,直到本文要讲的集群模式。
计算机科学领域的任何问题都可以通过增加一个间接的中间层来解决!
Redis的集群也有演变历史,早期有Twemproxy是Twitter团队的开源的解决分片方案,还有Codis的方案值得注意这是一个国人开源的,也就是现在pingcap几位创始人,前豌豆荚团队的,不得不说屌爆了。
他们方案都有一个共同特点,都是通过一个proxy代理层进行的分片处理的,不知道有没有使用过Nginx的,使用过大家应该知道Nginx反向代理一些服务程序,例如Nginx和Tomcat组成一个反向代理负载均衡的集群,codis也是类似这样的方案。
codis代理方案
Codis就是起着一个中间代理的作用,Codis内部维护一个key映射算法,客户端访问codis和直接访问redis没有区别的,因为codis实现了redis的通讯协议,协议这个东西很重要的,双方都商量好的怎么处理数据,做起事来,就很方便,大家懂得。。。。Codis是一个无状态的,所以可以增加多个Codis来提升QPS,同时也可以起着扩容的作用。
Codis怎么工作的?
- 在Codis会把所有的key分成1024个槽,这1024个槽对应着的就是Redis的集群,这个在Codis中是会在内存中维护着这1024个槽与Redis实例的映射的,当然这个是可以配置,就看你的Redis的节点数量有多少,偏多的话,可以设置槽多一些。
- codis会先是把key进行CRC32 后,得到一个32位的数字,然后再hash24后得到一个余数,这个值就是这个key对应着的槽,这槽后面对应着的就是redis的实例。
那么问题来了?我多个codis怎么解决槽位共享?
Codis把这个工作交给了ZooKeeper来管理,Codis节点会监听到ZooKeeper的槽位变化,会及时同步过来,如下图
槽位共享
这种方式有木有什么弊端???
如果是使用mset批量设置值,值是是不是被分散在各个节点上,还有如果rename的时候也是一个问题,不过我认为有一种解决方案在映射的时候给key生成一个唯一的uuid,我rename的只是key,而映射的时候使用uuid计算槽位,当然这些问题在后面的redis官方解决方案里面得到了解决。
官方集群方案说起官方集群解决方案,是redis作者在前面那些代理中间件出现之后发布的,我看他这个设计之后感觉应该借鉴了codis的设计方案,只是把集群槽位信息同步改成p2p然后通过gossip协议来解决了同步的问题,个人感觉都是都是redis在设计之初留下技术负债问题,早期的时候估计作者自己也没有打算写集群方案,被逼的,当然这个是我个人的想法和看法。
相对于 Codis 的不同,它是去中心化的,如图下面我画的图,该集群有4个 Redis 节点组成, 每个节点负责整个集群的一部分数据,每个节点负责的数据多少可能不一样,这4个节点相 互连接组成一个对等的集群,它们之间通过一种gossip协议相互交互集群信息。
示意图
当然我上面画的图太接近一致性哈希环了,但是设计思想是接近的,Redis Cluster 将所有数据划分为 16384 的 slots,比 Codis 的 1024 个槽划分得更为精细,每个节点负责其中一部分槽,槽位的信息存储于每个节点中,它不像 Codis存储在zookeeper中,相信大家都听说过p2p,不清楚自己去查吧。。
那么问题来了!客户端怎么获取不同节点中的key呢?
有木有想过,我客户端连接的是a节点,而我要的数据在b节点上,这个问题怎么解决的呢?redis在通讯协议上下了功夫!它借鉴了http协议的重定向的概念,大家的应该熟悉http中重定向协议吧,如下图:
重定向过程
客户端来连接集群时,它也会得到一份集群的槽位配置信息。这样当客户端要查找某个 key 时,可以直接定位到目标节点,这么一来就解决了数据重定向拿取问题,上面我说到了codis的rename问题,官方解决方案是可以让客户端把每个key设置一个tag然后指定到对应节点槽位上,想深入了解的自己看看一些资料吧,我这里主要讲大问题思路。
这种模式就十全十美了吗?没有其他问题了吗?
从上面我画的图就能看出来,集群内置了16384个slot,并且把所有的物理节点映射到了这16384个slot上,或者说把这些slot均等的分配给了各个节点,但是这种分片模式,肯定是有问题的,例如一个节点挂掉了,那么整个集群中的一部分数据丢失了,数据就不是完整的了,有什么办法解决?
解决办法还是有的每个分片节点备几个从节点,从主节点上读取数据到从节点上,和哨兵模式一样,有问题自己从节点顶上。
小结redis集群采用P2P模式,是完全去中心化的,不存在中心节点或者代理节点,都是历史遇到问题就出解决方案演进过来的。估计作者早期也就是打算设计的时候一个单机就能解决问题了,没想到,万万没想到啊,用的人那么多,然后出现分布式问题哈哈哈哈。。所以出现了各种解决方案,不管什么解决方案,redis数据不能做到保证强一致性,一些已经向客户端确认写成功的操作,会在某些不确定的情况下丢失,而且由于集群方案都是把一大块数据使用分治法打散在各个节点上,支持acid更是一个问题,这估计也是在历史设计的时候留下的问题,但是大部分场景redis都是做缓存服务器用用可以了。
好了本文是redis系统设计系列最后一章了,后面再找找其他NB的系统设计写写,好看不过瘾,那你倒是点个关注啊。。。。
原文链接:https://mp.weixin.qq.com/s/OWd1IEAhu2bQNvo79cAnvw
,