17道Redis 面试题

阿里云国内75折 回扣 微信号:monov8
阿里云国际,腾讯云国际,低至75折。AWS 93折 免费开户实名账号 代冲值 优惠多多 微信号:monov8 飞机:@monov6
  • Redis 持久化机制

  • 缓存雪崩、缓存穿透、缓存预热、缓存更新、缓存降级等问题

  • 热点数据和冷数据是什么

  • Memcache与Redis的区别都有哪些

  • 单线程的redis为什么这么快

  • redis的数据类型以及每种数据类型的使用场景Redis 内部结构

  • redis的过期策略以及内存淘汰机制【~】

  • Redis 为什么是单线程的优点

  • 如何解决redis的并发竞争key问题

  • Redis 集群方案应该怎么做都有哪些方案

  • 有没有尝试进行多机redis 的部署如何保证数据一致的

  • 对于大量的请求怎么样处理

  • Redis 常见性能问题和解决方案

  • 讲解下Redis线程模型

  • 为什么Redis的操作是原子性的怎么保证原子性的

  • Redis事务

  • Redis实现分布式锁

Redis 持久化机制

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

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

RDB是Redis默认的持久化方式。按照一定的时间周期策略把内存的数据以快照的形式保存到硬盘的二进制文件。即Snapshot快照存储对应产生的数据文件为dump.rdb通过配置文件中的save参数来定义快照的周期。 快照可以是其所表示的数据的一个副本也可以是数据的一个复制品。

AOFRedis会将每一个收到的写命令都通过Write函数追加到文件最后类似于MySQL的binlog。当Redis重启是会通过重新执行文件中保存的写命令来在内存中重建整个数据库的内容。

当两种方式同时开启时数据恢复Redis会优先选择AOF恢复。

对了我把发布过的 Redis 相关的文章整理成了 PDF关注微信公众号 Java后端回复 666 下载这本 Java技术栈手册。

缓存雪崩、缓存穿透、缓存预热、缓存更新、缓存降级等问题

缓存雪崩

缓存雪崩我们可以简单的理解为由于原有缓存失效新缓存未到期间

(例如我们设置缓存时采用了相同的过期时间在同一时刻出现大面积的缓存过期)所有原本应该访问缓存的请求都去查询数据库了而对数据库CPU和内存造成巨大压力严重的会造成数据库宕机。从而形成一系列连锁反应造成整个系统崩溃。

解决办法

大多数系统设计者考虑用加锁 最多的解决方案或者队列的方式保证来保证不会有大量的线程对数据库一次性进行读写从而避免失效时大量的并发请求落到底层存储系统上。还有一个简单方案就时讲缓存失效时间分散开。

缓存穿透

缓存穿透是指用户查询数据在数据库没有自然在缓存中也不会有。这样就导致用户查询的时候在缓存中找不到每次都要去数据库再查询一遍然后返回空相当于进行了两次无用的查询。这样请求就绕过缓存直接查数据库这也是经常提的缓存命中率问题。

解决办法

最常见的则是采用布隆过滤器将所有可能存在的数据哈希到一个足够大的bitmap中一个一定不存在的数据会被这个bitmap拦截掉从而避免了对底层存储系统的查询压力。

另外也有一个更为简单粗暴的方法如果一个查询返回的数据为空不管是数据不存在还是系统故障我们仍然把这个空结果进行缓存但它的过期时间会很短最长不超过五分钟。通过这个直接设置的默认值存放到缓存这样第二次到缓冲中获取就有值了而不会继续访问数据库这种办法最简单粗暴。

5TB的硬盘上放满了数据请写一个算法将这些数据进行排重。如果这些数据是一些32bit大小的数据该如何解决如果是64bit的呢

对于空间的利用到达了一种极致那就是Bitmap和布隆过滤器(Bloom Filter)。

Bitmap典型的就是哈希表

缺点是Bitmap对于每个元素只能记录1bit信息如果还想完成额外的功能恐怕只能靠牺牲更多的空间、时间来完成了。

布隆过滤器推荐

就是引入了k(k>1)k(k>1)个相互独立的哈希函数保证在给定的空间、误判率下完成元素判重的过程。

它的优点是空间效率和查询时间都远远超过一般的算法缺点是有一定的误识别率和删除困难。

Bloom-Filter算法的核心思想就是利用多个不同的Hash函数来解决“冲突”。

Hash存在一个冲突碰撞的问题用同一个Hash得到的两个URL的值有可能相同。为了减少冲突我们可以多引入几个Hash如果通过其中的一个Hash值我们得出某元素不在集合中那么该元素肯定不在集合中。只有在所有的Hash函数告诉我们该元素在集合中时才能确定该元素存在于集合中。这便是Bloom-Filter的基本思想。

