Redis一些基本要知道的东西

Redis 一些知识(基础篇)

基础

1、redis 中的数据格式

String,List,Set,sort set,hash,

2、redis 底层数据结构

动态字符串,双向链表,压缩列表,哈希表,跳表,整型数组

3、redis 基本数据结构与底层数据结构的对应关系是什么?

  • string 动态字符串
  • list 对应压缩列表与双向链表
  • hash 对应压缩列表与哈希表
  • set 对应哈希表与整型数组
  • sortset对应压缩列表与跳表

4、redis 怎么保存数据的?

redis 采用全局哈希表来保存 key value 的,value 存的是 entry 对象(key* + value*)的地址

5、redis是怎么解决hash 冲突的?

redis 采用链表法解决hash冲突,他的next 指针保存在 entry 中(key*,value*,next*)

数据量变多后就会 rehash操作,增加现有的 hash 桶的数量

6、redis 怎么 rehash 的?

redis 有两个全局 hash 表,当一开始插入数据的时候,默认第一个表,当数据量逐渐增多,开始 rehash 的时候就执行以下操作:

  • 给 hash2 分配更大的空间(hash1 * 2)
  • 把 hash1 中的数据重新映射到 hash2 中
  • 释放 hash1 的空间

但 redis 采用渐进式 rehash,在第二步的时候仍然正常处理客户端请求,每处理一个请求,就从 hash1 中的第一个索引位置开始,顺带着将这个索引位置上的所有的 entry 都拷贝到 hash2 中,等待下一个请求。

redis 也有一个后台线程在每 100ms 做一次部分迁移的操作,可以减短 rehash 的实践

a、Redis 什么时候做 rehash?

会根据装载因子判断,装载因子是 enrty 的个数 / hash 桶的数量,分以下两种情况触发 rehash

  • 装载因子≥1,同时,哈希表被允许进行 rehash(没有在 AOF 或 RDB)
  • 装载因子≥5

b、采用渐进式 hash 时,如果实例暂时没有收到新请求,是不是就不做 rehash 了?

在 rehash 被触发后,即使没有收到新请求,Redis 也会定时执行一次 rehash 操作,而且,每次执行时长不会超过 1ms,以免对其他任务造成影响。

c、Redis 中用 fork 创建的子进程有哪些?

fork 创建的是进程

  • 创建 RDB 的后台子进程,同时由它负责在主从同步时传输 RDB 给从库
  • 通过无盘复制方式传输 RDB 的子进程
  • bgrewriteaof 子进程

从 4.0 版本开始,Redis 也开始使用 pthread_create 创建线程

7、压缩列表 长啥样?

类似一个数组,头部有三个字段,zlbtyes列表长度、zltail尾部偏移量、zllen列表中entry个数;尾部还一个 zlend表示列表结束

img

8、整数数组和压缩列表在查找时间复杂度方面并没有很大的优势,那为什么 Redis 还会把它们作为底层数据结构呢?

1、内存利用率,数组和压缩列表都是非常紧凑的数据结构,它比链表占用的内存要更少。

2、数组对CPU高速缓存支持更友好,所以Redis在设计时,集合数据元素较少情况下,默认采用内存紧凑排列的方式存储,同时利用CPU高速缓存不会降低访问速度。当数据元素超过设定阈值后,避免查询时间复杂度太高,转为哈希和跳表数据结构存储,保证查询效率。

9、为什么单线程的 Redis 能那么快?

Redis 是单线程,主要是指 Redis 的网络 IO 和键值对读写是由一个线程来完成的,这也是 Redis 对外提供键值存储服务的主要流程。但 Redis 的其他功能,比如持久化、异步删除、集群数据同步等,其实是由额外的线程执行的。

10、Redis 为什么用单线程?

减少多线程cpu切换带来的额外开销和多线程同时访问共享资源的并发问题

11、单线程 Redis 为什么那么快?

  • 大部分操作在内存进行
  • 采用高效的数据结构
  • 采用 io 多路复用在网络 io 中并发处理大量客户端请求

