记Redis面试篇

955 阅读12分钟

一、Redis持久化机制

redis是一个持久化的内存数据库,通过持久化机制把内存中的数据同步到硬盘文件来保证数据持久化,当redis重启后通过把磁盘文件重新加载到内存,就能达到恢复数据的目的。

实现:单独创建fock()一个子进程,将当前父进程的数据库数据复制到子进程的内存中,然后由子进程写入临时文件中,持久化的过程就结束了,再用这个临时文件替换上次的快照文件,然后子进程退出,内存释放。

redis有以下两种持久化方式:

  1. RDB是redis默认的持久化方式,按照一定的时间周期策略把内存的数据以快照的形式保存到硬盘的二进制文件,即SnapShot快照存储,对应产生的数据文件为dmp.rdb,通过配置文件中的save参数来定义快照的周期。

  2. AOF:redis会将每一个收到的写命令都通过write函数追加到文件末尾,类似于mysql的binlog,当redis重启会通过重新执行文件中保存的写命令来在内存中重建整个数据库的内容。

    一般redis的持久化方式都采用RDB+AOP的方式,两种方式并用时redis会优先采用AOP进行数据恢复。

二、缓存雪崩、缓存穿透、缓存预热、缓存更新、缓存降级

1、什么是缓存雪崩?

高并发情况加,多个请求访问设置了相同过期时间的缓存数据时,出现大面积的缓存过去,导致原本访问缓存的大量请求涌向数据库,使数据库CPU和内存压力瞬间增大,严重时会导致数据库宕机,从而发生连锁反应,造成系统的奔溃。

2、那有什么办法能防止缓存雪崩?

可以使用锁或者队列的方式保存不会出现大量线程对数据发生请求,从而避免失效时大量的并发请求涌向数据库,或者将缓存的失效时间分段设置。

3、什么是缓存穿透?

缓存穿透是指当用户查询数据时,在数据库与缓存同时都没有的请款家,导致多次进行数据库查询但是结果返回的是空数据,这样子请求就绕过了缓存直接访问数据库,也是缓存命中率的问题。

4、那有什么办法能防止缓存穿透?

最常见的方式就是采用布隆过滤器,将所有可能存在的数据哈希到一个足够大的bitmap中,一个一定不存在数据会被这个bitmap拦截调,从而避免了对底层存储系统的查询压力,另外一个办法就是如果查询结果返回空时,仍然将它进行缓存并设置最短的过期时间,通过这个设置能使第二次查询时能在缓存中获取到数据从而不会直接去访问数据库。

4、什么是缓存预热?

缓存预热就是在系统启动的时候将相关的缓存数据加载到缓存系统,这样就避免了用户在做查询的时候先查询数据库然后在将数据进行缓存的问题。

解决思路:

1、开发缓存刷新页面上线时手工操作下

2、数据量不大情况下可以在项目启动时自动加载进缓存

3、定时刷新缓存

5、什么是缓存更新?

除了缓存服务器自带的缓存失效侧列之外,我们还可以根据具体的业务需求进行自定义缓存淘汰策略,常见的策略有两种:

1)、定时清理过去的数据(需要维护大量的key)

2)、当用户请求时先判断这个请求所用到的缓存是否失效,失效的话则去进行数据库查询新的数据并更新至缓存系统(每次请求都要进行缓存判断,逻辑相对复杂)

6、什么是缓存降级?

在高并发情况下,服务器出现问题或者非核心业务影响到核心流程的性能时,任然需要保证服务可用,系统可以针对一些关键数据进行自动降级,也可以配置开关实现人工降级。

7、有什么方法可以使缓存降级?

日志级别设置预案:

1)、一般:例如有些服务器偶尔发送网络抖动或者服务正在上线而超时,可以自动降级

2)、警告:例如有些服务在一定时间内发生成功率有波动,可以自动降级成人工降级并发送告警

3)、错误:例如系统可用率低于90%时,或者数据库连接池被打爆,或者访问量突然增大,导致系统的承受能力到达了阈值,此时可以根据情况自动降级或者人工降级

4)、严重错误:因为某些特殊原因,此时需要紧急人工降级

在高并发情况下,如果redis服务不可用,可以直接返回默认值给用户,从而避免大量请求涌向数据库,避免数据库宕机,发生连锁反应,导致系统崩溃

三、热点数据和冷数据是什么?

所谓热点数据就是经常被大量访问的数据

所谓冷数据就是频繁被修改的数据或者访问次数少之又少的数据

经常被访问的数据可以直接进行缓存,这样做可以避免高并发情况下大量请求访问数据库,造成数据库压力瞬间增大,只有数据在更新前至少被读取两次的才进行缓存,对于频繁被更改并且频繁被访问的数据也可以进行缓存,减少数据库的压力(点赞数、分享数、收藏数)

四、Memcache与Redis的区别有哪些?

1、存储方式Memcache把数据全部存储在内存之中,断点后会挂掉,部署不能超过内存大小,Redis有部分存储在硬盘上,redis可以持久化数据。

2、数据支撑类型memcache所有的值都是简单的字符串类型,而redis拥有丰富的数据类型,提供了list、set、zset、hash等数据结构的存储。

3、使用的底层模型不一样,memcahce与redis的底层实现方式以及客户端之间通信的应用协议不一样,redis直接自己构建vm机制,因为一般的系统调用系统函数的时候会浪费一定的时间去请求

4、value值大小不一样,redis最大可以达到1gb,memcache只有1mb

5、redis的速度比memcache快很多