Bloom-Filter一般用于在大数据量的集合中判定某元素是否存在。

缓存穿透与缓存击穿的区别

缓存击穿是指一个key非常热点在不停的扛着大并发大并发集中对这一个点进行访问当这个key在失效的瞬间持续的大并发就穿破缓存直接请求数据。

解决方案在访问key之前采用SETNXset if not exists来设置另一个短期key来锁住当前key的访问访问结束再删除该短期key。

给一个我公司处理的案例背景双机拿tokentoken在存一份到redis保证系统在token过期时都只有一个线程去获取token;线上环境有两台机器故使用分布式锁实现。

三、缓存预热

缓存预热这个应该是一个比较常见的概念相信很多小伙伴都应该可以很容易的理解缓存预热就是系统上线后将相关的缓存数据直接加载到缓存系统。这样就可以避免在用户请求的时候先查询数据库然后再将数据缓存的问题用户直接查询事先被预热的缓存数据

解决思路

  • 直接写个缓存刷新页面上线时手工操作下

  • 数据量不大可以在项目启动的时候自动进行加载

  • 定时刷新缓存

四、缓存更新

除了缓存服务器自带的缓存失效策略之外Redis默认的有6中策略可供选择我们还可以根据具体的业务需求进行自定义的缓存淘汰常见的策略有两种

  • 定时去清理过期的缓存

  • 当有用户请求过来时再判断这个请求所用到的缓存是否过期过期的话就去底层系统得到新数据并更新缓存。

两者各有优劣第一种的缺点是维护大量缓存的key是比较麻烦的第二种的缺点就是每次用户请求过来都要判断缓存失效逻辑相对比较复杂具体用哪种方案大家可以根据自己的应用场景来权衡。

五、缓存降级

当访问量剧增、服务出现问题如响应时间慢或不响应或非核心服务影响到核心流程的性能时仍然需要保证服务还是可用的即使是有损服务。系统可以根据一些关键数据进行自动降级也可以配置开关实现人工降级。

降级的最终目的是保证核心服务可用即使是有损的。而且有些服务是无法降级的如加入购物车、结算。

以参考日志级别设置预案

  • 一般比如有些服务偶尔因为网络抖动或者服务正在上线而超时可以自动降级

  • 警告有些服务在一段时间内成功率有波动如在95~100%之间可以自动降级或人工降级并发送告警

  • 错误比如可用率低于90%或者数据库连接池被打爆了或者访问量突然猛增到系统能承受的最大阀值此时可以根据情况自动降级或者人工降级

  • 严重错误比如因为特殊原因数据错误了此时需要紧急人工降级。

服务降级的目的是为了防止Redis服务故障导致数据库跟着一起发生雪崩问题。因此对于不重要的缓存数据可以采取服务降级策略例如一个比较常见的做法就是Redis出现问题不去数据库查询而是直接返回默认值给用户。

热点数据和冷数据是什么

热点数据缓存才有价值

对于冷数据而言大部分数据可能还没有再次访问到就已经被挤出内存不仅占用内存而且价值不大。频繁修改的数据看情况考虑使用缓存

对于上面两个例子寿星列表、导航信息都存在一个特点就是信息修改频率不高读取通常非常高的场景。

对于热点数据比如我们的某IM产品生日祝福模块当天的寿星列表缓存以后可能读取数十万次。再举个例子某导航产品我们将导航信息缓存以后可能读取数百万次。

数据更新前至少读取两次 缓存才有意义。这个是最基本的策略如果缓存还没有起作用就失效了那就没有太大价值了。

那存不存在修改频率很高但是又不得不考虑缓存的场景呢有比如这个读取接口对数据库的压力很大但是又是热点数据这个时候就需要考虑通过缓存手段减少数据库的压力比如我们的某助手产品的点赞数收藏数分享数等是非常典型的热点数据但是又不断变化此时就需要将数据同步保存到Redis缓存减少数据库压力。

Memcache与Redis的区别都有哪些

1)、存储方式 Memecache把数据全部存在内存之中断电后会挂掉数据不能超过内存大小。Redis有部份存在硬盘上redis可以持久化其数据

2)、数据支持类型 memcached所有的值均是简单的字符串redis作为其替代者支持更为丰富的数据类型 提供listsetzsethash等数据结构的存储

3)、使用底层模型不同 它们之间底层实现方式 以及与客户端之间通信的应用协议不一样。Redis直接自己构建了VM 机制 因为一般的系统调用系统函数的话会浪费一定的时间去移动和请求。

