细说Redis-p1(2022.03.13)

Posted Mr. Dreamer Z

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了细说Redis-p1(2022.03.13)相关的知识,希望对你有一定的参考价值。

Redis在我们工作中可以说用的非常多。大多用来会话存放、消息队列(支付)、活动排行榜或计数等。但我们其实很多时候是只管去用,却也没有对它进行更深层次的探索。这段时间正好也重温了一下Redis,写下来的话感觉能够让自己更深入的去理解Redis。

目录

1.Redis是什么

Redis是什么,这应该是一个老生常谈的问题了。几乎用过的人都知道,或者说面试的时候也会问到。Redis就是一个NoSQL数据库。
其实想知道Redis是什么,我们可以通过官方文档来看看

Redis官网解释了Redis是什么,简单来说,它就是一款内存高速缓存数据库。画重点,基于内存,支持多种类型数据结构。

2.为什么Redis这么快

我想这个问题,大家在面试的时候应该都遇到过。

2.1 基于内存实现



数据库的工作模式按存储方式可分为:硬盘数据库和内存数据库。Redis 将数据储存在内存里面,读写数据的时候都不会受到硬盘 I/O 速度的限制,所以速度极快。

2.2 采用单线程

Redis进行命令的读写都是基于单线程,避免了不必的上下文切换。

为什么Redis要使用单线程(注意,我没有说Redis完全是单线程的)?
在此之前,我想问一个问题。就是在I/O操作都时候,例如磁盘I/O,网络I/O等!为什么一般是在I/O操作都时候,要用多线程呢?
因为I/O操作一般可以分为两个阶段:即等待I/O准备就绪和真正操作I/O资源。
以磁盘操作为例,磁盘的结构如下:

“在磁盘上数据是分磁道、分簇存储的,而数据往往并不是连续排列在同一磁道上,所以磁头在读取数据时往往需要在磁道之间反复移动,因此这里就有一个寻道耗时!另外,盘面旋转将请求数据所在扇区移至读写头下方也是需要时间,这里还存在一个旋转耗时!”
那么,在这一时间段(即"I/O等待")内,线程是在“阻塞”着等待磁盘,此时操作系统可以将那个空闲的CPU核心用于服务其他线程。因此在I/O操作的情况下,使用多线程,效率会更高!

回到刚刚的问题,我想现在大家应该知道答案了吧!
Redis读写数据基于内存,是不涉及到IO操作的。如果不涉及到的话,那么单线程执行肯定要比多线程更快。
官方FAQ表示,因为Redis是基于内存的操作,CPU不是Redis的瓶颈,Redis的瓶颈最有可能是机器内存的大小或者网络带宽。既然单线程容易实现,而且CPU不会成为瓶颈,那就顺理成章地采用单线程的方案了(毕竟采用多线程会有很多麻烦!)。

2.3 多路IO复用

用一个线程接收客户的连接请求。
多路I/O复用模型是利用 select、poll、epoll 可以同时监察多个流的 I/O 事件的能力,在空闲的时候,会把当前线程阻塞掉,当有一个或多个流有 I/O 事件时,就从阻塞态中唤醒,于是程序就会轮询一遍所有的流(epoll 是只轮询那些真正发出了事件的流),并且只依次顺序的处理就绪的流,这种做法就避免了大量的无用操作。

**这里“多路”指的是多个网络连接,“复用”指的是复用同一个线程。**采用多路 I/O 复用技术可以让单个线程高效的处理多个连接请求(尽量减少网络 IO 的时间消耗),且 Redis 在内存中操作数据的速度非常快,也就是说内存内的操作不会成为影响Redis性能的瓶颈,主要由以上几点造就了 Redis 具有很高的吞吐量

2.Redis的持久化方式

2.1 RDB(快照)

将内存数据快照保存到名字为dump.rdb的二进制文件中(保存的是二进制压缩文件)。

# save 60 1000 //60秒内至少有1000个键的改动,那么保存一次

手动操作-将这一时刻的数据保存到文件中:
save:同步线程执行。
bgsave:异步线程执行。
bgsave提供的是写时复制(copy-on-write,cow),在生产快照的同时,依然可以正常的处理些命令。bgsave子线程是由主线程fork生成,可以共享主线程的内存数据,且互不影响。如果此时主线程正在对这些数据进行写操作,那么这块数据就会被复制一份,生产该数据的副本。然后bgsave把这个副本数据写入RDB文件。而在这个过程中,主线程依然可以正常处理写命令。

save和bgSave对比:

2.2 AOF(append-only file)

AOF是将每一条写指令记录到appendonly.aof文件中(先写入os cache,每隔一段时间fsync到磁盘)
例如:命令 “set zhuge 666”,aof文件会记录如下数据
*3
$3
set
$5
zhuge
$3
666

这是一种resp协议格式的数据,*号代表有多少个参数,$代表有几个字符。

# appendonly yes 开启AOF

当我们开启之后,可以在我们的文件夹中找到appnendonly.aof文件

进去看看

可以看到,我们刚刚写的命令。已经成功的写入文件中了。

aof还可以手动重写,bgrewriteaof。也是子进程去操作。
从现在开始, 每当 Redis 执行一个改变数据的命令时(比如 SET), 这个命令就会被追加到 AOF 文 件的末尾。 这样的话, 当 Redis 重新启动时, 程序就可以通过重新执行 AOF 文件中的命令来达到重建数据集的目的。你可以配置 Redis 多久才将数据 fsync 到磁盘一次。

# 每次有新命令追加到 AOF 文件时就执行一次 fsync ,非常慢,也非常安全
appendfsync always

# 每秒 fsync 一次,足够快,并且在故障时只会丢失 1 秒钟的数据
appendfsync everysec

# 从不 fsync ,将数据交给操作系统来处理。更快,也更不安全的选择。
appendfsync no

推荐(并且也是默认)的措施为每秒 fsync 一次, 这种 fsync 策略可以兼顾速度和安全性。

如下两个配置可以控制AOF自动重写频率 :

# auto‐aof‐rewrite‐min‐size 64mb //aof文件至少要达到64M才会自动重写,文件太小恢复速度本来就 很快,重写的意义不大 
 
# auto‐aof‐rewrite‐percentage 100 //aof文件自上一次重写后文件大小增长了100%则再次触发重写

2.3 混合持久化

Redis4.0带来了混合持久化
简单来说,就是两个都一起使用。充分利用这两种方式的优点进行集成

#aof-use-rdb-preamble yes

如果开启了混合持久化,将这一刻之前的内存做rdb快照处理,然后将RDB快照内容和增量的AOF修改内存数据的命令都放到AOF文件中。重写时,会覆盖原有的AOF文件。
因此,在Redis重启的时候,可以先加载RDB的内容,然后再重放增量的AOF日志,这样来进行效率的提升。比如重启之后会发现之前的数据同步变成rdb,然后后续新增的操作是aof格式
这样一来,既提高了效率,又尽可能的保证的数据的安全性(防止同步的时候丢失)

混合持久化内容格式如下

咱们开启之后来操作一下,

接着来看看持久化文件到底是咋样的

3.Redis主从架构


主从架构主要为了读写分离,以及保证Redis的可用性。
1.Redis主从工作的原理:
为一个master配置了slave,不管这个slave是否第一次连接上master,它都会发送一个PSYNC命令给master请求复制数据。
master接收到命令之后,会在后台进行数据持久化通过bgsave生成最新的rdb快照文件,持久化期间,master会继续接收客户端的请求,它会把这些可能修改数据的请求缓存在缓存中。当持久化进行完成以后,master会把这份rdb文件数据集发送给slave,slave会把接收到的数据进行持久化生产rdb,然后加载到内存中。然后,master再将之前缓存在内存中的命令发送给slave。
当master和slave之间的链接由于某些原因断开时,slave能够自动重链接master,如果master收到了多个slave并发链接请求,它只会进行一次持久化,而不是链接一次,持久化一次。然后将这一份持久化的数据发送給多个并发连接的slave。

主从复制(全量复制)流程图

数据部分复制
当master和slave断开重连后,一般都会对整份数据进行复制。但从redis2.8版本开始,redis改用可以支持部分数据复制的命令PSYNC去master同步数据,slave与master能够在网络连接断开重连后只进行部分 数据复制(断点续传)。 master会在其内存中创建一个复制数据用的缓存队列,缓存最近一段时间的数据,master和它所有的 slave都维护了复制的数据下标offset和master的进程id,因此,当网络连接断开后,slave会请求master 继续进行未完成的复制,从所记录的数据下标开始。如果master进程id变化了,或者从节点数据下标 offset太旧,已经不在master的缓存队列里了,那么将会进行一次全量数据的复制。

如果有很多从节点,为了缓解主从复制风暴(多个从节点同时复制主节点导致主节点压力过大),可以做如 下架构,让部分从节点与从节点(与主节点同步)同步数据

4.Redis哨兵高可用


sentinel哨兵是特殊的redis服务,不提供读写服务,主要用来监控redis实例节点。 哨兵架构下client端第一次从哨兵找出redis的主节点,后续就直接访问redis的主节点,不会每次都通过 sentinel代理访问redis的主节点,当redis的主节点发生变化,哨兵会第一时间感知到,并且将新的redis 主节点通知给client端(这里面redis的client端一般都实现了订阅功能,订阅sentinel发布的节点变动消息)
可以理解为哨兵就是单独拿出一部分来做从节点的管理。即使更新、通知主从节点的信息。

5.高可用集群模式


redis集群是一个由多个主从节点组成的分布式服务器群,它具有复制、高可用和分片特性。
redis集群不需要sentinel哨兵,也能完成节点移除和故障转移的功能。需要将每个节点设置成集群模式,这种集群模式没有中心节点,可水平扩展。据官网文档说看可以扩展上万个节点(但是官网推荐不超哥1000个节点)。
redis集群的性能和高可用性均由于之前版本的哨兵模式,且集群配置非常简单。

5.1 集群的好处

集群会进行分片操作,不会将数据都丢到一个master上。所以对于之前的哨兵模式来说,如果master挂
掉,会花费几秒或者10几秒进行重新选举master,会极大的影响操作。但是集群模式下,不会将数据都
丢到一个master上,所以能够避免读写都会受到影(hash运算到某一个片区上)。

5.2 Redis集群原理分析

Redis Cluster将所有数据划分为16384个slot(槽位),每个节点负责其中一部分的槽位。槽位的信息存储于每个节点中。
当Redis Cluster的客户端来连接集群时,他也会得到一份集群的槽位配置信息并将其缓存于客户端本地。这样当客户端查找某个key时,可以直接定位到目标节点。同时因为槽位的信息可能会存在客户端本地。

5.2.1 槽位定位算法

Cluster默认会对key值使用crc16算法进行hash得到一个整数值,然后用这个整数值对16384进行取模来得到具体槽位
Hash_Slot = CRC16(key) mod 16384
为什么是16384(2^14)
简单来说,就是对数据存储和网络传输的权衡之后的结果。
感兴趣的同学可以看下下面这个帖子:
https://www.jianshu.com/p/de268f62f99b

5.2.2 Redis集群选举原理分析

当slave发现自己的master变为FAIL状态时,便尝试进行Failover,以期成为新的master。由于挂掉的master 可能会有多个slave,从而存在多个slave竞争成为master节点的过程, 其过程如下:
1.slave发现自己的master变为FAIL
2.将自己记录的集群currentEpoch加1,并广播FAILOVER_AUTH_REQUEST 信息
3.其他节点收到该信息,只有master响应,判断请求者的合法性,并发送FAILOVER_AUTH_ACK,对每一个 epoch只发送一次ack
4.尝试failover的slave收集master返回的FAILOVER_AUTH_ACK
5.slave收到超过半数master的ack后变成新Master(这里解释了集群为什么至少需要三个主节点,如果只有两 个,当其中一个挂了,只剩一个主节点是不能选举成功的)
6.slave广播Pong消息通知其他集群节点。 从节点并不是在主节点一进入 FAIL 状态就马上尝试发起选举,而是有一定延迟,一定的延迟确保我们等待 FAIL状态在集群中传播,slave如果立即尝试选举,其它masters或许尚未意识到FAIL状态,可能会拒绝投票 •延迟计算公式: DELAY = 500ms + random(0 ~ 500ms) + SLAVE_RANK * 1000ms •SLAVE_RANK表示此slave已经从master复制数据的总量的rank。Rank越小代表已复制的数据越新。这种方 式下,持有最新数据的slave将会首先发起选举(理论上)。

5.3 网络抖动

由于网络原因可能造成部分连接变得不可访问,然后很快又恢复正常。
为了解决这个问题,redis cluster提供了一种选项cluster-node-timeout,表似乎某个节点持续timeout的时间无法连接时,才可以认为节点出了故障,需要进行主从切换。如果没有这个选项,网络抖动会导致主从频繁切换(数据的重新复制).

简单描述一下:上述有三个3个主节点每个主节点带着2个从节点,一开始一切正常。
但是到第2步的时候,由于网络抖动造成的网络分区,某一时刻我们的第一个主节点挂了,和自己的两个从节点断开了。由于超过delay的时间,它自己的从节点和其他主节点感知不到主节点。那么这两个从节点会发起重新选举,选举之后假设之前的从节点1变成了主节点,并且广播给其他节点。
但是,当选举成之后,之前的主节点又恢复正常了。那么在这个节点群众,就出现了两个主节点,那么就会出现这两个主节点都去写数据的情况。
过了一会,网络分区恢复了。之前断掉的主节点需要重回之前的集群中。但是呢,由于之前重新选举之后,其他节点都认为之前的从节点1才是真正的主节点了,那么之前断掉的主节点就会变成从节点,之后就会被复制数据,它自己的数据就会被丢掉,造成数据的丢失。
这就是“脑裂”问题。就像我们只有一个脑子,也只能有一个脑子。如果一个时间段你突然有两个脑子,这两个脑子都在记一些不同的事情。但是你迟早会恢复正常,那么那时候你必须丢掉一个脑子,以及整个脑子的记忆。

我们可以通过一个配置来解决这个问题:

min‐replicas‐to‐write 1 //写数据成功最少同步的slave数量,这个数量可以模仿大于半数机制配置,比如 集群总共三个节点可以配置1,加上leader就是2,超过了半数

这个配置代表主节点必须写成功向1个从节点同步数据,才能向这个主节点写数据。–通常配置是节点的半数。

这个配置在一定程度上会影响集群的可用性,比如slave要是少于1个,这个集群就算leader正常也不能 提供服务了,需要具体场景权衡选择。要权衡配置,因为这个会出现有从节点都挂掉的情况。那么该主节点无法对外提供服务!!!

所以建议最好不用,因为Redis更关注的AP!对于某些数据丢失来说,Redis更注重的是避免对磁盘(数据库)的直接冲击。

5.4 集群是否完整才能对外提供服务

当redis.conf的配置cluster-require-full-coverage为no时,表示当负责一个插槽的主库下线且没有相应的从 库进行故障恢复时,集群仍然可用,如果为yes则集群不可用。

5.5 Redis集群为什么至少需要三个master节点,并且推荐节点数为奇数?

因为新master的选举需要大于半数的集群master节点同意才能选举成功,如果只有两个master节点,当其中 一个挂了,是达不到选举新master的条件的。 奇数个master节点可以在满足选举该条件的基础上节省一个节点,比如三个master节点和四个master节点的 集群相比,大家如果都挂了一个master节点都能选举新master节点,如果都挂了两个master节点都没法选举 新master节点了,所以奇数的master节点更多的是从节省机器资源角度出发说的。

5.6 哨兵leader选举流程

当一个master服务器被某sentinel视为下线状态后,该sentinel会与其他sentinel协商选出sentinel的leader进行故障转移工作。每个发现master服务器进入下线的sentinel都可以要求其他sentinel选自己为sentinel的 leader,选举是先到先得。同时每个sentinel每次选举都会自增配置纪元(选举周期),每个纪元中只会选择一 个sentinel的leader。如果所有超过一半的sentinel选举某sentinel作为leader。之后该sentinel进行故障转移 操作,从存活的slave中选举出新的master,这个选举过程跟集群的master选举很类似。 哨兵集群只有一个哨兵节点,redis的主从也能正常运行以及选举master,如果master挂了,那唯一的那个哨 兵节点就是哨兵leader了,可以正常选举新master。

以上是关于细说Redis-p1(2022.03.13)的主要内容,如果未能解决你的问题,请参考以下文章

细说Redis-p2(2022.03.20)

细说Redis-p2(2022.03.20)

细说Redis-p2(2022.03.20)

《安富莱嵌入式周报》第256期:2022.03.07--2022.03.13

算法分析 | 细说二分查找(折半查找)算法

细说Typescript中的装饰器