数据库(Redis)笔记与总结

前言

你好,我是苏青羽,是一名默默无闻的计算机爱好者。

本笔记是我在学习Redis时所总结的学习笔记,主要记录了Redis的一些基础知识点,在这里分享给大家,希望通过该笔记能帮助更多的人去理解Redis。

笔记中参考了网上的很多博客、公众号、网课、面经等等,这些均在笔记末尾的参考资料中有所展示,大家可自行查看,做更深入的了解。

本笔记主要用于在学习上的复盘,并不适合初学者学习。如果在笔记中遇到了不懂的知识点,一定要去自己看书或者上网查询相关知识点!

如果发现本文有较大的硬性错误、或者是某些内容存在侵权、以及有什么想补充的问题和内容,请点击“关于”,通过里面预留的联系方式同我联系!

本笔记正在实时更新中,若是想获取笔记的最新PDF版以及了解关于我的更多信息,可扫描下方二维码,或微信搜索公众号“苏青羽”关注我!

前提基础

学习本笔记前,请事先掌握以下基础知识

  • C语言基础(或C++基础)
  • 操作系统
  • 计算机网络
  • MySQL

Redis概述

1. 什么是Redis?

Redis是一个流行的非关系型数据库管理系统,由C语言编写并开源。

2. 说说Redis的特点?

(1) 采取了基于键值对(Key-Value)的存储系统,操作简单方便。

(2) 内置了丰富的数据类型,可以保存列表、哈希、集合等数据。

(3) 保证了单操作的原子性,同时支持简单的事务以保证多操作的原子性。

(4) 数据保存在内存中,读写速度快,性能极高,因此经常被作为缓存使用。

(5) 有着十分丰富的特性,支持数据持久化、分布式和集群

(6) 有着良好的跨平台特性,提供了多种语言的API。

3. 说说Redis的用途?

服务器的缓存(内存读写性能高)、排行榜(利用有序集合)、好友关系推荐(利用集合的交并运算)、简单的消息队列(利用发布/订阅模式)

4. 为什么要用Redis,只用MySQL不行吗?

当数据库的访问量逐渐增大时,MySQL因为其速度较慢的磁盘I/O,导致性能瓶颈越来越明显。Redis的出现很好地解决了这个问题,由于数据保存在内存中,读写速度快,它经常作为客户端与MySQL服务器的中间缓存件,大大提升了数据库的整体性能与并发度。

5. 为什么不使用编程语言提供的数据结构作缓存?

(1) 以C++为例,STL自带的容器Map可以实现类似Redis的缓存。它的特点是轻量和快速,而且无需借助额外的应用程序。但它只能将数据存储在本地的程序中,即本地缓存,数据会随着程序的结束而销毁,同时又很难在分布式环境下保持数据一致性。

(2) Redis实现的分布式缓存独立于某个单独的程序,即使程序结束数据仍然存在,同时还保证了分布式环境下数据的一致性,当然这也导致了内部的程序架构会比较复杂。

6. Redis与其他基于键值对的数据库有什么区别?

在保证高性能的同时还支持数据持久化,断电不丢失。有着更加丰富的内置数据类型。采取了高效率的单线程多路I/O复用模型。支持集群和分布式。

7. Redis的网络应用模型是什么?

Redis采用了客户/服务器(C/S)模型,一个Redis实例只能是两种角色之一:要么为其他主机提供各种Redis数据库服务,即Redis服务器。要么通过Redis连接来获取或更改服务器的数据库状态,即Redis客户端

数据类型与结构

1. Redis有几种数据类型?

Redis对外提供了十种可用的数据类型:字符串(String)、列表(List)、集合(Set)、有序集合(Sorted Set)、哈希(Hash)、HyperLogLog消息队列(Stream)、地理空间(Geospatial)、位图(Bitmap)、位域(Bitfield),其中前五种是Redis的五种基本数据类型

2. Redis如何实现这些数据类型?

(1) Redis是用C语言编写的。它的优点是贴近底层,运行效率极高。缺点是不支持很多高级语言的特性,如面向对象,也没办法使用一些现成的库,如STL。为了解决这个问题,Redis在内部建立了一个独特的对象系统,以实现丰富的数据类型。

(2) 对于对象系统来说,每个Redis数据类型都是一种对象,如字符串对象,列表对象等等。为了支持这些对象的特性,Redis在内部构建了数种不同的数据结构,一个对象的底层实现可能会包含多种数据结构,也可能会根据当前使用场景来切换不同的数据结构。

(3) Redis是基于键值对的数据库,每个键值对至少包含两个对象:键对象和值对象。键对象只能是字符串对象,而值对象可以是各种对象,因此:当我们说“字符串键”时,指的是这个键对应的值是字符串。同理,“列表键”指的是这个键对应的值是列表。

3. Redis实现了几种数据结构?

Redis对内实现了几种不同的数据结构来提供数据类型的底层支持,其中包括:简单动态字符串(SDS)、链表、字典、跳表、整数集合、压缩列表。

4. 说说简单动态字符串(SDS)?

(1) Redis 构建了一种名为简单动态字符串(simple dynamic string,SDS)的数据结构,用于保存一个字符串变量,C字符串则用来保存字符串常量。

(2) SDS的字符串格式兼容C字符串,用‘0’作为字符串结尾,以重用C字符串函数库的部分函数。SDS保存了字符串长度,获取长度的复杂度为O(1),也杜绝了缓冲区溢出问题。SDS总是会预留一部分可用空间,比如在进行字符串空间扩展时会分配额外的未使用空间,在缩小空间时也会预留一部分的剩余空间,避免频繁的内存重分配。

(3) SDS是二进制安全的。数据在进行读写时不会做任何的假设或者限制,可以存储任意的二进制数据。比如说‘0’不再是字符串的末尾标志,它仅用来兼容C字符串。

(4) SDS可以实现字符串对象, 而且它还常作为其他功能的缓冲区(buffer)来被使用。

5. 说说Redis的链表?

(1) 由于 C 语言并没有内置链表, 所以Redis在底层构建了链表的数据结构。

(2) Redis的链表是无环双端链表,每个结点都有前指针和后指针,但头尾结点不会形成环,而且每个链表结点都可以自由保存各种对象。

(3) 链表可以实现列表对象, 同时像发布/订阅、慢查询、监视器等功能也用到了链表。

6. 说说Redis的字典?

(1) Redis的字典是一种用于保存键值对的数据结构。它可以将键(key)和值(value)建立映射关系,且每个键都具有唯一性,它实际上是一个哈希表

(2) 字典使用了开链法来解决哈希冲突,多个同哈希值的结点可以连接形成单向链表。

(3) 字典可以用来实现Redis数据库的键值对和哈希对象,还有不少功能也用到了字典。

7. Redis的字典为什么会保存两个哈希表?

(1) 为了让哈希表的装载因子维持在合理范围内(太小会浪费空间,太大会容易哈希冲突),Redis会根据当前装载因子的大小来调整哈希表空间,通常是将原来哈希表的结点ReHash到新的哈希表上,然后再释放原来哈希表的空间。因此字典会保存两个哈希表,其中一个正常为空,仅在ReHash时分配空间。

(2) 如果Redis正在执行某些会创建子进程的命令,如“BGSAVE”,那么在子进程存在期间, Redis会提高哈希表的装载因子上限,尽可能避免扩展哈希表空间。这是为了充分利用子进程的“写时复制”,减少内存写入次数,提高程序效率