12、Redis 基本 IO 模型还有哪些潜在的性能瓶颈?

  1. 任意一个请求在server中一旦发生耗时,都会影响整个server的性能

    a、操作bigkey:写入一个bigkey在分配内存时需要消耗更多的时间,同样,删除bigkey释放内存同样会产生耗时;
    b、使用复杂度过高的命令:例如SORT/SUNION/ZUNIONSTORE,或者O(N)命令,但是N很大,例如lrange key 0 -1一次查询全量数据;
    c、大量key集中过期:Redis的过期机制也是在主线程中执行的,大量key集中过期会导致处理一个请求时,耗时都在删除过期key,耗时变长;
    d、淘汰策略:淘汰策略也是在主线程执行的,当内存超过Redis内存上限后,每次写入都需要淘汰一些key,也会造成耗时变长;
    e、AOF刷盘开启always机制:每次写入都需要把这个操作刷到磁盘,写磁盘的速度远比写内存慢,会拖慢Redis的性能;
    f、主从全量同步生成RDB:虽然采用fork子进程生成数据快照,但fork这一瞬间也是会阻塞整个线程的,实例越大,阻塞时间越久;

  2. 并发量非常大时,单线程读写客户端IO数据存在性能瓶颈

第一个采用 lazy-free 对 bigkey 异步释放内存资源,第二个 6.0 采取多线程读取客户端io

持久化

13、redis的持久化?

AOF(Append Only File)日志和 RDB 快照

14、AOF 日志是如何实现的?

AOF 是写后日志,而MySQL是经典的 ahead_log

15、AOF 里记录了什么内容?

以 Redis 收到“set testkey testvalue”命令后记录的日志为例,看看 AOF 日志的内容。其中,“*3”表示当前命令有三个部分,每部分都是由“$+数字”开头,后面紧跟着具体的命令、键或值。这里,“数字”表示这部分中的命令、键或值一共有多少字节。例如,“$3 set”表示这部分有 3 个字节,也就是“set”命令。

img

16、redis 为什么是写后日志

  • 避免出现记录错误命令的情况
  • 不会阻塞当前写操作

17、AOF 有什么风险

  • 如果 aof 还未写,机器宕机,丢失数据
  • 可能会给下一个操作带来阻塞风险,AOF 写是主线程完成的,如果日志写入磁盘的时候磁盘压力大,就会导致写盘很慢,导致后续操作无法执行

控制 aof 的写回策略可以解除风险

18、aof 的写回策略

  • everyone 每次
  • everysec 每秒
  • no 只把日志记入 aof 缓存,由操作系统将缓存内容刷回磁盘

19、什么是 AOF 重写

Redis 根据数据库的现状创建一个新的 AOF 文件,读取数据库中的所有键值对,然后对每一个键值对用一条命令记录它的写入。

20、AOF 重写会阻塞吗?

重写过程是由后台子进程 bgrewriteaof 来完成的,不会阻塞;一个拷贝,两处日志

每次创建 bgrewriteaof 子进程后会把主线程的内存fork一份给它,重写期间的操作会被记录 aof 缓存区中,也会记录到 aof 重写缓冲区中,当重写完成的时候会把缓冲区里的数据也记录到新的 aof 中,然后将新的 aof 替代掉原先的 aof

21、Redis采用fork子进程重写AOF文件时,有什么潜在的阻塞风险?

