持续输出面试题之Redis篇
开篇介绍
大家好,我是Java最全面试题库
的提裤姐,今天这篇是数据库面试题系列的第五篇,主要总结了Redis相关的面试题;在后续,会沿着第一篇开篇的知识线路一直总结下去,做到日更!如果我能做到百日百更,希望你也可以跟着百日百刷,一百天养成一个好习惯。
redis的数据类型有哪些?
String
常规的set/get操作,value可以是String也可以是数字。
应用:一般做一些复杂的计数功能的缓存。
hash
这里value存放的是结构化的对象,比较方便的就是操作其中的某个字段。
应用:单点登录,就是用这种数据结构存储用户信息,以cookieId
作为key,设置30分钟为缓存过期时间,能很好的模拟出类似session的效果。
list
List的数据结构
应用:可以做简单的消息队列的功能。另外,可以利用lrange
命令,做基于redis的分页功能,性能极佳,用户体验好。生产者和消费者的场景,LIST可以很好的完成排队,先进先出的原则。
set
因为set堆放的是一堆不重复值的集合。
应用:可以做全局去重的功能。为什么不用JVM自带的Set进行去重?因为我们的系统一般都是集群部署,使用JVM自带的Set,比较麻烦,难道为了一个做一个全局去重,再起一个公共服务,太麻烦了。 另外,就是利用交集、并集、差集等操作,可以计算共同喜好,全部的喜好,自己独有的喜好等功能。
sorted set
sorted set多了一个权重参数score,集合中的元素能够按score进行排列。
应用:可以做排行榜应用,取TOP N操作。
Redis 内部结构有哪些?
dict
本质上是为了解决算法中的查找问题(Searching)是一个用于维护key和value映射关系的数据结构,与很多语言中的Map或dictionary类似。 本质上是为了解决算法中的查找问题(Searching)。
sds
sds就等同于char * 它可以存储任意二进制数据,不能像C语言字符串那样以字符’\0’
来标识字符串的结束,因此它必然有个长度字段。
skiplist (跳跃表)
跳表是一种实现起来很简单,单层多指针的链表,它查找效率很高,堪比优化过的二叉平衡树,且比平衡树的实现。
ziplist 压缩表
ziplist是一个编码后的列表,是由一系列特殊编码的连续内存块组成的顺序型数据结构。
Memcache 与 Redis 的区别都有哪些?
存储方式不同:
Memcache 是把数据全部存在内存中,数据不能超过内存的大小,断电后数据库会挂掉。
Redis 有部分存在硬盘上,这样能保证数据的持久性。
数据支持的类型不同:
memcahe 对数据类型支持相对简单
redis 有复杂的数据类型。
使用底层模型不同:
它们之间底层实现方式 以及与客户端之间通信的应用协议不一样。
Redis 直接自己构建了 VM 机制 ,因为一般的系统调用系统函数的话,会浪费一定的时间去移动和请求。
支持的 value 大小不一样:
redis 最大可以达到 1GB
而 memcache 只有 1MB
为什么 redis 需要把所有数据放到内存中?
Redis 为了达到最快的读写速度将数据都读到内存中,并通过异步的方式将数据写入磁盘。所以 redis 具有快速和数据持久化的特征。如果不将数据放在内存中,磁盘 I/O 速度为严重影响 redis 的性能。在内存越来越便宜的今天,redis 将会越来越受欢迎。如果设置了最大使用的内存,则数据已有记录数达到内存限值后不能继续插入新值。
Redis 如何进行持久化?
Redis是一个支持持久化的内存数据库,通过持久化机制把内存中的数据同步到硬盘文件来保证数据持久化。当Redis重启后通过把硬盘文件重新加载到内存,就能达到恢复数据的目的。
实现:
单独创建fork()一个子进程,将当前父进程的数据库数据复制到子进程的内存中,然后由子进程写入到临时文件中,持久化的过程结束了,再用这个临时文件替换上次的快照文件,然后子进程退出,内存释放。
方式:
- RDB是Redis默认的持久化方式。按照一定的时间周期策略把内存的数据以快照的形式保存到硬盘的二进制文件。即
Snapshot
快照存储,对应产生的数据文件为dump.rdb
,通过配置文件中的save
参数来定义快照的周期。( 快照可以是其所表示的数据的一个副本,也可以是数据的一个复制品。) - AOF:Redis会将每一个收到的写命令都通过
Write
函数追加到文件最后,类似于MySQL的binlog
。当Redis重启是会通过重新执行文件中保存的写命令来在内存中重建整个数据库的内容。
当两种方式同时开启时,数据恢复Redis会优先选择AOF恢复。
RDB和AOF的优缺点有哪些?
RDB持久化
-
优点:RDB文件紧凑,体积小,网络传输快,适合全量复制;恢复速度比AOF快很多。当然,与AOF相比,RDB最重要的优点之一是对性能的影响相对较小。
-
缺点:RDB文件的致命缺点在于其数据快照的持久化方式决定了必然做不到实时持久化,而在数据越来越重要的今天,数据的大量丢失很多时候是无法接受的,因此AOF持久化成为主流。此外,RDB文件需要满足特定格式,兼容性差(如老版本的Redis不兼容新版本的RDB文件)。
AOF持久化
- 优点:支持秒级持久化、兼容性好
- 缺点:文件大、恢复速度慢、对性能影响大。
什么是缓存穿透?如何避免?
缓存穿透
一般的缓存系统,都是按照key去缓存查询,如果不存在对应的value,就应该去后端系统查找(比如DB)。一些恶意的请求会故意查询不存在的key,请求量很大,就会对后端系统造成很大的压力,就叫做缓存穿透。
避免
- 对查询结果为空的情况也进行缓存,缓存时间设置短一点,或者该key对应的数据insert了之后清理缓存。
- 对一定不存在的key进行过滤。可以把所有的可能存在的key放到一个大的
Bitmap
中,查询时通过该bitmap过滤。
什么是缓存雪崩?何如避免?
缓存雪崩
当缓存服务器重启或者大量缓存集中在某一个时间段失效,这样在失效的时候,会给后端系统带来很大压力。导致系统崩溃。
避免
- 在缓存失效后,通过加锁或者队列来控制读数据库写缓存的线程数量。比如对某个key只允许一个线程查询数据和写缓存,其他线程等待。
- 做二级缓存,A1为原始缓存,A2为拷贝缓存;A1失效时,可以访问A2,A1缓存失效时间设置为短期,A2设置为长期
- 不同的key,设置不同的过期时间,让缓存失效的时间点尽量均匀。
redis的淘汰策略有哪些?
-
noeviction
:不删除策略,达到最大内存限制时,如果需要更多内存,直接返回错误信息。大多数写命令都会导致占用更多的内存(有极少数会例外) -
allkeys-lru
:所有key通用;优先删除最近最少使用(less recently used ,LRU) 的 key。 -
volatile-lru
:只限于设置了 expire 的部分;优先删除最近最少使用(less recently used ,LRU) 的 key。 -
allkeys-random
:所有key通用;随机删除一部分 key。 -
volatile-random
:只限于设置了 expire 的部分;随机删除一部分 key。 -
volatile-ttl
:只限于设置了 expire 的部分;优先删除剩余时间(time to live,TTL) 短的key。
Redis是单线程的,但为什么这么快?
1、完全基于内存,绝大部分请求是纯粹的内存操作,非常快速。数据存在内存中,类似于HashMap,HashMap的优势就是查找和操作的时间复杂度都是O(1)
;
2、数据结构简单,对数据操作也简单,Redis中的数据结构是专门进行设计的;
3、采用单线程,避免了不必要的上下文切换和竞争条件,也不存在多进程或者多线程导致的切换而消耗 CPU,不用去考虑各种锁的问题,不存在加锁释放锁操作,没有因为可能出现死锁而导致的性能消耗;
4、使用多路I/O复用模型,非阻塞IO;这里“多路”指的是多个网络连接,“复用”指的是复用同一个线程
5、使用底层模型不同,它们之间底层实现方式以及与客户端之间通信的应用协议不一样,Redis直接自己构建了VM 机制 ,因为一般的系统调用系统函数的话,会浪费一定的时间去移动和请求;
Redis的内存分为哪些?
数据
作为数据库,数据是最主要的部分;这部分占用的内存会统计在used_memory
中。
进程本身运行需要的内存
Redis主进程本身运行肯定需要占用内存,如代码、常量池等等;这部分内存大约几兆,在大多数生产环境中与Redis数据占用的内存相比可以忽略。这部分内存不是由jemalloc分配,因此不会统计在used_memory
中。
缓冲内存
缓冲内存包括客户端缓冲区
、复制积压缓冲区
、AOF缓冲区
等;
客户端缓冲存储客户端连接的输入输出缓冲;
复制积压缓冲用于部分复制功能;
AOF缓冲区用于在进行AOF重写时,保存最近的写入命令。
在了解相应功能之前,不需要知道这些缓冲的细节;这部分内存由jemalloc分配,因此会统计在used_memory
中。
内存碎片
内存碎片是Redis在分配、回收物理内存过程中产生的。例如,如果对数据的更改频繁,而且数据之间的大小相差很大,可能导致redis释放的空间在物理内存中并没有释放,但redis又无法有效利用,这就形成了内存碎片。内存碎片不会统计在used_memory
中。
说说Redis事务?
Redis事务功能是通过MULTI
、EXEC
、DISCARD
和WATCH
四个原语实现的
Redis会将一个事务中的所有命令序列化,然后按顺序执行。
- redis 不支持回滚“Redis 在事务失败时不进行回滚,而是继续执行余下的命令”, 所以 Redis 的内部可以保持简单且快速。
- 如果在一个事务中的命令出现错误,那么所有的命令都不会执行;
- 如果在一个事务中出现运行错误,那么正确的命令会被执行。
命令
-
MULTI
命令用于开启一个事务,它总是返回OK。 MULTI执行之后,客户端可以继续向服务器发送任意多条命令,这些命令不会立即被执行,而是被放到一个队列中,当EXEC命令被调用时,所有队列中的命令才会被执行。 -
EXEC
:执行所有事务块内的命令。返回事务块内所有命令的返回值,按命令执行的先后顺序排列。 当操作被打断时,返回空值 nil 。 -
DISCARD
:通过调用DISCARD,客户端可以清空事务队列,并放弃执行事务, 并且客户端会从事务状态中退出。 -
WATCH
命令可以为 Redis 事务提供check-and-set (CAS)
行为。 可以监控一个或多个键,一旦其中有一个键被修改(或删除),之后的事务就不会执行,监控一直持续到EXEC命令。
Redis 常见的性能问题都有哪些?如何解决?
1.Master 写内存快照,save 命令调度 rdbSave
函数,会阻塞主线程的工作,当快照比较大时对性能影响是非常大的,会间断性暂停服务,所以 Master 最好不要写内存快照。
2.Master AOF 持久化,如果不重写 AOF 文件,这个持久化方式对性能的影响是最小的,但是 AOF 文件会不断增大,AOF 文件过大会影响 Master 重启的恢复速度。
Master 最好不要做任何持久化工作,包括内存快照和 AOF。
日志文件,特别是不要启用内存快照做持久化,如果数据比较关键,某个 Slave 开启 AOF 备份数据,策略为每秒同步一次。
3.Master 调用 BGREWRITEAOF 重写 AOF 文件,AOF 在重写的时候会占大量的 CPU 和内存资源,导致服务 load 过高,出现短暂服务暂停现象。
4.Redis 主从复制的性能问题,为了主从复制的速度和连接的稳定性,Slave 和 Master 最好在同一个局域网内
Redis 适合的场景有哪些?
- 会话缓存(Session Cache)
- 全页缓存(FPC)
- 队列
- 排行榜/计数器
- 发布/订阅