4). value 值大小不同Redis 最大可以达到 512Mmemcache 只有 1mb。

5redis的速度比memcached快很多

6Redis支持数据的备份即master-slave模式的数据备份。

单线程的redis为什么这么快

(一)纯内存操作

(二)单线程操作避免了频繁的上下文切换

(三)采用了非阻塞I/O多路复用机制

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 跳跃表 跳表是一种实现起来很简单单层多指针的链表它查找效率很高堪比优化过的二叉平衡树且比平衡树的实现

quicklist

ziplist 压缩表 ziplist是一个编码后的列表是由一系列特殊编码的连续内存块组成的顺序型数据结构

redis的过期策略以及内存淘汰机制

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

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

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

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

定期删除redis默认每个100ms检查是否有过期的key,有过期key则删除。需要说明的是redis不是每个100ms将所有的key检查一次而是随机抽取进行检查(如果每隔100ms,全部key进行检查redis岂不是卡死)。因此如果只采用定期删除策略会导致很多key到时间没有删除。

于是惰性删除派上用场。也就是说在你获取某个key的时候redis会检查一下这个key如果设置了过期时间那么是否过期了如果过期了此时就会删除。

采用定期删除+惰性删除就没其他问题了么?

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

在redis.conf中有一行配置

maxmemory-policy volatile-lru1

该配置就是配内存淘汰策略的(什么你没配过好好反省一下自己)

  • volatile-lru从已设置过期时间的数据集server.db[i].expires中挑选最近最少使用的数据淘汰

  • volatile-ttl从已设置过期时间的数据集server.db[i].expires中挑选将要过期的数据淘汰

  • volatile-random从已设置过期时间的数据集server.db[i].expires中任意选择数据淘汰

  • allkeys-lru从数据集server.db[i].dict中挑选最近最少使用的数据淘汰

  • allkeys-random从数据集server.db[i].dict中任意选择数据淘汰

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

ps如果没有设置 expire 的key, 不满足先决条件(prerequisites); 那么 volatile-lru, volatile-random 和 volatile-ttl 策略的行为, 和 noeviction(不删除) 基本上一致。

Redis 为什么是单线程的

官方FAQ表示因为Redis是基于内存的操作CPU不是Redis的瓶颈Redis的瓶颈最有可能是机器内存的大小或者网络带宽。

既然单线程容易实现而且CPU不会成为瓶颈那就顺理成章地采用单线程的方案了毕竟采用多线程会有很多麻烦Redis利用队列技术将并发访问变为串行访问

1绝大部分请求是纯粹的内存操作非常快速

2采用单线程,避免了不必要的上下文切换和竞争条件

3非阻塞IO优点

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

  • 支持丰富数据类型支持stringlistsetsorted sethash

  • 支持事务操作都是原子性所谓的原子性就是对数据的更改要么全部执行要么全部不执行

  • 丰富的特性可用于缓存消息按key设置过期时间过期后将会自动删除如何解决redis的并发竞争key问题

同时有多个子系统去set一个key。这个时候要注意什么呢

不推荐使用redis的事务机制。因为我们的生产环境基本都是redis集群环境做了数据分片操作。你一个事务中有涉及到多个key操作的时候这多个key不一定都存储在同一个redis-server上。因此redis的事务机制十分鸡肋。

  • 如果对这个key操作不要求顺序准备一个分布式锁大家去抢锁抢到锁就做set操作即可

  • 如果对这个key操作要求顺序分布式锁+时间戳。假设这会系统B先抢到锁将key1设置为{valueB 3:05}。接下来系统A抢到锁发现自己的valueA的时间戳早于缓存中的时间戳那就不做set操作了。以此类推。

  • 利用队列将set方法变成串行访问也可以redis遇到高并发如果保证读写key的一致性

对redis的操作都是具有原子性的,是线程安全的操作,你不用考虑并发问题,redis内部已经帮你处理好并发的问题了。

Redis 集群方案应该怎么做都有哪些方案

1.twemproxy大概概念是它类似于一个代理方式 使用时在本需要连接 redis 的地方改为连接 twemproxy 它会以一个代理的身份接收请求并使用一致性 hash 算法将请求转接到具体 redis将结果再返回 twemproxy。

缺点twemproxy 自身单端口实例的压力使用一致性 hash 后对 redis 节点数量改变时候的计算值的改变数据无法自动移动到新的节点。

2.codis目前用的最多的集群方案基本和 twemproxy 一致的效果但它支持在 节点数量改变情况下旧节点数据可恢复到新 hash 节点

3.redis cluster3.0 自带的集群特点在于他的分布式算法不是一致性 hash而是 hash 槽的概念以及自身支持节点设置从节点。具体看官方文档介绍。