fork子进程 和 AOF重写过程中父进程产生写入的场景

  • fork子进程,fork这个瞬间一定是会阻塞主线程的,fork采用操作系统提供的写实复制(Copy On Write)机制,就是为了避免一次性拷贝大量内存数据给子进程造成的长时间阻塞问题,但fork子进程需要拷贝进程必要的数据结构,其中有一项就是拷贝内存页表(虚拟内存和物理内存的映射索引表),这个拷贝过程会消耗大量CPU资源,拷贝完成之前整个进程是会阻塞的,阻塞时间取决于整个实例的内存大小,实例越大,内存页表越大,fork阻塞时间越久。拷贝内存页表完成后,子进程与父进程指向相同的内存地址空间,也就是说此时虽然产生了子进程,但是并没有申请与父进程相同的内存大小。那什么时候父子进程才会真正内存分离呢?“写实复制”顾名思义,就是在写发生时,才真正拷贝内存真正的数据,这个过程中,父进程也可能会产生阻塞的风险,就是下面介绍的场景。
  • fork出的子进程指向与父进程相同的内存地址空间,此时子进程就可以执行AOF重写,把内存中的所有数据写入到AOF文件中。但是此时父进程依旧是会有流量写入的,如果父进程操作的是一个已经存在的key,那么这个时候父进程就会真正拷贝这个key对应的内存数据,申请新的内存空间,这样逐渐地,父子进程内存数据开始分离,父子进程逐渐拥有各自独立的内存空间。因为内存分配是以页为单位进行分配的,默认4k,如果父进程此时操作的是一个bigkey,重新申请大块内存耗时会变长,可能会产阻塞风险。另外,如果操作系统开启了内存大页机制(Huge Page,页面大小2M),那么父进程申请内存时阻塞的概率将会大大提高,所以在Redis机器上需要关闭Huge Page机制。Redis每次fork生成RDB或AOF重写完成后,都可以在Redis log中看到父进程重新申请了多大的内存空间。

22、为什么AOF重写不复用AOF本身的日志

  • 父子进程竞争资源
  • 一旦失败污染原先的资源

一个原因是父子进程写同一个文件必然会产生竞争问题,控制竞争就意味着会影响父进程的性能。二是如果AOF重写过程中失败了,那么原本的AOF文件相当于被污染了,无法做恢复使用。所以Redis AOF重写一个新文件,重写失败的话,直接删除这个文件就好了,不会对原先的AOF文件产生影响。等重写完成之后,直接替换旧文件即可。

23、什么时候会触发AOF 重写呢?

有两个配置项在控制AOF重写的触发时机:

  1. auto-aof-rewrite-min-size: 表示运行AOF重写时文件的最小大小,默认为64MB
  2. auto-aof-rewrite-percentage: 这个值的计算方法是:当前AOF文件大小和上一次重写后AOF文件大小的差值,再除以上一次重写后AOF文件大小。也就是当前AOF文件比上一次重写后AOF文件的增量大小,和上一次重写后AOF文件大小的比值。

AOF文件大小同时超出上面这两个配置项时,会触发AOF重写。

24、什么是RDB?

Redis Database,给 redis 的全部数据做一次全量快照

25、RDB 的命令?

save 与 bgsave

  • save 在主进程中进行,阻塞主线程
  • bgsave 创建一个子进程,专门写 RDB,避免阻塞主线程

26、RDB 怎么进行的?

RDB 原理跟 AOF 差不多,同样利用 COW(写时复制);主线程 fork 一个 bgsave 子进程,共享主线程的所有线程,bgsave 进程启动,读取主线程的数据,写入RDB文件,如果一起读的话两个进程互不影响,当有新数据写入或数据修改时,主线程会把新数据或修改后的数据写到一个新的物理内存地址上,并修改主线程自己的页表映射。

子进程复制数据时,也需要加锁,避免主线程同时修改,如果此时,主线程正好有写请求要处理,主线程同样会被阻塞。

27、频繁的 RDB 会怎么样?

  • 频繁将全量数据写入磁盘,会给磁盘带来很大压力,第一份文件还没写完,就要进行第二份,形成恶性循环
  • fork 这个创建过程本身会阻塞主线程,而且主线程的内存越大,阻塞时间越长。

28、2核CPU、4GB内存、500G磁盘,Redis实例占用2GB,写读比例为8:2,此时做RDB持久化有什么风险

