首页 > 数据库 >关于redis的描述、数据结构、持久化学习笔记

关于redis的描述、数据结构、持久化学习笔记

时间:2023-05-23 23:47:04浏览次数:61  
标签:AOF 缓存 持久 redis 笔记 RDB 数据结构 重写

前言

本文围绕面试问题、redis学习记录。

本文是个人的笔记,会有遗漏或含糊的地方。

描述下redis

redis是一款非关系型数据库,它是以key-value的形式存在数据,因为它的数据在内存中所以它的读写速度极高。
当然它支持持久化,将数据以二进制形式或者以命令的形式持久化到磁盘。
然后它是单线程的具有原子性,支持lua脚本,包含多种数据类型。
我们除了用redis进行数据的缓存,还能利用它实现分布式锁、订阅、队列

redis数据类型

类型名 结构 描述
String int或sds(简单动态字符串) 字符串
List 双向链表+压缩列表
(3.2版本后由quicklist实现)
字符串列表
Hash 压缩列表(7.0后换listpack)或哈希表 键值对集合
Set 哈希表或整数集合 无序并唯一集合
ZSet 压缩列表(7.0后换listpack)或跳表 有序并唯一集合
BitMap string,可看作bit数组 位图

redis持久化

持久化分为两种:AOF(保存写操作命令)、RDB(保存二进制数据)。第一个简单来说就是在写的时候对写的命令进行持久化,第二个就是在指定的间隔时间后对内存中的数据进行保存(但是二进制数据,并不是存操作命令)。

AOF持久化

在客户端发送一个写的操作到redis当中,redis会先把数据写到内存,写完后再对此次的写操作进行存储(保证了写操作是正常的并不是无效的)。

redis启动手会指向AOF的文件里的命令来达到恢复数据的效果。

存储的格式是什么样的呢?
例如我写入的数据命令是:set name liuscraft 那么先写到缓存,发现正常写入,就记录此次写操作的日志,内容如下:

*3 		# 三个值
$3 		# 第一个值的长度
set		# 第一个值的内容
$4		# 第二个值的长度
name		# 第二个值的内容
$9		# 第三个值的长度
liuscraft	# 第三个值的内容

AOF三种策略

fasync(int fd):该函数功能是确保文件fd所有已修改的内容已经正确同步到硬盘上,该调用会阻塞等待直到设备报告IO完成。

  1. always(同步写回):就是在每次进行写操作后日志写入AOF缓冲区后都会立马进行 fasync()
    该策略,性能差,但是它可靠性高,最大程度保证了数据不丢失。
  2. everysec(每秒写回): 在每次写操作后日志写入AOF缓冲区但不立马进行fasync,而是每个一秒进行fasync()
    该策略,性能适中,但在宕机后,redis的1秒内写操作的数据会丢失。
  3. no(由操作系统控制):在每次写操作后日志写入AOF缓冲区,但是不进行fasync的调用,而且由系统内核自行调用。
    该策略,性能好,但在宕机后,会丢失很多的数据,因为fasync操作是由系统控制。

AOF重写(对AOF持久化文件进行压缩)

因为AOF持久化存储的是写命令,然后我们如何进行该文件进行压缩?
其实就是将AOF文件里只保存当前key的最新的一次命令即可。
例如:
set name liuscraft 这是一个上一次执行的,它存在aof中。
set name xiaoming 这是一个最后一次执行的(当前内存中的数据),但也在aof中。
我们会发现其实aof中貌似对这个name的数据重复了!那我们对他们进行去重嘛。
这时我们就可以用到:AOF重写机制。
AOF重写机制就是:当AOF的文件大小达到我限定的阈值后就会进行AOF重写,但是该重写是在后台进行的,不会影响主进程。

AOF后台重写,其实就是创建了一个子进程,然后进行一个AOF重写,因为AOF重写它相当于是新建一个AOF文件,然后把内存中的数据生成命令然后写入到新的AOF文件中,那么这就是一个耗时操作,所以需要在后台进行这个重写操作避免了主进程的阻塞。

在AOF后台进行重写时,其实主进程还是能够正常写命令的(客户端存取数据的操作),因为在AOF进行后台重写时它是在子进程完成的,创建的子进程会把父进程的页表数据复制一份到子进程,但是父进程和子进程的页表都是指向的一个物理内存,但是主进程进行写操作时系统会对物理内存进行一个复制,从而保证了主进程不会影响子进程,子进程不会影响父进程。

但是需要注意的是:如果在进行AOF重写的时候主进程修改了key-value的修改,它的数据会存储在AOF重写缓冲区里,当AOF重写的子进程完成后会把缓冲区里的内容也写到新AOF文件当中,这样就保证了在AOF重写时新的数据写入导致了主进程于子进程的数据不一致问题。

