REDIS-读书笔记


1 Redis的概念:

Redis是一种key-value类型的内存数据库,可以用于保存string,list,set,sorted set,hash等多种数据结构。由于整个数据库统统加载在内存中进行操作,所以性能也非常出色,每秒可以处理超过10万次读写操作,是已知性能最快的Key-Value DB。此外通过定期异步操作把数据库数据flush到硬盘上进行了持久化的保存。

2 Redis有五种常用的数据类型:

string:字符串类型:一个字符串类型键允许存储的数据的最大容量是512MB。字符串类型是其他4种数据类型的基础,其他数据类型和字符串类型的差别从某种角度来说只是组织字符串的形式不同。例如,列表类型是以列表的形式组织字符串,而集合类型是以集合的形式组织字符串。

list:向列表两端压入和弹出元素

set:在集合中的每个元素都是不同的,且没有顺序。一个集合类型(set)键可以存储至多2次方32-1个(相信这个数字对大家来说已经很熟悉了)字符串。集合类型的常用操作是向集合中加入或删除元素、判断某个元素是否存在等,由于集合类型在Redis内部是使用值为空的散列表(hash table)实现的,所以这些操作的时间复杂度都是0(1)。最方便的是多个集合类型键之间还可以进行并集、交集和差集运算

sorted set:在集合类型的基础上有序集合类型为集合中的每个元素都关联了一个分数,这使得我们不仅可以完成插入、删除和判断元素是否存在等集合类型支持的操作,还能够获得分数最高(或最低)的前N个元素、获得指定分数范围内的元素等与分数有关的操作。虽然集合中每个元素都是不同的,但是它们的分数却可以相同。
 有序集合类型在某些方面和列表类型有些相似。
(1)二者都是有序的。
(2)二者都可以获得某一范围的元素。
但是二者有着很大的区别,这使得它们的应用场景也是不同的。
(1)列表类型是通过链表实现的,获取靠近两端的数据速度极快,而当元素增多后,访问中间数据的速度会较慢,所以它更加适合实现如“新鲜事”或“日志”这样很少访问中间元素的应用。
(2)有序集合类型是使用散列表和跳跃表(Skip list)实现的,所以即使读取位于中间部分的数据速度也很快(时间复杂度是O(log(N)))。
(3)列表中不能简单地调整某个元素的位置,但是有序集合可以(通过更改这个元素的分数)。
(4)有序集合要比列表类型更耗费内存
有序集合类型算得上是 Redis的5种数据类型中最高级的类型。

hash: 散列类型(hash)的键值是一种字典结构,其存储了字段(field)和字段值的映射,但字段值只能是字符串,不支持其他数据类型,一个散列类型键可以包含至多2的32次方-1个字段。

3 Redis过期策略和内存淘汰机制:

Redis 过期数据清理机制:

过期数据清理主要有定时删除、定期删除和惰性删除

1 定时删除:是在给数据设置过期时间时,创建一个定时器,让定时器在数据过期来临时立即执行对键的删除。

优点:可以保证过期键会尽可能被删除,及时释放过期键所占用的内存。

缺点:在过期键比较多时,对于删除自己和当前任务无关的过期键,占用较多的CPU时间,在内存不紧张CPU紧张的情况下,此时会影响服务器的响应时间和吞吐量。

2 惰性删除:是暂时不管数据的过期时间,当获取数据时检查数据是否过期,如果已经过期就删除,未过期则返回数据。

优点:对CPU来说是最友好的,程序只会在取出键时会检查键是否过期,检查的目标只限于当前访问的键,这个策略不会在删除其他无关的过期键上花费任何CPU时间

缺点:因为是在访问时才删除过期键,假如一直不访问,就永远也不会释放内存,容易造成内存的泄露,对内存是最不有好的。

3 定期删除:是每隔一段时间,对数据库进行一次检查,删除过期的数据,至于删除多少数据以及删除几个库的数据由算法决定。定期删除的关键是限制删除操作的执行时长和频率。

如果执行的时长太长或者删除太频繁,cpu时间会过多浪费在删除过期键上面。如果删除操作执行的太少,或者执行时间太短,又容易出现浪费内存。

Redis的数据删除选取了定期删除和惰性删除两种过期数据删除策略。(存在潜在的问题,假如某些key没有设置过期时间,但是又不是经常访问它,这样定期删除和惰性删除就相当于对这些key都失效了。基于此问题,内存达到一定阙值时,就会进行淘汰)

