如果你看过我的一些文章,你应该知道,我一般不会把知识点给你直接列出来;
这样的文章网上有一大把,你大可不必来我这看;
如果你要看我的文章,那么,就请你做好思考的准备,跟着我的思路,去一点一点,把这么一个知识的历程,把它研究透彻,你会受益匪浅。

首先,如果要给 Redis 做集群,那一定是单机存在了某种不足,
要是你的系统,单机已经完全可以满足需求,那么,就没有必要,去折腾这些个集群。

就是:
不为技术而技术

单机单实例的不足

既然要牵扯到技术,那么首先,就必须得涉及到 Redis 的用途,
然后,根据你 redis 的使用目的,才去选择合适的技术:
一是缓存、一是数据库。

假设是缓存,那么就不必特别在意数据的完整性,有些时候,假设进程挂了,也不会使真正数据库的数据丢失;
所以一般做缓存,可以用 RDB,做一个持久化,这样即使挂掉了,也可以自身恢复起来,就带有一定量的数据,而不会是空的,请求全部压到数据库上。

假设是数据库,那么就必须用到 AOF,那么才能保证数据的完整,而且现在版本的 AOF 也内含了 RDB,既保证快,也能保证全。

那么,既然是牵扯到集群,那么一定是单机出现了不足或者问题,那么才会考虑到利用集群,去解决单机的问题。
否则,集群是没有意义的,
因为不但不会提升性能,反而会增加系统的复杂度和维护成本,
这样,就不会增加项目的经济效益。

既然要做集群,那么,你可以先思考一下,但机单实例的 Redis,存在哪些不足?

  1. 我们首先想到的问题就是单点故障
    假设 Redis 进程挂了,在并发量大的情况下,假如是作为缓存,那么这层屏障瞬间消失,所有的请求都会打进数据库,很有可能导致数据库直接挂掉;
    假设 Redis 是直接作为数据库,那么就会导致服务直接不可用
  2. 那么接着想,第二个问题,单实例的容量问题
    假设这个系统的业务,所包含的数据体量非常巨大,那么一个小小的 Redis,它能存的下吗?
    是不是容量有限?
  3. 还有,就是 Redis 的压力问题:
    当所有的服务,都去请求这唯一的 Redis 时,那么是不是 I/O 压力就特别大?
    还有,是不是计算的压力也特别大?

AKF

聊到这些个问题,想要去解决,那么每个问题,都会有自己独立的解决方案。
不过,要是想要同时解决这三个问题,有一个组合拳,就是:
AKF。

什么是 AKF 呢?没有多么高深,实际上就是一个对问题思考解决而划分出的三个维度。

也就是说,想要对一些单结点的应用去进行集群扩展(不仅仅是对于 Redis),就可以从这三个维度,在高层次的空间和视角,去对集群进行划分和扩容。

首先,对于单节点,就会存在单点故障的问题。
虽然说,可以在故障后人工进行恢复,但是,修复的这段时间,尤其是对于一些大型互联网公司,一点故障时间所造成的损失都是巨大难以想象的。

所以,第一个维度就是 x轴:
水平横向扩容。

相当于对一个单点进行横向复制,做该结点的副本。
不为技术而技术:Redis 从单点到集群
这样,想一想,可以解决什么问题?

首先,水平横向复制,也就是做了 Redis 的副本,就可以成为主从,那么,就可以解决单点故障的问题,一个 Redis 挂了,后边的立刻顶上。

第二,由于创建出了副本,也就是新增结点的数据,和主节点的数据是一样的,那么,对于读的请求,就可以分离到后边的从结点,就可以实现读写分离。
不为技术而技术:Redis 从单点到集群
这样一来,对于上边提及的三个问题,这种基于 x 轴水平复制的方式,则可以解决单点故障和一定的压力问题。

但是,光一个维度,虽然可以解决一定的问题,但是:

  • 首先,它没法解决容量有限的问题,因为所有的结点都是对主节点的复制;
  • 其次,假设业务量继续增大,那么,光靠主从复制的话,虽然可以靠从节点分离读请求,
    但是,主节点的写请求依然无法分离,压力仍然会成为瓶颈。