但是需要注意的是,用fork函数(C语言的函数)创建子进程时,它会把父进程的页表复制一份到子进程,这个操作是阻塞的,然后如果在父进程有新的数据写入时会复制一份新的物理内存,这也是一个阻塞操作(如果遇到的是BigKey就会造成长时间的阻塞)。

重写后的AOF文件其实就是把旧命令都去掉了,然后只保留最新的一次。

AOF有个问题就是在启动后恢复数据的速度慢。

RDB持久化(redis默认持久化策略)

RDB(快照持久化)简单来说就是对内存数据直接存储到本地,不存储额外的内容(指令那种)。
RDB触发就是设置一个时间间隔,进行一次内存数据的持久化,这个操作它也是会阻塞的所以也是跟上面AOF重写差不多用到了子进程,区别就是它存储的是直接存储内存数据不用写成命令,然后它不一样的是没有跟AOF一样在子进程进行持久化时,主进程有新的写入操作时会进行缓存等待子进程持久化完后一并写入,它是写入的数据只在内存存储,只有等待下次持久化时才能持久化到磁盘。

RDB 文件的加载工作是在服务器启动时自动执行的,Redis 并没有提供专门用于加载 RDB 文件的命令。

RDB触发方式

  1. RDB手动触发:
    执行了 save 命令,就会在主线程生成 RDB 文件,由于和执行操作命令在同一个线程,所以如果写入 RDB 文件的时间太长,会阻塞主线程
    执行了 bgsave 命令,会创建一个子进程来生成 RDB 文件,这样可以避免主线程的阻塞
  2. RDB自带触发,修改配置文件内容(redis.conf文件):
    下面是默认配置:可以根据需求更改该配置。
    save 900 1            # 900秒内执行一次set操作 则持久化1次
    save 300 10           # 300秒内执行10次set操作,则持久化1次
    save 60 10000         # 60秒内执行10000次set操作,则持久化1次
    
    它配置了 redis 服务器在什么情况下自动触发 bgsave 异步 RDB 备份文件生成。

RDB虽然它是一次性对内存进行一次存储(快照),但是如果频率高会对redis性能造成影响,但如果频率低的情况如果服务器宕机则会导致redis丢失大量数据,它不像AOF那种秒级的存储丢失数据量少。

AOF于RDB混合

为了解决它们之间的问题,我们可以混合使用这两种持久化方式。

这样既可以解决启动redis数据恢复的快的问题,又能解决数据大量丢失的问题。

在进行RDB时,如果发生了写操作,它会放在AOF缓冲区,等RDB持久化完成后会把AOF缓冲区的内容追加到RDB数据后面,然后在redis启动时能够保证不会大量数据丢失。

在redis启动后它恢复数据会先恢复RDB数据在恢复AOF的数据,整个持久化数据分为前半部分(RDB)和后半部分(AOF增量的数据)。

缓存穿透、缓存击穿、缓存雪崩

缓存穿透

就是绕过了缓存、直接走数据库,这种情况一般是查询的未知数,都是些恶意请求。
解决方方法:

  1. redis缓存null,然后直接返回null,但是无法避免恶意请求的key带来更多的key缓存null,提升了对redis的维护成本,而且它与布隆过滤器比起来响应速度不快。
  2. 加入布隆过滤器,这个它是一种通过存储元素的哈希值和位数数组中的位置,可通过该比过滤器快速匹配可能存在于集合中,它有失精度(虽然可以调节),它跟redis一样,并不能完全防住,但是在速度和内存占用上肯定是少了很多。

但通过上面两个方式我们可以选用布隆过滤器外加配合限流、黑名单这种安全措施来防止恶意用户通过随机key来进行恶意请求的操作。

缓存击穿

指在缓存过期了在重写被缓存之前,有大量请求进来都无法从缓存得到数据则全部转向数据库,从而导致数据库压力剧增、请求大量阻塞最终导致直接挂掉。
解决方法:

  1. 在特定的时间段不让它过期,过期时间设置长点。如果是预知的情况下可在大量请求来之前的时间点先把数据缓存好然后设置过期时间要到大流量时间段过去,要么就不设置过期。
  2. 还可以利用分布式锁解决该问题,当请求进来时发现缓存没有数据那么就加入分布式锁 ,然后开始从数据库中获取数据并且更新缓存中最后释放锁(考虑到可能加锁后服务出现了问题导致死锁,可给分布式锁设置过期时间),然后其它请求也没找到缓存时如果发现无法获得分布式锁可直接返回服务繁忙(服务降级)

