Redis的诞生:
redis的诞生和mysql脱不了关系,在redis还未出现时,用户的每次请求都是直接访问mysql,渐渐的人们发现,请求大部分都是读操作,而且很多都是重复的数据,磁盘的i/o是很慢的,所以人们就想,能不能学学cpu建立的缓存机制,mysql也搞一个缓存,就这样一个基于内存的数据库诞生了,它就是Redis。
redis是基于内存的,查redis的速度要比查mysql快非常多。当一个请求来的时候,先查询redis中是否存在,若存在返回,否则继续去查数据库,同时将查出的数据放入redis,以便下次查询。
这样,我们就将常用的热点数据放入redis中,就可以分担mysql的很多压力,同时更加的快。
redis内存管理机制:
不过又出现了其他问题,我们都知道内存是昂贵且有限的,随着时间的推移,内存很快的会满的,我们不希望这种事情发生。
redis提供了数据过期的设置,程序员可以自主设置数据过期时间,redis会把过期的数据删除掉。那么redis什么时候删除呢?有两种策略:
定期删除:redis每隔一段时间就会随机删除一些过期的数据,为什么不全部删除呢,主要还是数据量多,全部删除需要耗费时间太长,所以只随机删除一部分能保证正常工作就行。
惰性删除:在定期删除中,有些数据可能每次都能逃脱随机删除算法留了下来,所以额外又有一个策略就是,每当请求查询到redis中已经过期的数据就直接进行删除。
内存淘汰策略:
但是有的数据在定期和懒惰删除下都能逃脱,内存会慢慢的还是变满,这就有了内存淘汰策略,很像我们操作系统中的内存页的置换算法,有如下一些策略:
-
No Eviction (无淘汰): 当内存达到限制时,新写入操作会导致写入操作失败并返回错误。
-
All Keys Random (随机淘汰): Redis 会从所有的键中随机选择一个进行淘汰。
-
Volatitle Least Recently Used (Volatile-LRU): Redis 会从设置了过期时间的键中,选择最近最少使用的键进行淘汰。
-
Volatile Least Frequently Used (Volatile-LFU): Redis 会从设置了过期时间的键中,选择最不经常使用的键进行淘汰。
-
Volatile Random (Volatile-Random): Redis 会从设置了过期时间的键中随机选择一个进行淘汰。
-
Least Recently Used (LRU): Redis 会从所有的键中,选择最近最少使用的键进行淘汰。
-
Least Frequently Used (LFU): Redis 会从所有的键中,选择最不经常使用的键进行淘汰。
-
Random (随机): Redis 会从所有的键中随机选择一个进行淘汰。
欸嘿,定期删除+惰性删除+淘汰策略,终于,redis的内存管理问题解决了。
缓存穿透:
当一个数据在mysql中不存在时,先是查询redis没查到,然后又去查mysql还是没查到,mysql白忙活一场,这就是缓存击穿,如何避免?
布隆过滤器:
布隆过滤器诞生了
布隆过滤器(Bloom Filter)是一种用于快速检查一个元素是否可能存在于一个集合中的数据结构,它通过使用位数组和哈希函数来实现。虽然它可以告诉你一个元素“可能存在”或“肯定不存在”,但它无法告诉你一个元素“肯定存在”。
他的原理就是通过多个哈希函数,将一个元素映射到位数组的多个位置上,将0置为1。在判断一个元素是否存在时,就会用多个相同哈希函数映射,然后判断映射的位置上是否都为1,若都为1说明可能存在,因为可能有其他的一些元素映射会将这些位置正好都置为了1,所以可能会发生很小概率的误判,当然如果不都为1,那么一定是不存在的。
这就是为什么说它可以告诉你一个元素“可能存在”或“肯定不存在”,但它无法告诉你一个元素“肯定存在”。 图片来源于网络。
这样一个请求的数据,就可以很快的判断是否存在于mysql中,过滤掉没必要的请求。
缓存击穿:
因为某个热点数据过期,正好有这个热点数据的大量请求,这些请求就都去查mysql,mysql压力大了。
缓存雪崩:
缓存击穿的加强版,这次不是某个热点数据了,是大量的热点数据都在同一时刻过期。解决方法:
1、设置热点数据过期时间均匀一点,别都一起过期了。
2、设置某些热点数据永不过期。
呕吼,这样缓存穿透+缓存击穿+缓存雪崩的问题就解决。
redis持久化:
redis为什么要持久化呢?比如某一次redis崩溃,当重启的时候如何恢复里面原有的数据呢,这就是持久化的作用。
如何持久化?
RDB:
redis去fork一个子进程将数据遍历一遍,然后二进制(压缩空间)写入文件中,相当于一个备份(快照),不过遍历一遍是要花费很多时间和性能的,而且很多请求都是读操作,原数据都没改动,没必要再去备份。redis提供了可选的配置参数:
格式为save <seconds> <changes>
,其中<seconds>
表示多少秒内至少发生了多少次修改操作(默认配置是save 900 1
,表示如果在900秒内至少发生了1次修改,则执行快照操作)。可以根据需求调整这个参数,例如增加保存的频率以提高数据的持久性,或者减少保存的频率以减少磁盘IO开销。
不过,每次备份一次也太慢了,要想想请求都是每秒那么多次,备份又慢又不能太频繁,如何解决呢?
AOF:
将请求对于数据的操作记录再aof文件在中,同时为了保证性能,在redis中再建立一个aof_buf缓存区零时存放操作记录,等空闲再写入aof文件中。
随着时间的推移,又出问题了,aof文件慢慢的变得巨大无比。这就有了aof重写功能,会将冗余的指令去掉,同时去掉一些不影响结果的操作,比如数据修改a->b->c,其实只记录最后修改成c的操作就够了。
在重写的过程中,可能还会有新的请求进入,导致最终结果不一致的情况,这就引入了另一个缓存区,aof_rewrite_buf,重写过程中用来存储新AOF内容的缓冲区,等重写完毕后,新aof文件再检测aof_rewrite_buf进行补充到新aof文件中,最后重命名新aof文件替换掉原有的文件。
当然重写也是fork一个子进程做的,不会影响redis的工作。
高可用的redis:
哎,可以这样还是不行啊,毕竟只有一个redis,虽然崩溃可以恢复,但是这还远远不够啊,如何实现高可用的redis呢?
主-从模式:
一个redis当主节点(Master),主要负责写数据,从节点(Slave)负责读数据,这样读写分离,当主节点挂掉时,从节点还可以变主机点继续工作。多个redis一起工作,肯定是比一个强的。
所以现在做好数据同步就好了。
主从复制:
主节点将自己的RDB文件发送给从节点,以及传输期间将新的删除和修改命令发送,保证主从的数据一致。
当一个从节点短暂掉线,为了数据一致,主节点会将全部数据发给掉线节点,但是这是没必要的,只要同步一下刚刚缺失的文件即可。但是如何知道丢失的那些数据,从哪里开始发送呢?
这就产生了一个复制偏移量的东西。
表示了主节点和从节点在复制过程中的同步位置,即已复制的字节数量。
主节点会持续地将写入操作记录到复制缓冲区,并将这些写入操作的信息发送给从节点。从节点在接收到主节点发送的复制命令后,会将这些写入操作逐个应用到自己的数据副本中,同时更新自己的复制偏移量。
这样根据偏移量,就可以知道从节点差了那些数据,只传缺失的数据即可。
哨兵监控:
虽然有了主从这个机制,但是每次redis挂了,还需要程序员自己去将从节点升级为主节点,还是不够智能,在这个需求下,哨兵(sentinel)机制就诞生了。
一个哨兵肯定是不够的,万一哨兵挂了怎么办,所以一般有多个哨兵。
这个图片,让我想起了一位故人hhh。
心跳检测:每个哨兵每隔一段时间向节点发送心跳检测,如果在一定的时间内未收到节点的响应,哨兵将认为该节点已经下线。
不过一个哨兵认定某个主节点下线可能是误判(主观下限),需要超过一定数量的哨兵都认为这个节点下线了,那么就可以判断其真的下线了(客观下限)。这就是多数投票机制。
多数投票机制:Redis 哨兵是分布式系统,通常由多个哨兵节点组成。当一个哨兵节点判断某个 Redis 节点下线时,它会向其他哨兵节点发出通知,并请求它们的投票。如果大多数哨兵节点都同意某个节点已经下线,那么就认为该节点已经下线。
欸嘿,现在判断某个主节点真的下线了,那么就可以进行故障转移了。
故障转移:
-
选举新的主节点:当主节点不可用时,哨兵会从当前的从节点中选举出一个新的主节点。哨兵会考虑到每个从节点的优先级、复制偏移量、复制延迟等因素,选择出一个适合的从节点作为新的主节点。
-
更新配置:一旦新的主节点被选举出来,哨兵会更新所有其他节点的配置,以使它们知道新的主节点的位置。
-
客户端重定向:在完成故障转移后,哨兵会通知客户端发生了主节点切换,并提供新的主节点的地址。客户端收到通知后,会重新连接到新的主节点上,从而完成故障转移过程。
Redis集群:
随着发展,虽然redis实现了高可用,但是还是解决不了数据量大的问题,虽然redis数量多,但是每一个redis都是存储的一样的全部数据,就这样,redis集群诞生了(其实形容为分布式更为准确),将数据分布到不同的redis上,每个redis上的数据不同。
redis加入集群通过类似TCP三次握手加入,成为集群一份子。
数据划分:
Redis集群将数据划分为16384个槽(Slot),每个槽对应一个唯一的哈希值。每个节点负责管理其中一部分槽,以存储和处理对应的数据。槽的划分和迁移由集群自动管理。
程序员可以根据redis能力大小,分配槽位。
槽位用0,1来记录,压缩空间,这样redis之间传输自己管理的槽位信息就更加方便。
不过这样还是不够方便,根据空间换时间的原则,用一个大小16384大小的数组去记录每个槽位由谁去管理,对应的redis的ip和端口号,这样每次数据来的时候,之间根据数组去寻找对应的redis。
每个槽位都需要有redis去管理,否则为下线状态,
当然,每个单独的集群节点也由主从构成,防止某个redis突然下线,slot槽无人管理。
完结:
现在,redis即使实现了内存管理,又实现了持久化,并且高可用、又是集群很稳定。成为了完全体状态。