Redis学习笔记《一》

Redis实战与源码

image-20210316081204206

一、问题画像

image-20210316081358165

二、Redis的数据类型 与 底层数据结构

image-20210315193216923

为什么Redis快?

  • 内存数据库高效的数据结构

为了实现从键到值的快速访问,Redis 使用了一个哈希表来保存所有键值对。 key - entry (任意集合的类型,都能这样保存)

因为这个哈希表保存了所有的键值对,所以,我也把它称为全局哈希表

全局hash表存在的问题?

  • hash碰撞、rehash问题

rehash的过程:其实redis默认使用两张全局hash表

  1. 给哈希表 2 分配更大的空间,例如是当前哈希表 1 大小的两倍;

  2. 把哈希表 1 中的数据重新映射并拷贝到哈希表 2 中;

  3. 释放哈希表 1 的空间。

但是是在处理过程中渐进式处理:每次执行的时候,复制一次,避免了耗时的操作,保证了数据的正常访问。

三、为什么redis那么快

我们通常说,Redis 是单线程,主要是指 Redis 的网络 IO和键值对读写是由一个线程来完成的,这也是 Redis 对外提供键值存储服务的主要流程。(但是确能达到每秒数10万的级别的访问能力)--------------多路复用

单线程的好处:没有多线程面临的共享资源的并发控制问题

扩展:IO模型

1、阻塞式IO模型 :这是最传统的IO模型, sockt.read() 没有等到结果会一直等待

2、非阻塞式IO模型:不会阻塞但是,客户端会一直轮询,CPU还是被占用

3、IO复用模型:内核有一个线程会不断去轮询每个连接的状态,观察是否有事件发生

4、信号驱动IO模型:发起的sockt请求,会注册一个信号函数,然后用户线程会继续执行,准备就绪的时候会发一个信息

5、异步IO模型:用户线程发起一个read操作后可以立马去做另外的事情了,当内核返回一个成功的信号表示IO已经完成了,就可以直接去使用数据。是真正的异步IO

信号驱动IO模型 和 IO 复用模型 的第二个阶段 当内核进行数据拷贝的过程会让用户线程阻塞。

在 Redis 只运行单线程的情况下,该机制允许内核中,同时存在多个监听套接字和已连接套接字;内核会一直监听,连接请求 和数据请求,一旦有请求到达就会交给redis。 从而实现一个Redis线程处理多个IO流的效果

Reis 实际的IO模型

image-20210316210814199
  1. 不断轮询,将发生的事件放到队列当中;redis基于事件处理队列 直接处理事件;所以如果队列中有处理慢点操作,就会影响其他

四、AOF日志

扩展:Mysql 的redo log 是重做日志 记录的是物理日志 也就修改之后的数据

binlog 记录的 逻辑日志,就是sql语句

执行命令>写入内存 >写日志(优点:避免记录错命令、不会阻塞当前操作)

写日志,宕机数据丢失问题,配置回写磁盘的策略

image-20210316220948622

4.1重写机制

重写机制具有多变一的功能,旧日志文件中的多条命令可以再重写后的新日志就变成一条新的日志了,具有多变一的功能。

4.2 会不会阻塞主线程呢?

不会,是利用子线程进行复制拷贝,总结来说就是 一个拷贝,两处日志

1、“一个拷贝”就是指,每次执行重写时,主线程 fork 出后台的 bgrewriteaof 子进程,将数据 写成操作

2、拷贝过来后,新的有操作记录到 AOF 缓冲区,同时也会在AOF重做缓冲区,等拷贝后,新的操作也会被记录到重写AOF当中

image-20210317123827790

4.3 是否存在阻塞风险?

1、子线程复制主线程的数据,是复制主线程 的内存表,也就是 (虚拟内存 与物理内存的映射关系),这个过程中可能会发生阻塞

2、重写复制的过程中,如果有一个bigkey进来,重新申请大块内存风险会变大,可能会产生阻塞风险。

配置

auto-aof-rewrite-percentage 100

auto-aof-rewrite-min-size 64mb

// 文件体量超过64mb,且比上次重写后的体量增加了100%时自动触发重写

五、RDB日志

内存数据的某一个状态时刻记录

5.1 两个命令 save、bgsave

  1. Save: 会阻塞主线程
  2. Bgsave: 专门个子进程,专门用于写入 RDB 文件,避免了主线程的阻塞,这也是Redis RDB 文件生成的默认配置。