缓存雪崩

指在同一时间点大量缓存过期或被删,导致所有请求全部打进了数据库,从而导致数据库压力剧增、请求大量阻塞最终导致挂掉。
解决方法:

  1. 缓存过期时间设置的均匀分散,不要集中在一个点,我们可以在过期时间后面加上有个小范围的随机数从而达到时间不集中在一个点。
  2. 当然我们还可以利用其它策略比如:分布式锁、不设置过期时间等。

标签:AOF,缓存,持久,redis,笔记,RDB,数据结构,重写
From: https://www.cnblogs.com/liuscraft/p/17426781.html

相关文章

  • es笔记五之term-level的查询操作
    本文首发于公众号:Hunter后端原文链接:es笔记五之term-level的查询操作官方文档上写的是term-levelqueries,表义为基于准确值的对文档的查询,可以理解为对keyword类型或者text类型分词为keyword的字段进行term形式的精确查找。以下是本篇笔记目录:是否存在值前缀搜索......
  • K8S-学习笔记-001-容器Docker和K8S
    K8S-学习笔记-001-容器Docker和K8S容器是什么?简单来说,它就是个小工具,可以把你想跑的程序,库文件,配置文件都一起“打包”。在任何一个计算机的节点上,都可以使用这个打好的包。有了容器,一个命令就能把你想跑的程序跑起来,做到了一次打包,就可以到处使用。比如:可以把整套Zabbix环境(httpd......
  • 学习笔记-第06天-命令合集5
    1.grep过滤内容,筛选内容(三剑客之一,排行老三)按行输出grep [选项] 内容 文件---color-auto过滤的内容显示颜色1) 过滤出含有root字符串的行:[root@localhost~]#greprootoldboy.txtroot:x:0:0:root:/root:/bin/bashoperator:x:11:0:operator:/root:/sbin/nologin[root......
  • Spring笔记
    Spring笔记Spring提供两种容器类型:BeanFactory和ApplicationContext。BeanFactory:适用场景:资源有限,并且功能要求不是很严格的场景。为什么适用这个场景?默认采用延迟初始化策略(lazy-load)。只有当客户端对象需要访问容器中的某个受管对象的时候,才对该受管对象进行初始化以及......
  • Linux安装软件包(b站up主"码农论坛"所讲的做的笔记)
    在线安装/升级:yum-yinstall 软件包名删除软件:yum-yremove 软件包名如果不确定软件包名, 可以百度一下yum(YellowdogUpdater,Modified) 可以解决软件包的依赖关系, 下面另一种方法却没有 ----------------------------------------------------------------......
  • Linux常用命令(b站up主"码农论坛"所讲的做的笔记)
    重启: reboot 或者 init6关机:init0 或者 halt清屏: clear查看IP:ipaddr查看时间:date修改时间: date-s"2020-5-212:22:22"复制:Ctrl+Insert 粘贴: Shift+Insert  这和WINDOWS上一样中止命令: Ctrl+c 意思是废除输入命令, 不执行查看当......
  • Redis源码安装(Linux环境)
     下载源码:wgethttps://download.redis.io/redis-stable.tar.gz  解压:tar-xzvfredis-stable.tar.gz  编译&安装:cdredis-stablemakePREFIX=~/redisinstall    =================================== ......
  • UE4学习笔记:Windows系统下如何在C++项目里调用第三方动态库
    本随笔介绍在Windows系统下,由UE4引擎创建的C++项目里如何实现调用第三方动态库的方法。随笔作者还在学习阶段,对UE4引擎的使用和理解还不是非常透彻,难免会在随笔内容里出现技术上或书写上的问题,如果出现了类似的问题欢迎在评论区或者私信讨论。 目录设置第三方库头文件的路......
  • Redis数据类型及存取命令
    理想三旬浓烟下,奔赴山海与荒野一,Redis常用五种数据类型及存取命令1,字符串(String)1#设置字符串的值2SETkeyvalue34#获取字符串的值5GETkey2,列表(List)1#在列表左侧添加一个元素2LPUSHkeyvalue34#在列表右侧添加一个元素5RPUSHkeyvalue67#获......
  • docker安装redis
    docker安装mysql1、下载镜像文件dockerpullmysql:5.72、创建实例并启动dockerrun-p3306:3306--namemysql\-v/mydata/mysql/log:/var/log/mysql\-v/mydata/mysql/data:/var/lib/mysql\-v/mydata/mysql/conf:/etc/mysql\-eMYSQL_ROOT_PASSWORD=root\-dmy......