产生的风险主要在于 CPU资源 和 内存资源 这2方面:

  • 内存资源风险:Redis fork子进程做RDB持久化,由于写的比例为80%,那么在持久化过程中,“写实复制”会重新分配整个实例80%的内存副本,大约需要重新分配1.6GB内存空间,这样整个系统的内存使用接近饱和,如果此时父进程又有大量新key写入,很快机器内存就会被吃光,如果机器开启了Swap机制,那么Redis会有一部分数据被换到磁盘上,当Redis访问这部分在磁盘上的数据时,性能会急剧下降,已经达不到高性能的标准(可以理解为武功被废)。如果机器没有开启Swap,会直接触发OOM,父子进程会面临被系统kill掉的风险。
  • CPU资源风险:虽然子进程在做RDB持久化,但生成RDB快照过程会消耗大量的CPU资源,虽然Redis处理处理请求是单线程的,但Redis Server还有其他线程在后台工作,例如AOF每秒刷盘、异步关闭文件描述符这些操作。由于机器只有2核CPU,这也就意味着父进程占用了超过一半的CPU资源,此时子进程做RDB持久化,可能会产生CPU竞争,导致的结果就是父进程处理请求延迟增大,子进程生成RDB快照的时间也会变长,整个Redis Server性能下降。
  • 如果绑定了CPU,那么子进程会继承父进程的CPU亲和性属性,子进程必然会与父进程争夺同一个CPU资源,整个Redis Server的性能必然会受到影响

主从

29、主从库间如何进行第一次同步?

replicaof(saveof)

img

  1. 从库给主库发送 psync 命令,表示要进行数据同步,主库根据这个命令的参数来启动复制。psync 命令包含了主库的 runID复制进度 offset 两个参数
    • 第一次因为不知道主库的 runID 所以是 ?
    • offset 是 -1,表示第一次复制
  2. 主库收到 psync 回复 fullresync 并带两个参数:主库 runID 与 主库目前的复制进度 offset,从库收到会记录下来,FULLRESYNC 响应表示第一次复制采用的全量复制,也就是说,主库会把当前所有的数据都复制给从库。
  3. 主库将所有数据同步给从库。从库收到数据后,在本地完成数据加载。这个过程依赖于内存快照生成的 RDB 文件。主库执行 bgsave 命令,生成 RDB 文件,接着将文件发给从库。从库接收到 RDB 文件后,会先清空当前数据库,然后加载 RDB 文件。
  4. 为了保证主从库的数据一致性,主库会在内存中用专门的 replication buffer,记录 RDB 文件生成后收到的所有写操作。
  5. 等主库完成RDB文件发送后,会把 replication buffer 中的数据发送个从库,从库继续执行

30、主从库间网络断了怎么办?

2.8 以前是重新全量复制,2.8 以后是增量复制,利用 repl_backlog_buffer 缓冲区,当主从库断连后,主库会把断连期间收到的写操作命令,写入 replication buffer,同时也会把这些操作命令也写入 repl_backlog_buffer 这个缓冲区。

repl_backlog_buffer 是一个环形缓冲区,主库会记录自己写到的位置,从库则会记录自己已经读到的位置。

  • 刚开始的时候,主库和从库的写读位置在一起,这算是它们的起始位置。随着主库不断接收新的写操作,它在缓冲区中的写位置会逐步偏离起始位置,我们通常用偏移量来衡量这个偏移距离的大小,对主库来说,对应的偏移量就是 master_repl_offset。主库接收的新写操作越多,这个值就会越大。
  • 从库在复制完写操作命令后,它在缓冲区中的读位置也开始逐步偏移刚才的起始位置,此时,从库已复制的偏移量 slave_repl_offset 也在不断增加。正常情况下,这两个偏移量基本相等。
  • 主从库的连接恢复之后,从库首先会给主库发送 psync 命令,并把自己当前的 slave_repl_offset 发给主库,主库会判断自己的 master_repl_offset 和 slave_repl_offset 之间的差距。
  • 一般来说,master_repl_offset 会大于 slave_repl_offset。此时,主库只用把 master_repl_offset 和 slave_repl_offset 之间的命令操作同步给从库就行。

