Redis-0-目录

0.背景

本文,参考B站博主轩辕的编程宇宙-趣话Redis系列进行整理

由于最近复习了Redis相关内容,自己整理了笔记,所以刚好结合博主聊到的内容串一下。

字幕,借助: Greasy Fork中的Bilibili CC字幕工具整理

内容段落整合,由GPT完成。

1.内容

1.1 Redis缓存管理机制

你好,我是REDIS。一个叫Antirez的男人把我带到了这个世界上。

说起我的诞生,跟关系数据库MYSQL还挺有渊源的。

在我还没来到这个世界上的时候,MYSQL过得很辛苦。

互联网发展的越来越快,它容纳的数据也越来越多,用户请求也随之暴涨。

而每一个用户请求都变成了对他的一个又一个读写操作,MYSQL是苦不堪言。

尤其是像618这种全民购物狂欢的日子,都是MYSQL受苦受难的日子。

据后来他告诉我说,其实有一大半的用户请求都是独操作,而且经常都是重复查询一个东西,浪费他很多时间去进行磁盘IO。

后来有人就琢磨是不是可以学学CPU,给数据库也加一个缓存呢?

Redis-6-三种缓存读写策略

于是我就诞生了。

出生不久,我就和MYSQL成为了好朋友。

我们俩常常携手出现在后端服务器中。

应用程序们从MYSQL查询到的数据,在我这里登记一下,后面在需要用到的时候就先找我要,我这里没有再找MYSQL。

要为了方便使用,我支持好几种数据结构的存储。

因为我把登记的数据都记录在内存中,不用去执行慢如蜗牛的IO操作,所以找我要比找MYSQL要省去了不少的时间。

可别小瞧这简单的一个改变,我可谓MYSQL减轻了不小的负担。

Redis-1-底层数据结构、为什么快

Redis-2-基本数据类型

随着程序的运行,我缓存的数据越来越多,有相当部分时间我都给他挡住了用户请求。

不过很快我发现事情不妙了,我缓存的数据都是在内存中,不能无节制的这么存下去,我得想个办法,不然吃枣药丸。

不久我想到了一个办法,给缓存内容设置一个超时时间,具体设置多少时间,我不管交给应用程序自己来。

过期时间淘汰策略

我要做的就是把过期了的内容从我里面删除掉,其实腾出空间就行了。

我决定100ms就做一次,一秒钟就是十次。

我清理的时候也不能一口气把所有过期的都给删除掉,我这里内存了大量的数据,要全面扫一遍的话,那不知道要花多久时间,会严重影响我接待新的客户请求的时间紧任务重,我只好随机选择一部分能缓解内存压力就行了。

就这样过了一段日子,我发现有些个键值运气比较好,每次都没有被我的随机算法选中,这个不行。

于是在原来定期删除的基础上又加了一招,那些原来逃脱我随机选择算法的键值,一旦遇到查询请求被我发现已经超期了,那我就绝不客气,立即删除。

这种方式,因为是被动式触发的,不查询就不会发生,所以也叫惰性删除。

内存淘汰策略

可是还是有部分兼职既逃脱了我的随机选择算法,又一直没有被查询,导致他们一直逍遥法外,而与此同时,可以使用的内存空间却越来越少。

而且就算退一步讲,我能够把过期的数据都删除掉,那万一过期时间设置的很长,还没等到我去清理内存,就吃满了,一样要吃枣药丸。

所以我还得想个办法,我苦思良久,终于憋出了个大招,内存淘汰策略。

这一次我要彻底解决问题,我提供了八种策略供应用程序选择,用于我遇到内存不足时该如何决策。

有了上面几套组合拳,我再也不用担心过期数据多了,把空间充满的问题了。

Redis-3-过期时间淘汰策略与内存淘汰策略

我的日子过得还挺舒坦,不过MYSQL大哥就没我这么舒坦了。

有时候遇到些烦人的请求,查询的数据不存在,MYSQL就要白忙活一场。

不仅如此,因为不存在,我也没法缓存啊,导致同样的请求来了,每次都要去让MYSQL白忙活一场,我作为缓存的价值就没得到体现了,这就是人们常说的缓存穿透。

有一次MYSQL那家伙正悠哉悠哉的摸鱼,突然一大堆请求给他怼了过去,给他打了一个措手不及。

一阵忙活之后,MYSQL怒气冲冲的找到了我兄弟,咋回事啊,怎么一下子来得这么猛?

我查看了日志,赶紧解释道:大哥实在不好意思,刚刚有一个热点数据到了过期时间被我删掉了,不巧的是,随后就有对这个数据的大量查询请求来了,我这里已经删了,所以请求都发到你那里来了。

