目录
一、淘汰策略
1、背景
2、淘汰策略
二、持久化
1、背景
2、fork进程写时复制机制
3、Redis持久化方式
1、aof
2、rdb
三、高可用
1、主从复制
2、Redis哨兵模式
3、Redis cluster集群
首先Redis是一个内存数据库,将所有数据存放在内存中,通过对K值进行hash后存储在散列表中。有一个小问题Redis数据库占96G,但为什么最终占满只有48G呢。因为中间有个过程会进行fork子进程,所以会减半。后面会仔细讲解。
首先数据库的数据,我们是可以进行删除和过期操作的。当对数据进行过期淘汰的时候,其实这个数据并未进行删除,只是已经过期,无法使用。但是当这样的数据越来越多的时候,我们不得不进行删除一些。那么如何在这些过期的Key中进行删除呢?
volatile-lru:最长时间没有使用
volatile-lfu:最少次数使用的,使用随机采样
volatile-ttl:最近要过期
volatile-random:随机
上述四种全都是对于过期Key中的策略。那么当数据库内存要满了,我们对于所有的key如何做一个淘汰策略呢?
allkeys-lru:最长时间没有使用
allkeys-lfu:最少次数使用的,使用随机采样
allkeys-random:随机
那么为了保证数据的完整性,我们还可以选择禁止淘汰,数据库满了直接就不让你添加数据。
no-eviction:直接报错
Redis是内存数据库,每次重启数据库,都要进行加载之前的数据到内存中去,我们在这里想一下如何将数据写入到文件中去的。
在上面的图中是写入磁盘文件的过程,在这里有个面试题:
1:在写文件的时候,机器突然断电,那么1,2,3,谁会丢失?答案是1,2会丢失。3已经写入文件了,所以不会丢失。
2:在写文件的时候,进程宕机了突然关闭,这时候谁会丢失?答案是1会丢失。因为2是内层中的,与进程无关系,虽然在1中宕机,无法及时调用close,但是内存中会调用close,这样2中就不会丢失。
下面这张图就是fork进程写时复制的具体过程,下面就具体讲一下过程:
首先就是我们之前所说的明明96G内存却只用了48G,因为进行了fork进程。我们的数据存放在页表中,因此fork进程也会发生页表的复制(页表的复制是为了加快fork的速度),这里页表发生复制之后,父进程的页表就变成只读状态了。这里会把页表映射在物理内存中,虽然页表进行了复制,但是父子进程的页表都映射在同一块物理内存中,这是一开始的过程。
此时有客户端开始发送数据,但是父进程的页表是只读状态,所以会触发写保护中断。触发写保护中断会造成物理页的复制,并且让父进程重新映射到新的物理内存中。
这里只有发生写操作的时候,系统才会去复制物理内存。为了避免物理内存的复制时间过长导致父进程长时间阻塞。
下面是aof存储的方式,是按着顺序进行存储,也就是顺序磁盘IO,约等于内存随机IO。
举个栗子: 下面存储三个数据,下面的数据肯定会把上面的数据给覆盖掉。但是上面的数据还是存在,这样就会造成数据冗余。
set teacher King
set teacher Mark
set teacher Darren
aof有三种写入数据的策略:always,every_sec,no。
always:每一次写入数据都直接写入内存。
every_sec:将写入数据的操作先写入缓冲区,每隔一秒写入到内存中。
no:通过page cache系统调用,让系统自己调用,但是就是用户不知道什么时候系统进行调用。
aof-rewrite:aof重写机制
aof有个弊端就是aof的文件太大,对于数据的恢复速度很慢。因为它在存储数据的时候,是按着Redis的协议进行存储的,而恢复数据的时候,要读取文件解析协议然后在进行执行命令。因此会很慢。
但是重写机制可以解决上述数据冗余的问题,是因为fork进程的时候,会根据内存数据生成aof文件,避免同一个key历史冗余数据。通俗来说就是只能看到当前的状态,直接加载最近的一次。举个例子:一个女生谈了好几个对象,但是她现在是单身,那么一个男生去相亲,只能看到她现在是单身的状态。再重写aof期间,对Redis的写操作回记录到重写缓冲区,当重写aof结束后,附加到aof文件末尾。
rdb的数据恢复速度很快,因为它是将数据直接存储到结构中,不需要进行解析以及执行的命令。但是rdb需要经常fork子进程来保存数据集到硬盘上。因此当数据集比较大的时候,fork的过程是很耗时的,可能会导致在一些毫秒级内不能相应客户端的请求。
因此可以将aof和rdb进行混用,从上面知道,rdb 文件小且加载快但丢失多,aof 文件大且加载慢但丢失少;混合持久化是吸取 rdb 和 aof 两者优点的一种持久化方案;aof-rewrite 的时候实际持久化的内容是 rdb,等持久化后,持久化期间修改的数据以 aof 的形式附加到文件的尾部;
如果我们只有一个数据库的话,当Redis所存在的磁盘损坏的话,那就会造成数据的永久损坏。这样我们可以设置几个从数据库,将数据也存放其他数据库,就可以保证高可用性。
上面的图是一个主数据库,两个从数据库。我们可以考虑一下对于数据库之间数据同步的方式是同步还是异步?是主连接从,还是从连接主?
首先如果数据同步方式是同步的,那么一个客户端发送了一个命令,这个命令要先在主数据库执行,然后在两个从数据库中也执行,如果都成功那么就返回成功,这样耗时是很长的,但是一个不成功,那么就直接失败了,所以同步的方式是不太好的。因此使用异步。
如果是主数据库连接从数据库的话,那么我们要在一开始就要规划整个数据库的数量,而且在启动之后,很难在用主数据库去连接从数据库。但是从数据库连接主数据库的话,就不需要考虑这些。因此使用从连接主。
对于上述的异步方式,以及从连接主,我们还有个小细节就是全量同步和增量同步。
当一个从数据库进行数据同步的时候,我们每次同步肯定只同步我们这个数据库没有的,因此对于从数据库来说可能过程中与主数据库可能不太一样,但是最终结果肯定是一样的,而且其中还有个策略就是从数据库中有一半已经同步了,主数据库才进行返回操作,这样保证数据的可用性。
当我们新增加一个从数据库,它没有任何数据,这个时候我们采取全量同步,要将全部的数据进行同步,之后再进行增量同步操作。
当我们有主数据库和从数据库的时候,我们发现只有一个数据库可以进行连接使用,如果它突然宕机,是没有办法自动连接到另一个数据库的。因此我们使用一些哨兵进行检测,检测谁可以被使用。
哨兵模式是 Redis 可用性的解决方案;它由一个或多个 sentinel 实例构成 sentinel 系统;该系统可 以监视任意多个主库以及这些主库所属的从库;当主库处于下线状态,自动将该主库所属的某个从 库升级为新的主库; 客户端来连接集群时,会首先连接 sentinel,通过 sentinel 来查询主节点的地址,并且通过 subscribe 监听主节点切换,然后再连接主节点进行数据交互。当主节点发生故障时,sentinel 主 动推送新的主库地址。通过这样客户端无须重启即可自动完成节点切换。
但是对于上述哨兵模式,我们还是有一些问题,虽然它可以自动切换可以使用的数据库,但是我们可以连接的数据库仍然只有一个,并没有多个,这样的情况下就会造成当主节点挂掉之后,从节点可能没有收到全部的同步消息,这部分未同步的消息将丢失。如果主从延迟特别大,那么丢失可能会特别多。sentinel 无法保证消息完全不丢失,但是可以通过配置来尽量保证少丢失。同时它的致命缺点是不能进行横向扩展。
Redis cluster 将所有数据划分为 16384(2的14次)个槽位,每个 redis 节点负责其中一部分槽位。 cluster 集群是一种去中心化的集群方式;
如下图,该集群由三个 redis 节点组成,每个节点负责整个集群的一部分数据,每个节点负责的数据多少可能不一样。这三个节点相互连接组成一个对等的集群,它们之间通过一种特殊的二进制协议交互集群信息;
当 redis cluster 的客户端来连接集群时,会得到一份集群的槽位配置信息。这样当客户端要查找某个 key 时,可以直接定位到目标节点。
客户端为了可以直接定位(对 key 通过 crc16 进行 hash 再对2的14次取余)某个具体的 key 所在节 点,需要缓存槽位相关信息,这样才可以准确快速地定位到相应的节点。同时因为可能会存在客户 端与服务器存储槽位的信息不一致的情况,还需要纠正机制来实现槽位信息的校验调整。
另外,redis cluster 的每个节点会将集群的配置信息持久化到配置文件中,这就要求确保配置文件是可写的,而且尽量不要依靠人工修改配置文件; 下图来源于网络; 尽量使三个主节点的数据均衡;分布式一致性hash
Redis cluster集群解决了数据扩容问题,去中心化,还有主节点对等。客户端与服务端缓存槽位信息,以服务端为准,客户节点缓存主要为了避免连接切换,它通过hash随机,数据不均衡,并且还可以增加样本数,也就是增加虚拟节点。并且还可以人为数据迁移,它的读写操作都是走主节点。
流程:首先连接集群中任意一个节点,如果数据不在该节点,将收到连接切换的命令,继而连接到目标节点。故障转移(主节点下线)集群节点间会互相发送消息,交换节点的状态信息。如果某主节点下线,将会被其他主节点标记下线。接着在该下线的主节点的从节点中选择一个数据最新的从节点作为主节点。从节点继承下线主节点的槽位信息,并广播改消息给集群中其他的节点。
缺点:因为主从节点中还是异步同步数据的方式,当发生故障转移的时候仍然会发生数据的丢失。
Redis的讲解完啦!感谢观看!0voice · GitHub