repl_backlog_buffer 是一块专用 buffer,在 Redis 服务器启动后,开始一直接收写操作命令,这是所有从库共享的主库和从库会各自记录自己的复制进度,所以,不同的从库在进行恢复时,会把自己的复制进度(slave_repl_offset)发给主库,主库就可以和它独立同步。

31、repl_backlog_buffer 有什么问题?

可能会导致数据不一致,因为 repl_backlog_buffer 是一个环形缓冲区,所以在缓冲区写满后,主库会继续写入,此时,就会覆盖掉之前写入的操作。如果从库的读取速度比较慢,就有可能导致从库还未读取的操作被主库新写的操作覆盖了,这会导致主从库间的数据不一致。

32、relp_backlog_buffer 的大小怎么得到?

repl_backlog_size =(主库写入命令 - 主从库网络传输命令速度)* 操作大小 * 2

33、relo_backlog_buffer 的注意事项

  1. 一个从库如果和主库断连时间过长,造成它在主库repl_backlog_buffer的slave_repl_offset位置上的数据已经被覆盖掉了,此时从库和主库间将进行全量复制
  2. 每个从库会记录自己的slave_repl_offset,每个从库的复制进度也不一定相同。在和主库重连进行恢复时,从库会通过psync命令把自己记录的slave_repl_offset发给主库,主库会根据从库各自的复制进度,来决定这个从库可以进行增量复制,还是全量复制

34、主从全量同步使用RDB而不使用AOF?

  • RDB 数据量小,AOF放到从库还要一句一句的重放,RDB是二进制文件,加载即可
  • 要使用 aof,就得打开 aof 的配置,但是很多场景不打开
  • 在从库端进行恢复时,用 RDB 的恢复效率要高于用 AOF。

35、replication buffer

Redis和客户端通信也好,和从库通信也好,Redis都需要给分配一个 内存buffer进行数据交互,客户端是一个client,从库也是一个client,我们每个client连上Redis后,Redis都会分配一个client buffer,所有数据交互都是通过这个buffer进行的:Redis先把数据写到这个buffer中,然后再把buffer中的数据发到client socket中再通过网络发送出去,这样就完成了数据交互。所以主从在增量同步时,从库作为一个client,也会分配一个buffer,只不过这个buffer专门用来传播用户的写命令到从库,保证主从数据一致,我们通常把它叫做replication buffer。

36、replication buffer 有什么限制?

如果主从在传播命令时,因为某些原因从库处理得非常慢,那么主库上的这个buffer就会持续增长,消耗大量的内存资源,甚至OOM。所以Redis提供了client-output-buffer-limit参数限制这个buffer的大小,如果超过限制,主库会强制断开这个client的连接,也就是说从库处理慢导致主库内存buffer的积压达到限制后,主库会强制断开从库的连接,此时主从复制会中断,中断后如果从库再次发起复制请求,那么此时可能会导致恶性循环,引发复制风暴

a、replication buffer 与 repl_backlog_buffer 的区别是什么?

replication buffer 是主从库在进行全量复制时,主库上用于和从库连接的客户端的 buffer,而 repl_backlog_buffer 是为了支持从库增量复制,主库上用于持续保存写操作的一块专用 buffer。

37、哨兵的任务?

监控,选主,通知

38、哨兵的监控?

监控redis 主从集群,如果是 从库被哨兵判断为下线,则直接主观下线即可,但是从库要是被判断为下线的话,就得哨兵集群多数主管下线,变成客观下线才被下线

39、主库的选举流程?

一般过程称之为 筛选 + 打分 ,在多个从库中,先按照一定的筛选条件,把不符合条件的从库去掉。然后,我们再按照一定的规则,给剩下的从库逐个打分,将得分最高的从库选为新主库。

40、选主对从库的筛选条件?

对于连接在 down-after-milliseconds 毫秒内,主从节点都没有通过网络联系上,我们就可以认为主从节点断连了。如果发生断连的次数超过了 10 次,就说明这个从库的网络状况不好,不适合作为新主库。