内存淘汰策略是当内存不够用时才会触发的一种机制,是缓存服务层面的操作,而过期策略定义的是具体缓存数据何时失效。我们在使用Redis的时候经常会给redis的key设置一个过期时间如:EXPIRE key 30,过期策略就是指当 Redis 中缓存的 key 过期了,Redis 如何处理。

内存淘汰策略:

当 Redis 节点分配的内存使用到达最大值以后,为了继续提供服务,Redis 会启动内存淘汰策略,在Redis4.0之前主要是以下六种淘汰策略:

noeviction: 如果内存使用达到了maxmemory,client还要继续写入数据,那么就直接报错给客户端。即该策略不淘汰任何数据,当内存不足时,执行缓存新增操作会报错,这种策略下可以保证数据不丢失,它是 Redis 默认的内存淘汰策略。

volatile-random: 随机选择一些设置了TTL的key来删除掉,但仅限于在过期集合的键。

volatile-ttl:回收在过期集合的键,并且优先回收存活时间(TTL)较短的键,使得新添加的数据有空间存放。

volatile-lru: 也是采取LRU算法,但是仅仅针对那些设置了指定存活时间(TTL)的key才会清理掉

volatile-lfu:在设置了过期时间的key中使用LFU算法淘汰key

allkeys-lfu:在所有的key中使用LFU算法淘汰数据

allkeys-lru: 就是我们常说的LRU算法,移除掉最近最少使用的那些keys对应的数据

allkeys-random: 随机选择一些key来删除掉

LRU(Least Recently Used,最近最少使用),根据最近被使用的时间,离当前最远的数据优先被淘汰;

LFU(Least Frequently Used,最不经常使用),在一段时间内,缓存数据被使用次数最少的会被淘汰。

4 RDB文件对过期键的处理:

写RDB文件:当执行SAVE或者BGSAVE命令创建新的RDB文件时,程序会对数据库中的过期键进行检查,已经过期的键不会保存到新的RDB文件中。

载入RDB文件:如果服务器开启了RDB功能,那么服务器是主服务器时,当载入RDB文件时,程序会对保存的键进行检查,未过期的键会被载入到数据库中,过期的就不会,这样就保证了主数据库中保存的是新鲜的数据。如果服务器是从服务器时,不论保存的键是否过期,都会被载入到数据库中的。不过,在进行主从服务器的数据同步时,由于从服务器的数据库会被清空,所以一般来讲,过期键对RDB文件载入从服务器也不会造成影响。

5 RDB 文件的创建与载入:

redis有两个命令用于生成rdb文件:1 SAVE命令 2 BGSAVE命令. 

SAVE命令会阻塞redis服务器进程,直到rdb文件完全创建为止,在服务器阻塞期间,服务器不能处理任何命令请求;

BGSAVE命令会派生出一个子进程,由子进程负责创建RDB文件,服务器父进程继续处理命令请求。基于此,允许redis用户通过设置服务器配置的save选项,让服务器每间隔一段时间自动执行BGSAVE命令。

BGSAVE命令执行时的服务器状态:

BGSAVE命令在执行时,服务器可以处理大部分的客户端发来的命令请求,但是以下场景则有所特殊:

1 在BGSAVE命令执行时,客户端发送的SAVE命令会被服务器拒绝,这么做的目的是,避免父进程(SAVE命令占用)和子进程(BGSAVE命令)同时执行两个rdbsave调用,防止产生竞争条件。

2 BGSAVE命令在执行时时,对于客户端发送的BGSAVE命令也会拒绝,防止两个BGSAVE命令产生竞争条件。

3 BGSAVE命令不能与BGREWREITEAOF命令同时执行:如果当前执行的是BGSAVE命令,那么客户端发送的BGREWREITEAOF命令会被延迟到BGSAVE命令执行完毕之后再执行。相反,如果BGREWREITEAOF正在执行,则客户端发送的BGSAVE命令会被服务器拒绝。

服务器在载入RDB文件期间会一直处于阻塞状态,直到载入工作完成为止。

6 RDB文件结构

RDB文件结构:

REDIS-读书笔记

拥有两个数据库的RDB文件结构

REDIS-读书笔记

数据库文件结构:

REDIS-读书笔记

拥有两个数据库的RDB文件结构:

REDIS-读书笔记

key_value_pairs结构:

带有过期键的:

REDIS-读书笔记