5.2 如何执行

写时复制技术

1、bgsave 子进程是由主线程 fork 生成的,可以共享主线程的所有内存数据。bgsave 子进程运行后,开始读取主线程的内存数据,并把它们写入 RDB 文件(复制的其实是基于原始数据的。虚拟- 物理 映射表,当主线程数据修改后,对应的物理地址也会修改,但是并不会影响子线程 bgsave 生成 RDB文件)

2、拍快照过程中产生的写操作会被复制一份,bgsave函数会将他读取到 RDB中

image-20210317205723291

5.3 频繁执行会带来额外的开销

1、磁盘的压力 2、fork创建这个过程会阻塞主线程

Redis 4.0 中提出了一个混合使用 AOF 日志和内存快照的功能,拍快照之后,到下次快照的中间,会有AOF增量变化的日志。

六、数据同步:主从库如何实现数据一致?

6.1 Redis的高可用代表什么呢?

答: 包含了两层含义:数据尽量不丢失;服务尽量不中断

问题 方法
数据尽量不丢失 AOF 和 RDB日志
服务尽量不中段 通过主、从库 读写分离的模式

6.2 主从库之间完成第一次数据同步

image-20210321205550895

runID: 表示 主库的实例id

offset:表示开始复制的位置

Real buffer:是主库从库复制过程中,主库接收到的写请求

主-从-从-从的级联分担模式,来分散主库的压力

image-20210321210330802

主从复制完成之后,主-从之间用基于长连接的命令传播,用于后续命令的传播,但是这个过程中存在网络风险点:

网络阻塞、网络断开

6.3 同步时如果网络断开如何处理?

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

已经读到的位置

repl_backlog_size :这个配置参数。如果它配置得过小,在增量复制阶段,可能

会导致从库的复制进度赶不上主库,进而导致从库重新进行全量复制。

**答:**第一次全量同步后,网络断开恢复时采用的是 增量同步。那么如何确定增量同步的点 呢,就是通过

repl_backlog_buffer。从库首先会给主库发送 psync 命令,并把自己当前的slave_repl_offset 发给主库,主库会判断自己的 master_repl_offset 和 slave_repl_offset之间的差距。 然后增量同步时,将这部分数据同步给从库就可以了

6.4 小结

Redis 主从同步:全量复制、基于长连接的命令传播,以及增量复制。

6.5 为什么主从库的复制不使用AOF

RDB是二进制文件,传输和存储都更有优势

恢复数据库时 RDB的效率要高于AOF

七、哨兵机制

主要就是解决主库挂了后,从库切换成主库面领的3个问题

哨兵其实就是一个运行在特殊模式下的 Redis 进程,主从库实例运行的同时,它也在运行。哨兵主要负责的就是三个任务:监控、选主(选择主库)和通知。

  1. 主库真的挂了吗?

  2. 该选择哪个从库作为主库?

  3. 怎么把新主库的相关信息通知给从库和客户端呢?

image-20210325212150293

7.1 主观下线和客观下线

哨兵集群通过投票判断 “客观下线”,通常超过2/n + 1个之后,会将主库标记为 客观下线; 而哨兵判断从库就可以直接标记为客观下线;

7.2 如何选择新的主库

通过 筛选 + 打分

筛选: 正常运行的从库,并且之前的网络状态要良好

打分:从库优先级、从库复制进度以及从库 ID 号

从库优先级: slave-priority 配置

7.2 问题: 在主从切换的过程中,是否还能接受客户端的请求?

主从集群一般是采用读写分离模式,当主库故障后,客户端仍然可以把读请求发送给从库,让从库服务。但是,对于写请求操作,客户端就无法执行了。

八、哨兵挂了,主从库还能切换吗?

8.1 哨兵是如何组合成集群的

sentinel_:hello 不同的哨兵就是通过他来发现,并且相互通信的

哨兵实例之间可以相互发现,要归功于 Redis 提供的 pub/sub 机制,也就是发布 / 订阅机制。通过一个共同订阅消息的频道,就可以共享不同哨兵的 基本信息。ip + 端口

image-20210325230228077

8.2 哨兵是如何知道从库的信息,并通知

是通过 哨兵像主库 发送info命令,他会返回所有slave从库节点的列表

image-20210325230815064

8.3 确定由哪个哨兵来进行实际的主从切换