你这蛋子叫啥事,下次注意点啊。MYSQL大哥一脸不高兴的离开了。

这一件小事,我也没怎么放在心上,随后就抛之脑后了。

却没曾想几天之后竟捅了更大的篓子。那一天又出现了大量的网络请求,发到了MYSQL那边。上一次的规模大得多,MYSQL大哥一会功夫就给单趴下了好几次,等了好半天,这一波流量才算过去,MYSQL才缓过神来。

老弟这次又是什么原因?MYSQL大哥累得没了力气,这一次比上一次更不巧,这一次是一大批数据几乎同时过了有效期,然后又发生了很多对这些数据的请求。

所以比起上一次这规模更大了。

MYSQL大哥听了眉头一皱,那你倒是想个办法呀,三天两头折磨我,这谁顶得住啊。

其实我也很无奈,这个时间也不是我设置的,要不我去找应用程序说说,让他把缓存过期时间设置的均匀一些,至少别让大量数据集体失效。

走咱俩一起去。

后来我俩去找应用程序商量了,不仅把兼职的过期时间随机了一下,还设置了热点数据永不过期,这个问题缓解了不少。

我们终于又过上了舒坦的日子。

哦对了,我们还把这两次发生的问题分别取了个名字:缓存击穿和缓存雪崩。

Redis_缓存穿透、雪崩以及击穿

有一天我正在努力工作中,不小心出了错,整个进程都崩溃了。

当我再次启动后,之前缓存的数据全都没了,暴风雨式的请求再一次全都对到了MYSQL大哥那里,被他喷了个狗血淋头。

唉要是我能够记住崩溃前缓存的内容就好了。

1.2 Redis持久化存储机制

Redis-4-持久化

你好,我是REDIS。一个叫安特瑞斯的男人把我带到了这个世界上。

上回说到有一次我不小心崩溃了。等我重新启动后,MYSQL大哥已经急得团团转了。

"你总算起来了,刚才咋回事啊?又一大堆查询请求发到我这来了!"

"唉,别提了,我刚才运行触发了bug,整个进程都崩溃了。"

"你这也太不靠谱了,赶紧起来干活吧!"

"糟了,我之前缓存的数据全都不见了。"

"什么?你的数据没有做持久化存储吗?"

MYSQL大哥一听,脸色都变了。我尴尬地摇了摇头。

"我都是保存在内存中的,所以才那么快啊。"

"那也可以在硬盘上保存一下,遇到这种情况,全部从头再来建立缓存,这不浪费时间吗?"

我点了点头,让我琢磨一下,看看怎么做这个持久化。

没几天,我就拿出了一套方案,RDB。

既然我的数据都在内存中存放着,最简单的就是遍历一遍,把它们全都写入文件中。为了节约空间,我定义了一个二进制的格式,把数据一条一条码在一起,生成了一个RDB文件。不过我的数据量有点大,要是全部备份一次得花不少时间,所以不能太频繁地去做这事,要不然我不用干正事了,光花时间去备份了。还有啊,要是一直没有写入操作,都是读取操作,那我也不用重复备份,浪费时间。思来想去,我决定提供一个配置参数,既可以支持周期性备份,也可以避免做无用功。就像这样,多个条件可以组合使用。后来我又想了一下,这样还是不行,我得创建一个子进程去做这件事,不能浪费我的时间。

有了备份文件,下次我再遇到崩溃退出,甚至服务器断电罢工了,只要我的备份文件还在,我就能在启动的时候读取,快速恢复之前的状态了。我带着这套方案兴冲冲地拿给了MYSQL大哥看了,期待他给我一些鼓励。

"老弟你这个方案有点问题啊。"

没想到他竟给我浇了一盆冷水。

"问题?有什么问题?"

"你看啊,你这个周期性备份周期还是分钟级别的。你可知道咱们这服务每秒钟都要响应多少请求,像你这样不得丢失多少数据。"

MYSQL语重心长地说道。我一下有些气短了。

"可是这个备份一次要遍历全部数据,开销还是挺大的,不是个高频执行啊。"

"谁叫你一次遍历全部数据了,来来来,我给你看个东西。"

MYSQL大哥把我带到了一个文件目录下。

"看,这些是我的二进制日志(binlog),你猜猜看里面都装了些什么?"

MYSQL大哥指着这一堆文件说道。

我看了一眼,全是一堆二进制数据,这哪看得懂,我摇了摇头。