所以,为了分离主节点的压力,其次,为了解决单机容量有限的问题,
则可以进行 y 轴上的扩容,进行业务拆分;
这样,对于不同的业务,客户端可以选择不同的 redis 集群进行读取和写入;

那么就算总体量很大,那只需要对业务进行拆分,就可以把数据拆分得很小很小。
这样,就可以一定程度结局容量问题,以及进一步解决压力问题。
不为技术而技术:Redis 从单点到集群

但是,这样就足够了吗?
对于大部分场景来说,确实已经足够了。

不过,假设你的业务体量仍在不断扩大,导致,即使业务拆分也会有很大的并发,以及极多的数据量。
这时,业务已经拆分得很细了,实在不适合再进行拆分了;
所以,这时候,就只能对集群进行 z 轴的扩容。

也就是将之前的 x、y 轴的整体集群,做再复制。
可以按照优先级、逻辑等等进行再度划分。
这样,就算体量无限增大,那么就可以对 z 轴无限扩容。
不为技术而技术:Redis 从单点到集群
感觉有点看不清,再画一张:
不为技术而技术:Redis 从单点到集群
这就是 AKF 所代表的三个维度的解决思想:

  • 水平主从复制
  • 垂直业务拆分
  • 逻辑优先级等再拆分

这样,通过三个维度的集群,来达到对结点的无限扩展,以解决不足。

数据一致性

虽然说上面的 AKF 看起来很完美,不过这终究是一种高层次的划分,没有涉及到各种下层的实现。

为什么一般不能对系统进行过度设计?
最主要的就是,因为往往一个技术点,为了解决某个问题,必然会带来新的问题。

首先,我们先看主从复制,也就是 AKF 的 x 轴。

假设,某一时刻,一个客户端,对主节点发生了写操作,比如 set k1 v1,
然后,主节点需要把自己的数据,同步到从结点。
不为技术而技术:Redis 从单点到集群
这就立刻涉及到了一个问题,就是数据一致性问题。

最普通的解决办法就是,客户端发送写请求给主节点之后,主节点阻塞不返回,给后边的从结点写入数据,直到两个从结点都写入完成,主节点才给客户端返回写入成功。

这就是强一致性,让所有结点阻塞,直到数据全部一致。
不过这是所有企业都追求的,但是无法实现且成本及其高昂的一种一致性。

假设有这么一种情况,客户端给主节点写了一个数据,很快完成了,然后在同步从节点的时候,由于网络抖动,丢包等现象,导致某个结点写了很久才写成功。
我们都知道,客户端有一个超时的机制,这么久都不返回,那就直接表示失败了,
就是客户端直接认为这次写操作失败了,换言之,写失败,就代表着服务不可用,

也就是,强一致性,会降低可用性。
不为技术而技术:Redis 从单点到集群

然后,就可以思考一个问题,为什么要把一个 redis,复制出多个 redis。
因为一个 redis 会单点故障,对不对?
所以采用多个 redis 要解决单点故障,也就是可用性问题,对不对?
但是,追求强一致性,反而丧失了可用性,那是不是就有违我们集群的初衷!

所以强一致性是有问题的。
所以,我们就必须对强一致性降级。

那么,我们就可以选择,容忍一部分数据丢失。
我们让 redis 异步往从节点更新数据。
假设,客户端往主节点写了一个数据,然后就直接返回,对于客户端来说,就已经表示写入成功了,
然后,redis 主节点,异步地往从结点去写数据。
但是,假设从结点挂了,或者,网络断了,那么数据就会写失败,从节点的数据就丢了。

所以,用这种异步方式,就需要容忍一部分的数据不一致。
不为技术而技术:Redis 从单点到集群

