文章目录
说一下 Redis 有什么特点?
- Redis 基于内存的 K-V 数据库,访问速度快;
- 支持数据的持久化(可以将数据保存到硬盘,重启 Redis 之后可以重新写入内存);
- 支持丰富数据类型,主要包括 string、list、hash、set、zset(sorted set)
Redis5.0 新增了 Stream 类型 - 支持主从数据备份;
- 支持事务;
说一下 Redis 的常用命令 ?
Redis 的命令非常多,这里按照五个重要的数据类型举几个例子
string 类型
- set:赋值;
- get:获取指定 key 的值;
- setnx:当设置的 key 不存在时则进行设置;
- setex:设置 key 的有效时间;
- mset:一次为多个 key 赋值;
- mget:一次获取多个 key 的值;
- incr:对 key 的值做加加操作,并返回新的值;
【注意】面试常问设置 key 的失效时间?
hash 类型 - hset:赋值;
- hget:获取某个 key 下的某个 field 的值;
- hgetall:获取一个 key 下所有 field 和 value;
- hkeys:获取所有的 key;
- hvals:获取某个 key 下所有的 value;
- hexists:测试给定 key 下的 field 是否存在;
【注意】此处有个面试题–> 得到 hash 中的所有的值用哪个方法?
list 类型 - lpush:从头部(链表左侧)添加元素;
- lpop:从 list 头部获取元素
- rpush:从尾部(链表右侧)添加元素;
- rpop:从 list 尾部获取元素
- lrange:查看 list 的所有元素:lrange list 名称 0 -1
- linsert:在某个元素的前后插入元素 linsert list before/after 原有元素 新元素
- lrem:移除元素 lrem list 2(移除个数) “key”
- rpoplpush:从原来的 list 的尾部删除元素,并将其插入到新的 list 的头部
- lindex:返回指定索引的值
- llen:返回 list 的元素个数
set 类型 - sadd:添加元素;
- smembers:获取集合中所有元素
- sismember:判断元素是否在集合中
- srem:删除元素
- scard:获取元素个数,相当于 count
- spop:随机返回删除的元素
- sdiff:差集,返回在第一个 set 里面而不在后面任何一个 set 里面的项(谁在前以谁为标准)
- sdiffstore:差集并保留结果
- sinter:交集,返回多个 set 里面都有的项
- sinterstore:交集并保留结果
- sunion:并集
- sunionstore:并集并保留结果
- smove:移动元素到另一个集合
zset 类型 - zadd:添加元素;
- zrange:获取索引区间内的元素;
- zrangebyscore:获取分数区间内的元素;
关于 Redis 的回收策略,过期机制的相关问题 ?
问题 1:redis 回收策略、过期机制?Redis 内存满了怎么办?有什么替换策略?
问题 2:假如我有 10 亿条数据,可以拿 30%放进 redis 缓存吗?redis 缓存有没有限制?
问题 3:MySQL 有 1000 万数据,Redis 只存 20w 的数据,如何保证 redis 中的数据都是热点数据?
以上三个问题,第一个问题问题问的比较直接,就是问 Redis 的回收策略问题,第二个问题转了一圈,但还是想问 Redis 的
回收策略。
回答:
- Redis 内存数据集大小上升到一定大小的时候,会实行数据淘汰策略(回收策略)
- Redis key 过期的方式有三种
- 被动删除:当读/写一个已经过期的 key 时,会触发惰性删除策略,直接删除掉这个过期 key;
- 主动删除:由于惰性删除策略无法保证冷数据被及时删掉,所以 Redis 会定期主动淘汰一批已过期的 key;
- 当前已用内存超过 maxmemory 限定时,触发主动清理策略;
- 当前已用内存超过 maxmemory 限定时,触发主动清理策略,主动清理策略有 6 种:
- volitile-lru:从已设置过期时间的数据集中挑选"最近最少使用"的数据进行淘汰;
- volitile-ttl:从已设置过期时间的数据集中挑选"将要过期"的数据进行淘汰;
- volitile-random:从已设置过期时间的数据集中"任意"挑选数据进行淘汰;
- allkeys-lru:从数据集中挑选"最近最少使用"的数据进行淘汰;
- allkeys-random:从数据集中"任意"选择数据进行淘汰;
- no-envicition:设置永不过期,禁止驱逐数据;
Redis 缓存怎么与数据库数据保持一致? (缓存与数据双写一致)?
一、什么是缓存与数据库双写一致问题?
如果仅仅查询的话,缓存的数据和数据库的数据是没问题的。
但是,当我们要更新时候呢?各种情况很可能就造成数据库和缓存的数据不一致了。
比如:数据库中存储的某篇文章的点赞数为 10,而 Redis 中存储的点赞数为 11;
二、对于读操作
- 如果数据在 Redis 缓存里,那么就直接取 Redis 缓存的数据。
- 如果数据不在 Redis 缓存里,那么先去查询数据库,然后将数据库查出来的数据写到 Redis 缓存中。
- 最后将数据返回;
从理论上说,只要我们设置了键的过期时间,我们就能保证缓存和数据库的数据最终是一致的。
因为只要缓存数据过期了,就会被删除。之后再读的时候,如果缓存里没有,可以查数据库的数据,然后将查出来的数据写入
到缓存中。
除了设置过期时间,我们还需要做更多的措施来尽量避免数据库与缓存处于不一致的情况发生。
这个时候,跟面试官说一下几种处理策略: - 先更新数据库,再删除缓存
这种策略会出现如下情况
情形 1. 如果更新数据库就失败了,程序可以直接返回错误(Exception),不会出现数据不一致。
情形 2. 更新数据库成功,删除缓存失败;这就会导致数据库里是新数据,而缓存里是旧数据。
解决情形 2 思路:
1.先删除缓存,再更新数据库。如果数据库更新失败了,那么数据库中是旧数据,缓存中是空的,那么数据不会不一致。
这样读的时候缓存没有,所以去读了数据库中的旧数据,然后更新到缓存中。
- 将需要删除的 key 发送到消息队列中,自己消费消息,获得需要删除的 key,不断重试删除操作,直到成功
- 先删除缓存,再更新数据库
这种策略会出现如下美好情况
- 删除缓存成功,更新数据库失败,这样的话,数据库和缓存的数据最终是一致的。
- 删除缓存失败了,我们可以直接返回错误(Exception),数据库和缓存的数据还是一致的。
但是在并发场景下,就会下面的问题:
- 线程 1 删除了缓存;
- 线程 2 查询,发现缓存已不存在;
- 线程 2 去数据库查询得到旧值;
- 线程 2 将旧值写入缓存;
- 线程 1 将新值写入数据库;
- 所以也会导致数据库和缓存不一致的问题;
解决思路:
将删除缓存、修改数据库、读取缓存等的操作积压到队列里边,实现串行化。
Redis 负载均衡了解吗?怎么实现的?
利用 Redis 官方提供的 Redis 集群方案实现 Redis 数据的负载均衡;
Redis 集群自动分割数据到不同的节点上,整个集群的部分节点失败或者不可达的情况下能够继续处理命令。
Redis 集群引入了哈希槽的概念,集群有 16384 个哈希槽,每个 key 通过 CRC16 校验后对 16384 取模来决定放置哪个槽,
集群的每个节点负责一部分 hash 槽。
你们 Redis 做读写分离了么 ?
不做读写分离,我们用的是 Redis 集群架构,是属于分片集群的架构;
Redis 本身在内存上操作,不会涉及 IO 吞吐,即使读写分离也不会提升太多性能;
Redis 在生产上的主要问题是考虑容量,单机最多 10-20G,key 太多降低 Redis 性能,因此采用分片集群结构,已经能保
证了我们的性能。
如果使用了读写分离后,还要考虑主从一致性,主从延迟等问题,会增加务复杂度。
Redis 集群机制中,你觉得有什么不足的地方吗?
- 默认不支持批量操作,如不支持 mset、mget 等命令;
- 如果一个 key 对应的 value 是 Hash 类型的,若 Hash 对象非常大,是不支持映射到不同节点的,只能映射到集群中的一
个节点上!
了解 Redis 的多数据库机制吗?了 解多少?
- 单机下的 Redis 默认支持 16 个数据库;但是:
- 不支持自定义数据库名称。
- 不支持为每个数据库设置访问密码。
- 数据库之间不是完全隔离的,FLUSHALL 命令会清空所有数据库的数据。
- 多数据库不适用存储不同应用的数据。
- 我们生产环境中使用 Redis 集群,集群架构下只有一个数据库空间,即 db0。
因此,我们没有使用 Redis 的多数据库功能!
Redis 持久化的两种方式及应用场景?
-
Redis 持久化分成两种方式:RDB(redis database)、 AOF(append only file)
-
RDB 是在不同的时间点,将 Redis 某一时刻的数据生成快照并存储到磁盘上。
-
AOF 是只允许追加不允许改写文件,是将 Redis 执行过的所有写指令记录下来,在下次 redis 重启的时候,只要把这些写
指令从前到后重复执行一遍,就可以实现数据恢复。 -
RDB 持久化的触发方式:自动触发和手动触发;
自动触发方式在 redis.conf 文件配置
save 900 1 900 秒内至少有一个 key 被改变就自动触发 RDB 持久化
save 300 10 300 秒内至少有 10 个 key 被改变就自动触发 RDB 持久化
save 60 10000 60 秒内至少有 10000 个 key 被改变就自动触发 RDB 持久化
手动触发方式
save 命令:执行此命令会阻塞 Redis 服务器,执行命令期间,Redis 不能处理其它命令,直到 RDB 过程完成为止。
bgsave 命令:执行该命令时,Redis 会在后台异步进行快照操作,做快照的同时还可以响应客户端请求;
此时 Redis 进程执行"fork 操作"创建子进程,持久化过程由子进程负责,完成后自动结束。
阻塞只发生在 fork 阶段,一般时间很短。 -
AOF 持久化
默认的 AOF 持久化策略是每秒钟一次同步策略;,
AOF 的同步策略还有 always 和 no 两种,加上 everysec,共三种;
说完这些之后,可能还会问:RDB 和 AOF 可以同时使用吗?
RDB 和 AOF 两种方式可以同时使用,这时如果 Redis 重启,则会优先采用 AOF 方式进行数据恢复,因为 AOF 方式的数据恢复
完整度更高;
Redis分布式锁底层是如何实现的?
- ⾸先利⽤setnx来保证:如果key不存在才能获取到锁,如果key存在,则获取不到锁
- 然后还要利⽤lua脚本来保证多个redis操作的原⼦性
- 同时还要考虑到锁过期,所以需要额外的⼀个看⻔狗定时任务来监听锁是否需要续约
- 同时还要考虑到redis节点挂掉后的情况,所以需要采⽤红锁的⽅式来同时向N/2+1个节点申请锁,
都申请到了才证明获取锁成功,这样就算其中某个redis节点挂掉了,锁也不能被其他客户端获取到
Redis主从复制的核⼼原理 ?
Redis的主从复制是提⾼Redis的可靠性的有效措施,主从复制的流程如下:
- 集群启动时,主从库间会先建⽴连接,为全量复制做准备
- 主库将所有数据同步给从库。从库收到数据后,在本地完成数据加载,这个过程依赖于内存快照
RDB - 在主库将数据同步给从库的过程中,主库不会阻塞,仍然可以正常接收请求。否则,redis的服务就被中断
了。但是,这些请求中的写操作并没有记录到刚刚⽣成的RDB⽂件中。为了保证主从库的数据⼀致性,主
库会在内存中⽤专⻔的replication buffer,记录RDB⽂件⽣成收到的所有写操作。 - 最后,也就是第三个阶段,主库会把第⼆阶段执⾏过程中新收到的写命令,再发送给从库。具体的操作
是,当主库完成RDB⽂件发送后,就会把此时replocation buffer中修改操作发送给从库,从库再执⾏这些
操作。这样⼀来,主从库就实现同步了 - 后续主库和从库都可以处理客户端读操作,写操作只能交给主库处理,主库接收到写操作后,还会将写操
作发送给从库,实现增量同步
Redis集群策略 ?
Redis提供了三种集群策略:
- 主从模式:这种模式⽐较简单,主库可以读写,并且会和从库进⾏数据同步,这种模式下,客户端
直接连主库或某个从库,但是但主库或从库宕机后,客户端需要⼿动修改IP,另外,这种模式也⽐
较难进⾏扩容,整个集群所能存储的数据受到某台机器的内存容量,所以不可能⽀持特⼤数据量 - 哨兵模式:这种模式在主从的基础上新增了哨兵节点,但主库节点宕机后,哨兵会发现主库节点宕
机,然后在从库中选择⼀个库作为进的主库,另外哨兵也可以做集群,从⽽可以保证但某⼀个哨兵
节点宕机后,还有其他哨兵节点可以继续⼯作,这种模式可以⽐较好的保证Redis集群的⾼可⽤,但
是仍然不能很好的解决Redis的容量上限问题。 - Cluster模式:Cluster模式是⽤得⽐较多的模式,它⽀持多主多从,这种模式会按照key进⾏槽位的
分配,可以使得不同的key分散到不同的主节点上,利⽤这种模式可以使得整个集群⽀持更⼤的数据
Redis分布式锁底层是如何实现的?
Redis主从复制的核⼼原理
Redis集群策略
39
容量,同时每个主节点可以拥有⾃⼰的多个从节点,如果该主节点宕机,会从它的从节点中选举⼀
个新的主节点。
对于这三种模式,如果Redis要存的数据量不⼤,可以选择哨兵模式,如果Redis要存的数据量⼤,并且
需要持续的扩容,那么选择Cluster模式。
缓存穿透、缓存击穿、缓存雪崩分别是什么 ?
缓存中存放的⼤多都是热点数据,⽬的就是防⽌请求可以直接从缓存中获取到数据,⽽不⽤访问
Mysql。
- 缓存雪崩:如果缓存中某⼀时刻⼤批热点数据同时过期,那么就可能导致⼤量请求直接访问Mysql
了,解决办法就是在过期时间上增加⼀点随机值,另外如果搭建⼀个⾼可⽤的Redis集群也是防⽌缓
存雪崩的有效⼿段 - 缓存击穿:和缓存雪崩类似,缓存雪崩是⼤批热点数据失效,⽽缓存击穿是指某⼀个热点key突然失
效,也导致了⼤量请求直接访问Mysql数据库,这就是缓存击穿,解决⽅案就是考虑这个热点key不
设过期时间 - 缓存穿透:假如某⼀时刻访问redis的⼤量key都在redis中不存在(⽐如⿊客故意伪造⼀些乱七⼋糟
的key),那么也会给数据造成压⼒,这就是缓存穿透,解决⽅案是使⽤布隆过滤器,它的作⽤就是
如果它认为⼀个key不存在,那么这个key就肯定不存在,所以可以在缓存之前加⼀层布隆过滤器来
拦截不存在的key