6、redis支持数据备份,即主从模式的数据备份

五、单线程的redis为什么这么快?

1、纯内存操作

2、单线程操作避免了大量的上下文切换

3、使用了非阻塞i/O多路复用机制

六、redis的数据类型,以及每种数据类型的使用场景

string类型:常规的json串存储

hash类型:这里value存放的是结构化的对象,比较方便的就行可以操作其中的某个字段

list类型:可以做简单的消息队列,也可以做基于redis的分页功能

set类型:因为set是不可重复的集合,所以可以做全局去重的功能,另外可以利用交集、并集、差集等操作计算共同喜好,全部喜好,区别的喜好等功能

sorted set类型:sorted set多了一个权重参数的score,集合中的元素能按score元素进行排序,可以做排行版的功能

七、redis的过期策略以及缓存淘汰机制

redis采用的是定期删除+惰性删除策略

为什么不用定时删除策略?

定时删除,用一个定时器来负责监视key,过期则自动删除,虽然内存及时释放,但是十分消耗cpu资源,在高并发请求下,CPU要将时间应用在处理请求,而不是删除key,因此没有采用定时删除策略。

定期删除+惰性删除是如何工作的?

定期删除,redis默认每隔100ms随机抽取进行检查,是否有过期的key,有过期的key则删除,但是如果单单才用定期删除,那么还是会存在很多的过期key没有被删除,所以惰性删除就起作用了,惰性删除也就是在获取某个key的时候,redis会检查下,这个key如果设置了过去时间并且已经过期,那么这个key就会被删除

采用定期删除+惰性删除就没有问题了吗?

不是的,如果定期删除没删除key,然后你也没去请求key,这样redis的内存会越来越高,那么就应该采用缓存淘汰机制。

在redis.conf配置文件有一行配置

maxmenory-policy volatile-lru

该配置就是配内存淘汰策略的

volatile-lru:从已设置过期的数据集中挑选最近最少使用的数据淘汰

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

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

allkeys-lru:从数据集中挑选最近最少使用的数据淘汰

allkeys-random:从数据集中随机挑选任意数据淘汰

no-enviction:禁止驱逐数据,新写入操作会报错

如果没有设置expire的key,不满足先决条件,那么和volaltile-lru、volaltile-random和volaltile-ttl策略行为,和noeviction基本一致

八、redis为什么是单线程的?

因为redis是基于内存的,cpu不是redis的瓶颈,redis的瓶颈最有可能是机器内存的大小或者网络带宽。既然单线程容易实现,而且CPU不会成为瓶颈,那就顺利成章地采用单线程的方案了。

1、绝大部分请求是纯粹的内存操作

2、采用单线程可以避免频繁的上下文切换喝竞争条件

3、非阻塞IO有点

  • 速度快,因为数据存储在内存中,类似于hashmap,hashmap的优势就是查找和操作的时间复杂度都是o(1)

  • 支持丰富的数据类型,如list、set、zset、hash、sorted set

  • 支持事务,操作都是原子性,所谓原子性就是对数据的更改要么全部成功,要么全部失败

  • 丰富的特性,可用于缓存、消息,按key设置过期时间,过期后将会自动删除

如何解决redis在高并发情况下竞争key的问题?

  • 如果对这个key不要求顺序,则可以采用分布式锁的方式

  • 如果对这个key操作要求顺序,则可以采用分布式锁加时间戳的方式

  • 可以利用队列的方式

九、redis的常见性能问题和解决方案?

1、master最好不要做任何持久化工作,如RDB内存快照和AOF日志文件

2、如果数据比较重要,某个slave开启AOF备份数据,策略设置每秒同步一次

3、为了主从负责的速度和连接稳定性,master和slave最好部署在泳衣局域网内

4、尽量避免在压力很大的主库上增加从库

5、主从负责不要用图状结构,用单项链表结构更为稳定,即:master <- slave1 <- slave2 <- slave3

十、为什么redis的操作是原子性的,怎么保证原子性的?

redis的原子性指的是一个操作不可再分,操作要么成功,要么失败。

redis的操作之所以是原子性的,是因为redis是单线程的

redis本身提供的所有api都是原子性的,redis中事务其实是要保证批量操作的原子性

多个命令在并发中也是原子性吗?

不一定,将get喝set改成单命令操作,incr.使用redis事务,或者使用redis+lua==的方式实现

十一、redis事务

redis事务功能是通过multi、exec、discard、watch四个原语实现的

redis会将一个事务中的所有命令进行序列化,然后按顺序执行

1、redis不支持回滚操作,redis在事务失败时不进行回滚,而是继续执行余下的命令,所以redis的内部可以保持简单快速

2、在同一事务内,如果某条命令出现错误,那么其他命令则不会执行

3、在同一事务内,如果出现命令的运行错误,那么正确的命令会被执行

1)、multi命令用于开启一个事务,它总是返回ok。multi执行之后,客户端可以继续想服务器发送任意多条命令,这些命令不会立即执行,而是被放到一个队列中,当exex命令被调用时,所有队列中的命令才会被执行。

2)、exec执行所有事物块内的命令,返回事物块内所有命令的返回值,按命令执行的先后顺序排列,当操作被打断时,返回空值null

3)、通过调用discard,客户端可以情况事物队列,并放弃执行事物,并且客户端会从事物状态中退出

4)、watch命令可以为redis事物提供check-and-set(CAS)行为,可以监控一个或多个键,一旦其中有一个键被修改,之后的事物就不会执行,监控一直持续到ecex命令。