RDB文件中的每个value部分都保存了一个值对象,每个值对象的类型都由与之对应的TYPE记录,根据类型的不同,value部分的结构、长度也会有所不同。

value对象类型:

各种不同类型的value值对象在RDB文件中的保存结构如下所示:

1 字符串对象:

如果TYPE的值为REDIS_RDB_TYPE_STRING,那么value保存的就是一个字符串对象,字符串对象的编码可以是REDIS_ENCODING_INT或者REDIS_ENCODING_RAW。

如果字符串对象的编码为REDIS_ENCODING_INT,那么说明对象中保存的是长度不超过32位的整数。

结构为:

REDIS-读书笔记

比如:

REDIS-读书笔记

如果字符串对象的编码为REDIS_ENCODING_RAW,那么说明对象所保存的是一个字符串值,根据字符串长度的不同,有压缩和不压缩两种方法来保存这个字符串:

❑如果字符串的长度小于等于20字节,那么这个字符串会直接被原样保存。

REDIS-读书笔记

❑如果字符串的长度大于20字节,那么这个字符串会被压缩之后再保存。

REDIS-读书笔记

2 列表对象:

如果TYPE的值为REDIS_RDB_TYPE_LIST,那么value保存的就是一个REDIS_ENCODING_LINKEDLIST编码的列表对象,RDB文件保存这种对象的结构:

REDIS-读书笔记

3 集合对象:

如果TYPE的值为REDIS_RDB_TYPE_SET,那么value保存的就是一个REDIS_ENCODING_HT编码的集合对象,RDB文件保存这种对象的结构如图所示:

REDIS-读书笔记

4 哈希表对象:

如果TYPE的值为REDIS_RDB_TYPE_HASH,那么value保存的就是一个REDIS_ENCODING_HT编码的集合对象,RDB文件保存这种对象的结构如图所示:

REDIS-读书笔记

5 有序集合

如果TYPE的值为REDIS_RDB_TYPE_ZSET,那么value保存的就是一个REDIS_ENCODING_SKIPLIST编码的有序集合对象,RDB文件保存这种对象的结构如图所示:

REDIS-读书笔记

7 AOF持久化:

Redis除了提供RDB持久化功能之外,还提供了AOF(Append Only File)持久化功能。

AOF持久化的实现可以分为:命令追加,文件写入,文件同步三个步骤。

命令追加:AOF持久化功能打开时,服务器在执行完一条写命令后,会以协议格式将被执行的这条写命令追加到服务器的aof_buf缓冲区的末尾。

文件写入与同步:Redis服务器进程其实就是一个时间循环,循环中的文件事件负责接收客户端发来的命令请求,以及向客户端发送命令回复。时间时间负责执行像serverCron函数这样的定时函数。

在执行Redis服务器进程在执行完时间事件后执行flushAppendOnlyFile函数,Redis会根据服务器配置的appendfsync选项的值来决定是否将aof_buf缓冲区的内容是否以及何时写入和保存到AOF文件。

appendfsync flushAppendOnlyFile函数的行为 影响
always 将缓冲区的所有内容写入并同步到AOF文件 出现故障宕机,最多只会丢失一个事件循环中所产生的命令数据。(最安全)
everysec 将aof_buf缓冲区的所有内容写入AOF文件,如果上次同步AOF文件的时间距离现在超过一秒种,那么对AOF文件进行同步,并且该操作是由一个线程专门负责。 出现故障宕机,最多丢失一秒的数据
no 将aof缓冲区的所有内容写入到AOF文件,但并不对AOF文件进行同步,何时同步由操作系统决定。 出现故障宕机,将丢失上次AOF文件同步之后的所有命令数据。

 

8 AOF重写:

AOF文件通过保存服务器执行过的写命令来记录数据库的状态,但是随着时间的推移,AOF文件中的内容会越来越多,文件的体积也越来越大,这样是非常消耗内存的。且利用这样的体积庞大的AOF文件来还原数据所需的时间内也是非常耗时的。

AOF文件重写并不是对AOF文件进行任何的读取、分析或者写入操作,而是通过读取服务器当前的数据库状态来实现的。

为了不阻塞服务器主进程与客户端的交互,AOF重写是在子进程中进行的,这样就不会影响服务器的正常运转。但是子进程在执行AOF重写的这段时间内,服务器仍然可能在修改着数据库的状态,那么就会导致服务器当前的数据库状态和重写后AOF文件所保存的数据库状态不一致。