(3) 为了避免ReHash对数据库性能造成影响,Redis并不会一次性将哈希表的所有结点进行ReHash,而是分多次,渐进式地完成的。在这个过程中,字典会同时使用两个哈希表,但是在插入时只会将新结点插入到新哈希表中,保证原哈希表的结点数量只减不增, 并随着ReHash的执行而变成空表。

8. 说说Redis的跳表?

(1) 跳表(skiplist)实际上是一种多层级有序链表。第n+1层的链表总是第n层链表的子集,而第1层链表会包含所有的结点,可以简单地理解为上层链表是下层链表的索引。通过跳表查找数据,其时间复杂度接近于二分查找,效率极高

(2) Redis的跳表限制了最高层级为32,每个跳表结点都包含了一个分值(可以相同),以及一个对象指针(必须是唯一的)。跳表结点按照分值大小进行排序, 当分值相同时, 则按照对象指针所指向对象的大小进行排序。

(3) 跳跃表可以实现有序集合对象, 同时也可以在集群中发挥作用。

9. 为什么Redis不用B+树?为什么MySQL不用跳表?

(1) MySQL需要频繁地操作硬盘。如果使用跳表,在查找过程中需要遍历多个结点,容易导致频繁的磁盘I/O,查找效率也不够稳定,因此需要使用B+树来尽可能减少磁盘I/O。

(2) Redis是基于内存的数据库,读写数据不涉及磁盘I/O。跳表在查找效率上与B+树相差无几,代码实现又比B+树简单,明显更符合Redis的设计理念。

10. 说说Redis的整数集合?

(1) 整数集合(intset)是一个无重复元素的有序动态数组,可以保存2、4或8字节的整数。

(2) 整数集合支持升级操作。若新插入的元素类型比其他元素的类型都要长时,整数集合会将原来的短类型元素升级为长类型,然后再进行插入。升级带来了操作上的灵活性,还节约了数组内存。整数集合只支持升级操作, 不支持降级操作。

(3) 整数集合可以实现集合对象

11. 说说Redis的压缩列表?

(1) 压缩列表(ziplist)是一种为了节约内存而开发的顺序型数据结构,类似于数组,但其中的每个元素都是不定长的,每个元素可以保存一个字节数组(类型为char,可以存储任意的二进制数据)或者整数值

(2) 因为每个数组元素不定长,为了支持逆向遍历操作,每个元素都记录了前一个元素的长度。当我们插入或删除时,前一个元素的长度值可能需要更大的空间来记录,导致内存的重分配,而这可能又会引发了后一个元素的更新和空间扩展,进而引起多次内存重分配。但这个现象出现的几率并不高,而且在元素数量较少的情况下,性能影响也不大。

(3) 压缩列表可以实现列表对象和哈希对象

12. 说说Redis的字符串类型/对象(String)?

(1) Redis的字符串是二进制安全的,它可以包含任何二进制数据,比如图片。一个字符串最大能存储512M。字符串的底层数据结构有三种,可以根据场景自动切换

① 如果字符串保存的是一个可以被C语言的long类型表示的整数(浮点数不包括在内),则由C语言的long保存。

② 如果字符串保存的是一个大于 39 字节的字符串, 则由Redis的SDS保存。

