打开网易新闻 查看精彩图片

追根究底Redis,面试过程真的很好。

艾分项网易云Vue3实战商城后台管理系统开发

download:https://www.zxit666.com/5214/

在Web应用开发的初期,一个网站本身的访问量并不是很高,直接使用关系型数据库就可以应对大部分场景。然而,随着互联网时代的兴起,人们对网站访问速度的要求越来越高,直接使用关系数据库的方案性能成为瓶颈。所以客户端和数据层之间需要一个缓存层来分担请求压力。Redis作为一款优秀的缓存中间件,在企业架构中占据着重要的地位,所以Redis也是面试中的必考项目。
本文通过30个问题,最大程度地涵盖了Redis的全部问答内容。
Redis(远程字典服务器)是一个开源的键值对数据存储系统。用C语言编写,符合BSD协议,可以基于内存也可以持久化的日志数据库,提供多种语言的API,广泛应用于数据库、缓存、消息中间件。并且支持各种类型的数据结构来处理各种场景。它可以存储不同类型的值之间的映射,支持事务、持久性、LUA脚本和各种集群模式。
优势:
完全基于内存的操作,性能高,读写速度快,Redis可以支持超过100 KB/s的读写速率。
支持高并发和10万级并发读写。
支持主从模式,读写分离和分配。
丰富的数据类型和丰富的功能(发布和订阅模式)
支持持久化操作,不丢失数据。
缺点:
数据库的容量受限于物理内存,无法高性能读写海量数据。
与关系数据库相比,不支持复杂的逻辑查询,存储结构相对简单。
虽然提供了持久化能力,但实际上更多的是一种磁盘备份的功能,与传统意义上的持久化不同。

打开网易新闻 查看精彩图片

Memcache也是一个开源的、高性能的分布式内存对象缓存系统。所有数据都存储在内存中,在服务器重启后会消失。需要重新加载数据。所有数据以哈希表的方式缓存在内存中,并使用LRU算法逐步删除过期数据。
数据类型:Memcache只支持字符串类型,Redis支持5种不同的数据类型。
数据持久化:Memcache不支持持久化,而Redis支持两种持久化策略,RDB快照和AOF日志。
分布式:Memcache不支持分布式存储,只有在客户端使用一致哈希才能实现分布式存储。Redis3.0之后,可以在服务器端构建分布式存储。Redis集群没有中心节点,所有节点地位平等,具有线性和可扩展的功能。
内存管理机制:Memcache数据量不能超过系统内存,但内存大小可以调整,淘汰策略采用LRU算法。Redis增加了VM特性,实现了物理内存的限制,它们的底层实现和客户端之间通信的应用协议都不一样。
数据大小限制:Memcache对单个键值的大小有限制,Value的最大容量为1MB,Redis的最大容量为512 MB。
基本数据类型:
字符串(字符串)
哈希(哈希)
列表(列表)
集合(集合)
有序集
高级数据类型:
超对数:一种用于进行基数统计的算法。当输入元素的数量或体积非常大时,计算基数所需的空间总是固定的并且非常小。HyperLogLog只根据输入元素计算基数,而不存储输入元素本身。
Geo:用于存储和计算地理位置。
位图:其实并不是一种特殊的存储结构,本质上是一个二进制字符串,可以进行位操作,常用于统计日活跃用户。
扩展:geohash通过算法将一个位置的经度和纬度两个值转换成一个哈希字符串。如果两个地方之间的距离更近,它们的哈希值的前缀将是相同的。
Redis的底层实现了简单的动态字符串(SDS)来表示字符串类型。C语言中没有直接定义的字符串类型。
SDS实现了相对于c语言字符串模式的改进。
避免去除缓冲液。修改字符时,可以根据len属性检查空格是否符合要求。
获取字符串长度的复杂性很低。
减少内存分配时间。
C字符串函数兼容,C语言函数库的部分函数可以重用。
Redis通过直接存储在内存中,可以达到最快的读写速度。如果开启持久化,数据会异步写入磁盘,所以Redis具有数据快速持久的特点。
内存中的操作比磁盘中的操作更快,并且不受磁盘I/O速度的影响。如果将数据保存到磁盘而不是内存中,磁盘I/O速度会严重影响Redis的性能,如果数据集大小达到内存的最大限制,则无法插入新值。
如果开启了虚拟内存功能,当内存用完时,Redis会将那些不常用的数据存储到磁盘上。如果Redis中的虚拟内存被禁用,会对系统的虚拟内存(交换内存)进行操作,但随后Redis的性能会急剧下降。如果配置了失效机制,旧数据将根据配置的数据失效机制失效。
1.尽可能使用哈希表(Hash数据结构):Redis在存储100个字段以下的哈希结构时,存储效率非常高。所以,当不需要对链表进行set操作或push/pop操作时,应该尽量使用hash结构。
2.根据业务场景考虑使用位图。
3.充分利用共享对象池:Redis启动时会自动创建一个[0-9999]的整数对象池。对于0-9999的内部整数类型元素,整数值对象会直接引用整数对象池中的对象,所以尽量使用0-9999的整数对象来节省内存。
4.合理使用内存回收策略:清除过期数据,通过expire设置数据过期时间等。