"这里面记录了我对数据执行更改的所有操作,像是insert、update、delete等等动作。等我要进行数据恢复的时候,就可以派上大用场了。"

听他这么一说,我一下来了灵感。告别了MYSQL大哥,回去研究起新的方案来了。你们也知道我也是基于命令式的,每天的工作就是响应业务程序发来的命令请求。回来以后,我决定照葫芦画瓢,学着MYSQL大哥的样子,把我执行的所有写入命令都记录下来,专门写入了一个文件,并给这种持久化方式也取了一个名字AOF。

不过我遇到了RDB方案同样的问题,我该多久写一次文件呢?我肯定不能每执行一条写入命令,就记录到文件中,那会严重拖垮我的性能。我决定准备一个缓冲区,然后把要记录的命令先临时保存在这里,然后再择机写入文件。我把这个临时缓冲区叫做AOF buffer。

这一次我不像之前那么冲动,我决定先试运行一段时间,再去告诉MYSQL大哥,免得又被他戳到软肋。试用了一段时间,各方面都运行良好。不过我发现随着时间的推移,我写的这个AOF备份文件越来越大,不仅非常占硬盘空间,复制移动加载分析都非常的麻烦耗时。我得想个办法把文件给压缩一下,我把这个过程叫做AOF重写。

一开始我打算去分析原来的AOF文件,然后将其中的冗余指令去掉,来给AOF文件瘦瘦身。不过我很快放弃了这个想法,这工作量实在太大了,分析起来也颇为麻烦,浪费很多精力和时间。原来的一条条记录,这种方式实在是太笨了,数据改来改去,有很多中间状态都没用。我何不就把最终的数据状态记录下来就好了,比如这三条指令可以合并成一条。搞定AOF文件重写的思路我是有的,不过这件事看起来还是很耗时间,我决定和RDB方式一样,fork出一个子进程来做这件事情。

谨慎如我,发现这样做之后,子进程在重写期间,我要是修改了数据,就会出现和重写的内容不一致的情况。MYSQL大哥肯定会挑刺,我还得把这个漏洞给补上。于是我在之前的AOF buffer之外,又准备了一个缓冲区,AOF重写缓冲区。从创建重写子进程开始的那一刻起,我把后面来的写入命令也copy一份,写到这个重写缓冲区中。等到子进程重写文件结束之后,我再把这个缓冲区中的命令写入到新的AOF文件中,最后再重命名新的文件,替换掉原来的那个臃肿不堪的大文件。终于大功告成,再三确定我的思路没有问题之后,我带着新的方案再次找到了MYSQL大哥。

我都做到这份上了,这次想必他应该无话可说了吧。MYSQL大哥看了我的方案,露出了满意的笑容,只是问了一个问题:

"这AOF方案这么好了,RDB方案是不是可以不要了呢?"

万万没想到他居然问我这个问题,我竟陷入了沉思。你觉得我该怎么回答好呢?

那天我不小心又挂掉了,MYSQL大哥找到了我。

"你怎么又崩溃了?"

"不好意思,又遇到bug了。不过不用担心,我现在可以快速恢复数据了。"

"那老崩溃也不是事,你只有一个实例,太不可靠了,去找几个帮手吧。"

预知后事如何,请关注我,第一时间了解后续故事哦。

1.3 Redis哨兵与高可用原理

Redis-5-高可用

你好,我是REDIS。一个叫安特瑞斯的男人把我带到了这个世界上。前两个视频给大家讲述了我的缓存管理机制和持久化存储原理,还没看过的朋友可以点这个复习一下哦。

那天REDIS集群里许久未见的大白发来了一条消息。

"兄弟们最近工作咋样?"

"大白,好久不见啊!"

"是啊,大白在哪发财呢?"

"还行吧,就是日常被MYSQL大哥DISS嫌我没办法高可用。我也被DISS了,我就一个服务器咋可能高可用吗?"

"我有一个想法,要不要兄弟们一起干,票大的。"

"你想干啥?"

"咱们组个团,用主从模式。主节点主要负责写数据,从节点主要负责读数据,然后做好数据同步读写分离,提高性能。另外主节点崩溃了,从节点就顶上去,还能实现高可用。这个主意怎么样?"

"大白牛逼!愿意加入的回复一。咱们拉个新的工作群。"

于是大白拉了一个新的群。

"欢迎两位老哥,大家团结协作,一定能干出一番大事。"

"嗯嗯,我就先来当这个主节点了。主节点任务重要,负责数据写入和同步。大家先当一段时间从节点,熟悉熟悉工作节奏。"