为了解决这个一致性的问题,Redis服务器设置了AOF重写缓冲区,该缓冲区在服务器创建子进程之后才开始使用。当服务器执行写命令时,会同时被发送给AOF缓冲区和AOF重写缓冲区。

在子进完成AOF重写工作后(异步重写不阻塞父进程),它会向父进程发信号,随后父进程调用一个信号处理函数,执行如下操作:

将AOF重写缓冲区中的内容写到新AOF文件中。此时新AOF文件就和数据库状态保持一致。之后对新的AOF文件进行改名,覆盖掉老的文件(同步更名要阻塞父进程)。由于父进程只会在执行信号函数时,才被阻塞,而后台重写并不会阻塞父进程,因此整个AOF重写过程,将对父进程性能的影响降到了最低。

 

9 主从复制:

redis的复制功能分为同步和命令传播两个操作:

1 同步操作用于将从服务器的数据库状态更新至主服务器当前所处的数据库状态。

2 命令传播操作则用于在主服务器的状态被修改,导致主从服务器的数据库状态不一致时,让主从服务器的数据库重新回到一致状态。

从服务器对主服务器的同步操作需要通过向主服务器发送SYNC命令来完成,如下图是主从服务器在执行SYNC命令期间的通信过程:

REDIS-读书笔记

在实行SYNC命令后主从服务器达到了一致状态,之后主服务在接受到客户端的请求后,可能数据库状态发生了变更,为了让主从服务器再次回到一致性状态,主服务器需要对从服务器执行命令传播。传播的命令是造成主从服务器状态不一致的命令。

旧版本的SYNC命令在解决从服务器断线后恢复时,由于在从服务器恢复后,又重新执行了SYNC命令。而SYNC命令是一个非常耗资源的操作:

1主服务器执行BGSAVE命令来生成RDB文件,这个生成操作会耗费主服务器大量的CPU、内存和磁盘I/O资源

2主服务器需要将生成的RDB文件传送给从服务器,发送操作需要耗费大量的网络资源,并对主服务器响应命令请求的时间产生影响。

3 接受到RDB文件的从服务器需要载入,在载入期间从服务器因为阻塞而没办法处理命令请求。

考虑到在断线期间,主服务器只接受了一小部分的命令。但是如果再次执行比较笨重的SYNC命令就显得很低效。为了弥补该缺陷,REDIS从2.8版本开始使用PSYNC命令代替SYNC命令来执行复制时的同步操作。

 

PSYNC命令具有完整重同步操作和部分重同步操作。完整重同步操作的执行步骤与SYNC的同步基本一样。

部分重同步则用于断线后的复制情况,当从服务器在短线后重新连接主服务器时,如果条件允许(复制积压缓冲区的大小决定),主服务器可以将从服务器断开期间执行的写命令发送给从服务器【从断线执行失败到开始复制这期间的命令 + 缓冲区的命令】,以保证主从服务器的状态一致。而无需从头开始创建RDB文件。

如下主从服务器执行部分重同步的过程如下:

REDIS-读书笔记

 

 

PSYNC执行完整重同步和部分重同步时可能遇到的情况:

REDIS-读书笔记

 

复制的整体过程:

REDIS-读书笔记

心跳检测:在命令传播阶段,从服务器默认会以每秒一次的频率,向主服务器发送命令:

REPLCONF ACK <replication_offset> 其中replication_offset是从服务器当前的复制偏移量。

REPLCONF ACK命令有三个用途:

1 检测主从服务器之间的网络状态,如果主服务器超过一秒钟没有收到从服务器发来的REPLCONF ACK命令,那么主服务器就知道与从服务器之间的网络连接出现了问题。

2 辅助实现min-slaves配置选项:Redis的min-slaves-to-write和min-slaves-max-log两个选项可以防止主服务器在不安全的情况下执行写命令。(如果从服务器的数量少于三个,且三个从服务器的延迟都大于等于10秒时,主服务器将拒绝执行写命令)

3 检测命令丢失,主服务器通过经常比较REPLCONF ACK命令中携带的偏移量,来判断是否在命令传播时命令出现丢失,从而可以及时补发缺失的数据。

 

原创文章,作者:ItWorker,如若转载,请注明出处:https://blog.ytso.com/282704.html

(0)
上一篇 2022年8月28日
下一篇 2022年8月28日

相关推荐

发表回复

登录后才能评论