客观下线的仲裁过程:

1.任何实例只要自己判断主库下线后,就会给其他哨兵发送 is-master-downby-addr。哨兵通过 Y/N 表示赞成或反对;

2.当自己的票获得数量 超过。quorum 设置的值 (就可以设置为客观下线了)

3.表明自己想执行主从切换,并且让哨兵为他进行投票,这个投票的过程称为leader选举

成为leader的条件:** 当拿到半数投票后,就可以充当leader了**

第一,拿到半数以上的赞成票;第二,拿到的票数同时还需要大于等于哨兵配置文件中的 quorum 值

image-20210326084201510

8.4 小结

为了实现主库切换,我们引入了哨兵;为了减少单个哨兵不可用或者误判,引入哨兵集群。

所有哨兵判断客观下线的配置值需要是一直的,否则可能会出现无法达成共识,然后误判主库下线

8.5 问题

如果只有5个哨兵集群,如果3个挂了,剩下3个能否实现主从切换。quorum 值设为 2

答:可以,因为 2个投票可以判断主管下线;但是 要大于半数才可以完成leader的选举

哨兵对主从库进行的在线状态检查等操作,是属于一种时间事件,用一个定时器来完成,一般来说每100ms执行一次这些事件。每个哨兵的定时器执行周期都会加上一个小小的随机时间偏移,目的是让每个哨兵执行上述操作的时间能稍微错开些,也是为了避免它们都同时判定主库下线,同时选举Leader。

九、切片数据集,是加实例 还是加内存

1.数据量太大数

据量越大,fork 操作造成的主线程阻塞的时间越长。所以,在使用 RDB 对 25GB 的数据进行持久化时,数据量较大,后台运行的子进程在 fork 创建时阻塞了主线程,于是就导致Redis 响应变慢了。

9.1 如何存储更多的数据

纵向扩展:升级单个redis实例的资源配置;特点:实施简单,但是主线程fork子线程的时候可能会阻塞;受到硬件的制约

横向扩展: 横向增加当前 Redis 实例的个数;性能提升,但是数据如何分布,客户端如何访问

9.2 如何实现数据分片

采用 Redis Cluster 方案采用哈希槽Hash Slot,一个切片集群中有16384个哈希槽,这些槽类似与数据分区。会有一个映射关系

采用CRC16 算法对Key处理 然后 摩 16384 得到槽点位置

image-20210326212243505

在手动分配哈希槽时,需要把 16384 个槽都分配完,否则Redis 集群无法正常工作

9.3 客户端如何知道实例槽的信息

客户端会缓存,实例的槽位信息,(Redis 实例会把自己的哈希槽信息发给和它相连接的其它实例,来完成哈希槽分配信

息的扩散),所以访问任何实例的时候能够拿到所有实例的节点信息

9.3 实例槽位发生变化时,如何处理

为什么会变化?

在集群中,实例有新增或删除,Redis 需要重新分配哈希槽;

为了负载均衡,Redis 需要把哈希槽在所有实例上重新分布一遍。

Redis Cluster提供了一种 重定向的方案

image-20210326213428218

9.4 槽位正在迁移时

如果正在迁移,那么会返回 ASK命令 第一表明Solt数据还在迁移中;第二把最新实例的地址返回给客户端,可以通过ASKING 命令,

然后再发送操作命令。

image-20210326224139794

1 - 9 问题合集

1.Redis 什么时候做 rehash?

1、装载因子≥1,同时,哈希表被允许进行 rehash;装载因子≥5。

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

Redis会定时执行一次rehash操作,并且时间不会超过1ms

3. 主线程、子进程和后台线程的联系与区别

进程一般是指资源分配单元,例如一个进程拥有自己的堆、栈、虚存空间(页表)、文件描述符等;而线程一般是指 CPU 进行调度和执行的实体。 reids线程 一般指redis的这个进程

4. 写时复制的底层实现机制?

主线程 fork 出 bgsave 子进程后,bgsave 子进程实际是复制了主线程的页表。然后根据复制的页表生成 RDB文件,此时主线程的写操作并不影响生成RDB文件,而是将这个过程中的 写操作复制一份,然后执行到RDB文件当中

5. replication buffer repl_backlog_buffer 的区别

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

参考

《Redis实战与源码》

猜你喜欢

转载自blog.csdn.net/weixin_43732955/article/details/115413431