或者,假设我们不希望数据丢失,尽量保证最终一致性,那么还可以对上边的异步模式做一个改进。
于是,可以用一个可靠的消息中间件,各种 mq、kafka 集群等,
写入主节点之后,不直接将数据同步到从节点,而是先存入 kafka 集群,然后,再从 kafka 将数据刷到从节点。
这样,由于 kafka 集群保证可靠,再加上响应的速度足快,
既可以迅速返回响应,又可以保证数据最终一致性。
不为技术而技术:Redis 从单点到集群
现在,我们就可以看出,虽然舍弃了强一致性,可以提高系统的可用性,
但是,不保证强一致,就必然会出现这么一种情况:

假设,客户请求了一个随机的 redis 结点,然后,这个时候,由于数据还没有被同步过来,就会取到还没有同步的数据,也就是取到了不一致的数据。
这就是无法避免的一种情况,所以这就需要我们系统架构时的取舍。

主仍然是单点

对于上面提到的,采用主从的这么一种方式,也就是主可读写,从只读。
还有主备模型,客户端只操作主,而不能访问备。

不论是哪一种,都只有一个主,也就是,主,自己就是一个单点,
那么无论有多少从再后面跟着,从挂了没事,
但是,主挂了,立刻就是单点故障。

所以,就涉及到,要对主做高可用(HA)。

高可用不是指主不会出问题,而是指,在主挂掉了之后,立刻会有一个节点来顶替它,
对外的表现是,从来没有出现过问题。

所以,要解决主的单点问题,那么,就可以在主挂掉之后,人工把一个从、或者一个备,去设置为主,让其它节点去追随它。
但是,很多时候,我们都是希望一切是自动化的,
人毕竟手速有限,当发现主挂了之后,要去亲手操作,肯定会花费一定时间,
此外,由于主挂掉的情况,毕竟只是少数,所以大部分的监守都是无效的,只有偶尔,很少的情况,才会主挂掉,找备顶替。
而且人需要吃饭,上洗手间,睡觉,休息,过成人生活…不可能 24 小时一直盯着机器。
所以人工监控的成本是很高的。

那么,我们就需要由机器,来自动帮我们完成这件事。
其实,机器和人也很相像,人会休息、睡觉、生病,所以不能 24 小时可靠;
监控程序也会故障,所以也是不可靠的,只要是一个程序,它就会有单点故障的问题;
因此,监控主节点,也需要多个结点,组成一个集群。
不为技术而技术:Redis 从单点到集群

但是,用多个节点去监控一个 redis 进程,是否会存在什么问题??

假设,我们现在有三个监控程序,去监控一个 redis 进程;
那么,它们要怎样才能确定,这个 redis 进程到底是好好的呢?还是已经挂掉了。

要是它们三个全部给出它挂了,它才是挂了,
那是不是又回到上面说的,这是强一致性啊。

假设,三个结点,有一个结点,网络延迟,然后给出结论不一样了,那就是监控程序此刻不可用了。
也就是强一致性,再一次降低了可用性。

那么,既然如此,不能采取强一致的策略,
我们选择,让部分结点给出决策,但是,随之,又引出了新的问题,
该听谁的好?

要是有人说,主还活着,
有些人愣是认为,主已经挂了,必须赶快换一个新的主上去,这该怎么办?

先不要急着给出答案,我们来推导,
因为即使你知道答案,但是一步步发现和解决的问题你不知道,那么你的整个思维逻辑,也是不健全的。

现在,假设有 3 个结点,那么,就可以给出两种情况:
一是,一个人说了就算
二是,两个人说了才算
(三个人的话就强一致了,所以就不用考虑)

假设,现在一个人说了算的话,那么比如 A,说主死了,就要把它踢掉,
这时候,很可能其他节点都和主联系正常,主也没有任何问题,
很可能,只是这个节点,自身的连接出了问题。

那么,情况就有可能统计不准确。

此外,由于还有另外两个结点,要是它们也分别给出自己的意见,人人各执己见,那么这个集群就乱套了,
人人都能给出决策,那么就代表人人都无法给出决策。

那么,假设,现在,我们要求,必须两个结点达成一致,才能给出决定。
即使有一个结点给出不同的答案,由于另外两个保持了一致的决策,它们就可以负责,对主的存活情况给出反馈,决定是否要替换主;
而另一个结点,由于没有达到 2个 这么一个势力范围,所以,它没有权力给出意见;