有没有尝试进行多机redis 的部署如何保证数据一致的

主从复制读写分离

一类是主数据库master一类是从数据库slave主数据库可以进行读写操作当发生写操作的时候自动将数据同步到从数据库而从数据库一般是只读的并接收主数据库同步过来的数据一个主数据库可以有多个从数据库而一个从数据库只能有一个主数据库。

对于大量的请求怎么样处理

redis是一个单线程程序也就说同一时刻它只能处理一个客户端请求

redis是通过IO多路复用selectepoll, kqueue依据不同的平台采取不同的实现来处理多个客户端请求的

Redis 常见性能问题和解决方案

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

(2) 如果数据比较重要某个 Slave 开启 AOF 备份数据策略设置为每秒同步一次

(3) 为了主从复制的速度和连接的稳定性 Master 和 Slave 最好在同一个局域网内

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

(5) 主从复制不要用图状结构用单向链表结构更为稳定即Master <- Slave1 <- Slave2 <-

Slave3…

往期面试题汇总001期~150期汇总

讲解下Redis线程模型

文件事件处理器包括分别是套接字、 I/O 多路复用程序、 文件事件分派器dispatcher、 以及事件处理器。使用 I/O 多路复用程序来同时监听多个套接字 并根据套接字目前执行的任务来为套接字关联不同的事件处理器。

当被监听的套接字准备好执行连接应答accept、读取read、写入write、关闭close等操作时 与操作相对应的文件事件就会产生 这时文件事件处理器就会调用套接字之前关联好的事件处理器来处理这些事件。

I/O 多路复用程序负责监听多个套接字 并向文件事件分派器传送那些产生了事件的套接字。

工作原理

I/O 多路复用程序负责监听多个套接字 并向文件事件分派器传送那些产生了事件的套接字。

尽管多个文件事件可能会并发地出现 但 I/O 多路复用程序总是会将所有产生事件的套接字都入队到一个队列里面 然后通过这个队列 以有序sequentially、同步synchronously、每次一个套接字的方式向文件事件分派器传送套接字

当上一个套接字产生的事件被处理完毕之后该套接字为事件所关联的事件处理器执行完毕 I/O 多路复用程序才会继续向文件事件分派器传送下一个套接字。如果一个套接字又可读又可写的话 那么服务器将先读套接字 后写套接字.

为什么Redis的操作是原子性的怎么保证原子性的

对于Redis而言命令的原子性指的是一个操作的不可以再分操作要么执行要么不执行。

Redis的操作之所以是原子性的是因为Redis是单线程的。Redis新版本已经引入多线程这里基于旧版本的Redis

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

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

不一定 将get和set改成单命令操作incr 。使用Redis的事务或者使用Redis+Lua==的方式实现.

Redis事务

Redis事务功能是通过MULTI、EXEC、DISCARD和WATCH 四个原语实现的

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

  1. redis 不支持回滚“Redis 在事务失败时不进行回滚而是继续执行余下的命令” 所以 Redis 的内部可以保持简单且快速。

  1. 如果在一个事务中的命令出现错误那么所有的命令都不会执行

  1. 如果在一个事务中出现运行错误那么正确的命令会被执行。

注redis的discard只是结束本次事务,正确命令造成的影响仍然存在.

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

2EXEC执行所有事务块内的命令。返回事务块内所有命令的返回值按命令执行的先后顺序排列。当操作被打断时返回空值 nil 。

3通过调用DISCARD客户端可以清空事务队列并放弃执行事务 并且客户端会从事务状态中退出。

4WATCH 命令可以为 Redis 事务提供 check-and-set CAS行为。可以监控一个或多个键一旦其中有一个键被修改或删除之后的事务就不会执行监控一直持续到EXEC命令。

Redis实现分布式锁

Redis为单进程单线程模式采用队列模式将并发访问变成串行访问且多客户端对Redis的连接并不存在竞争关系Redis中可以使用SETNX命令实现分布式锁。

将 key 的值设为 value 当且仅当 key 不存在。若给定的 key 已经存在则 SETNX 不做任何动作

解锁使用 del key 命令就能释放锁

解决死锁

  • 通过Redis中expire()给锁设定最大持有时间如果超过则Redis来帮我们释放锁。

  • 使用 setnx key “当前系统时间+锁持有的时间”和getset key “当前系统时间+锁持有的时间”组合的命令就可以实现。

阿里云国内75折 回扣 微信号:monov8
阿里云国际,腾讯云国际,低至75折。AWS 93折 免费开户实名账号 代冲值 优惠多多 微信号:monov8 飞机:@monov6
标签: redis