不为技术而技术:Redis 从单点到集群

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

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

就是:
不为技术而技术

单机单实例的不足

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

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

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

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

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

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

AKF

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

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

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

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

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

相当于对一个单点进行横向复制,做该结点的副本。
x
这样,想一想,可以解决什么问题?

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

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

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

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

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

那么就算总体量很大,那只需要对业务进行拆分,就可以把数据拆分得很小很小。
这样,就可以一定程度结局容量问题,以及进一步解决压力问题。
y业务拆分

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

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

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

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

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

数据一致性

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

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

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

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

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

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

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

也就是,强一致性,会降低可用性。
同步阻塞

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

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

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

所以,用这种异步方式,就需要容忍一部分的数据不一致。
异步

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

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

主仍然是单点

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

继续,要是整个集群有 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 还是一样,它们是不用关心自己存储了哪一部分的数据。
客户端拆分业务
业务拆分确实可以解决部分问题,但是,如果数据量很大,光拆分了业务之后,还是每个业务拥有大量数据;
其次,有些情况下,业务是很难拆分的,或者业务已经拆分得足够细小了,无法再进一步拆分了。

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

成本问题

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

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

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

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

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

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

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

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

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

这样,无论后端的技术有多么复杂,对于客户端来说都是透明的,因此,客户端的 API 就可以极其简单。
keepalived

数据预分区

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

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

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

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

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

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

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

而在预分配处理之后,只需要对每个 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 不背,但是要由人来背。

这个锅就叫:hash tag

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

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

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

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

综上

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

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

而不是因为技术而技术!

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

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

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

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

  • 18
    点赞
  • 33
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值