因此,整个集群,给出的意见永远会是一致的。

也就是,我们的集群,给出意见,需要过半!

因为不过半的话,就会有可能不同势力范围的结点,给出不同的结论,
从而导致脑裂,
集群分区。

不过要注意一点,并不是说,脑裂,就一定不好,就一定要避免。
实际上,对于 CAP,有一个 P,叫分区容忍性,
也就是,虽然网络分区了,但是这种情况也可以接受。

比如,对于一个大片的集群,此时对于它们的监控,不一定非要用过半来保证数据结论的一致。
就算,产生了脑裂,
也就是客户访问一部分程序,得出结果是还有 2 台机器可用;另一部分程序,给出反馈 4 台机器可用;
虽然程序给出的结论产生了不一致,但是这不影响具体的使用,
因为,只要有机器存活,那么只需要选出一台机器提供服务就可以了,而不必去纠结到底有多少台还可用。

所以,对于分区的容忍性,是要看承载的数据是什么东西。
比如,上面提到的,对于一个集群有多少可用,就不一定非要数据全部一致;
但是,要是有一个 key,它的 value 是 1,还是 2,这能否允许分区,一般是不会被允许的。

所以,这还得看实际对数据的要求。

所以,对于监控主节点的过半决策,则就是避免了分区导致的结论不一致,
这里是不容许给出不一致的结论的。
不为技术而技术:Redis 从单点到集群
所以,我们只需要让过半给出结论即可。
但是,我再抛出一个问题,结点数量用奇数台,还是偶数台?

你可能以前没有思考过这个问题,
要是,一共有三个结点,那么就必须保证两台结点是好的,那么,整个集群才是可用的,能给出决策的。
也就是允许挂一台,
只要挂了两台,整个集群就不可用。

继续,要是整个集群有 4 台,
那么,过半就是 3 台,
也就是只允许最多挂掉 1 台,要是挂掉了 2 台,就会导致整个集群不可用。

现在,同样是只允许挂掉一台,挂掉两台就不可用了,
现在,要是总体是 3 台,那么就代表挂掉 66.7% 不可用;
要是总体是 4 台,那么就代表挂掉 50% 不可用;
4 台中,挂掉 2 台的概率会比 3 台中挂掉 2 台的概率更加大,
也就是,采用偶数台会增加风险,这时绝对不允许的。

数据分片

之前提到的主从复制,也就是基于 AKF 的 x轴,它可以对主做 HA(高可用),
也可以对从节点进行读写分离,或者从不去读,仅仅做一个备机用。

但是,这没有解决一个问题,就是容量有限的问题。

一般我们的 redis 进程,我们都只会给它分配几个 G 的大小,
即便我们的一台服务器可能有上百 G 的内存,但是我们也不可能真的给我们的 redis 分配一百多 G 的内存,
因为这个持久化所消耗的时间会很大。
所以我们一般会控制我们的 redis 实例,让它只吃几个 G 的内存,这样它就会更轻盈,效率也会更高。

所以,一但数据量很大的时候,我们就需要对 redis 进行纵向集群扩容。

首先,我们可以参考上面提到的 AKF 的 y轴 做纵向业务拆分,
这样把存储的数据,在客户端层面就决定好,哪一部分的数据应该存到哪,
这样,使得每一部分的 redis 集群,只需要负责存储一部分的数据即可。

但是,这有一个问题,就是所有的业务拆分需要写在客户端的代码中,这就会造成客户端代码复杂化,
每个 redis 还是一样,它们是不用关心自己存储了哪一部分的数据。
不为技术而技术:Redis 从单点到集群
业务拆分确实可以解决部分问题,但是,如果数据量很大,光拆分了业务之后,还是每个业务拥有大量数据;
其次,有些情况下,业务是很难拆分的,或者业务已经拆分得足够细小了,无法再进一步拆分了。

这样的情况下,就无法再对业务进行拆分了,那我们就得用其他方式,来从非业务的角度,来拆分数据。

于是就进入 sharding 分片的模式。