41、选主对从库的打分条件?

  • 从库优先级 如果优先级都一致则进入第二轮打分
  • 从库复制进度 如果复制进度(slave_repl_offset 需要最接近 master_repl_offset,sentinel是直接比较从库的slave_repl_offset,来选择和主库最接近的从库,越大越吃香)都一致则进入第三轮打分
  • 从库 ID 号 从库id越小

42、哨兵在操作主从切换的过程中,客户端能否正常地进行请求操作?

读操作没事,写操作阻塞,如果不想让服务感知到,可以先把数据缓存在消息队列里

43、应用程序不感知服务的中断,还需要哨兵和客户端做些什么?

  • 哨兵提升一个从库为新主库后,哨兵会把新主库的地址写入自己实例的pubsub(switch-master)中。客户端需要订阅这个pubsub,当这个pubsub有数据时,客户端就能感知到主库发生变更
  • 客户端需要访问主从库时,不能直接写死主从库的地址了,而是需要从哨兵集群中获取最新的地址(sentinel get-master-addr-by-name命令),这样当实例异常时,哨兵切换后或者客户端断开重连,都可以从哨兵集群中拿到最新的实例地址。

44、哨兵集群中有实例挂了,怎么办,会影响主库状态判断和选主吗?

存在故障节点时,只要集群中大多数节点状态正常,集群依旧可以对外提供服务。(拜占庭将军问题)

45、哨兵集群多数实例达成共识,判断出主库“客观下线”后,由哪个实例来执行主从切换呢?

每个哨兵设置一个随机超时时间,超时后每个哨兵会请求其他哨兵为自己投票,其他哨兵节点对收到的第一个请求进行投票确认,一轮投票下来后,首先达到多数选票的哨兵节点成为“哨兵领导者”,如果没有达到多数选票的哨兵节点,那么会重新选举,直到能够成功选出“哨兵领导者”。

46、哨兵是如何知道从库的 IP 地址和端口的呢?

哨兵向主库发送 INFO 命令,主库接受到这个命令后,就会把从库列表返回给哨兵,哨兵就可以根据从库列表中的连接信息,和每个从库建立连接,并在这个连接上持续地对从库进行监控

57、哨兵配置的一个坑

要保证所有哨兵实例的配置是一致的,尤其是主观下线的判断值 down-after-milliseconds。

58、quorum

主库客观下线所需哨兵数量

集群

59、redis cluster重新分配槽后访问节点没数据会怎么样?

重定向机制,如果没有数据,那么就返回 MOVED 槽号 新实例地址

60、redis cluster 重新分配槽数据迁移时有操作来会怎么样?

客户端就会收到一条 ASK(ASK 槽位 HOST:IP) 报错信息,这个 ASK 表示,客户端请求的数据在这个实例上,但是正在迁移,客户端需要先发送 asking 命令,让这个实例允许执行客户端接下来的命令,然后客户端再发送 get 请求

61、 moved 与 ask 有什么区别?

ASK 命令并不会更新客户端缓存的哈希槽分配信息,moved 会更新客户端缓存的值,让后续所有命令都发往新实例。

62、为什么 Redis Cluster不采用把key直接映射到实例的方式,而采用哈希槽的方式?

  1. 集群数据量无法估计,如果有映射关系则占用太大的内存空间
  2. Redis Cluster采用无中心化的模式,如果访问的 key 不在这个节点的时候,需要有纠正正确节点的方式,就需要交换路由表,每个节点有整个集群完整的路由关系,如果采用,则交换数据太大
  3. 集群扩容缩容维护成本太大

基于记录key进行哈希后再取模,好处是能把数据打得比较散,不太容易引起数据倾斜,还是为了访问时请求负载能在不同数据分片分布地均衡些,提高访问性能。

ZGC说:有什么能比不给牛吃草,还挤牛奶更快乐的事情呢?
原文地址:https://www.cnblogs.com/lovestart/p/14365144.html