打开网易新闻 查看精彩图片

Redis可以使用INCR、SETNX、SET来实现分布式锁,并使用expiration命令expire作为辅助。
1:使用INCR
如果键不存在,初始化值为0,然后用INCR加1。如果后续用户获得的值大于或等于1,则意味着它已被其他线程锁定。当持有锁的用户完成任务时,使用DECR命令将key的值减1,这意味着释放锁。
2:使用SETNX
首先使用setnx来争夺锁,然后使用expire来设置过期时间,以防止锁被释放。setnx的意义是将键设置为value,如果键不存在则返回1。如果它已经存在,什么也不做,返回0。
3:使用设置
Set命令有非常复杂的参数,相当于组合了setnx和expire两个命令的功能。命令格式设置($ key,$ value,array ('NX ',' ex' = > $ TTL))。
完全基于内存
数据结构简单,操作方便,不同的数据结构可以应对不同的场景。
采用单线程(网络请求模块使用单线程,其他模块仍然使用多线程)避免了不必要的上下文切换和争用情况,不存在多进程或多线程切换带来的CPU消耗,因此不需要考虑各种锁。
使用多路输入/输出模型,它是非阻塞输入/输出。
Redis本身设置了VM机制,不使用OS Swap,可以实现冷热数据分离,避免内存不足导致的访问速度下降问题。
1.RDB的持久性(Redis数据库)
Rdb是Redis中默认的持久化机制,将内存中的数据在一定时间内以快照的形式保存到磁盘上。它将产生一个特殊类型的文件。RDB文件,快照周期可以由配置文件中的保存参数定义。
在RDB有两个核心概念,叉子和牛。备份过程如下:
当执行bgsave时,Redis将分叉主进程以获得一个新的子进程。子进程将共享主进程的内存数据,并将数据写入临时的。磁盘上的rdb文件。当子进程写完临时文件时,它将替换原来的文件。rdb文件。这就是叉子的概念。cow的全称是copy-on-write。当主进程执行读操作时,它访问共享存储器,而当主进程执行写操作时,它复制数据的副本并执行写操作。
优势
只有一个文件dump.rdb,方便持久化。
良好的容错性,可以将文件保存到安全的磁盘上。
为了最大化性能,fork通过单独的子进程完成持久化,主进程继续处理命令,主进程不执行任何I/O操作,保证了Redis的高性能。
RDB是紧凑的二进制文化,RDB重启时加载效率比AOF高,尤其是数据量大的时候。
缺点
可能会发生数据丢失。在两次RDB持久化之间的间隔中,如果有停机,这段时间内的数据将会丢失。
由于RDB通过fork子进程辅助数据持久化,如果数据集很大,可能会导致整个服务器间歇性暂停服务。
2.AOF的持久性(仅附加文件)
AOF的全名是仅附加文件。Redis处理的每个写命令都会被记录在AOF文件中,这个文件可以看作是一个命令日志文件。此方法需要设置AOF的同步选项,因为写入文件不会立即将内容同步到磁盘,而是先将其存储在缓冲区中。同步选项有三个配置项:
一直:同步刷机,可靠性高,但对性能影响大。
Everysec:每秒刷一次磁盘,性能中等,最多丢失1秒数据。
否:操作系统控制,性能最好,可靠性最差。
为了解决aof文件物理检查增加的问题,用户可以向Redis发送bgrewriteaof命令,压缩AOF文件,或者自动触发并在配置文件中进行配置。
自动重写百分比100
auto-aof-rewrite-min-zise 64mb
复制代码
优势
实现持久性、数据安全性和AOF持久性。appendfsync属性可以配置为always,每个命令操作都会在AOF文件中记录一次,数据最多丢失一次。
通过append模式写文件,即使服务器中途宕机,也可以通过Redis-check-aof工具解决数据一致性问题。
AOF机制的重写模式。当AOF文件的文件大小达到临界点时,将运行重写模式重写内存中的所有数据,从而减小文件大小。
缺点
AOF文件很大,通常比RDB文件大得多。
启动效率低于RDB持久性,在数据集较大时更明显。
AOF文件大小可能会迅速增加,因此有必要定期重写以减小文件大小。
1:定期删除。
在设置密钥到期时间的同时,将创建一个定时器计时器。当密钥到期时,计时器会立即删除密钥。
特点:内存友好,CPU不友好。当有很多过期密钥时,使用定时器删除过期密钥会占用相当多的CPU。
2:懒删
不使用密钥时,不管密钥是否过期,每次使用时只会检查密钥是否过期,过期则删除。
特点:CPU友好,内存不友好。不会花费额外的CPU资源来检测密钥是否过期,但是如果有很多未使用的和过期的密钥,则占用的内存不会被释放。
3:定期删除。
每隔一段时间会检查数据库删除里面过期的密钥,检查多少个数据库由算法决定。
特点:定期删除是上述两种过期策略的折中,即内存友好和CPU友好的折中。每隔一段时间执行一次删除过期密钥的任务,通过限制操作的持续时间和频率来减少对CPU时间的占用。
Redis主从同步分为增量同步和全量同步。Redis将首先尝试增量同步,如果不成功,将执行全量同步。
同步:
当从服务器初始化并开始正常工作时,将主服务器的写操作同步到从服务器的过程。同步的过程是主服务器每执行一次写命令,就向从服务器发送相同的写命令。
完全同步:
当从服务器初始化时,它将向主服务器发送一个psync命令。如果是第一次同步,主服务器将执行bgsave,并将后续修改操作记录在内存缓冲区中。bgsave完成后,RDB文件将完全同步到从属服务器。从服务器收到后,RDB快照将被加载到内存中,然后写入本地磁盘。处理完成后,会通知主服务器将修改后的操作记录同步到复制节点进行回放,这样就完成了整个完整的同步过程。
在Redis中,最大内存大小由Redis.conf中的参数maxmemory决定。默认值为0,这意味着没有限制。这时候其实就相当于现在系统的内存了。但是,随着数据的增加,如果内存中的数据没有管理机制,当数据集大小达到或超过最大内存大小时,Redis就会崩溃。因此,需要内存数据消除机制。
设置到期时间
Volatile-lru:尝试回收最少使用的密钥。
Volatile-random:回收随机密钥。
Volatile-ttl:优先考虑生存期短的密钥。
没有过期时间。
尝试回收最少使用的密钥。
随机:回收随机密钥
Noeviction:当内存达到极限并且客户端试图添加时,将会返回一个错误。
淘汰策略规则
如果数据呈现幂律分布,即有些数据访问频繁,有些数据访问不频繁,那么就使用allKeys-lru。
如果数据是平均分布的,即所有数据的访问频率大致相同,则使用allKeys-random。
关于lru策略,Redis并不完全删除所有密钥中最近最少使用的密钥,而是随机选择五个密钥(数量由参数maxmemory-samples决定,默认值为5),删除这五个密钥中最近最少使用的密钥。
1:缓存渗透率
缓存穿透是指缓存和数据库中不可用的数据,导致所有请求都落在数据库上,导致数据库短时间内承担大量请求,造成宕机。
解决:
使用Bloom filter:将所有查询到的参数存储在位图中。查询缓存前,如果位图存在,查询底层缓存数据;如果不是,则截取它,并且不查询缓存的数据。
空缓存对象:如果数据库查询为空,数据仍将被缓存,过期时间将被设置。当数据库被多次访问时,可以直接返回结果,避免多次访问数据库,但在数据库有数据时要及时更新缓存。
2:高速缓存故障
缓存崩溃是指缓存中没有数据但数据库中有数据(通常在缓存时间到期时),这将导致所有请求都落在数据库上,导致数据库中出现大量请求和宕机。
解决:
将热点数据设置为永不过期。
您可以使用互斥更新来确保同一进程中的相同数据不会并发请求到DB,从而减轻DB的压力。
使用随机退避方法,失败时随机休眠一小段时间,再次查询,失败时再次更新。
问题3:缓存雪崩
缓存雪崩是指大量缓存同时大面积失效,后续请求会落在数据库上,导致数据库在一段时间内无法承受大量请求而失效。
解决:
缓存失败后,读取数据库和写入缓存的线程数量由锁定或排队控制。例如,对于某个键,只允许一个线程查询和写缓存,而其他线程等待。
通过缓存重载机制,提前更新缓存,在高并发访问即将发生之前,手动触发缓存加载。
为不同的键设置不同的过期时间,使缓存过期时间尽可能统一。比如我们可以在原来的过期时间的基础上增加一个随机值,比如1~5分钟随机,这样每次缓存过期时间的重复率就会降低。
设置二级缓存或双缓存策略。
缓存降级,其实都应该意味着服务降级。在流量快速增加、服务响应问题(如响应延迟或无响应)或非核心服务影响核心进程性能的情况下,仍然需要保证核心服务的可用性,虽然部分非主要服务可能不可用,然后可以采取服务降级策略。
服务降级的最终目的是确保核心服务的可用性,即使这些服务被损坏。服务降级要提前确定降级方案,确定哪些服务可以降级,哪些服务不可以降级。根据当前的业务情况和流量,对部分服务和页面进行策略降级,以释放服务器资源,保证核心服务的正常运行。
降级往往指定不同的级别,面对不同的异常级别执行不同的处理。根据服务方式:可以拒绝服务、延迟服务、随机提供服务。根据服务范围:可以暂时禁用某些功能或禁用某些功能模块。简而言之,服务降级需要根据不同的业务需求采取不同的降级策略。主要目的是虽然服务不好,但是聊胜于无。
实时数据同步无效或已更新。这是一种增量式的主动方案,可以保证数据的强一致性,在数据库数据更新后主动请求缓存更新。
数据的异步更新。这是一种增量被动方案,数据一致性弱,数据更新延迟。数据库数据更新后,可以通过异步模式、多线程模式或消息队列进行更新。
计划任务更新。这是一种增量/全卷被动方案,数据一致性最差,因为调度任务以一定的频率更新。
直接写一个缓存刷新页面,上线的时候手动做。
数据量小,项目启动时可以自动加载。
定时刷新缓存
Sentinel是一个监控Redis集群中主从状态的工具,是Redis的高可用性解决方案。
主要功能
监视器。Sentry会不断检查用户的主从是否正常工作。
提醒一下。当受监控的Redis节点出现问题时,sentinel可以通过API向管理员或其他应用程序发送通知。
自动故障转移。当一个主设备无法正常工作时,哨兵将启动自动故障转移操作,将集群中的一个从设备提升为新的主设备,并使其他从设备改为与新的主设备同步。当客户端尝试连接到发生故障的主服务器时,群集也希望客户端返回新的主服务器地址。当主从服务器切换时,新的主服务器的Redis.conf、从服务器的Redis.conf和Sentinel的Redis.conf的配置文件都会发生相应的变化。
问题背景
Redis是基于TCP协议的请求/响应服务器。每一次通信都要经过TCP协议的三次握手,所以当要执行的命令足够复杂时,会造成很大的网络延迟,而且还不包括网络传输时间开销和服务器开销,所以总延迟可能更大。
管道解决方案
管道主要是解决这种情况。使用流水线模式,客户端可以一次发送多个命令,无需等待服务器返回,可以将多个I/O往返的时间缩短为一个,大大减少网络往返时间,提高系统性能。
流水线是基于队列实现的,基于先进先出的原则,满足数据顺序。如果同时提交了许多命令,队列需要大量内存来组织返回的数据内容。如果大量使用管道,则应该合理地批量提交命令。
管道默认同步数为53,累积到53条数据时提交。
注意:Redis集群不能使用管道,对可靠性要求高。每个操作都需要立即获得该操作的结果的场景不适合流水线。
Master最好不要持久化RDB,因为此时save命令调度rdbSave函数,会阻塞主线程的工作,当数据集较大时,可能会导致主线程间歇性暂停服务。
如果数据很重要,在一个从节点上打开AOF数据备份,策略设置为每秒一次。
为了主从复制的速度和连接的稳定性,主从应该在同一个局域网内。
尽量避免在操作压力大的主库上增加从库。
主从复制不应该使用图形结构,但是单向链表结构更稳定。Mater->Slave1->Slave2->Slave3...这种结构便于解决单点故障问题,实现从机替代主机。如果主设备崩溃,可以立即启用从设备1来替换主设备,而其他依赖关系保持不变。
总结:
跳转表是一个可以实现二分搜索法的有序链表。
每个元素在插入时都会随机生成其级别。
底层包含所有元素。
如果一个元素出现在级别(x)上,那么它必须出现在低于x的级别上。
每个索引节点包含两个指针,一个向下,一个向右。
跳转表的查询、插入和删除的时间复杂度为O(log n),接近平衡二叉树。
为什么不选择红黑树来实现呢?
让我们首先分析Redis有序集支持的操作:
插入元素
删除元素
查找元素
按顺序输出所有元素。
找出区间中的所有元素。
其中前四棵红黑树可以完成,时间复杂度与跳表一致,但最后一棵红黑树的效率没有跳表高。在跳转表中,要找到一个区间的元素,只需定位最底层的两个区间端点,然后依次遍历元素,效率非常高。