首先,最容易想到的就是哈希+取模,大部分程序员对哈希都是比较熟悉的。
这样,我们只需要在客户端的代码中融入哈希取模的方式,来进行存取数据,就可以实现数据的分片。

但是,这种方式最为简单,但是,问题也很显然:
就是取模的数值,是固定的,假设原本有 3 台机器,那么取模之后假设是第一台,
如果公司业务发展,数据量增大,想要增加集群数目,那么如果添加一台,那么就很可能导致,原先取模之后本来存储在第一台上的数据,现在取模之后,结果到第二台了,那么就会使得数据大片失效。

所以,这种哈希取模的方式,会影响分布式系统的扩展性。
不为技术而技术:Redis 从单点到集群
那我们继续探讨,假设,不用哈希取模,那么用随机的策略。
这样的话,客户端自己都不知道自己把数据塞哪了,那要怎么去取?

所以,这个方式的适用场景比较窄。
一般,只有不要求取指定单独数据的话,可以做一个消息队列的集群,
一端随机往 redis 中不断存储数据,另一端随机从 redis 中取数据,这样就不用太在意数据被存到了哪,只要最终被消费掉即可。
不为技术而技术:Redis 从单点到集群
还有一种方式,叫做一致性哈希算法。
听名字好像和第一种很像,都有个哈希在里面,不过它的区别在于,不用对哈希值进行取模。

说白了,哈希算法,也就是映射算法,将一堆各种各样的数据,映射出一堆等宽的数据,
比如 crc16、crc32、fnv、md5 等等。

那么,我们在对这些哈希处理的时候,更倾向于作为一个环,哈希环。
不为技术而技术:Redis 从单点到集群
我在这里不会对各种算法做详细的解释,你更多需要的,是理解这些分布式的各种解决方案。
对于这个哈希环,它需要的不仅仅是对 key 进行哈希,也需要对各个 redis 结点进行哈希运算。

比如现在,有一个大的环,上面分成了 2的32次方 个点,分别代表了 0~2的32次方-1 的所有数值,
然后,我们的所有 redis 结点,我们都可以给它一个唯一的 id 号,
这样,我们的结点,就可以经过一个固定的哈希算法,得到一个值,然后就可以映射到哈希环的某个点上。
不为技术而技术:Redis 从单点到集群
于是我们的 redis 结点,就可以分布到哈希环上。
这时,如果想要存储数据,我们也可以对数据进行哈希,得到一个值,映射到哈希环上。

这样,由于多个结点,将环分割,所以数据存储的时候,在每两个结点之间的那段数据,就归一个节点所有,又一段数据,归另一个结点所有。
不为技术而技术:Redis 从单点到集群
为什么会搞出这么一个东西出来,那么你可以去想,现在这个哈希环上有三台结点,
假设我们现在想要增加一台,且这个节点哈希过后映射到哈希环上,恰巧是这个位置:

那么,我们会发现,现在,按照哈希映射,原本由之前的一个节点存储的数据,部分映射到了新增的结点上,
这样,就会导致,在读取数据时,会读取不到数据,导致数据丢失。
也就是,新增一个节点,不会影响前面一个节点的数据,但会导致后一个节点的部分数据丢失。
不为技术而技术:Redis 从单点到集群
不过,我们看问题也得有两面性。
为什么会出现这个哈希环,其实也是因为它的优点:
就是,在新增结点的时候,的确可以分担其他节点的压力,也就不会造成全局洗牌。
(之前哈希取模的时候,增加结点,会导致全部数据重新分配)

不过,它也不是完美的,也就是,在新增结点之后,会导致后边一个节点的部分无法被命中。

所以,如果 redis 是作为缓存,那么就会导致击穿,请求直接压到后边的 mysql 上。
那么,也可以增加解决方案,就是取数据的时候,如果前一个结点取不到数据,
就额外再到后边那一个结点取一次数据。

虽然可以使得数据又被取到,但是这样也增加了业务逻辑复杂度,也增加了网络的开销,降低了一部分效率。