"好的好的。以后的日子中,咱们哥仨相互配合,日常工作中最多的就是数据同步了。"

"来,我刚刚把数据写成了一个RDB文件,来同步一下。"

"收到收到,继续。还有一个命令列表。"

"这又是个啥玩意儿?"

"这是我刚刚生成RDB文件期间,又收到的几条数据修改命令。我缓存起来了,现在一并发给你们。你们加载完RDB文件后,记得也要执行一下这些命令,这样我们的数据才能一致。如果主节点有数据写入、删除、修改命令,也会把这些命令挨个通知到从节点。我们把这叫做命令传播。通过这样的方式,我们主节点与从节点之间数据就能保持同步了。"

有一次我不小心掉线了。

"不好意思,各位,我这网络抽风,刚才不小心掉线了。"

"没关系,我把最新的数据写到RDB文件发给你,同步一下就好了。"

"先别急,我只是掉线了一小会儿,没必要把全部数据都发给我吧,太浪费时间了。"

"那咋整呢?我也不知道你那里差了哪些数据。"

"我有个建议,主节点大白,你内部准备一个缓冲区,后面传播命令的时候,除了同步给我们从节点,也往缓冲区写一份。下次我们再掉线了,你就把最近的命令发给我们就好了,就不用全部从头再来了。"

"好主意,在让我想想啊,我怎么知道我缓存的和你们缺失的能不能对得上呢?又怎么知道该发缓存中的哪些给你们呢?"

"要不咱们弄个游标吧,叫做复制偏移量。最开始从零开始,随着数据复制和同步,大家一起更新。后面只需要比较各自的偏移量,就能知道缺失哪些数据了。"

我们用上了新的数据同步策略,效率高了不少。就算偶尔掉线,也能很快把缺失的数据给补上。

就这样过了一段时间,咱们虽然有主从复制,但主节点要是挂了,还是需要程序员们来手动选择从节点升级为主节点来提供写入服务,感觉不够智能啊。

"确实是有这个问题。要不这样,咱们选一个人出来当管理员,不用负责数据的读写,专门来统筹协调,谁要是掉线了,就在从节点里面选择一个出来顶上。我看行,就把这个管理员叫哨兵吧。"

"好主意。不过一个管理员怕是不够,万一管理员挂了,那不完蛋了。"

"得多找几个管理员。可是咱们人手有限,都做管理员,谁干活呀?"

"之前基友群有几个小弟加我了,都想跟我混。我拉进来,让他们去做数据读写,咱们仨都来做管理员吧。"

"嗯嗯嗯,欢迎新朋友,欢迎欢迎。"

"R1、R2、R3,你们三位以后就负责数据读写了。R1你先来当主节点,我和小黑、小R3负责监控各位的工作情况。以后工作上的事情,我们主要和主节点R1来沟通了。辛苦大家了。为了及时获得和更新主从节点的信息,咱们哨兵每隔十秒钟就要用INFO命令去问候一下主节点,主节点会告诉我他有哪些从节点。为了更加及时知道大家是否掉线,咱们哨兵每隔一秒都要用PING命令问候一下群里的各个小伙伴。如果在设置的时间里没有收到回复,我就知道这家伙多半是挂了,就该启动故障转移了。"

"不过这只是我的主观意见,光我一个人说了不算。为了防止误判,我还得去管理员小群里征求一下大家的意见。"

"咋又拉一个群?"

"这是咱们管理员哨兵群,以后有什么事就在这里沟通吧。"

"可以可以。老铁们,我发现R1这个节点好像挂掉了。"

"没有吧,我刚还跟他联系过呢。"

"那到底是掉没掉线?这不行啊,以谁说的为准呢?"

"咱们得定个规矩吧。我有个提议,管理员发现主节点掉线后,这时候判定为主观下线,然后来这个群确认一下。如果有多个管理员都判定为下线,才能认定为客观下线。具体需要几个管理员同时认定,大家可以自己定义。"

"同意。"

"没问题。"

"咦,我发现我好像也联系不上R1了。"

"我看看,还真是。看来R1真的挂了。现在总该启动故障转移了吧。知道怎么做故障转移吗?"

"知道,简单得很。第一步,选个新主节点;第二步,让其他从节点从新的主节点那里同步数据;第三步,把原来旧的主节点改成从节点。加油兄弟们,现在还有R2和R3两个节点,该扶谁上位啊?看来还得定一套选择新的主节点的规则才行。大家可以想想思路。我先提一个,可以给不同节点设定优先级,硬件配置高的优先级越高,挑选的时候参考这个优先级来。"

