|
| 1 | +# Redis面试题 |
| 2 | + |
| 3 | +### redis是什么 |
| 4 | + |
| 5 | +Redis是C语言开发的一个开源的(遵从BSD协议)高性能键值对(key-value)的内存数据库,可以用作数据库、缓存、消息中间件等。它是一种NoSQL(not-only sql,泛指非关系型数据库)的数据库。 |
| 6 | + |
| 7 | +Redis作为一个内存数据库。 性能优秀,数据在内存中,读写速度非常快 , 单进程单线程,是线程安全的,采用IO多路复用机制; |
| 8 | + |
| 9 | +丰富的数据类型,支持字符串(strings)、散列(hashes)、列表(lists)、集合(sets)、有序集合(sorted sets)等; |
| 10 | + |
| 11 | +支持数据持久化。可以将内存中数据保存在磁盘中,重启时加载; |
| 12 | + |
| 13 | +支持 主从复制,哨兵,高可用; |
| 14 | + |
| 15 | +可以用作分布式锁; 可以作为消息中间件使用,支持发布订阅 |
| 16 | + |
| 17 | +### redis 和 memcached 的区别 |
| 18 | + |
| 19 | +1. **redis支持更丰富的数据类型(支持更复杂的应用场景)**:Redis不仅仅支持简单的k/v类型的数据,同时还提供list,set,zset,hash等数据结构的存储。memcache支持简单的数据类型,String。 |
| 20 | +2. **Redis支持数据的持久化,可以将内存中的数据保持在磁盘中,重启的时候可以再次加载进行使用,而Memecache把数据全部存在内存之中。** |
| 21 | +3. **集群模式**:memcached没有原生的集群模式,需要依靠客户端来实现往集群中分片写入数据;但是 redis 目前是原生支持 cluster 模式的. |
| 22 | +4. **Memcached是多线程,非阻塞IO复用的网络模型;Redis使用单线程的多路 IO 复用模型。** |
| 23 | + |
| 24 | +### redis 为什么是单线程的? |
| 25 | + |
| 26 | + 因为 cpu 不是 Redis 的瓶颈,Redis 的瓶颈最有可能是机器内存或者网络带宽。既然单线程容易实现,而且 cpu 又不会成为瓶颈,那就顺理成章地采用单线程的方案了。 可以避免多线程上下文切换。 |
| 27 | + |
| 28 | + |
| 29 | + |
| 30 | +### 为什么redis这么快? |
| 31 | + |
| 32 | +完全基于内存,绝大部分请求是纯粹的内存操作,执行效率高 |
| 33 | +采用单线程,单线程也能处理高并发请求,想多核也可启动多实例 |
| 34 | + |
| 35 | +单线程反而避免了多线程的频繁上下文切换问题,预防了多线程可能产生的竞争问题。 |
| 36 | + |
| 37 | +核心是基于非阻塞的 IO 多路复用机制。 |
| 38 | + |
| 39 | +### redis 支持的数据类型有哪些?应用? |
| 40 | + |
| 41 | +string、list、hash、set、zset。 |
| 42 | + |
| 43 | +**String 在你们项目怎么用的?** |
| 44 | + |
| 45 | + **常用命令:** set,get,decr,incr,mget 等。 |
| 46 | + |
| 47 | +在显示某个人的基本数据的时候,比如名字,粉丝数,关注数,使用 String 保存: |
| 48 | + |
| 49 | +``` |
| 50 | +eg: user:id:3506728370 |
| 51 | +{"id":3506728370,"name":"春晚","fans":12210862,"blogs":6164, "focus":83} |
| 52 | +``` |
| 53 | + |
| 54 | +设置一个定时刷新的操作,这样用户不需要直接读取数据库。怎么设置?setx key value,一定时间循环判断key是否失效,到期后再去数据库读取。 |
| 55 | + |
| 56 | +**List 在你们项目怎么用的?** |
| 57 | + |
| 58 | + **常用命令:** lpush,rpush,lpop,rpop,lrange等 |
| 59 | + |
| 60 | +1. 朋友圈点赞,要求按照点赞顺序显示点赞好友信息 |
| 61 | + 如果取消点赞,移除对应好友信息,但是不能使用pop了,怎么办呢? |
| 62 | + |
| 63 | + 解决方案 |
| 64 | + |
| 65 | + lrem key count value **移除指定数据** |
| 66 | + count:移除的数目 |
| 67 | + value:具体要移除的内容 |
| 68 | + |
| 69 | +2. 个人用户的关注列表需要按照用户的关注顺序展示。 |
| 70 | + |
| 71 | +**Set 在你们项目怎么用的?** |
| 72 | + |
| 73 | +每位用户首次使用今日头条时会设置3项爱好的内容,但是后期为了增加用户的活跃度、兴趣点,必须让用户 |
| 74 | +对其他信息类别逐渐产生兴趣,增加客户留存度,如何实现? |
| 75 | + |
| 76 | +**分析** |
| 77 | +系统分析出各个分类的最新或最热点信息条目并组织成set集合 |
| 78 | +随机挑选其中部分信息 |
| 79 | +配合用户关注信息分类中的热点信息组织成展示的全信息集合 |
| 80 | + |
| 81 | +**解决方案** |
| 82 | + |
| 83 | +- 随机获取集合中指定数量的数据 |
| 84 | + |
| 85 | + srandmember key [count] |
| 86 | + |
| 87 | +- 随机获取集合中的某个数据并将该数据移出集合 |
| 88 | + |
| 89 | + spop key [count] |
| 90 | + |
| 91 | +**zset** |
| 92 | + |
| 93 | + 在直播系统中,实时排行信息包含直播间在线用户列表,各种礼物排行榜,弹幕消息(可以理解为按消息维度的消息排行榜)等信息,适合使用 Redis 中的 Sorted Set 结构进行存储。 |
| 94 | + |
| 95 | +### redis 设置过期时间 |
| 96 | + |
| 97 | +Redis中有个设置时间过期的功能,即对存储在 redis 数据库中的值可以设置一个过期时间。作为一个缓存数据库,这是非常实用的。如我们一般项目中的 token 或者一些登录信息,尤其是**短信验证码都是有时间限制的**,按照传统的数据库处理方式,一般都是自己判断过期,这样无疑会严重影响项目性能。 |
| 98 | + |
| 99 | +我们 set key 的时候,都可以给一个 expire time,就是过期时间,通过过期时间我们可以指定这个 key 可以存活的时间。 |
| 100 | + |
| 101 | +如果假设你设置了一批 key 只能存活1个小时,那么接下来1小时后,redis是怎么对这批key进行删除的? |
| 102 | + |
| 103 | +**定期删除+惰性删除。** |
| 104 | + |
| 105 | +通过名字大概就能猜出这两个删除方式的意思了。 |
| 106 | + |
| 107 | +- **定期删除**:redis默认是每隔 100ms 就**随机抽取**一些设置了过期时间的key,检查其是否过期,如果过期就删除。注意这里是随机抽取的。为什么要随机呢?你想一想假如 redis 存了几十万个 key ,每隔100ms就遍历所有的设置过期时间的 key 的话,就会给 CPU 带来很大的负载! |
| 108 | +- **惰性删除** :定期删除可能会导致很多过期 key 到了时间并没有被删除掉。所以就有了惰性删除。假如你的过期 key,靠定期删除没有被删除掉,还停留在内存里,除非你的系统去查一下那个 key,才会被redis给删除掉。 |
| 109 | + |
| 110 | +但是仅仅通过设置过期时间还是有问题的。我们想一下:如果定期删除漏掉了很多过期 key,然后你也没及时去查,也就没走惰性删除,此时会怎么样?如果大量过期key堆积在内存里,导致redis内存块耗尽了。怎么解决这个问题呢? **redis 内存淘汰机制。** |
| 111 | + |
| 112 | +### 数据淘汰机制 |
| 113 | + |
| 114 | +当内存到达最大内存限制时进行的数据淘汰策略 |
| 115 | + |
| 116 | +**配置** |
| 117 | + |
| 118 | +最大可用内存:maxmemory //默认为0,一般设置全部内存50%以上 |
| 119 | +每次选取带删除数据个数:maxmemory-samples //采用随机获取方式 |
| 120 | +删除策略:maxmemory-policy //达到最大内存后,对被选取带数据进行的删除策略 |
| 121 | + |
| 122 | +**检测易失数据集( **设置了过期时间的键空间) |
| 123 | +volatile-lru:挑选最近最少使用的数据淘汰(最近数据中使用时间离当前最远的数据)。**常用** |
| 124 | +volatile-lfu:挑选最近使用次数最少的数据淘汰(最近数据中使用次数最少的数据) |
| 125 | +volatile-ttl:挑选将要过期数据淘汰 |
| 126 | +volatile-random:任意挑选数据淘汰 |
| 127 | + |
| 128 | +**检测全库数据(所有数据集)** |
| 129 | +allkeys-lru:挑选最近最少使用的数据淘汰 |
| 130 | +allkeys-lfu:挑选最近使用次数最少的数据淘汰 |
| 131 | +allkeys-random:任意挑选数据淘汰 |
| 132 | + |
| 133 | +**放弃数据驱逐** |
| 134 | + |
| 135 | +no-enviction //禁止驱逐数据 4.0中默认策略,会引发OOM |
| 136 | + |
| 137 | +**LRU 算法实现**:1.通过双向链表来实现,新数据插入到链表头部;2.每当缓存命中(即缓存 |
| 138 | +数据被访问),则将数据移到链表头部;3.当链表满的时候,将链表尾部的数据丢弃。 |
| 139 | +LinkedHashMap:HashMap 和双向链表合二为一即是 LinkedHashMap。HashMap 是无序 |
| 140 | +的,LinkedHashMap 通过维护一个额外的双向链表保证了迭代顺序。该迭代顺序可以是插 |
| 141 | +入顺序(默认),也可以是访问顺序。 |
| 142 | + |
| 143 | + |
| 144 | + |
| 145 | +### redis 持久化的两种方式 |
| 146 | + |
| 147 | +- RDB:RDB 持久化机制,是对 redis 中的数据执行**周期性**的持久化。 |
| 148 | +- AOF:AOF 机制对每条写入命令作为日志,以 `append-only` 的模式写入一个日志文件中,在 redis 重启的时候,可以通过**回放** AOF 日志中的写入指令来重新构建整个数据集。 |
| 149 | + |
| 150 | +通过 RDB 或 AOF,都可以将 redis 内存中的数据给持久化到磁盘上面来,然后可以将这些数据备份到别的地方去,比如说阿里云等云服务。 |
| 151 | + |
| 152 | +如果 redis 挂了,服务器上的内存和磁盘上的数据都丢了,可以从云服务上拷贝回来之前的数据,放到指定的目录中,然后重新启动 redis,redis 就会自动根据持久化数据文件中的数据,去恢复内存中的数据,继续对外提供服务。 |
| 153 | + |
| 154 | +如果同时使用 RDB 和 AOF 两种持久化机制,那么在 redis 重启的时候,会使用 **AOF** 来重新构建数据,因为 AOF 中的**数据更加完整**。 |
| 155 | + |
| 156 | +**RDB 持久化优点** |
| 157 | +RDB是一个紧凑压缩的二进制文件,存储效率高 |
| 158 | +RDB恢复数据速度比AOF快 |
| 159 | + |
| 160 | + **RDB持久化缺点** |
| 161 | +无法做到实时持久化,具有较大可能丢失数据 |
| 162 | +存储数量较大时,效率较低,I/O性能较低 |
| 163 | +基于fork创建子进程,内存产生额外消耗 |
| 164 | +宕机带来的数据丢失风险 |
| 165 | + |
| 166 | +**AOF 优点** |
| 167 | + |
| 168 | +- AOF 可以更好的保护 数据不丢失,一般 AOF 会每隔 1 秒, 最多丢失 1 秒钟的数据。 |
| 169 | +- 写入性能非常高,而且文件不容易破损 |
| 170 | +- **适合做灾难性的误删除的紧急恢复**。 |
| 171 | + |
| 172 | +**AOF 缺点** |
| 173 | + |
| 174 | + 对于同一份数据来说,AOF 日志文件通常比 RDB 数据快照文件更大。 |
| 175 | + |
| 176 | + 恢复速度较慢 |
| 177 | + |
| 178 | +#### RDB 与 AOF 如何选择 |
| 179 | + |
| 180 | +对数据非常敏感,建议使用默认的AOF持久化方案 |
| 181 | +AOF策略使用everysec,每秒fsync一次,该策略仍可保持很好性能,出现问题最多丢失一秒内的数据 |
| 182 | +数据呈现阶段有效性,建议使用RDB持久化方案 |
| 183 | +数据可以做到阶段内无丢失,且恢复较快,阶段点数据恢复通常使用RDB方案 |
| 184 | + |
| 185 | +注意: |
| 186 | +AOF文件存储体积较大,恢复速度较慢 |
| 187 | +利用RDB使用线紧凑的数据持久化会使Redis性能降低 |
| 188 | + |
| 189 | +综合: |
| 190 | +RDB与AOF选择实际上是在一种权衡,每种都有利有弊 |
| 191 | +如果不能承受分钟内的数据丢失,对业务数据非常敏感,选用AOF |
| 192 | +如果能承受分钟内的数据丢失,且追求大数据集的恢复速度选用RDB |
| 193 | +灾难恢复选用RDB |
| 194 | +双保险策略,同时开启RDB和AOF,重启后Redis优先使用AOF来恢复数据,降低丢失数据量 |
| 195 | + |
| 196 | + |
| 197 | + |
| 198 | +### 项目中缓存是如何使用的? |
| 199 | + |
| 200 | +这个,需要结合自己项目的业务来。 |
| 201 | + |
| 202 | +### 为什么要用缓存? |
| 203 | + |
| 204 | +用缓存,主要有两个用途:**高性能**、**高并发**。 |
| 205 | + |
| 206 | + |
| 207 | + |
| 208 | +### 怎么保证缓存和数据库数据的一致性? |
| 209 | + |
| 210 | +- 合理设置缓存的过期时间。 |
| 211 | +- 新增、更改、删除数据库操作时同步更新 Redis,可以使用事务机制来保证数据的一致性。 |
| 212 | + |
| 213 | +### redis 怎么实现分布式锁? |
| 214 | + |
| 215 | +Redis 分布式锁其实就是在系统里面占一个“坑”,其他程序也要占“坑”的时候,占用成功了就可以继续执行,失败了就只能放弃或稍后重试。 |
| 216 | + |
| 217 | +占坑一般使用 setnx(set if not exists)指令,只允许被一个程序占有,使用完调用 del 释放锁 |
| 218 | + |
| 219 | +也可以配合`EXPIRE key seconds`自动释放锁 |
| 220 | +设置key的生存时间,当key过期时(生存时间为0) ,会被自动删除 |
| 221 | +风险/ **缺陷** :原子性没有得到满足,所以不建议。 |
| 222 | + |
| 223 | +### 缓存雪崩 |
| 224 | + |
| 225 | + |
| 226 | + |
| 227 | +**在一个较短的时间内,缓存中较多的key集中过期或者缓存挂了**,导致了**数据库服务器崩溃** |
| 228 | + |
| 229 | +缓存雪崩的事前事中事后的解决方案如下: |
| 230 | + |
| 231 | +在批量往Redis存数据的时候,把每个Key的失效时间都加个随机值就好了,这样可以保证数据不会再同一时间大面积失效。 |
| 232 | + |
| 233 | +### 缓存穿透 |
| 234 | + |
| 235 | + **原因:** |
| 236 | + |
| 237 | +1. Redis中大面积出现未命中 |
| 238 | +2. 出现非正常URL访问 |
| 239 | + |
| 240 | + 解决方案:最简单粗暴的方法如果一个查询返回的数据为空(不管是数据不存在,还是系统故障),我们就把这个空结果进行缓存,但它的过期时间会很短,最长不超过五分钟。 |
| 241 | + |
| 242 | + **布隆过滤器(Bloom Filter)**这个也能很好的预防缓存穿透的发生,他的原理也很简单,就是利用高效的数据结构和算法快速判断出你这个Key是否在数据库中存在,不存在你return就好了,存在你就去查DB刷新KV再return |
| 243 | + |
| 244 | +### 缓存击穿 |
| 245 | + |
| 246 | +缓存击穿是指一个Key非常热点,在不停地扛着大量的请求,大并发集中对这一个点进行访问,当这个Key在失效的瞬间,持续的大并发直接落到了数据库上,就在这个Key的点上击穿了缓存。 |
| 247 | + |
| 248 | +解决:设置热点数据永不过期,或者加上互斥锁就搞定了。 |
| 249 | + |
| 250 | +**假如 Redis 里面有 1 亿个 key ,其中有 10w 个 个 key 是以某个固定的已知的前缀开头的,如** |
| 251 | +**果将它们全部找出来?** |
| 252 | +使用 keys 指令可以扫出指定模式的 key 列表。 |
| 253 | +对方接着追问:如果这个 redis 正在给线上的业务提供服务,那使用 keys 指令会有什么问 |
| 254 | +题? |
| 255 | +这个时候你要回答 redis 关键的一个特性:redis 的单线程的。keys 指令会导致线程阻塞一 |
| 256 | +段时间,线上服务会停顿,直到指令执行完毕,服务才能恢复。这个时候可以使用 scan 指 |
| 257 | +令,scan 指令可以无阻塞的提取出指定模式的 key 列表,但是会有一定的重复概率,在客 |
| 258 | +户端做一次去重就可以了,但是整体所花费的时间会比直接用 keys 指令长。 |
| 259 | + |
| 260 | + |
0 commit comments