Redis面试题

1、Redis简介
简单来说 redis 是⼀个非关系型数据库,不过与传统数据库不同的是 redis 的数据是存在内存中的,所以读写速度⾮常快,因此 redis 被⼴泛应⽤于缓存⽅向。另外,redis 也经常⽤来做分布式锁。redis 提供了多种数据类型来⽀持不同的业务场景。除此之外,redis ⽀持事务 、持久化、LUA脚本、LRU驱动事件、多种集群⽅案。

2、Redis五种数据结构、底层原理、使用场景
redis 自身是一个 Map,其中所有的数据都是采用 key : value 的形式存储;
数据类型指的是存储的数据的类型,也就是 value 部分的类型,key 部分永远都是字符串。
2.1 String
字符串类型是 Redis 最基础的数据结构,键都是字符串类型,而且其他几种数据结构都是在字符串类型的基础上构建的。字符串类型的值可以实际可以是字符串(简单的字符串、复杂的字符串如 JSON、XML)、数字(整形、浮点数)、甚至二进制(图片、音频、视频),但是值最大不能超过 512 MB。

◆String命令
添加/修改数据 set key value
获取数据 get key
删除数据 del key
添加/修改多个数据 mset key1 value1 key2 value2 …
获取多个数据 mget key1 key2 …
获取数据字符个数(字符串长度)strlen key
追加信息到原始信息后部(如果原始信息存在就追加,否则新建)
append key value
设置数值数据增加指定范围的值
incr key
incrby key increment
incrbyfloat key increment
设置数值数据减少指定范围的值
decr key
decrby key increment
设置数据具有指定的生命周期
setex key seconds value
psetex key milliseconds value

◆string 的内部编码
int:8 个字节的长整形
embstr:小于等于 39 个字节的字符串
raw:大于 39 个字节的字符串

◆string使用场景
缓存功能:Redis 作为缓存层,MySQL 作为存储层,首先从 Redis 获取数据,如果失败就从 MySQL 获取并将结果写回 Redis 并添加过期时间。
计数:Redis 可以实现快速计数功能,例如视频每播放一次就用 incy 把播放数加 1。
限速:例如为了短信接口不被频繁访问会限制用户每分钟获取验证码的次数或者网站限制一个 IP 地址不能在一秒内访问超过 n 次。可以使用键过期策略和自增计数实现。

2.2 hash
哈希类型指键值本身又是一个键值对结构,哈希类型中的映射关系叫 field-value,这里的 value 是指field 对于的值而不是键对于的值。
◆hash 的命令
添加/修改数据 hset key field value
获取数据 hget key field hgetall key
删除数据 hdel key field1 [field2]
添加/修改多个数据 hmset key field1 value1 field2 value2 …
获取多个数据 hmget key field1 field2 …
获取哈希表中字段的数量 hlen key
获取哈希表中是否存在指定的字段 hexists key field
获取哈希表中所有的字段名或字段值
hkeys key hvals key
设置指定字段的数值数据增加指定范围的值
hincrby key field increment
hincrbyfloat key field increment
只在 key 指定的哈希集中不存在指定的字段时,设置字段的值。如果 key 指定的哈希集不存在,会创建一个新的哈希集并与 key 关联
hsetnx key field value

◆hash 的内部编码
ziplist 压缩列表:当哈希类型元素个数和值小于配置值(默认 512 个和 64 字节)时会使用 ziplist 作为
内部实现,使用更紧凑的结构实现多个元素的连续存储,在节省内存方面比 hashtable 更优秀。
hashtable 哈希表:当哈希类型无法满足 ziplist 的条件时会使用 hashtable 作为哈希的内部实现,因为
此时 ziplist 的读写效率会下降,而 hashtable 的读写时间复杂度都为 O(1)。

◆hash使用场景
缓存用户信息,每个用户属性使用一对 field-value,但只用一个键保存。
优点:简单直观,如果合理使用可以减少内存空间使用。
缺点:要控制哈希在 ziplist 和 hashtable 两种内部编码的转换,hashtable 会消耗更多内存。

2.3 list
list 是用来存储多个有序的字符串,列表中的每个字符串称为元素,一个列表最多可以存储 2^32 -1 个元素。可以对列表两端插入(push)和弹出(pop),还可以获取指定范围的元素列表、获取指定索引下标的元素等。列表是一种比较灵活的数据结构,它可以充当栈和队列的角色,在实际开发中有很多应用场景。
list 有两个特点:① 列表中的元素是有序的,可以通过索引下标获取某个元素或者某个范围内的元素列
表。② 列表中的元素可以重复。