其实这里还有一个小问题,就是数据在结点的分布不均匀的问题:
假设,有两个结点,但是在哈希环上的映射分布不均匀,
这样的话,大量数据都会压到一个 redis 节点上,而另一个 redis 结点,则只承担少量数据。
这样则会使资源分配不合理,一个 redis 压力大,一个 redis 资源浪费。
不为技术而技术:Redis 从单点到集群
所以,这就引申出虚拟结点这么一个概念:
我们的一个 redis,不仅仅用一个点去表示它,
我们可以通过多个哈希函数,得到多个结果值,将一个节点,分布在环的多个位置上,
这样,就可以使得数据的分配,更为均衡。
不为技术而技术:Redis 从单点到集群

所以,这些个方案由于这些种种问题,存在数据的丢失,所以更倾向于 redis 作为缓存的时候去使用,
而不是作为数据库去使用。

所以,技术的选择取决于业务的需求,也取决于人的判断,没有技术是一成不变的,也没有什么技术是完美无缺的。

成本问题

如果我们继续探究的话,还能发现问题,

首先,无论采取什么实现,客户端都要为此融入逻辑代码来进行处理;

还有就是对于我们的 redis 结点,可能只有那么几个,但是用来连接 redis 的客户端,数量非常多,
而且,并不是一个客户端就是一个连接,我们的客户端往往会有一个连接池,一个客户端就建立了很多很多连接,需要连接的时候就直接从连接处取出一个。

所以,对于 redis,它的连接成本很高。
不为技术而技术:Redis 从单点到集群
有此,我们可以想到,Nginx 是什么,它可以作为反向代理服务器,还有负载均衡。
也就是它自己不处理业务,不存储数据,只负责接收来自客户端的请求,把它代理到后端的服务器上。

这样,redis 服务端的连接压力就减轻了,
我们就只需要关注代理层的性能。

此外,客户端的逻辑也可以迁移,由代理层来负责实现,
这样,客户端就可以很轻盈。

其次,还可以带出来的一个词,就叫无状态。
代理层由于不需要存储数据,不牵扯到任何客户端的状态,所以本身是可以很轻易的动态增删的,
一个代理挂了,可以立刻添加上一个,
代理不够了,可以再往里面追加,所以本身是非常灵活的。
所以,一个东西,只要能做成无状态,就可以很容易的一变多。

由此,我们又可以推导出下面这个模型:
不为技术而技术:Redis 从单点到集群
要是连代理层 Nginx 都扛不住怎么办,那就可以对代理层再做一次集群,其中的每一部分客户端都只连接一个代理;
或者,我们也可以再统一接入一个负载均衡 LVS。

然后,就需要对负载做一个高可用,也就是创建一个备用机,用 keepalived 进行管理。
同时,也可以对后端的代理层监控健康状态。

这样,无论后端的技术有多么复杂,对于客户端来说都是透明的,因此,客户端的 API 就可以极其简单。
不为技术而技术:Redis 从单点到集群

数据预分区

上面探讨了三种分片算法,哈希取模,随机,一致性哈希。
看起来应该是一致性哈希最好对不对。

但是,不知道细心的你有没有发现一个问题,就是好像这种方式,都只能让 redis 在缓存的情况下才可以使用,
也就是不适用于 redis 做数据库的场景。

假设,现在有两台 redis 服务器,但是,不代表,过一年还是两台 redis 服务器。
那么,不论分片逻辑是写在客户端里,还是写在代理层里,
那么,对分片算法,都是一个挑战。

之前探讨的哈希取模,以及一致性哈希,再此时,都会因为其不足而放弃使用。
不为技术而技术:Redis 从单点到集群
那么,我们现在可以想,既然曾经是两个结点,我们要取模的话就是 %2,
那么,我们为什么不能直接在当初,就直接当成 3 个结点来进行取模分配?

所以,假设以后还会有很多次扩容,那么,这次就直接干脆点,一次性取模取 10 个,
这样,现在的两个结点,未来就可以直接增加到 10 个节点。