"那我也提一个,可以优先选择跟主节点断开连接最短的节点,这样它的数据会新一点。还可以参考一下复制偏移量,复制偏移量越大的数据应该越全。"

"可以,那就选择优先级更高,复制偏移量最大的节点。"

"同意。"

"同意。"

经过一番努力,我终于完成了故障转移。以上就是我们的日常工作了。通过咱们几个小伙伴的齐心协力,构成了一个高可用的缓存服务。我高兴地把这个消息告诉了MYSQL大哥,没想到又一次被DISS。

"难道不同节点还可以保存不同的数据?"

想知道后面的故事,记得帮我三连支持一下,下一期告诉你我们Redis集群的工作原理。

1.4 Redis集群是如何工作的

Redis-5-高可用

你好,我是REDIS。一个叫安特瑞斯的男人把我带到了这个世界上。自从上次被拉入群聊之后,我就从一个人单打独斗变成了团队合作。在小伙伴们的共同努力下,不仅有存储复制、可以备份数据,还有哨兵节点负责监控管理。我现在也可以拍拍胸脯说我们是高可用服务了。

但是幸福的日子没过太久,我们就笑不起来了。随着业务的发展,数据量越来越大,我们承受了巨大的压力。虽然有主从复制加哨兵,但只能解决高可用的问题,解决不了数据量大的问题。因为我们看起来人手多,但都是存储的全量数据,所以对于数据容量提升并没有什么帮助。

这一天我找到了大白和小黑,咱们仨合计了一下,一个节点的力量不足,但众人划桨可以开大船啊。我们决定把三个人的内存空间拼起来,每个人负责一部分数据,合体进化成一个大的缓存服务器,变成一个集群。

既然是集群,首要问题当然是团队建设了。我们得想一套办法来组建团队,还要考虑到以后可能会扩容,会有新的伙伴加入我们。我们仨憋了半天,抄袭人家TCP的三次握手,也搞了一个握手协议出来。想要加入集群,得有一个介绍人才行,通过团队里的任何一个成员都行。

比如我吧,只要告诉我IP和端口,我就给他发送一个meet信息,发起握手。对方得回我一个pong信息,同意入伙。最后我再回他一个ping信息,三次握手就完成了。然后我再把这件事告诉团队中其他成员,新的伙伴就算正式成为我们的一份子了。

第二件很重要的事情就是要解决数据存储的公平问题,不能旱的旱死,涝的涝死。我们争论了很久,最后决定学习人家哈希表的方法。我们总共划分了16384个哈希槽位,我们把它叫做槽位(Slot)。程序员可以按照我们的能力大小,给我们各自分配一部分槽位。

比如我们团队,我比较菜,只分到了4000个槽位,小黑老哥最辛苦,要负责7000多个槽位。正所谓能力越大,责任越大,谁叫他内存空间最大呢。数据读写的时候,对键值进行哈希计算,映射到哪个槽,就由谁负责。

为了让大家的信息达成一致,启动的时候,每个人都得把自己负责的槽位信息告诉其他伙伴。我们准备了一个超大的数组来存储每个槽由哪个节点负责。这样一来,遇到数据访问的时候,我们就能快速知道这个数据是由谁来负责了。

对了,这16384个槽位必须都得有人来负责,我们整个集群才算是正常工作,处于上线状态。否则就是下线状态。你想啊,万一哪个键值哈希映射后的槽位没人负责,那该从哪里读,又该写到哪里去呢?

数据分配的问题解决了,我们团队总算可以正式上线工作了。和原来不同的是,数据读写的时候多了一个步骤,得先检查数据是不是由自己负责。如果是自己负责,那就进行处理,不然的话就要返回一个MOVED错误给请求端,同时把槽位、IP和端口告诉他,让他知道该去找谁处理。

不过程序员们是感知不到的,他们都是用封装好的库来操作,才不会亲自写代码来跟我通信呢。经过一段时间的磨合,我们集群小分队配合得越来越默契。

不过光靠咱们仨还是不行,万一哪天有人挂了,整个集群就得下线了。咱们三个每人至少得有一个backup才行。于是我找到了原来的一帮小弟,让他们也加入我们,继续给我们当起了从节点。平时当我们的backup从我们这里复制数据,一旦我们遇到故障,他们就能快速顶上。

有了集群工作加主从复制,我们现在不仅高可用,数据容量也大大提升了。就算以后不够用了,也有办法扩容。我们又过上了舒服的日子。

我是Redis,我的故事到这里就告一段落了。

posted @ 2024-06-09 19:25  羊37  阅读(8)  评论(0编辑  收藏  举报