③ 如果字符串保存的是一个小于 39 字节的字符串,则会进行特殊的优化。它会只调用一次内存分配函数,然后在内存中顺序保存Redis对象和SDS,降低内存分配和释放的次数。(普通字符串采用两次分配,然后在Redis对象中使用指针指向另一块区域的SDS

(2) 值得注意的是,对于第三种情况,一旦发生任何形式的修改,即使字符串的长度仍然小于39字节,也会自动切换保存方式为第二种。

13. 说说Redis的列表类型/对象(List)?

Redis的列表与常见编程语言中的链表没有太大差异,列表的每个元素保存的都是字符串或数值。列表的底层数据结构有两种,可以根据场景自动切换

(1) 如果列表保存的所有元素的长度都小于 64 字节并且元素的数量小于 512 ,则由Redis的压缩列表保存。

(2) 如果列表不满足压缩列表的保存条件,则由Redis的链表保存,链表的每个结点都保存了一个Redis字符串。

14. 说说Redis的集合类型/对象(Set)?

Redis的集合与列表功能类似,但它具备了去重功能,并且可以轻易实现集合中的交集、并集、差集的操作。集合的底层数据结构有两种,可以根据场景自动切换。

(1) 如果集合保存的元素均为整数并且元素的数量小于 512 ,则由Redis的整数集合保存。

(2) 如果集合不满足整数集合的保存条件,则由Redis的字典保存。字典的每个键都是redis的字符串,而值全部设置为NULL。

15. 说说Redis的有序集合类型/对象(Sorted Set)?

Redis的有序集合与集合功能类似,但它会为其中的每个元素增加一个分值,所有元素按分值进行排序。有序集合的底层数据结构有两种,可以根据场景自动切换。

(1) 如果有序集合保存的所有元素的长度都小于 64 字节并且元素的数量小于 128,则由Redis的压缩列表保存。每个有序集合元素由相邻的两个压缩列表元素来保存(元素值和对应的分值),并根据分值自动排序。

(2) 如果有序集合不满足压缩列表的保存条件,则由Redis的字典和跳表保存。

16. 为什么有序集合需要同时使用字典和跳表来实现?

有序集合其实可以单独使用字典或者跳表来实现, 但在性能上会有所降低,所以一般会同时使用这两种数据结构。

(1) 若单独使用字典,字典的键保存了有序集合的所有元素值,而字典的值则保存每个元素对应的分值。虽然查询给定元素对应分值的效率极高,但因为字典的无序性,在进行范围查询时都需要进行额外的排序,效率较低。

(2) 若单独使用跳表,每个跳表结点都保存了有序集合元素的元素值和分值,并自动根据分值进行排序。虽然范围查询时效率极高,无需额外的排序,但查询给定元素对应分值的效率比起字典会有所降低。

17. 说说Redis的哈希类型/对象(Hash)?

Redis的哈希是一种键值对集合,哈希的每个元素都保存了一对键值,其中的键和值可以是字符串或数值。哈希的底层数据结构有两种,可以根据场景自动切换。

(1) 如果哈希保存的所有键值对的键和值的长度都小于 64 字节并且键值对的数量小于 128,则由Redis的压缩列表保存。每个键值对由相邻的两个压缩列表元素来保存。

(2) 如果哈希不满足压缩列表的保存条件,则由Redis的字典保存。

18. 说说Redis的HyperLogLog?

(1) Redis的HyperLogLog可以用来统计基数,即一个数据集合中不重复元素的个数,于Redis的2.8.9版本添加。它的内部算法能以很小的内存计算海量数据的基数,但是可能会存在一定误差(在可接受的范围内)。

(2) HyperLogLog并不储存元素,不能像集合那样返回输入的各个元素。

19. 说说Redis的消息队列(Stream)?

Redis的Stream是一个轻量级的消息队列,于Redis的5.0版本添加。它提供了消息的持久化和主从复制功能,可以让任何客户端访问任何时刻的数据,并且能记住每一个客户端的访问位置,还能保证消息不丢失。

20. 说说Redis的地理空间(Geospatial)?

Redis的地理空间是一种存储地理位置信息的数据结构,于Redis的3.2版本添加。它支持对地理位置进行各种运算,比如计算距离,获取经纬度,查找附件的人等等。

21. 说说Redis的位图(Bitmap)?

Redis的位图是字符串类型的扩展。它使用Redis的SDS来保存位数组,支持二进制的与或非操作。应用场景广泛,比如可以记录签到情况。

22. 说说Redis的位域(Bitfield)?

Redis的位域是位图类型的扩展。它可以将很多小的整数保存在一个位图的各个区域中,并且支持单独取出位图中的某个值(由多个位构成),以此高效地使用内存。

数据库与键值对

1. Redis的对象、键值对和数据库之间的关系?

它们之间是一种子集包含的关系。首先Redis操作的基本单位是对象,一种Redis对象对应一种Redis数据类型。它的底层通常会由多种Redis数据结构组成,但这些对用户透明,用户只能看到和操作对象。其次,Redis是基于键值对的数据库,一个键值对由两个对象构成(键对象和值对象),用户只能以键值对的方式保存数据。最后,多个键值对组成了一个Redis数据库,而一个Redis实例拥有多个数据库,客户端可以选择将数据(键值对)存储在服务器实例的指定数据库中。

2. 说说Redis服务器的数据库?

(1) 每个数据库以一个从0开始的递增数字命名,Redis默认支持16个数据库(可以通过配置文件支持更多,无上限),客户端与服务器建立连接后会自动选择0号数据库,但是可以随时更换。

(2) Redis的数据库特点如下:Redis不支持对数据库改名或者自定义名字,只能以数字标记。同时也不支持单个数据库的权限控制,一个客户端要么可以访问全部数据库,要么连一个数据库也没有权限访问。多个数据库之间具有不完全的隔离性,虽然数据不能跨库共享,但却可以使用FLUSHALL命令清空所有数据库中的数据。

(3) Redis的数据库更像是一种命名空间,它不适宜存储不同应用程序的数据,不同的应用应该使用不同的Redis实例存储数据。由于Redis是轻量级的数据库,所以不用担心多个Redis实例会额外占用很多内存。

3. 说说Redis的内存回收和对象共享机制?

(1) 为了方便管理对象,Redis自己构建了一个基于引用计数的内存回收机制。它会自动跟踪对象的引用计数信息,并在适当的时候自动释放对象。

(2) 基于引用计数,Redis还实现了对象共享功能。即不直接创建对象,而是尝试让多个对象指针指向同一对象,只在适当的时候才创建新对象,以此节约内存。

4. Redis的对象共享机制有什么限制?

(1) 当Redis考虑将一个对象指针指向共享对象时,它必须事先验证共享对象和当前想创建的对象的值是否相同。而一个共享对象保存的值越复杂,验证时就越耗性能。

(2) 因此,尽管共享更复杂的对象可以节约更多的内存,但为了提高整体的性能,Redis只对包含整数值的字符串对象进行共享

(3) Redis服务器在初始化时会创建一万个共享对象,包含从 0 到 9999 的所有整数值

5. Redis的对象空转时长是什么?

Redis的每个对象都保存了一个属性——空转时长, 该属性记录了对象最后一次被Redis正常访问的时间。一旦当前的内存空间紧张,Redis会根据用户配置主动释放部分对象,而其中空转时长较高的对象会被优先释放。

6. 什么是过期键?

(1) 客户端可以以秒或毫秒精度为数据库中的某个键设置生存时间,在经过指定时间之后,服务器就会自动删除生存时间为0的键,该键被称为过期键

(2) Redis有多个命令可以设置键的生存时间(键可以存在多久)或过期时间(键什么时候会被删除),但最终都会将某个键对象和对应的过期时间所组成的键值对保存在一个字典中。通过访问该字典,Redis可以快速得知某个键的过期时间并判断是否需要删除。

(3) Redis使用惰性删除和定期删除策略来删除过期键。惰性删除是指在执行Redis命令时总会先进行检查,获取输入键的过期时间并判断是否需要删除。定期删除是指Redis总会隔一段时间扫描一部分键,删除其中的过期键。通过这两个策略,Redis可以很好地利用CPU资源和避免过期键残留导致的内存浪费。

7. 什么是慢查询日志?

Redis的慢查询日志功能用于记录执行时间超过指定时长的命令。它保存在执行命令的Redis服务器中,以链表形式存在,每个链表结点都代表一条慢查询日志。打印和删除慢查询日志可以通过遍历链表完成。如果日志的数量超过了服务器配置文件中的指定上限,则会按照先进先出的规则删除多余日志。

服务器与客户端

1. 说说Redis服务器的配置文件?

Redis 的配置文件位于 Redis 安装目录下,文件名为“redis.conf”。当我们以服务器模式打开一个Redis实例,则会自动读取该配置文件完成Redis服务器实例的初始化。该文件非常重要,通过修改该文件可以使Redis按我们想要的模式运行。

2. 说说Redis的服务器架构?

(1) Redis服务器使用单进程单线程的方式来处理客户端的命令请求。但这并不是说Redis只有一个线程,它的其他功能比如持久化、主从复制等都由额外的线程或进程完成。

(2) 为了与多个客户端通信,Redis使用I/O多路复用技术来同时监听多个客户端套接字。其中的所有功能都是通过包装select、poll、epoll等常见I/O多路复用模型来实现,他们之间可以进行互换,在保证单线程设计的简单性下又实现了高性能的网络通信。

(3) 尽管多个客户端请求可能会并发出现,但由于需要保证Redis的单线程,因此Redis总会将所有客户端请求放到一个队列中,以有序同步,每次一个的方式来进行处理。

3. 为什么Redis要采用单进程单线程?

由于Redis是基于内存的,相比起CPU的性能,机器内存和网络带宽更可能成为它的瓶颈,那么采用单线程就是件理所当然的事。采用单线程,可以避免不必要的线程切换开销,也不用考虑多线程和锁的各种问题,易于实现。

4. Redis服务器是如何运行的?

(1) Redis服务器除了需要响应客户端的请求之外,它还需要定时地维护自身的状态,以保证数据的正确性,而这些操作全都被放在一个名为“serverCron”的周期性函数中

(2) Redis服务器会将处理客户端请求和执行周期性函数的操作置于一个循环中,加上初始化和清理函数,这就构成了Redis服务器的主函数

(3) 处理客户端请求和执行周期性函数之间是合作关系,服务器会轮流处理这两种事件,并且在处理过程中也不会发生抢占。不过因为Redis总是会先处理客户端请求,所以周期性函数或定时函数的实际处理时间通常会比预定时间晚一些。

5. 说说Redis服务器的RDB持久化?

(1) Redis是一个支持持久化的内存型数据库。它可以把内存中的数据同步保存到硬盘文件中,而当Redis重启后再把硬盘文件重新加载到内存,就能恢复原来的数据。

(2) RDB持久化是Redis默认的持久化方式,它既可以手动执行,也可以根据服务器配置选项定期执行,执行RDB持久化后会生成一个RDB文件。

(3) 有两个Redis命令可以生成RDB文件:SAVE和BGSAVE。SAVE命令会阻塞Redis服务器进程,直到RDB文件创建完毕。BGSAVE命令不阻塞服务器进程,而是会派生出一个子进程,然后由子进程负责创建RDB文件。

(4) 只要Redis服务器在启动时检测到RDB文件存在,就会自动执行RDB文件的载入和数据库的恢复,而且这个过程是阻塞的,直到载入工作完成才能执行下一步操作。

6. Redis能同时执行SAVE和BGSAVE命令吗?

服务器禁止SAVE命令和BGSAVE命令同时执行,在命令执行时,服务器会拒绝期间的所有SAVE或BGSAVE命令。这是为了避免父进程(服务器进程)和子进程同时执行RDB文件的创建和写入,从而导致不必要的系统资源竞争。

7. RDB文件是什么?

RDB文件是Redis经过RDB持久化操作后产生的数据文件,它是一个经过压缩的二进制文件,由多个部分组成。RDB文件会保存当前Redis实例中所有数据库的数据,并且对于不同类型的键值对,RDB文件会使用不同的方式来保存它们。

8. 说说Redis服务器的AOF持久化?

AOF是Redis提供的另一种持久化方式。与RDB不同的是,AOF持久化不保存数据库的数据,而是将Redis服务器所执行的写命令保存到AOF文件中。这样在服务器启动时,就可以通过载入和执行AOF文件中保存的命令来还原数据库。

9. Redis是如何执行AOF文件的写入和同步的?

Redis在开启AOF持久化功能后,Redis在执行客户端的写入命令时就会自动将命令保存到AOF的内存缓冲区,并根据不同的写入和同步方案(可在服务器配置文件中修改),将其写入到磁盘的AOF文件中。

(1) always:每次有数据修改发生时都会同步到AOF文件,性能最低,安全性最高。

(2) everysec:每秒钟同步一次,即使出错最多只会丢失一秒之内产生的数据,平衡性最好。

(3) no:让操作系统决定何时进行同步,性能最高,安全性最低。

10. 什么是AOF重写?

(1) AOF文件记录Redis命令的执行记录,其文件体积会随着时间流逝越来越大。为了解决这个问题,Redis提供了AOF文件重写(rewrite)功能。通过该功能,Redis服务器可以创建一个新的体积更小的AOF文件来替代原来的AOF文件。

(2) AOF重写命令为“ BGREWRITEAOF”,它是通过读取服务器当前的数据库状态来实现的。它每读取一个键值对就会为它创建相应的创建命令(为了避免单条命令过长导致的缓冲区溢出,实际可能会写入多条命令),并记录在新的AOF文件中,省去键值对命令记录中大量的修改命令,所以并不需要对原AOF文件进行任何操作

(3) AOF重写会交由子进程完成,服务器进程(父进程)在执行期间可以继续处理客户端请求。但是这期间数据库可能由于客户端请求而修改自身状态,导致最终生成的AOF文件和当前数据库状态不一致。为了解决这个问题,Redis在AOF重写期间会创建并维护一个 AOF 重写缓冲区,它是一个临时缓冲区,独立于AOF缓冲区,仅记录在AOF重写期间服务器执行的所有写命令。当AOF重写完成后,再把重写缓冲区中的所有内容追加到新AOF文件的末尾,并将该文件替换原来的AOF文件。

11. 为什么BGREWRITEAOF和BGSAVE命令都是由子进程而不是由线程完成?

子进程会带有服务器进程(父进程)的数据副本,使用子进程而不是线程,可以在避免使用锁的情况下,保证数据的安全性

12. 如果同时存在RDB和AOF文件,Reids会使用哪个来进行数据库的还原?

因为AOF文件通常比RDB文件的更新频率高,其实时性也更好。所以Redis会优先使用AOF文件来还原数据库状态,只有在关闭AOF持久化功能的情况下,才使用RDB文件进行还原

13. Reids能否同时执行BGREWRITEAOF和BGSAVE命令?

(1) 如果BGSAVE命令正在执行,那么BGREWRITEAOF命令会被延迟到BGSAVE命令执行完毕之后执行。

(2) 如果BGREWRITEAOF命令正在执行,那么客户端发送的BGSAVE命令会被服务器拒绝。

(3) 这两个命令都由子进程执行,同时执行并不会存在操作上的冲突,但因为会并发出两个执行大量磁盘I/O的子进程,在性能上会对Redis有较大影响,因此一般会有所限制

14. 说说Reids服务器的混合持久化?

(1) RDB和AOF持久化各有利弊。RDB的数据实时性差,可能会导致一定时间内的数据丢失,而AOF由于文件较大,在进行数据加载和还原时耗时较长。为了解决这个问题,Redis在4.0版本新增了混合持久化(默认关闭,可通过服务器配置文件开启)。

(2) 混合持久化会在进行AOF重写时把当前RDB文件的内容写到AOF文件开头,之后的数据再以AOF的格式化追加的文件的末尾。它结合了RDB和AOF的优点, 具有良好的数据实时性和快速恢复数据的能力。缺点是在AOF文件中添加了RDB格式的内容,使得 AOF 文件的可读性变得很差,而且其AOF文件也不兼容4.0版本之前的Redis。

15. 说说Redis客户端的架构?

(1) Redis是一种典型的一对多程序:一个服务器可以与多个客户端建立网络连接,客户端负责向服务器发出命令请求,而服务器负责接收和处理请求,并向客户端回复信息。

(2) 在Redis中,存在两类客户端。一种是伪客户端,比如在使用AOF文件还原数据库状态时就会创建一个伪客户端,它所发出的命令全都来自于AOF文件而不是网络。另一种是普通客户端,即命令请求来源于网络的其他客户端。

(3) 一个普通Redis客户端的背后角色多种多样,可以是用户主机、主从服务器、集群结点等等。但Redis服务器并不关心这些,它只站在自身的角度去处理客户端的所有请求。Redis以这种简单的处理模式完成了各种复杂的功能,保证了自身的高性能和轻量级。

16. 说说Redis客户端的发布/订阅功能?

(1) 发布/订阅功能实际上是Redis对于设计模式中的观察者模式的一种具体实现。客户端可以通过订阅一个或多个频道,从而成为这些频道的订阅者,每当有其他客户端向被订阅的频道发送消息时,频道的所有订阅者都会收到这条消息。

(2) 除了订阅频道之外,客户端还可以订阅一个或多个模式,从而成为这些模式的订阅者。一个模式会匹配若干个频道,并且在创建之时可以指定匹配关系。每当有其他客户端向某个频道发送消息时,消息不仅会被发送给这个频道的所有订阅者,它还会被发送给所有与这个频道相匹配的模式的订阅者。

17. 发布/订阅功能有什么缺点?

Redis可以利用发布/订阅功能来实现消息队列。但由于Redis服务器仅负责消息的转发,并不会主动保存客户端的消息,故这些消息无法被持久化,服务器也无法记录历史消息。而且一旦出现网络断开、Redis 意外关闭等情况时,消息就会被丢弃。故一般会采用Redis的Stream类型来实现消息队列,保证消息不丢失。

18. 说说Redis的事务?

Redis的事务可以一次性、有序地执行多个命令, 并且带有以下三个重要的保证:

(1) 在使用MULTI命令开启事务后,期间客户端发送所有的命令都不会被立即执行,而是被放入一个缓存队列,直到发送EXEC命令后才会顺序执行所有命令

(2) 收到EXEC命令后进入事务执行,即使某个命令执行失败,其余的命令依然会被执行

(3) 在事务执行过程中,其他客户端提交的命令请求不会被执行,直到事务执行完毕

19. Redis的事务具有ACID性质吗?

(1) Redis的事务具有原子性。事务的所有操作会被看作一个整体,但不具备事务回滚功能,即使某个命令出错也会执行完其余的命令。

(2) Redis的事务具有一致性。Redis在命令入队和事务执行时都会检查命令,错误的命令不会被执行,并且通过持久化功能也保证了即使Redis意外停机也不会出错。

(3) Redis的事务具有隔离性。Redis采用了单线程的方式执行事务,并且在执行事务期间也不会对事务进行中断,也就是以串行化的方式执行事务,因此事务总具有隔离性。

(4) Redis的事务具有不严格的持久性。事务持久性需要根据当前Redis的持久化方式来决定,Redis在这方面不做任何严格的保证。

20.为什么Redis的事务不支持事务回滚?

因为事务回滚这种复杂的功能和Redis追求简单高效的设计主旨不相符。而且Redis的作者认为,Redis事务的执行时错误通常都是在开发时由于编程错误产生的,而很少会在实际的生产环境中出现,没有必要为Redis开发事务回滚功能。

21. Redis的事务可以加锁吗?

(1) 虽然事务在执行期间不会被中断,但事务的命令在入队期间,服务器仍然可以接收和处理其他客户端的命令请求,这可能会导致数据更改从而使事务的某个命令执行失败。

(2) 我们可以在事务中加入WATCH命令给数据库加上乐观锁。它可以监视任意数量的Redis键值对,并在事务执行前检查这些键值对是否被修改。如果是的话,服务器将拒绝执行事务,并向客户端返回代表事务执行失败的空回复。

22. 说说Redis的监视器?

客户端可以通过执行MONITOR命令,将自己转换成监视器。每当服务器处理其他客户端的命令请求时,都会将关于这条命令请求的信息发送给所有监视器,由监视器负责接收打印。

集群与分布式

1. 说说Redis的主从复制和读写分离?

(1) Redis支持主从复制和读写分离。它可以将数据库存放在多个服务器上,主库负责写,从库负责读,一个主库可以连接多个从库,以此降低单个服务器数据库的压力和避免单点故障造成的严重影响。

(2) Redis使用完整重同步将数据从主库复制到从库。当从库想要复制主库时,主库会在后台生成一个RDB文件,并使用一个缓冲区记录从现在开始执行的所有写命令。主库生成RDB文件后会通过网络传给从库,从库通过接收并载入这个RDB文件来更新数据。最后主库再将缓冲区里最新的写命令发送给从库执行,这样就可以达到数据一致。

(3) 当主库与从库正常连接时,主库会通过命令传播来实时同步主从库。当主库执行写命令时,会将该命令发送给所有从库执行,以此保证主从库的数据一致。

(4) 如果主从库的网络连接断开,在下次重连成功后,会执行主从库的部分重同步。主库可以将连接断开期间所执行的写命令发送给从库,从库只要接收并执行这些写命令,就可以将数据库更新至主库当前所处的状态。部分重同步从Redis2.8版本开始支持

2. Redis的完整重同步和部分重同步之间的区别?

(1) 完整重同步和部分重同步都用于主从库的数据复制。其中完整重同步用于处理从库的初次复制,而部分重同步则用于处理断线重连后的从库复制。

(2) 完整重同步的安全性最好,但效率极低。每次都需要主库生成RDB文件并发送给从库,耗费主库的系统资源。并且从库在接收到RDB文件后会进入数据载入和恢复,进入阻塞态,不能处理客户端请求。

(3) 部分重同步的效率较高,但其前置条件也多,仅在Redis2.8及以上版本支持:

① 为了使用部分重同步,主库和从库都需要记录自己的复制偏移量。以此判断主从库数据是否一致,并计算出其中的未同步的差量。

② 主库需要维护一个固定长度的缓冲区队列,用于备份主库发送给从库的同步数据。执行部分重同步时会从该队列中获得主从库断开连接后未同步的数据,并发送给从库。之所以是固定长度,是因为过多的数据会影响部分重同步的效率,此时会执行完整重同步。

③ 最后从库还需要记录主库的服务器ID,以此验证主从库。当主库收到一个新从库的连接后,会判断从库之前保存的主库服务器ID是否与自己相同。若相同,则尝试执行部分重同步。若不同,则执行完整重同步,重置从库数据。

3. Redis的主从服务器在进行主从复制时的关系?

前文提过,Redis中只有服务器和客户端两种角色,在进行主从复制时也是一样。在主从复制初期,从库会成为主库的客户端,并通过向主库发送命令请求来执行复制步骤。而在主从复制后期,主从库会互相成为对方的客户端,可以相互发送命令请求来执行操作。

4. 说说Redis的主从复制中的心跳检测机制?

在主从库正常连接的情况下,从库默认会以每秒一次的频率,向主服务器发送心跳命令,它实际上就是一个带有从库当前复制偏移量的ACK确认消息。它可以检测当前主从库的网络连接状态,还可以快速恢复因网络故障而丢失的实时同步数据(通过对比复制偏离量判断是否丢失数据)。它于Redis2.8版本开始支持。

5. Redis的部分重同步为什么需要用服务器ID,用IP地址+端口号不行吗?

用IP地址+端口号确认主库是不靠谱的。如果主库重启,即使IP地址和端口号相同,也应该认为这是个新的主库,此时使用服务器ID来确认主库就非常合理。

6. 过期键在主从复制模式下是如何删除的?

当主库删除一个过期键后,它会命令所有从库显式地删除过期键。而从库即使发现过期键也不会主动删除它,而是等待主库的数据同步。这种统一、中心化的过期键删除策略可以保证主从服务器数据的一致性,但会增加过期键的时间误差。

7. Redis的哨兵(Sentinel)是什么?

哨兵(Sentinel)是Redis高可用的一种解决方案:由一个或多个哨兵实例组成的哨兵系统可以监视任意多个主库以及下属的所有从库。当某个主库下线时,会自动将该主库下的某个从库升级为新的主库,并且由该新主库代替已下线的主库继续处理命令请求。即使原来的主库重新上线,也会降级为新主库的从库,从而完成主库的故障转移,实现Redis的高可用。

8. 哨兵如何监视服务器?

(1) 启动哨兵和启动服务器类似,哨兵本质上只是一个运行在特殊模式下的Redis服务器,但是哨兵并不使用数据库,因此也不需要载入RDB或AOF文件。

(2) 哨兵在启用时会主动连接其他服务器,成为其他服务器的客户端。它可以向服务器发送命令并获取服务器的相关信息,通过实时维护这些服务器信息以监视服务器状态。

(3) 哨兵会与所有被监视的服务器(包括主从库)创建两个异步网络连接:命令连接(用于向其他服务器发送命令)和订阅连接(用于订阅服务器的__sentinel__:hello频道)。

(4) 在默认情况下,哨兵会以每两秒一次的频率,通过命令连接向所有被监视的服务器的sentinel:hello频道发送一条消息,其中包含了哨兵本身和被哨兵监视的主库的参数信息。这条消息会被监视同个服务器的其他哨兵的订阅连接所捕捉,这样多个哨兵就可以自动发现对方,并建立哨兵之间的网络连接,从而组成哨兵系统。

9. 为什么哨兵与服务器需要建立两个连接?

通过发布/订阅功能,多个哨兵可以互相发现,无需额外配置即可建立哨兵之间的网络连接,从而组成哨兵系统。但因为发布/订阅功能所传递的消息不支持持久化,客户端如果不事先建立网络连接并实时接收服务器转发的消息,就会丢失这个消息。因此哨兵在建立必要的命令连接之外,还必须专门用一个订阅连接来接收消息。

10. 哨兵与哨兵之间需要建立订阅连接吗?

不需要。订阅连接存在的目的是为了发现其他哨兵,但相互已知的哨兵只需要使用命令连接来进行通信就足够了。

11. 哨兵如何判断服务器的下线状态?

哨兵的一个重要职责就是监视服务器状态,判断某个主库下线后就会执行故障转移。哨兵的判断标准有主观下线和客观下线,只有判断主库为客观下线后才会执行故障转移。

(1) 主观下线:哨兵会以每秒一次的频率向被监视的服务器发送ping命令,并根据回复信息来判断是否主观下线。多个哨兵的判断标准可能不一样,比如一个哨兵与服务器断开一秒就认为服务器下线,另一个哨兵可能要与服务器断开十秒才认为服务器下线,这个属性可以在哨兵的配置文件中修改。

(2) 客观下线:当哨兵判断某个服务器主观下线后,会向监视同个服务器的其他哨兵询问,看它们是否认为服务器已经下线(可以是主观下线或客观下线)。如果从其他哨兵接收到足够数量的已下线判断之后,就会判定服务器为客观下线。多个哨兵的判断标准可能不一样,可以在哨兵配置文件中修改当前哨兵判断客观下线时所需的下线判断数量。

12. 哨兵如何执行服务器的故障转移?

(1) 在哨兵判断服务器客观下线后,需要执行服务器的故障转移。但在此之前需要进行哨兵选举,因为可能有多个哨兵监视同个服务器,为了避免多个哨兵之间互相干扰,必须要选举出领头哨兵来完成具体的故障转移操作。

(2) 哨兵选举使用了Raft算法来实现,它遵循以下几个规则:所有在线的哨兵都有成为领头哨兵的资格、每个发现服务器客观下线的哨兵都会主动争取成为领头哨兵、领头哨兵资格遵循先到先得的原则、如果某个哨兵获得了半数以上的其他哨兵推举则自动成为领头哨兵、在给定时限内没有选举成功则会在等待一段时间后重新选举直至成功。

(3) 选举出领头哨兵后,会进行具体的故障转移操作:领头哨兵会在已下线主库属下的所有从库中,挑选出一个状态最佳、数据最完整的从库作为新主库(挑选标准包括主从库断开的时长、从库优先级和从库的复制偏移量),然后修改其他从库的复制目标,让它们复制新主库的数据,即使旧主库上线也会自动降级成为新主库的从库。

13. 哨兵为什么需要设置多个?

哨兵是一种确保Redis高可用的机制,如果哨兵是单点的,那么一旦哨兵和主库同时出现故障,Redis就无法正常运行了。只有设置多个哨兵,并将其组成哨兵系统,这样即使单个哨兵出现故障也不会影响哨兵系统以及Redis的可用性。

14. Redis的集群是什么?

Redis的集群是Redis提供的分布式数据库方案,集群通过分片(sharding)来进行数据共享,并提供复制和故障转移功能。注意,Redis的集群实际是广义上的分布式系统,只不过在Redis中以集群命名,与广义的集群无关。

15. 如何构建Redis集群?

(1) 一个Redis集群通常由多个结点(node)组成,一个结点就是一个运行在集群模式下的Redis服务器,结点与普通的Redis服务器所提供的功能基本相同。

(2) 一开始每个结点都是独立的,它们都处于一个只包含自己的集群当中,管理员可以使用CLUSTER MEET命令来连接各个结点构成一个大的集群。

(3) 两个独立结点在收到连接命令后会通过三次握手来建立通信基础,这与计算机网络传输层的三次握手极为相似,只不过它发生在应用层,它的目的是为了在两个结点中构建对方的结点信息,方便后续的结点通信。若是集群中原先还有其他结点,则会让其他结点也与新结点进行握手,最后新结点会被集群中的所有结点认识。

16. Redis集群如何分片?

(1) Redis集群通过分片的方式来保存数据库中的键值对。集群的整个数据库被分为16384个槽(slot),数据库中的每个键值对都属于其中的一个槽,集群中的每个结点可以被指派并管理0~16384个槽。

(2) 当数据库中的所有槽都被指派给集群中的某个结点时,集群处于上线状态。相反,如果数据库中存在任何一个槽没有得到指派,那么集群处于下线状态

(3) 集群中的所有结点每隔一段时间都会进行信息交互,将自身负责的槽信息传递给其他结点。因此集群中的所有结点都知道每个槽的对应结点

17. Redis集群如何执行客户端的命令?

(1) 在对数据库中的所有槽都进行了指派之后,集群就会进入上线状态,这时客户端就可以向集群中的结点发送命令请求了。

(2) 当客户端向结点发送命令时,结点会计算出命令要处理的数据库键属于哪个槽,并检查这个槽是否指派给了自己。如果这个槽由自己管理,那么当前结点就会直接执行该命令。如果这个槽由其他结点管理,那么当前结点会向客户端返回一个MOVED错误,指引客户端重定向至正确的结点,并让客户端再次发送之前想要执行的命令。

(3) 集群模式下的客户端在接收到MOVED错误时会自动进行重定向,并打印出转向信息。而如果使用普通模式下的客户端,那么在遇到MOVED错误时会由于无法识别,不进行重定向操作并直接报错。

18. 说说Redis集群的重新分片?

(1) Redis可以将已经指派给某个结点的槽指派给另一个结点,相关槽所对应的键值对也会进行转移,并且不限制槽的转移数量,这就是Redis集群的重新分片

(2) 在重新分片期间,集群不需要下线,并且正在发生槽转移的结点也可以正常响应客户端请求,具有很高的灵活性。

19. 说说Redis集群的ASK错误?

在重新分片期间,也就是迁移槽的过程中,可能会导致某个槽所包含的键值对被分散在两个结点中。为了解决这个情况,当结点发现客户端要查询一个被迁移的槽中的键值对时,会先在自己的数据库里面查找。若未找到,则会返回一个ASK错误,指引客户端重定向至正在导入槽的另一个结点,并让客户端再次发送之前想要执行的命令。

20. MOVED错误和ASK错误的区别?

ASK错误和MOVED错误都会导致客户端转向,它们的区别是:

(1) MOVED错误代表槽的负责权已经被转移了。客户端收到MOVED错误后,下次再遇到关于该槽的命令请求时,都可以直接将命令请求发送至负责该槽的新结点。

(2) ASK错误只是迁移槽的过程中使用的临时措施。客户端收到ASK错误后,只会在接下来的一次命令请求中将其发送给新结点。但这种转向不具有永久性,下次再遇到关于该槽的命令请求时,客户端仍会发送给旧结点,除非再次遇到ASK错误或MOVED错误。

21. 说说Redis集群中的主从复制和故障转移?

(1) 在Redis集群中,可以对某个结点设置主结点和从结点,并且主从结点之间与主从库一样具有主从复制和故障转移功能。

(2) 当某个结点为从结点时,该从结点就会自动去复制主结点的数据,并实时同步。集群中的所有结点都会收到消息,得知某个结点是从结点,且正在复制集群中某个主结点。

(3) 当某个主结点下线时,其他主结点就会通知已下线主结点属下的所有从结点,并让从结点通过Raft算法选举一个领头从结点,它将成为新的主结点并重新指派槽,以此完成故障转移。

22. 集群结点的故障转移与主从库的故障转移有何不同?

主从库自己不能完成故障转移,需要借助哨兵。集群则比较特殊,由于集群中的各个结点会定时地互相发送消息,以此来交换彼此的状态信息。因此对于一个多结点的集群来说无需单独设置哨兵,所有的结点都可以成为哨兵并监视其他结点。除此之外,整个故障转移的过程与哨兵非常类似,可以说集群内部已经集成了主从复制和哨兵功能。

23. Redis在集群模式下是否可以切换数据库?

不可以。集群下的结点无法切换数据库,只能使用默认的0号数据库。

24. 什么叫高可用和高扩展?

(1) 高可用:一个系统必须保证在出现任何问题的情况下,还能够提供正常的服务,我们就称这个系统为高可用。Redis通过主从复制和哨兵机制保证了高可用。主从复制使得即使数据库具有多个从库备份,即使某个从库出现故障,也不会影响其他从库的使用。哨兵机制则保证了即使主库出现故障,哨兵也可以及时进行故障转移,建立新主库。

(2) 高扩展:一个系统如果可以在功能和规模上具有不断扩张和增强的能力,我们就称这个系统为高扩展。Redis通过集群提供了数据规模的高扩展性。在单个数据库因内存不足无法存储海量数据时,可以使用集群进行分片存储,使得数据规模不断扩展。

缓存与分布式锁

1. 什么是缓存?

缓存就是数据交换的缓冲区,可以存放临时数据,一般读写性能较高。通过引入缓存可以提高读写效率,降低后端服务器负载和客户端的响应时间,但与此同时会带来额外的维护成本,如数据一致性成本、缓存运行成本。

2. 说说Redis缓存模型?

Redis经常作为客户端与MySQL数据库通信时的中间缓存。在没有引入Redis之前,客户端会直接与MySQL进行数据交换。而在引入Redis缓存之后,客户端每次都会先去Redis中查找数据,如果未命中才会去MySQL数据库中查找,并写入到Redis中,下次再需要相同的数据时就可以在Redis中获取。

3. Redis缓存如何维护数据一致性?

Redis的数据一致性指的是Redis与MySQL的数据需要保持一致,Redis必须要及时清理无效数据和更新最新数据。Redis通过三种不同的缓存更新策略来保证数据一致性。

4. Redis缓存有多少种主动更新策略?

Redis缓存的主动更新策略大体上可分为三种。

5. 说说Redis缓存的Cache Aside(旁路缓存)策略?

Cache Aside(旁路缓存)策略是最常用的主动更新策略,应用程序(调用者)直接与「数据库、缓存」交互,并负责对缓存的维护,该策略又可以细分为「读策略」和「写策略」

(1) 读策略:从缓存中读取数据->如果缓存命中,则直接返回数据->如果缓存不命中,则从数据库中查询数据->查询到数据后,将数据写入到缓存中,并且返回给用户。

(2) 写策略:更新数据库->删除缓存。

6. 为什么Cache Aside策略要删除而不是更新缓存?

(1) 缓存的更新有时候是复杂的。比如现在可能更新了MySQL某个表的一个字段,但是客户端所查询的缓存需要额外查询其他表的数据,并通过计算才能获取最新的缓存值。显然,缓存更新的成本会比较高,使用简单的删除可以提高运行效率。

(2) 频繁地更新缓存也许是无用功。比如现在客户端可能需要频繁地修改某个数据,但却不需要去读取它,那么实时更新缓存就毫无意义。如果直接删除缓存,客户端在需要查询时也仅需要查询MySQL一次,显然这种策略更加合理。

7. Cache Aside策略能先删除缓存再更新数据库吗?

(1) 无论删除缓存和更新数据库的步骤顺序如何调整,都可能导致数据不一致,如下图所示。

(2) 由于缓存的写入通常要远快于数据库的写入,如果先删除缓存,那么再更新数据库之前,其他请求会有足够的时间去读取数据库的旧数据并更新缓存,导致数据不一致。相反,如果先更新数据库,那么只可能出现一种极端情况,那就是在其他请求查询完数据库的旧数据并写入缓存的这段时间中,出现另一个请求对数据库进行了更新,导致数据不一致。但是这段时间非常短,难以满足数据库的写入要求,出现概率很低,因此一般会先更新数据库,再删除缓存。

8. 说说Redis缓存的Read/Write Through(读穿/写穿)策略?

Read/Write Through(读穿/写穿)策略会将缓存和数据库合为一个服务,由服务维护一致性。应用程序(调用者)不必关心底层的数据一致性问题,仅需与服务进行交互即可。但由于Redis无法自动写入和加载MySQL数据库中的数据,因此不支持该策略。

9. 说说Redis缓存的Write Behind Caching(写回)策略?

Write Behind Caching(写回)策略在写入数据时只更新缓存,同时将缓存数据设置为脏数据,然后立马返回,并不会更新数据库。之后定期地通过批量异步的方式更新数据库的数据,适合应用在写多的场景下。这种方式会使得数据一致性变得非常差,而且一旦服务器死机,可能会导致大量数据丢失,因此在Redis中并没有实现该策略。

10. 说说Redis缓存的内存淘汰策略?

Redis的数据受到内存空间限制,当数据量达到一定程度时就会执行内存淘汰。Redis内置了六种内存淘汰策略,无需额外的维护成本便可保证一定程度上的数据一致性。

(1) voltile-lru:从已设置过期时间的数据集中挑选最近最少使用的数据淘汰

(2) volatile-ttl:从已设置过期时间的数据集中挑选即将要过期的数据淘汰

(3) volatile-random:从已设置过期时间的数据集中任意选择数据淘汰

(4) allkeys-lru:从所有数据集中挑选最近最少使用的数据淘汰

(5) allkeys-random:从所有数据集中任意选择数据淘汰

(6) no-enviction:禁止淘汰数据

11. 什么是分布式锁?

(1) 分布式锁是用来控制一个分布式系统各结点之间同步访问共享资源的一种方式。与单机锁不同,分布式锁应用在分布式系统中,系统中的各个结点不像单机中的线程或进程一样运行在同一个操作系统中,因此想要实现分布式锁必须借助一个外部系统。

(2) 分布式系统中的所有结点都需要去这个外部系统申请锁,并且这个外部系统还需要具备互斥能力,即同一时间仅可以给一个结点加锁成功。Redis可以很好地满足这个特性,它作为一个共享存储系统,本身可以被多个客户端共享访问,很适合用来保存分布式锁。而且 Redis 的读写性能高,可以应对高并发的锁操作场景。

12. 如何使用Redis实现分布式锁?

(1) Redis可以使用键值对来保存锁变量,锁变量的名字作为键值对的键,锁变量的值作为键值对的值。

(2) 若想要具有互斥性,可以使用SETNX命令。如果key不存在才会设置它的值,否则什么也不做。多个客户端可以并发执行这个命令,实现一个互斥的分布式锁。

13. 如何避免Redis分布式锁的饥饿现象?

(1) 当某个客户端获得锁后,如果因为某些异常原因没有及时释放锁,导致其它客户端永远无法获得锁,产生饥饿现象。

(2) 我们可以给锁加上过期时间,保证了锁会在某段时间后自动释放。由于加锁和设置过期时间是两条命令,可能加锁成功但后面设置过期时间失败了,仍存在导致饥饿的风险

(3) 在Redis2.6以上版本,Redis可以在设置键值对的同时设置过期时间,它们被集成为一个原子操作,避免了饥饿的潜在风险

14. 如何合理设置Redis分布式锁的过期时间?

(1) 通过给锁加上过期时间,可以避免饥饿现象,但这个过期时间必须经过合理的设置

(2) 如果过期时间太短,会导致当前客户端还未执行完业务,锁就被提前释放了,出现严重的数据差错。

(3) 如果过期时间太长,一旦客户端不及时释放锁,其他客户端必须等待漫长的过期时间才能获得锁,降低了整个分布式系统的运行效率。

(4) 为了解决这个问题,我们可以在加锁时,先设置一个比较短的预估过期时间,然后在客户端开启一个守护线程,定时去检测这个锁的失效时间。如果锁快要过期了,客户端对共享资源的访问还未完成,那么就自动对锁进行续期。

15. 如何避免Redis分布式锁被其他客户端释放?

(1) 如果当前客户端的某个锁因为过期时间到达被提前释放,然后又被其他客户端重新获取,当前客户端在访问完共享资源后可能会手动释放这个不属于自己的锁。

(2) 造成这种现象的原因是Redis的分布式锁操作单一,Redis不会主动检查这把锁的归属,可以随意被任意客户端释放。

(3) 解决方法是加锁时设置一个归属标识,并在解锁时通过编程语言进行检查。由于Redis支持原子性地执行一个Lua脚本的Redis命令,因此一般由Lua脚本来完成该任务

16. 如果Redis本身也是分布式的,如何实现分布式锁?

(1) 由于Redis支持分布式和集群架构,如果实现分布式锁的Redis本身也是分布式,则会面对更多的锁安全问题。虽然Redis保证了高可用性,但并没有对数据完整性做严格的保证,主库上的锁可能还没来得及同步到从库上就发生主库故障,导致锁丢失。

(2) 我们可以使用分布式锁算法RedLock来实现可靠的分布式锁,它一般由Lua脚本实现。RedLock让客户端和Redis集群中的多个独立结点依次请求加锁,如果客户端能和半数以上的结点成功完成加锁操作,才认为加锁成功,否则加锁失败。

(3) 该算法会在加锁操作上花费比较多的额外时间,但可以换取比较好的可靠性,即使某个结点发生故障,只要大多数结点还在正常运行,锁就不会丢失。

(4) RedLock仍然需要设置锁的过期时间,如果获取锁后的剩余时间不够完成接下来的共享资源访问操作,则会提前释放锁,之后再用一个更大的预估过期时间去申请锁。

(5) 与加锁操作不同,当客户端想要释放锁时,需要向Redis集群中的所有结点发起解锁操作。由于可能存在的网络波动,某个Redis结点已经成功加锁,但客户端没收到成功响应,因此认为加锁失败。如果不及时释放会导致锁残留,所以不管是否加锁成功,都必须释放Redis集群中所有结点上的锁。

17. 什么是布隆过滤器?

(1) 布隆过滤器(Bloom Filter)是一种数据结构,它可以快速检查一个元素是否属于某个集合中,而且不会占用太多内存空间,经常用于解决Redis的缓存雪崩。

(2) 添加元素到布隆过滤器:创建一个所有位为0的位数组并设定k个哈希函数,对要添加的元素进行k次哈希操作,得到k个哈希值,最后将位数组中这k个位置的值置1。

(3) 查询某个元素是否在布隆过滤器中:对要查询的元素进行k次哈希操作,得到k个哈希值,查询位数组中这k个位置的值是否都为1。如果是,则认为该元素在集合中;否则,认为该元素不在集合中。

(4) 优点:效率极高,时间和空间复杂度只取决于哈希函数的数量。支持高并发,可以在多线程环境下使用。易于实现,只要几个哈希函数和一个位数组即可。

(5) 缺点:可能出现误判,即把不在集合中的元素判断为在集合中。但不会漏判,即不会把在集合中的元素判断为不在集合中。由于哈希函数不具有可逆性,所以无法删除已添加的元素

18. 什么是缓存雪崩?

(1) 缓存雪崩指的是在Redis中存储的缓存在同一时间内出现大面积失效,或者Redis服务崩溃,导致后续的客户端请求都会落到MySQL数据库上,造成MySQL短时间内承受大量请求而崩溃。

(2) 解决办法:1、尽量保证整个服务器系统的高可用性,发现机器崩溃应及时执行故障转移。2、在客户端本地添加适当的缓存,并对客户端限流。3、减少MySQL的写线程数量,避免大量并发写线程使MySQL崩溃。

19. 什么是缓存穿透?

(1) 缓存穿透指的是客户端不断查询一个一定不存在的数据,由于Redis缓存不命中,接着查询MySQL数据库也无法查询出结果,因此也不会更新Redis缓存。这个过程会不断重复,导致Redis和MySQL短时间内承受大量压力。该现象一般出自于网络黑客的攻击

(2) 解决办法:1、使用布隆过滤器,在控制层先进行筛选,降低了底层存储系统的查询压力,但存在一定的误判率,准确性不高。2、缓存一个过期时间很短的空对象并返回,但过多的空对象会占用大量缓存空间,而且还会导致缓存和数据库的数据不一致。

20. 什么是缓存击穿?

(1) 缓存击穿指的是一个被高并发访问并且缓存重建业务较复杂的热点Redis缓存突然失效了,导致后续的客户端请求会在瞬间直接访问MySQL数据库,造成大量访问压力。

(2) 解决方法:1、在客户端获取热点缓存失败时申请分布式锁,直到缓存重建成功再释放,但会因此阻塞其他客户端,降低并发度。2、设置热点缓存为永不过期,但过多的不过期键会导致内存空间紧张,所以一般会由Redis的内存淘汰策略负责清理。

21. 什么是缓存预热?

缓存预热是指在系统上线后,将一些数据提前从数据库加载到缓存。这样可以降低用户直接查询数据库的次数,转而直接获取事先被预热的缓存数据。

更新记录

更新日期 更新详情
(2023年8月17日) 归纳总结了Redis的数据类型、数据库与键值对、客户端与服务器、集群与分布式、缓存与分布式锁等常见知识点,并根据实际总结了Redis的一些场景应用。
(2023年8月31日) 将各个大板块重新编号,现在各个版块都有独立的题目编号,互不干扰。将更新记录中的版本号替换为年份日期,取消版本号机制,方便记录更新。简化笔记名称,去掉“非关系型数据库”字样。
(2023年09月17日) 修改首页文字布局,统一化布局。修改前言。添加前提基础模块。更改正文和标题字体。更新目录。修改参考资料。所有的更新日期都添加前置0,统一长度。将笔记改名为“数据库(Redis)”。将“Redis服务器”和“Redis客户端”两个模块整合为“服务器与客户端”模块。

参考资料

《Redis》:https://interviewguide.cn/notes/03-hunting_job/02-interview/04-02-01-Redis.html

《【GeekHour】一小时Redis教程》:https://www.bilibili.com/video/BV1Jj411D7oG

《Redis 教程》:https://www.runoob.com/redis/redis-tutorial.html

《Redis 设计与实现》:http://redisbook.com/

《Redis是单线程还是多线程,为什么效率这么高?》:https://zhuanlan.zhihu.com/p/516939154

《Redis 持久化——混合持久化》:https://zhuanlan.zhihu.com/p/462906147

《硬核|Redis 布隆…》:https://baijiahao.baidu.com/s?id=1760676476679974031&wfr=spider&for=pc

《redis 三种缓存更新策略》:https://blog.csdn.net/sanylove/article/details/131625749

《Redis实战——缓存》:https://blog.csdn.net/qq_59212867/article/details/128007094

《如何用Redis实现分布式锁》:https://blog.csdn.net/fuzhongmin05/article/details/119251590


数据库(Redis)笔记与总结
http://example.com/2023/09/17/数据库(Redis)笔记与总结/
作者
苏青羽
发布于
2023年9月17日
许可协议