◆list命令
添加/修改数据
lpush key value1 [value2] ……
rpush key value1 [value2] ……
获取数据
lrange key start stop
lindex key index
llen key
获取并移除数据
lpop key
rpop key
规定时间内获取并移除数据
blpop key1 [key2] timeout
brpop key1 [key2] timeout
brpoplpush source destination timeout
移除指定数据
lrem key count value

◆list 的内部编码
ziplist 压缩列表:跟哈希的 zipilist 相同,元素个数和大小小于配置值(默认 512 个和 64 字节)时使用。
linkedlist 链表:当列表类型无法满足 ziplist 的条件时会使用linkedlist。
Redis 3.2 提供了 quicklist 内部编码,它是以一个 ziplist 为节点的 linkedlist,它结合了两者的优势,为列表类提供了一种更为优秀的内部编码实现。

◆list 的应用场景
消息队列
Redis 的 lpush + brpop 即可实现阻塞队列,生产者客户端使用 lpush 从列表左侧插入元素,多个消费者客户端使用 brpop 命令阻塞式地抢列表尾部的元素,多个客户端保证了消费的负载均衡和高可用性。
文章列表
每个用户有属于自己的文章列表,现在需要分页展示文章列表,就可以考虑使用列表。因为列表不但有序,同时支持按照索引范围获取元素。每篇文章使用哈希结构存储。
lpush + lpop = 栈、lpush + rpop = 队列、lpush + ltrim = 优先集合、lpush + brpop = 消息队列。

2.4 set
集合类型也是用来保存多个字符串元素,和列表不同的是集合不允许有重复元素,并且集合中的元素是无序的,不能通过索引下标获取元素。一个集合最多可以存储 2 32 -1 个元素。Redis 除了支持集合内的增删改查,还支持多个集合取交集、并集、差集。

◆set命令
添加数据 sadd key member1 [member2]
获取全部数据 smembers key
删除数据 srem key member1 [member2]
获取集合数据总量 scard key
判断集合中是否包含指定数据 sismember key member
随机获取集合中指定数量的数据 srandmember key [count]
随机获取集合中的某个数据并将该数据移出集合 spop key [count]
求两个集合的交、并、差集
sinter key1 [key2]
sunion key1 [key2]
sdiff key1 [key2]
求两个集合的交、并、差集并存储到指定集合中
sinterstore destination key1 [key2]
sunionstore destination key1 [key2]
sdiffstore destination key1 [key2]
将指定数据从原始集合中移动到目标集合中
smove source destination member

◆set 的内部编码
intset 整数集合:当集合中的元素个数小于配置值(默认 512 个时),使用 intset。
hashtable 哈希表:当集合类型无法满足 intset 条件时使用 hashtable。当某个元素不为整数时,也会使用 hashtable。

◆set 的应用场景
set 比较典型的使用场景是标签,例如一个用户可能与娱乐、体育比较感兴趣,另一个用户可能对例时、新闻比较感兴趣,这些兴趣点就是标签。这些数据对于用户体验以及增强用户黏度比较重要。
sadd = 标签、spop/srandmember = 生成随机数,比如抽奖、sadd + sinter = 社交需求。

2.5 zset
有序集合保留了集合不能有重复成员的特性,不同的是可以排序。但是它和列表使用索引下标作为排序依据不同的是,他给每个元素设置一个分数(score)作为排序的依据。有序集合提供了获取指定分数和元素查询范围、计算成员排名等功能。

◆zset命令
添加数据 zadd key score1 member1 [score2 member2]
获取全部数据
zrange key start stop [WITHSCORES]
zrevrange key start stop [WITHSCORES]
删除数据
zrem key member [member …]
按条件获取数据
zrangebyscore key min max [WITHSCORES] [LIMIT]
zrevrangebyscore key max min [WITHSCORES]
条件删除数据
zremrangebyrank key start stop
zremrangebyscore key min max
获取集合数据总量
zcard key
zcount key min max
集合交、并操作
zinterstore destination numkeys key [key …]
zunionstore destination numkeys key [key …]
获取数据对应的索引(排名)
zrank key member
zrevrank key member
score值获取与修改
zscore key member
zincrby key increment member

◆zset 的内部编码
ziplist 压缩列表:当有序集合元素个数和值小于配置值(默认128 个和 64 字节)时会使用 ziplist 作为内部实现。
skiplist 跳跃表:当 ziplist 不满足条件时使用,因为此时 ziplist 的读写效率会下降。

◆zset 的应用场景
有序集合的典型使用场景就是排行榜系统,例如用户上传了一个视频并获得了赞,可以使用 zadd 和zincrby。如果需要将用户从榜单删除,可以使用 zrem。如果要展示获取赞数最多的十个用户,可以使用 zrange。

3、redis 设置过期时间
Redis中有个设置时间过期的功能,即对存储在 redis 数据库中的值可以设置⼀个过期时间。作为⼀个缓存数据库,这是⾮常实⽤的。如我们⼀般项⽬中的 token 或者⼀些登录信息,尤其是短信验证码都是有时间限制的,按照传统的数据库处理⽅式,⼀般都是⾃⼰判断过期,这样⽆疑会严重影响项⽬性能。
我们 set key 的时候,都可以给⼀个 expire time,就是过期时间,通过过期时间我们可以指定这个key 可以存活的时间。
如果假设你设置了⼀批 key 只能存活1个⼩时,那么接下来1⼩时后,redis是怎么对这批key进⾏删除的?
定期删除+惰性删除。
通过名字⼤概就能猜出这两个删除⽅式的意思了。
定期删除:redis默认是每隔 100ms 就随机抽取⼀些设置了过期时间的key,检查其是否过期,如果过期就删除。注意这⾥是随机抽取的。为什么要随机呢?你想⼀想假如 redis 存了⼏⼗万个 key ,每隔100ms就遍历所有的设置过期时间的 key 的话,就会给 CPU 带来很⼤的负载!
惰性删除 :定期删除可能会导致很多过期 key 到了时间并没有被删除掉。所以就有了惰性删除。假如你的过期 key,靠定期删除没有被删除掉,还停留在内存⾥,除⾮你的系统去查⼀下那个 key,才会被redis给删除掉。这就是所谓的惰性删除,也是够懒的哈!
但是仅仅通过设置过期时间还是有问题的。我们想⼀下:如果定期删除漏掉了很多过期 key,然后你也没及时去查,也就没⾛惰性删除,此时会怎么样?如果⼤量过期key堆积在内存⾥,导致redis内存块耗尽了。怎么解决这个问题呢? redis 内存淘汰机制。

4、redis 内存淘汰机制(MySQL⾥有2000w数据,Redis中只存20w的数据,如何保证Redis中的数据都是热点数据?)
redis 提供 6种数据淘汰策略:

  1. volatile-lru:从已设置过期时间的数据集(server.db[i].expires)中挑选最近最少使⽤的数据淘汰;
  2. volatile-ttl:从已设置过期时间的数据集(server.db[i].expires)中挑选将要过期的数据淘汰;
  3. volatile-random:从已设置过期时间的数据集(server.db[i].expires)中任意选择数据淘汰;
  4. allkeys-lru:当内存不⾜以容纳新写⼊数据时,在键空间中,移除最近最少使⽤的key(这个是最常⽤的);
  5. allkeys-random:从数据集(server.db[i].dict)中任意选择数据淘汰;
  6. no-eviction:禁⽌驱逐数据,也就是说当内存不⾜以容纳新写⼊数据时,新写⼊操作会报错。
    4.0版本后增加以下两种:
  7. volatile-lfu:从已设置过期时间的数据集(server.db[i].expires)中挑选最不经常使⽤的数据淘汰;
  8. allkeys-lfu:当内存不⾜以容纳新写⼊数据时,在键空间中,移除最不经常使⽤的key。

5、redis 持久化机制(怎么保证 redis 挂掉之后再重启数据可以进⾏恢复)
Redis 是内存型数据库,为了保证数据在断电后不会丢失,需要将内存中的数据持久化到硬盘上。
RDB 快照和 AOF 日志
RDB 持久化
将某个时间点的所有数据都存放到硬盘上。
可以将快照复制到其它服务器从而创建具有相同数据的服务器副本。
如果系统发生故障,将会丢失最后一次创建快照之后的数据。
如果数据量很大,保存快照的时间会很长。
RDB三种启动方式对比

AOF 持久化
将写命令添加到 AOF 文件(Append Only File)的末尾。
使用 AOF 持久化需要设置同步选项,从而确保写命令同步到磁盘文件上的时机。这是因为对文件进行写入并不会马上将内容同步到磁盘上,而是先存储到缓冲区,然后由操作系统决定什么时候同步到磁盘。有以下同步选项:

always 选项会严重减低服务器的性能;
everysec 选项比较合适,可以保证系统崩溃时只会丢失一秒左右的数据,并且 Redis 每秒执行一次同步对服务
器性能几乎没有任何影响;
no 选项并不能给服务器性能带来多大的提升,而且也会增加系统崩溃时数据丢失的数量。
AOF重写方式
 手动重写
bgrewriteaof
 自动重写
auto-aof-rewrite-min-size size
auto-aof-rewrite-percentage percentage
RDB VS AOF