那这样,我们就只需要在中间加一层 mapping,让其中 0-4 这5份数据存到 1 结点,
然后让 5-9 这部分数据,存到 2 结点。
不为技术而技术:Redis 从单点到集群
这样,比如,当增加第三个结点的时候,
只需要,比如,把 1 号的 3、4 槽位以及数据分给它,把 2 号的 8、9 槽位和数据分给它,
这样,就可以继续保持每个节点,持有固定槽位的数据,而不会产生数据丢失。
不为技术而技术:Redis 从单点到集群
不过,这仍然要涉及数据迁移,可能有人会认为这样和上面所说的三种分片方式并没有本质的区别。

实际上不是的,对于之前的哈希取模,或者还是一致性哈希环,它们在新增结点的时候,都需要对数据进行重哈希,重新定位新的数据应存储在哪个结点。

而在预分配处理之后,只需要对每个 redis 加上一个哈希槽位的概念,
这样,在进行新增结点的时候,不需要对数据进行重哈希,而只是把分配过去的槽位的数据进行迁移。

有人又会问,数据迁移会不会有什么问题?
实际上,这就需要你对 redis 数据保存的知识的了解。

首先,在数据传输的过程中,肯定是先 RDB 把该传的数据全部传过去,
此外,由于服务不能停止中断,所以,可能就会有新的数据增加或修改,因此,就需要 AOF 传输少量变化的数据,
这样,它们就能将数据传输无误。

其实,redis 还有做的很好地一点,就是连代理层都不用。

在取数据的时候,客户端想连谁就连谁,
假设,客户端要 get k1,然后,连接上了 1 号 redis;
然而此时,由于 k1 取模之后的结果是 4,也就是存在了 3 号 redis 中。

那么,客户端在连接 redis 之后,redis 就需要自身去判断,这个 key 是不是应该存在我这个结点中的,
所以,redis 服务端就必须继承一个算法,对 key 进行哈希取模,
然后,也必须知晓,其它 redis 中所拥有的槽位(也就是了解整个 redis 集群中的槽位信息),

这样,1 号 redis 就会告诉客户端,这个 k1 不是存在我这里,你要去 3 号 redis 去取,
然后,客户端就可以再通过一次连接,取到正确的数据。
不为技术而技术:Redis 从单点到集群

但是,数据分治,必然会带来一个问题,就是
聚合操作很难实现,以及事务很难实现,
比如数据要取交集等等…

虽然 redis 作者可以给你实现,但是 redis 作者没有给你实现,
这时为什么?
因为这里边有数据移动的过程。

其实 redis 作者很细腻,它一般会让计算向数据移动,而不是移动数据。

而且 redis 的最大特征就是快,所以 redis 的作者一直在做一个取舍,就是把一部分功能抹杀掉。

所以 redis 自己不去实现这个东西。
不过没关系,我们人可以实现;
所以这个锅 redis 不背,但是要由人来背。

这个锅就叫:hash tag

因为数据一但被分开,就很难再被合并处理;
换言之,如果数据不被分开,那就可以进行事务。

假设有这么两个 key:
一个是:abc-k1,一个是 abc-k2;
那么,我们需要让这两个 key 存到一起,那么就可以只对这个 abc 取模,
而不是对整个 key 取模。

这样,redis 不背这个锅,我不帮你移动数据;
但是,你也不能就把这个锅背上,起码你得把这几个 key 给放到一起去。

这样,就依然可以执行事务,因为需要在一个事务的数据都在一个 redis 里。

综上

其实,你们应该也可以理解 redis 的作者,在涉及 redis 时候,他的一个出发点,
就是追求一个字:
快!

所以,我们很多时候,应该尽可能地,去利用 redis 的特性,来增强我们的系统。

而不是因为技术而技术!

你想,redis 连多线程这块领域都没有去触碰,足以说明,因为简单方而高效。

很多时候,我们过度的设计和包装,都会导致原有的高性能程序,丧失了它原有的优势。

所以更多时候,我们都会让 redis,去尽可能只是做一个缓存,而非数据库使用;
更多时候,不去追求数据的强一致性,而是允许一部分数据丢失;

所以,我们最需要的,是利用 redis 原有的优势,而不是通过技术,去强加一些 redis 本不具备的功能。

相关文章: