住宅百科

三万字介绍Redis是什么(一)

大家好,我是Leo

漫长的MySQL结束了,我们开始进入Redis的宫殿。最近在做Redis技术输出的时候,我明显发现,在更加熟悉了MySQL之后,我对Redis的理解变得容易多了。也许这就是进步!

以下思路可以帮助你更好地理解本文的知识体系。

思考

整体结构

Redis主要由访问框架、操作模块、索引模块、存储模块、高可用集群支持模块、高可用扩展支持模块等组成。

Redis还拥有一些丰富的数据类型,数据压缩、过期机制、数据淘汰策略、分片机制、哨兵模式、主从复制、集群、高可用、统计模块、通知模块、调试模块、元数据查询等其他辅助功能。

接下来的Redis学习路径主要是介绍上述模块的输出、功能、策略、机制、算法等知识。

五大类型

String类型应该是最常用的类型了。它的底层是通过一个简单的动态字符串来实现的。

哈希

哈希类型也是最常用的类型。是一种通过压缩列表+哈希表实现的数据类型

列表

list 是列表类型,也是我们常用的类型之一。是一种双向链表+压缩列表实现的数据类型

套装

套装集合与以上类型不同。它不允许重复,所以一些特定的场景会优先考虑set类型。是一个整数数组+哈希表实现的数据类型

分类套装

sortset是在set的基础上的改进。当不允许重复时,它还可以处理排序。主要应用于排序表等场景需求,是压缩链表+跳转表实现的数据类型

数据结构

哈希表将在下面的rehash中详细介绍。

整数数组和双向链表也很常见。它们的操作特点是顺序读写,即通过数组下标或链表指针来逐一访问元素。运算复杂度基本为O(N),运算效率比较低。 。

压缩列表实际上类似于数组,数组中的每个元素存储一条数据。与数组不同的是,压缩列表在头部有三个字段:zlbytes、zltail和zllen,分别表示列表的长度、列表末尾的偏移量和列表中的条目数;压缩列表在表的末尾还有一个 zlend,表示列表结束。在压缩列表中,如果我们想要查找定位第一个元素和最后一个元素,可以直接通过三个头字段的长度来定位,复杂度为O(1)。当搜索其他元素时,效率就不那么高了。只能一一查找,此时的复杂度是O(N)。

跳跃列表在链表的基础上增加了多级索引,通过索引位置的多次跳跃实现数据的快速定位。跳表的相关说明在下一篇文章第五章介绍。

三万字讲什么是MySQL

为什么哈希很慢

Redis在处理键值对时,会进行哈希处理,将key处理成地址码写入Redis存储模块。随着我们拥有越来越多的密钥,某些密钥将存在于同一地址。代码情况。 (我在写HashMap时就引入了哈希冲突的问题)

出现这种情况后,Redis对键值对做了扩展,就是键值对+链表的方式。如下图所示,多个数据经过哈希处理后,都落在key1值上。一个卡槽不可能存储两个值,所以这个卡槽中存储一个指向链表的指针,通过链表存储多个值。

哈希链表

链表处理同样的多个key的问题。随着数据量的发展,哈希冲突越来越频繁,链表包含的数据也越来越多。哈希的性能是O(1),链表的性能是O(n)。所以整体性能就被拖累了。为了改变这种情况,Redis引入了rehash。

重述

rehash是增加现有哈希桶的数量,使得越来越多的元素可以分布在更多的哈希桶中。这样就减少了单个桶的链表的元素数量,也减少了单个桶的冲突。

首先,Redis会创建两个全局哈希表,我们这里定义为哈希表A和哈希表B。当我们插入一条数据时,我们首先存储A。随着越来越多的A变得可用,Redis开始执行重新哈希操作。主要分为三个步骤:

  • 为B分配更多空间,通常是A的两倍
  • 将A中的数据全部复制到B
  • 释放A

从上面的rehash过程可以看出,当A中数据量很大时,复制效率是非常慢的!由于Redis的单线程特性也会造成阻塞,导致Redis短时间内无法提供服务。为了避免这个问题,Redis在rehash的基础上使用了渐进式rehash。

渐进式重述

进化点是,在第二步复制时,不是一次性复制,而是批量复制。处理请求时,从A表的第一个索引位置开始,将该索引位置的所有元素复制到B中。等待下一个请求后,从A表的下一个索引位置继续复制操作。这样,一次大量副本的开销被巧妙地分配到处理多个请求的过程中,避免了耗时的操作,保证了数据的快速访问。

Redis 是单线程还是多线程

首先来科普一下多线程的知识。当一个CPU运行多个线程时,会存在多线程调用的消耗问题,同时也存在多线程调用时数据一致性的问题。这些必须单独处理,单独处理会消耗性能。因此,Redis采取了统筹使用单线程和多线程的思想。

处理数据写入、读取时是键值对数据操作,采用单线程操作。请求连接时,从socket读取请求,解析客户端并发送请求,采用多线程操作

Redis巧妙地将所有需要延迟等待的操作转移到多线程处理上,消除了所有不需要等待的单线程处理。 个人觉得这个设计理念很棒

提示:如果不是这样设计的话,等待连接、等待发送、等待接收,你很可能会等死。这会导致Redis线程被阻塞,无法处理其他请求。

复用机制

IO复用机制是指一个线程处理多个IO流,这也是我们常听到的select/epoll机制。那么Redis是如何处理这些连接和等待操作的呢?

当Redis只运行单线程时,同时存在多个监听套接字和连接套接字,内核会一直监听这些连接请求和数据请求。一旦客户端发送请求,就会以事件的形式通知Redis主线程进行处理。这就是Redis线程处理多个IO流的效果。

如上所述,Redis是以事件的形式通知的。这里我们将做一个扩展。 select/epoll 提供了基于事件的回调机制。不同的事件会调用相应的处理函数。一旦请求到来,立即添加到事件队列中,Redis单线程会不断处理事件队列。解决了等待和扫描的资源浪费问题。

安全机制

Redis的持久化安全机制主要有两部分,一是AOFlog,二是RDB快照。接下来说一下AOF和RDB的一些区别

AOF

Redis 使用写入后 log 来提高性能。它先执行命令,然后写入日志。这样做有两个主要好处

  • 命令执行成功后才会写入日志。这样就避免了写入日志后如果命令执行错误,导致日志被删除的问题。
  • 先执行写操作,再写入日志。这也避免了阻塞当​​前的写操作

缺点是:

  • 如果执行命令后,在记录日志之前计算机死机,那么该命令和相应的数据就有丢失的风险。
  • AOF虽然避免了阻塞当​​前命令,但可能会给下一步操作带来阻塞风险。因为AOF日志也是在主线程中执行并写入磁盘的。

文件格式:

Redis收到“设置欢少公众号欢少的成长路径”命令后,AOF日志内容为,“*3”表示当前命令分为三部分,每部分编号为“+ Number 以 开头,后跟具体的命令、键或值。其中 number 表示该命令总共有多少个字节,该部分的key或value,例如3 set”表示该部分有3个字节,即“set”命令。

AOF写作策略

AOF提供了三个appendfsync可选值

  • 始终,同步回写:每次执行写命令后,日志立即同步写回磁盘;
  • Everysec,每秒回写:每次执行写命令后,首先将日志写入到AOF文件的内存缓冲区,每秒将缓冲区的内容写入磁盘;
  • 不是,操作系统控制的回写:每次执行写命令后,日志首先写入到AOF文件的内存缓冲区,由操作系统决定何时将缓冲区内容写回磁盘。

这三个人都无法两全其美。同步写入可以实现数据的一致性,但是写入磁盘的性能相对于内存来说太差了。如果你每秒写一次,你就会损失1秒。数据,如果不使用配置,关机后会丢失更多数据。

最后三种配置如何选择,需要根据具体业务场景而定。如果数据安全性要求太高,可以选择同步写回。如果数据安全性中等,选择每秒回写。如果没有安全性就选择否。

AOF重写机制

AOF 日志采用追加形式。文件过大后,写入日志的性能下降是不可避免的。为了解决这个问题,Redis引入了重写机制。

重写机制主要做的事情是记录一个键值的最终修改结果,修改的历史记录会被排除。这样,每个命令只有一个日志。如果想使用AOF日志来恢复数据,也可以恢复正确的数据。

重写机制流程是,主线程fork一个后台子线程bgrewriteaof后,fork会将主线程的内存复制到子线程bgrewriteaof中,这样子线程就可以重写,而不影响阻塞主线程。写操作完成。

在此期间,如果有新的请求写入,Redis将会有两条日志。一条日志是指正在使用的AOF日志。 Redis 会将此操作写入其缓冲区。这样,即使机器宕机了,这条AOF日志的操作仍然是完整的,可以用来恢复。另一个日志是指新的AOF重写日志。该操作也会被写入重写日志缓冲区。这样重写日志时最新的操作就不会丢失。复制数据的所有操作记录重写完成后,重写日志中记录的最新操作也会写入到新的AOF文件中,以保证记录数据库的最新状态。此时,我们可以用新的 AOF 文件替换旧文件。

RDB

RDB是内存快照,是系统某一时刻的数据写入磁盘的备份。这样,宕机后,某一时刻之前的所有数据都可以恢复。

生成RDB的两种方式

  • save:在主线程中执行,会造成阻塞;
  • bgsave:创建专门用于写入RDB文件的子进程,避免主线程的阻塞。这也是 Redis RDB 文件 生成的默认配置

写时复制技术

首先介绍一下写时复制技术的由来。当Redis正在拍摄RDB快照时(当前RDB尚未完成),修改数据的请求来了。如果该请求写入快照,则不符合该时刻的数据一致性。如果快照不写入而丢弃,就会导致数据丢失或者数据一致性问题。因此,Redis在执行快照的同时,利用操作系统提供的写时复制技术来正常处理写操作。

处理流程

主线程fork创建子线程bgsave,可以共享主线程的所有内存数据。 bgsave子线程运行后,开始读取主线程的内存数据,并将其写入RDB文件中。如果主线程读取这些数据,它们不会互相影响。如果是修改操作,则会复制数据,并生成数据的副本。然后主线程对此副本进行修改。同时bgsave子进程可以继续将原始数据写入RDB文件中。

这样保证了快照数据的一致性,也保证了快照期间对正常业务的影响。

既然RDB这么牛逼,那RDB可以用来持久化吗?

如果我们使用RDB进行持久化,我们必须始终拍摄RDB快照。如果我们每2秒拍一次快照,最坏的情况是数据量减少50%。如果每秒拍一次快照,我们可以完全保证数据一致性,但负面影响也很大。

  • 频繁快照影响磁盘IO使用,磁盘内存开销很大
  • RDB由bgsave处理。虽然它不会阻塞主线程,但是当主线程创建新的bgsave时,它会影响主线程。如果每秒创建一次,可能会阻塞主线程。

如果无法进行全量备份,增量备份可以使用RDB持久化吗?

增量备份和全量备份的区别在于增量备份只备份修改过的数据。如果是这样的话,我们需要为每条数据添加一条记录,这是非常昂贵的。如果为了增量备份而牺牲宝贵的内存资源,那是得不偿失。

实际应用

上面我们介绍了AOF和RDB的区别、流程以及优缺点。我们发现,如果仅仅依靠某种方法进行持久化,并不能有效地实现数据的一致性。

如果只使用RDB,快照的频率很难控制。如果使用AOF,文件继续增长将难以忍受。

最优的策略是RDB + AOF如果RDB每小时备份一次,我们可以使用RDB文件来恢复那一刻的所有数据,然后使用AOF日志来恢复数据这一小时。

看到这个的人一定都是真粉丝。点赞+分享+观看+关注就是对我最大的支持。 欢少的成长之路谢谢你

下次见!