6、redis 事务
开启事务 multi
作用:设定事务的开启位置,此指令执行后,后续的所有指令均加入到事务中;
执行事务 exec
作用:设定事务的结束位置,同时执行事务。与multi成对出现,成对使用;
注意:加入事务的命令暂时进入到任务队列中,并没有立即执行,只有执行exec命令才开始执行。
取消事务 discard
作用:终止当前事务的定义,发生在multi之后,exec之前

7、主从复制、哨兵、集群
主从复制即将master中的数据即时、有效的复制到slave中
特征:一个master可以拥有多个slave,一个slave只对应一个master
职责:
master:
写数据
执行写操作时,将出现变化的数据自动同步到slave
读数据(可忽略)
slave:
读数据
写数据(禁止)
主从复制的作用
 读写分离:master写、slave读,提高服务器的读写负载能力
 负载均衡:基于主从结构,配合读写分离,由slave分担master负载,并根据需求的变化,改变slave的数量,通过多个从节点分担数据读取负载,大大提高Redis服务器并发量与数据吞吐量
 故障恢复:当master出现问题时,由slave提供服务,实现快速的故障恢复
 数据冗余:实现数据热备份,是持久化之外的一种数据冗余方式
 高可用基石:基于主从复制,构建哨兵模式与集群,实现Redis的高可用方案

哨兵(sentinel) 是一个分布式系统,用于对主从结构中的每台服务器进行监控,当出现故障时通过投票机制选择新的master并将所有slave连接到新的master。
哨兵的作用
 监控
不断的检查master和slave是否正常运行。master存活检测、master与slave运行情况检测。
 通知(提醒)
当被监控的服务器出现问题时,向其他(哨兵间,客户端)发送通知。
 自动故障转移
断开master与slave连接,选取一个slave作为master,将其他slave连接到新的master,并告知客户端新的服务器地址。

集群架构
 集群就是使用网络将若干台计算机联通起来,并提供统一的管理方式,使其对外呈现单机的服务效果
集群作用
 分散单台服务器的访问压力,实现负载均衡
 分散单台服务器的存储压力,实现可扩展性
 降低单台服务器宕机带来的业务灾难

8、企业级问题
 缓存预热
“宕机”,服务器启动后迅速宕机。
问题排查

  1. 请求数量较高
  2. 主从之间数据吞吐量较大,数据同步操作频度较高
    解决方案
    前置准备工作:
  3. 日常例行统计数据访问记录,统计访问频度较高的热点数据
  4. 利用LRU数据删除策略,构建数据留存队列
    例如:storm与kafka配合
    准备工作:
  5. 将统计结果中的数据分类,根据级别,redis优先加载级别较高的热点数据
  6. 利用分布式多服务器同时进行数据读取,提速数据加载过程
  7. 热点数据主从同时预热
    实施:
  8. 使用脚本程序固定触发数据预热过程
  9. 如果条件允许,使用了CDN(内容分发网络),效果会更好
    总结
    缓存预热就是系统启动前,提前将相关的缓存数据直接加载到缓存系统。避免在用户请求的时候,先查询数据库,然后再将数据缓存的问题!用户直接查询事先被预热的缓存数据!

 缓存雪崩
缓存同⼀时间⼤⾯积的失效,所以,后⾯的请求都会落到数据库上,造成数据库短时间内承受⼤量请求⽽崩掉。
解决办法
事前:尽量保证整个 redis 集群的⾼可⽤性,发现机器宕机尽快补上。选择合适的内存淘汰策略。
事中:本地ehcache缓存 + hystrix限流&降级,避免MySQL崩掉
事后:利⽤ redis 持久化机制保存的数据尽快恢复缓存

 缓存击穿
问题排查

  1. Redis中某个key过期,该key访问量巨大
  2. 多个数据请求从服务器直接压到Redis后,均未命中
  3. Redis在短时间内发起了大量对数据库中同一数据的访问
    问题分析
    单个key高热数据
    key过期

 缓存穿透
缓存穿透说简单点就是⼤量请求的 key 根本不存在于缓存中,导致请求直接到了数据库上,根本没有经过缓存这⼀层。举个例⼦:某个⿊客故意制造我们缓存中不存在的 key 发起⼤量请求,导致⼤量请求落到数据库。
解决办法
缓存⽆效 key
布隆过滤器:把所有可能存在的请求的值都存放在布隆过滤器中,当⽤户请求过来,我会先判断⽤户发来的请求的值是否存在于布隆过滤器中。不存在的话,直接返回请求参数错误信息给客户端,存在的话才会⾛下⾯的流程。

©️2020 CSDN 皮肤主题: 数字20 设计师:CSDN官方博客 返回首页