Redis高频面试题(欢迎来学习讨论)

  • 关于redis基本了解
    • Redis是什么?
    • 为什么要用Redis?
    • Redis有哪些数据类型
    • Redis 的底层数据结构
    • String类型
    • Hash类型
    • List(列表)
    • Set(集合)
    • zset(sorted set:有序集合)
    • BitMap
    • HyperLogLog 结构
    • GEO
    • Stream
    • Redis是单线程还是多线程? 为什么说redis能够快速执行。
  • Redis是怎么持久化的?
    • RDB:
      • RDB持久化的触发方式:
      • RDB的优缺点:
    • AOF:
      • AOF为什么文件大?
      • 文件越来越大的解决方法是:
      • 三种写回策略
      • 同时开启aof和rdb,重启后会优先加载哪一个?
    • RDB和AOF合并: 4版本的时候提供了混合持久化方式
  • 缓存问题
    • 缓存-访问过程:
    • 缓存穿透
    • 缓存击穿
    • 缓存雪崩
    • 内存过期键的删除策略。缓存回收
    • 淘汰机制
    • 使用过Redis做异步队列么,你是怎么用的?
    • 如何进行缓存的预热?
  • 集群问题
    • 主从不一致的问题
    • 2.8版本(不包括2.8)之前的主从同步
    • 2.8版本之后的reids主从同步
    • Redis读写分离、哨兵机制、主备切换

本人是2022应届毕业生,在此积累一些常见面试题,一方面能总结知识,查漏补缺,另一方面希望自己能找到好的工作。

关于redis基本了解

Redis是什么?

Redis是开源,基于C语言构建的NOSQL数据库,内存存储的数据结构服务器,可用作数据库,高速缓存和消息队列代理。数据存储到内存当中,所以读写速度非常的快,官方给出的答案是在单线程的情况下,读写速度10万每秒。
支持9种数据类型
支持持久化
支持集群

性能极高 – Redis能读的速度是110000次/s,写的速度是81000次/s 。

为什么要用Redis?

因为传统的关系型数据库如Mysql已经不能适用所有的场景了,比如秒杀的库存扣减,APP首页的访问流量高峰等等,都很容易把数据库打崩,所以引入了缓存中间件-reids。

Redis有哪些数据类型

最为常见的有五种:String(字符串),Hash(哈希),List(列表),Set(集合),Zset(有序集合)
这五种数据类型都由多种数据结构实现的,主要是出于时间和空间的考虑,当数据量小的时候使用更简单的数据结构,有利于节省内存,提高性能。

随时版本进行更新,先后加入了四种数据类型有: BitMap(2.2 版新增)、HyperLogLog(2.8 版新增)、GEO(3.2 版新增)、Stream(5.0 版新增)。

String 类型的应用场景:缓存对象、常规计数、分布式锁等。
List 类型的应用场景:消息队列(有两个问题:1. 生产者需要自行实现全局唯一 ID;2. 不能以消费组形式消费数据)等。
Hash 类型:缓存对象、购物车等。
Set 类型:聚合计算(并集、交集、差集)场景,比如点赞、共同关注、抽奖活动等。
Zset 类型:排序场景,比如排行榜、电话和姓名排序等。BitMap(2.2 版新增):二值状态统计的场景,比如签到、判断用户登陆状态、连续签到用户总数等;
HyperLogLog(2.8 版新增):海量数据基数统计的场景,比如百万级网页 UV 计数等;
GEO(3.2 版新增):存储地理位置信息的场景,比如滴滴叫车;
Stream(5.0 版新增):消息队列,相比于基于 List 类型实现的消息队列,有这两个特有的特性:自动生成全局唯一消息ID,支持以消费组形式消费数据。

Redis 的底层数据结构

分别是,简单动态字符串,双向链表,压缩列表,哈希表,跳表和整数数组,它们和数据类型的对应关系如下图所示:

String类型

  1. 基本介绍:

String 类型的底层的数据结构实现主要是 int 和 SDS(简单动态字符串)。一个key对应一个value。

string类型是二进制安全的。意思是redis的string可以包含任何数据。比如jpg图片、视频、或者序列化的对象 。

string类型是Redis最基本的数据类型,一个键最大能存储512MB。
可以使用increase原子性的加一操作。

  1. 常用命令
# 设置一个值:
set name zhangsan # 根据Key 获取对应的value
get name
"zhangsan"#判断某个key是否存在
exists name
(integer)1# 返回Key所存储的字符串长度
strlen name
(integer)8# 删除某个key对应的值
del name
(integer)1# 分布式锁:
setnx key value:不存在则创建,存在则不创建
set key value[EX seconds] [PX milliseconds] [NX|XX]:
EX:key在多少秒之后过期
PX:key在多少毫秒之后过期
NX:当key不存在的时候,才创建key,效果等同于setnx
XX:当key存在的时候,覆盖key。【默认】127.0.0.1:6379[5]> setnx k001 textNX
(integer) 1
127.0.0.1:6379[5]> setnx k001 test
(integer) 0
127.0.0.1:6379[5]> 数值增减:
incr key:每次递增1
incrby key increment:指定递增整数
decr key:每次递减1
decrby key decrement:指定递减整数
```java
127.0.0.1:6379[5]> incr key01
(integer) 1
127.0.0.1:6379[5]> incr key01
(integer) 2
127.0.0.1:6379[5]> incr key01
(integer) 3
127.0.0.1:6379[5]> incr key01
(integer) 4
127.0.0.1:6379[5]> incrby key01 10
(integer) 14
127.0.0.1:6379[5]> incrby key01 10
(integer) 24
  1. 使用使用场景

第一:可以用来缓存JSON对象,SET user:1 ‘{“name”:“xiaolin”, “age”:18}’

第二:计数场景,如计算访问次数、点赞、转发、库存数量等等。因为 Redis 处理命令是单线程,所以执行命令的过程是原子的。所以适合计数场景。

每执行一次incr key,阅读量就+1
通过get key ,可以获取当前阅读量

127.0.0.1:6379[5]> incr like:001
(integer) 1
127.0.0.1:6379[5]> incr like:001
(integer) 2
127.0.0.1:6379[5]> incr like:001
(integer) 3
127.0.0.1:6379[5]> incr like:001
(integer) 4
127.0.0.1:6379[5]> get like:001
"4"

第三:分布式锁
setnx 可以用它来实现分布式锁:
如果 key 不存在,则显示插入成功,可以用来表示加锁成功;
如果 key 存在,则会显示插入失败,可以用来表示加锁失败。

解决缓存击穿、雪崩问题。抢到锁进行去数据库查询数据,当同步了缓存后,删除key。也就是释放了锁。

一般而言,还会对分布式锁加上过期时间,分布式锁的命令如下:

SET lock_key lock_value NX PX 10000

而解锁的过程就是将 lock_key 键删除,但不能乱删,要保证执行操作的客户端就是加锁的客户端。所以,解锁的时候,我们要先判断锁的 unique_value 是否为加锁客户端,是的话,才将 lock_key 键删除。

可以看到,解锁是有两个操作,这时就需要 Lua 脚本来保证解锁的原子性,因为 Redis 在执行 Lua 脚本时,可以以原子性的方式执行,保证了锁释放操作的原子性。

// 释放锁时,先比较 unique_value 是否相等,避免锁的误释放
if redis.call("get",KEYS[1]) == ARGV[1] thenreturn redis.call("del",KEYS[1])
elsereturn 0
end

这样一来,就通过使用 SET 命令和 Lua 脚本在 Redis 单节点上完成了分布式锁的加锁和解锁。

Hash类型

  1. 基本介绍

Redis hash 是一个键值对集合。Hash 类型的底层数据结构是由压缩列表或哈希表实现的。在 Redis 7.0 中,压缩列表数据结构已经废弃了,交由 listpack 数据结构来实现了

Redis hash是一个string类型的field和value的映射表,hash特别适合用于存储对象。

  1. 常用命令:
1. 设置、获取字段值
hset key field value:一次设置一个字段值
hget key field:一次设置一个字段值
一次设置、获取多个字段值:hmset /hmget
hgetall key : 获取所有字段值
2. hlen:获取某个key内的全部数量
3. 删除一个key :hdel# 返回哈希表key中field的数量
hlen key
# 返回哈希表key中所有的键值
hgetall key # 为哈希表key中field键的值加上增量n
hincrby key field n    127.0.0.1:6379[5]> hset person id 1
(integer) 1
127.0.0.1:6379[5]> hset person name zhangsan
(integer) 1
127.0.0.1:6379[5]> hmset person add beijing score 100
OK
127.0.0.1:6379[5]> HGETALL person
1) "id"
2) "1"
3) "name"
4) "zhangsan"
5) "add"
6) "beijing"
7) "score"
8) "100"

  1. 使用场景

1:用来存储对象,比如用户信息

# 存储一个哈希表uid:1的键值
> HSET uid:1 name Tom age 15
2
# 存储一个哈希表uid:2的键值
> HSET uid:2 name Jerry age 13
2
# 获取哈希表用户id为1中所有的键值
> HGETALL uid:1
1) "name"
2) "Tom"
3) "age"
4) "15"


List(列表)

  1. 介绍

list 列表是简单的字符串列表,按照插入顺序排序。可以从头部或尾部向 List 列表添加元素。
列表最多可存储 232 - 1 元素 (4294967295, 每个列表可存储40多亿)。

在 Redis 3.2 版本之后,List 数据类型底层数据结构就只由 quicklist 实现了

  1. 常用命令:
# 将一个或多个值value插入到key列表的表头(最左边),最后的值在最前面
LPUSH key value [value ...]
# 将一个或多个值value插入到key列表的表尾(最右边)
RPUSH key value [value ...]
# 移除并返回key列表的头元素
LPOP key
# 移除并返回key列表的尾元素
RPOP key # 返回列表key中指定区间内的元素,区间以偏移量start和stop指定,从0开始
LRANGE key start stop# 从key列表表头弹出一个元素,没有就阻塞timeout秒,如果timeout=0则一直阻塞
BLPOP key [key ...] timeout
# 从key列表表尾弹出一个元素,没有就阻塞timeout秒,如果timeout=0则一直阻塞
BRPOP key [key ...] timeout
  1. 应用场景:
    消息队列
    可以用于队列和栈
    如果头进尾出,就是队列
    如果头进头出,就是栈

Set(集合)

  1. 介绍:

Set 类型是一个无序并唯一的键值集合,它的存储顺序不会按照插入的先后顺序进行存储。
Set 类型除了支持集合内的增删改查,同时还支持多个集合取交集、并集、差集。

Set 类型和 List 类型的区别如下:
List 可以存储重复元素,Set 只能存储非重复元素;
List 是按照元素的先后顺序存储元素的,而 Set 则是无序方式存储元素的。

  1. 常用命令
# 往集合key中存入元素,元素存在则忽略,若key不存在则新建
SADD key member [member ...]
# 从集合key中删除元素
SREM key member [member ...]
# 获取集合key中所有元素
SMEMBERS key
# 获取集合key中的元素个数
SCARD key# 判断member元素是否存在于集合key中
SISMEMBER key member# 从集合key中随机选出count个元素,元素不从key中删除
SRANDMEMBER key [count]
# 从集合key中随机选出count个元素,元素从key中删除
SPOP key [count]
Set运算操作:# 交集运算
SINTER key [key ...]
# 将交集结果存入新集合destination中
SINTERSTORE destination key [key ...]# 并集运算
SUNION key [key ...]
# 将并集结果存入新集合destination中
SUNIONSTORE destination key [key ...]# 差集运算
SDIFF key [key ...]
# 将差集结果存入新集合destination中
SDIFFSTORE destination key [key ...]
  1. 应用场景
    一:点赞

因为set集合保证唯一性,从而可以保证一个用户只能点赞一次。key是文章id,value是点赞用户的id。

##  用户1点赞
127.0.0.1:6379[1]> sadd article:1 uid:1
(integer) 1
## 用户2点赞
127.0.0.1:6379[1]> sadd article:1 uid:2
(integer) 1
## 测试重复添加一条数据,发现添加失败
127.0.0.1:6379[1]> sadd article:1 uid:2
(integer) 0## 取消点赞
127.0.0.1:6379[1]> srem article:1 uid:2
(integer) 1
127.0.0.1:6379[1]> 获取 article:1 文章所有点赞用户 :
SMEMBERS article:1
"uid:1"获取 article:1 文章的点赞用户数量:
> SCARD article:1
(integer) 2判断用户 uid:1 是否对文章 article:1 点赞了:
> SISMEMBER article:1 uid:1
(integer) 0  # 返回0说明没点赞,返回1则说明点赞了

二:共同关注
Set 类型支持交集运算,所以可以用来计算共同关注的好友、公众号等。

# 获取共同关注
> SINTER uid:1 uid:2验证某个公众号是否同时被 uid:1 或 uid:2 关注:
> SISMEMBER uid:1 5
(integer) 1 # 返回0,说明关注了
> SISMEMBER uid:2 5
(integer) 0 # 返回0,说明没关注

三:抽奖活动
存储某活动中中奖的用户名 ,Set 类型因为有去重功能,可以保证同一个用户不会中奖两次。

zset(sorted set:有序集合)

  1. 介绍:

Zset 类型(有序集合类型)相比于 Set 类型多了一个排序属性 score(分值),对于有序集合 ZSet 来说,每个存储元素相当于有两个值组成的,一个是有序结合的元素值,一个是排序值。

有序集合保留了集合不能有重复成员的特性(分值可以重复),但不同的是,有序集合中的元素可以排序。

  1. 常用命令:
# 往有序集合key中加入带分值元素
ZADD key score member [[score member]...]
# 往有序集合key中删除元素
ZREM key member [member...]
# 返回有序集合key中元素member的分值
ZSCORE key member
# 返回有序集合key中元素个数
ZCARD key # 为有序集合key中元素member的分值加上increment
ZINCRBY key increment member # 正序获取有序集合key从start下标到stop下标的元素
ZRANGE key start stop [WITHSCORES]
# 倒序获取有序集合key从start下标到stop下标的元素
ZREVRANGE key start stop [WITHSCORES]# 返回有序集合中指定分数区间内的成员,分数由低到高排序。
ZRANGEBYSCORE key min max [WITHSCORES] [LIMIT offset count]# 返回指定成员区间内的成员,按字典正序排列, 分数必须相同。
ZRANGEBYLEX key min max [LIMIT offset count]
# 返回指定成员区间内的成员,按字典倒序排列, 分数必须相同
ZREVRANGEBYLEX key max min [LIMIT offset count]
  1. 应用场景:

排行榜
有序集合比较典型的使用场景就是排行榜。例如学生成绩的排名榜、游戏积分排行榜、视频播放排名、电商系统中商品的销量排名等。

BitMap

  1. 介绍

Bitmap,即位图,是一串连续的二进制数组(0和1),可以通过偏移量(offset)定位元素。BitMap通过最小的单位bit来进行0|1的设置,表示某个元素的值或者状态,时间复杂度为O(1)。

由于 bit 是计算机中最小的单位,使用它进行储存将非常节省空间,特别适合一些数据量大且使用二值统计的场景。

Bitmap 本身是用 String 类型作为底层数据结构实现的一种统计二值状态的数据类型。

  1. 应用场景
    Bitmap 类型非常适合二值状态统计的场景,这里的二值状态就是指集合元素的取值就只有 0 和 1 两种,在记录海量数据时,Bitmap 能够有效地节省内存空间。
  • 签到打卡。我们只用记录签到(1)或未签到(0),所以它就是非常典型的二值状态。

  • 判断用户登录状态
    Bitmap 提供了 GETBIT、SETBIT 操作,通过一个偏移值 offset 对 bit 数组的 offset 位置的 bit 位进行读写操作,需要注意的是 offset 从 0 开始。
    只需要一个 key = login_status 表示存储用户登陆状态集合数据, 将用户 ID 作为 offset,在线就设置为 1,下线设置 0。通过 GETBIT判断对应的用户是否在线。 50000 万 用户只需要 6 MB 的空间。

第一步,执行以下指令,表示用户已登录。
SETBIT login_status 10086 1第二步,检查该用户是否登陆,返回值 1 表示已登录。
GETBIT login_status 10086第三步,登出,将 offset 对应的 value 设置成 0。
SETBIT login_status 10086 0

HyperLogLog 结构

Redis 在 2.8.9 版本添加了 HyperLogLog 结构。
Redis HyperLogLog 是用来做基数统计的算法,HyperLogLog 的优点是,在输入元素的数量或者体积非常非常大时,计算基数所需的空间总是固定 的、并且是很小的。

在 Redis 里面,每个 HyperLogLog 键只需要花费 12 KB 内存,就可以计算接近 2^64 个不同元素的基 数。这和计算基数时,元素越多耗费内存就越多的集合形成鲜明对比。

但是,因为 HyperLogLog 只会根据输入元素来计算基数,而不会储存输入元素本身,所以 HyperLogLog 不能像集合那样,返回输入的各个元素。

什么是基数?
比如数据集 {1, 3, 5, 7, 5, 7, 8}, 那么这个数据集的基数集为 {1, 3, 5 ,7, 8}, 基数(不重复元素)为5。 基数估计就是在误差可接受的范围内,快速计算基数。

# 添加指定元素到 HyperLogLog 中
PFADD key element [element ...]# 返回给定 HyperLogLog 的基数估算值。
PFCOUNT key [key ...]# 将多个 HyperLogLog 合并为一个 HyperLogLog
PFMERGE destkey sourcekey [sourcekey ...]

GEO

  1. 介绍:

Redis GEO 是 Redis 3.2 版本新增的数据类型,主要用于存储地理位置信息,并对存储的信息进行操作。

在日常生活中,我们越来越依赖搜索“附近的餐馆”、在打车软件上叫车,这些都离不开基于位置信息服务(Location-Based Service,LBS)的应用。LBS 应用访问的数据是和人或物关联的一组经纬度信息,而且要能查询相邻的经纬度范围,GEO 就非常适合应用在 LBS 服务的场景中。

GEO 本身并没有设计新的底层数据结构,而是直接使用了 Sorted Set 集合类型。

这样一来,我们就可以把经纬度保存到 Sorted Set 中,利用 Sorted Set 提供的“按权重进行有序范围查找”的特性,实现 LBS 服务中频繁使用的“搜索附近”的需求

  1. 场景命令
# 存储指定的地理空间位置,可以将一个或多个经度(longitude)、纬度(latitude)、位置名称(member)添加到指定的 key 中。
GEOADD key longitude latitude member [longitude latitude member ...]# 从给定的 key 里返回所有指定名称(member)的位置(经度和纬度),不存在的返回 nil。
GEOPOS key member [member ...]# 返回两个给定位置之间的距离。
GEODIST key member1 member2 [m|km|ft|mi]# 根据用户给定的经纬度坐标来获取指定范围内的地理位置集合。
GEORADIUS key longitude latitude radius m|km|ft|mi [WITHCOORD] [WITHDIST] [WITHHASH] [COUNT count] [ASC|DESC] [STORE key] [STOREDIST key]
  1. 应用场景
    这里以滴滴叫车的场景为例,介绍下具体如何使用 GEO 命令:GEOADD 和 GEORADIUS 这两个命令。

假设车辆 ID 是 33,经纬度位置是(116.034579,39.030452),我们可以用一个 GEO 集合保存所有车辆的经纬度,集合 key 是 cars:locations。

执行下面的这个命令,就可以把 ID 号为 33 的车辆的当前经纬度位置存入 GEO 集合中:

GEOADD cars:locations 116.034579 39.030452 33
当用户想要寻找自己附近的网约车时,LBS 应用就可以使用 GEORADIUS 命令。

例如,LBS 应用执行下面的命令时,Redis 会根据输入的用户的经纬度信息(116.054579,39.030452 ),查找以这个经纬度为中心的 5 公里内的车辆信息,并返回给 LBS 应用。

GEORADIUS cars:locations 116.054579 39.030452 5 km ASC COUNT 10

Stream

  1. 介绍:
    Redis Stream 是 Redis 5.0 版本新增加的数据类型,Redis 专门为消息队列设计的数据类型。

List 类型实现的消息队列,有两个问题:1. 生产者需要自行实现全局唯一 ID;2. 不能以消费组形式消费数据。

基于 Stream 类型的消息队列就解决上面的问题,它不仅支持自动生成全局唯一 ID,而且支持以消费组形式消费数据。

常见命令
Stream 消息队列操作命令:

XADD:插入消息,保证有序,可以自动生成全局唯一 ID;
XREAD:用于读取消息,可以按 ID 读取数据;
XREADGROUP:按消费组形式读取消息;XPENDING 和 XACK:
XPENDING 命令可以用来查询每个消费组内所有消费者已读取但尚未确认的消息,而 XACK 命令用于向消息队列确认消息处理已完成。

Redis是单线程还是多线程? 为什么说redis能够快速执行。

redis6版本之前,它都是单线程的。这个单线程并不是redis内部只有一个线程,指的是redis的work工作线程是个单线程。6版本之后有了多线程的概念,但是还是一个work工作线程,只是IO线程有多个。

工作机制:

  • 6版本之前:worker单线程自己要执行三步操作:
    1.是读操作,2.是将流进行计算(说白了就是将流插入到内存当中)3.写操作。

例如有两个客户端和redis进行连接,客户端1发送了指令,客户端2也发送了一个指令。redis6版本以前,是单work线程,如果处理这两个指令的话,肯定是以串行的方式来执行。执行完一个指令,在执行下一指令。从而同时执行两个指令的话,需要6个时间维度

  • 6版本之后:worker线程还是单线程,负责计算操作。IO多路复用子线程:负责读写

例如有两个客户端和redis进行连接,客户端1发送了指令,客户端2也发送了一个指令。redis6版本以后,worker只负责计算,IO子线程负责读写,只需要4个时间维度就可以完成。

  1. 绝大部分请求是纯粹的内存操作,内存的读写是非常快的。
  2. 采用的是单线程,避免了多线程的上下文切换和竞争条件
  3. 核心是基于非阻塞的IO多路复用机制
  4. 即使持久化时,也是fork一个子进程来进行磁盘IO读写。所以说主进程不会IO操作。

Redis是怎么持久化的?

RDB:

RDB镜像全量持 久化,通过创建快照的方式进行持久化, RDB文件是一种压缩二进制文件形式存储到磁盘里面。RDB持久化是Redis默认的持久化方式。

在 Redis 恢复数据时, RDB 恢复数据的效率会比 AOF 高些,因为,不需要像 AOF 那样还需要额外执行操作命令的步骤才能恢复数据。

RDB持久化的触发方式:

RDB持久化的触发有两种方式:
第一种是save。目前很少使用,是因为需要主线程进行持久化,会拒绝其他用户的请求,从而会导致请求返回异常

第二种是bgsave。开启一个子进程,来执行持久化。

第三种是:配置文件中的save选项

当在执行bgsave时,即使再次执行bgsave或者save都会拒绝掉。
当在执行bgsave时,执行重写AOF时,会进行排队,先执行gbsave,然后在执行重写AOF。

当在执行writeOF 重写AOF时,执行bgsave,会拒绝bgsave操作。
所以说:当在执行AOF重写的过程时,一定要选择业务需求很少的情况下在执行。因为如果业务情况非常多的情况下,有可能会触及到redis为RDB做的bgsave选项。如果业务量很大,到达了bgsave选项的指标,会自动执行bgsave命令,那么这个bgsave命令就会拒绝掉。

save 900 1
save 300 10
save 60 10000
分别表示900秒(15分钟)内有1个更改,300秒(5分钟)内有10个更改以及60秒(1分钟)内有10000个更改。

RDB的优缺点:

  • 优点:RDB文件紧凑,体积小,网络传输快,适合全量复制;和AOF相比,1方面恢复速快,因为直接将 RDB 文件读入内存就可以。另一方面对性能的影响大。
  • 缺点:很可能存在数据丢失现象,不能实时持久化。

指定存储至本地数据库时是否压缩数据,默认为yes,Redis采用LZF压缩,如果为了节省CPU时间,可以关闭该选项,但会导致数据库文件变的巨大
rdbcompression yes

指定本地数据库文件名,默认值为dump.rdb
dbfilename dump.rdb
指定本地数据库存放目录
dir ./

AOF:

AOF(Append-Only-File)增量持久化:以日志的形式append 追加保存到AOF文件当中。文件中保存的是redis的执行命令。
然后在恢复时,以逐一执行命令的方式来进行数据恢复。

  • 优点:数据安全:AOF在于支持每秒同步、每修改同步,每秒同步是异步完成的,效率非常高。所以说即便中途宕机,不会破会已经存在的内容。
  • 缺点:AOF文件大,宕机后恢复速度慢,是因为模拟客户端向redis服务器发送命令的场景。

AOF为什么文件大?

是因为存储了过程化数据。 例如操作同一个库,set name zhangsan;set name lisi.;set name wangwu; 当get name 的时候,最终是wangwu。 AOF是会将所有语句都进行保存,而最终redis只是存放了wangwu。所以说AOF存放了过程化数据,所以AOF文件笨重。

文件越来越大的解决方法是:

AOF还有个技术是:rewrite:压缩重写,当设置好阈(yu)值,AOF文件达到阈值后,会自动触发rewrite,会将日志过程化的数据删除掉,只保留最终这个数据。

指定是否在每次更新操作后进行日志记录,Redis在默认情况下是异步的把数据写入磁盘,如果不开启,可能会在断电时导致一段时间内的数据丢失。因为 redis本身同步数据文件是按上面save条件来同步的,所以有的数据会在一段时间内只存在于内存中。默认为no
appendonly no

指定更新日志文件名,默认为appendonly.aof
appendfilename appendonly.aof

三种写回策略

Redis 提供了三种将 AOF 日志写回硬盘的策略,分别是 Always、Everysec 和 No,这三种策略在可靠性上是从高到低,而在性能上则是从低到高。

指定更新日志条件,共有3个可选值:
no:表示等操作系统进行数据缓存同步到磁盘(快)
always:表示每次更新操作后手动调用fsync()将数据写到磁盘(慢,安全)
everysec:表示每秒同步一次(折衷,默认值)
appendfsync everysec

在 redis.conf 配置文件中的 appendfsync 配置项可以有以下 3 种参数可填:

  • Always,这个单词的意思是「总是」,所以它的意思是每次写操作命令执行完后,同步将 AOF 日志数据写回硬盘;
  • Everysec,这个单词的意思是「每秒」,所以它的意思是每次写操作命令执行完后,先将命令写入到 AOF 文件的内核缓冲区,然后每隔一秒将缓冲区里的内容写回到硬盘;
  • No,意味着不由 Redis 控制写回硬盘的时机,转交给操作系统控制写回的时机,也就是每次写操作命令执行完后,先将命令写入到 AOF 文件的内核缓冲区,再由操作系统决定何时将缓冲区内容写回硬盘。

大家根据自己的业务场景进行选择:
如果要高性能,就选择 No 策略;
如果要高可靠,就选择 Always 策略;
如果允许数据丢失一点,但又想性能高,就选择 Everysec 策略。

同时开启aof和rdb,重启后会优先加载哪一个?

会优先加载AOF,因为AOF文件能够更加细粒度的控制redis的数据周期。

RDB和AOF合并: 4版本的时候提供了混合持久化方式

如果想要开启混合持久化功能,可以在 Redis 配置文件将下面这个配置项设置成 yes:
aof-use-rdb-preamble yes

当开启了混合持久化时,以 RDB 方式写入到 AOF 文件,重写缓冲区里的增量命令会以 AOF 方式写入到 AOF 文件。也就是说,使用了混合持久化,AOF 文件的前半部分是 RDB 格式的全量数据,后半部分是 AOF 格式的增量数据。
这样的好处在于,重启 Redis 加载数据的时候,由于前半部分是 RDB 内容,这样加载的时候速度会很快。

加载完 RDB 的内容后,才会加载后半部分的 AOF 内容,这里的内容是 Redis 后台子进程重写 AOF 期间,主线程处理的操作命令,可以使得数据更少的丢失。

缓存问题

缓存-访问过程:

说到缓存基础,那我就先说一下,用户请求是如何取到数据的:当接收到用户请求,首先先尝试从Redis缓存中获取到数据,如果缓存中能取到数据则直接返回结果,当缓存中不存在数据时从数据库获取数据,如果数据库成功取到数据,则更新Redis,然后返回数据。

缓存穿透

缓存穿透是指查询缓存和DB中都不存在的数据,这将导致这个不存在的数据每次请求都要到 DB 去查询,可能导致 DB 挂掉。比如通过id查询商品信息,id一般大于0,攻击者会故意传id为-1去查询,造成缓存穿透。

  • 解决方法:
    布隆过滤器:将所有可能存在的数据哈希到一个足够大的bitmap中,一个一定不存在的数据会被这个bitmap拦截掉,从而避免对DB的查询

缓存击穿

大量的用户来请求某个key时,redis中不存在,但是数据库中存在。高并发请求数据库中某个key,导致数据库重复处理这些高并发的key。

比如说有100个请求,访问同一个key。redis中没有该key,由于redis是串行操作,当100个请求都去数据库查询后,会都更新100次reids。

在redis中不存在有两种情况:
第一种情况:热点key不存了,比如过期、淘汰。对某个热点key设置了过期时间,当这个key过期的时候,恰好这时有大量的并发请求过来,这些请求发现缓存过期,就会去访问数据库,可能瞬间将数据库压垮
第二种情况:非热点key,并没有存在redis中,但是突然有大量的用户来访问该key。

  • 解决方案:
    1.使用互斥锁:当缓存失效时,不立即去load db,先使用Redis的setnx去设置一个互斥锁,当操作成功返回时在进行load db的操作,并返回设缓存,否则重试get缓存的方法。
  1. 当高并发来了以后,发现redis中没有该key
  2. 请求就会去抢锁。当某一个请求拿到锁后,才能去数据库进行访问数据,没有拿到锁的请求,进行睡眠。
  3. 抢到锁的请求,去访问了数据库,然后拿到了数据,进行更新数据库。然后在返回数据
  4. 然后睡眠中的请求,再去访问redis时,就会发现该key以及存在了。并不需要访问数据库了。

说明的是:
1.时间复杂度基本是o(1)
2.99个睡眠的话,是占用了99个线程。该现场是blocking的轻量级进程,所以不参与CPU及内核的调度,从而是不怎么影响性能的。

2.永远不过期:物理不过期,但逻辑过期(后台异步线程去刷新)

缓存雪崩

设置缓存时采用了相同的过期时间,导致缓存在某一时刻同时失效,请求全部转发到DB,DB瞬间压力过重导致雪崩。
缓存雪崩和缓存击穿的区别:雪崩有很多key,击穿是某个key缓存

  • 解决方法:
    将缓存失效时间分散开,比如可以在原有的失效时间基础上添加随机值,比如1-5分钟随机值,这样每个缓存失效时间的重复率就会降低,从而避免缓存雪崩。

内存过期键的删除策略。缓存回收

-删除策略有两种:

  1. 惰性删除:只有当访问这个Key时,才会判断这个key是否过期,过期则清除。
    特点:该操作能最大节省CPU资源。由于过期后还是依旧占着内存,所以对内存非常的不友好。

  2. 定期过期:每隔一段时间,会扫描一定数量的key,并且清除已经过期key。
    特点:消耗时间让服务变慢。

淘汰机制

  • 淘汰机制有6种
    no-enviction(默认):当内存不足以容纳新写入数据时,新写入操作会报错。
    allkeys-lru (推荐):当内存不足以容纳新写入数据时移除最近最少使用的key。

对于配置信息等重要数据,不应该设置过期时间,这样redis就不会淘汰这些重要的数据。
对于一般数据可以添加一个缓存时间,当数据失效则请求会从DB中获取重新存入redis中。

指定Redis最大内存限制,Redis在启动时会把数据加载到内存中,达到最大内存后,Redis会先尝试清除已到期或即将到期的Key,当此方法处理 后,仍然到达最大内存设置,将无法再进行写入操作,但仍然可以进行读取操作。Redis新的vm机制,会把Key存放内存,Value会存放在swap区
maxmemory < bytes>

使用过Redis做异步队列么,你是怎么用的?

一般使用list结构作为队列,rpush生产消息,lpop消费消息。当lpop没有消息的时候,要适当sleep一会再重试。list还有个指令叫blpop,在没有消息的时候,它会阻塞住直到消息到来。

如何进行缓存的预热?

  1. 提前将热点数据放入redis当中。
  2. 开发逻辑上规避差集。进行缓存穿透、击穿、雪崩。从而做好互斥锁方案。

集群问题

当master服务设置了密码保护时,slav服务连接master的密码
masterauth < master-password>
当master服务设置了密码保护时,slav服务连接master的密码
masterauth < master-password>

指定包含其它的配置文件,可以在同一主机上多个Redis实例之间使用同一份配置文件,而同时各个实例又拥有自己的特定配置文件
include /path/to/local.conf

主从不一致的问题

  1. redis默认是弱一致性的,异步的同步。
  2. 锁不能用主从的集群。(支持:单实例、分片集群、redlock)–》redisson
  3. 在配置中提供了必须有多少个client连接能同步,可以配置同步因子,趋向于强一致性。
  4. wait 2 5000

2.8版本(不包括2.8)之前的主从同步

主从在2.8版本后有很多大的改变。

  • 主从服务器的同步过程?
    1.在从服务器的配置文件里面配置好主服务器的IP地址和端口号,当启动从服务器的时候,会向主服务器发送一个sync包(同步包) ,告诉主服务器。
    2.一旦主服务器有任何写操作,都会以RDB快照文件记录。
    3.主服务主动将RDB快照文件发送给所有的从服务器。从而从服务器的内容和主服务器的内容就进行了同步。

1.从服务器发送sync给主服务器
2.主服务器接到命令以后,会开启一个子进程执行bgsave命令。主服务器在执行bgsave的同时,还会把bgsave开始之后的接收到的命令放入到主服务器的写缓存区里面。
3.当主服务器执行完了bgsave后,生成了一份新版本rdb文件后,会传送给从服务器。
4.从服务器接收到rdb文件后,进行加载rdb文件。rdb文件是能够直接加载。
(AOF文件加载,是需要一个所谓的客户端以命令发送的形式,发送给redis服务器中。一条条命令去执行。从而达到AOF文件的加载。)
4.1 上述到 在执行bgsave命令时,修改了主服务器中的数据了。那么主服务器会再次发送缓存区里面存储的命令,给从服务器。
4.2 从服务器接收到了命令之后,在本地执行。从而保证主从服务器的同步。

存在的问题:
当主从服务器因为某些原因断开了连接,那么从服务器会从新发送一个sync同步命令。主服务器会再次执行bgsave命令。可能会造成redis的CPU浪费

2.8版本之后的reids主从同步

引入了psync命令(p代表:portion 意为部分的)
全量同步和增量同步

  1. 复制偏移量
    如果从服务器断开了连接,主服务器中的数据做了修改。

Redis读写分离、哨兵机制、主备切换

  • 读写分离:
    主服务器负责读写,从服务器只负责读,从而会把读的压力分散出去。
    优提高了一定的可用性,一个从服务器宕机后,另外两台服务器还可以继续工作

  • 哨兵机制,主备切换
    监控主服务器有无挂机,挂机后,重新选举主服务器。

  • 哨兵的作用:
    1.监控redis主、从数据库是否正常运行
    2.出现故障自动将从数据库转换为主数据库

Redis高频面试题(欢迎来学习讨论)相关推荐

  1. Redis高频面试题汇总(2021最新版)

        本文已收录于专栏 ⭐️<Redis面试题汇总--2021最新版本>⭐️ 上千人点赞收藏,全套Redis高频面试题,大厂必备技能! 面试官心理分析 从面试官的角度分析,出这道题的目的 ...

  2. Redis高频面试题完整版

    文章目录: Redis概述 什么是Redis? Redis的优缺点? Redis为什么常常用做缓存?相比于guava有什么优势? Redis和Memcached的区别与共同点? Redis是单线程还是 ...

  3. 【Redis高频面试题系列】:说说Redis的rehash过程

    Redis的字典由 dict.h/dict 结构如下(rehash的重点) typedef struct dict {//类型特性函数dictType *type;//私有数据void *privda ...

  4. Redis高频面试题(2023最新)

    目录 前言 1.redis是什么 2.redis的存储结构有哪些 3.为什么要用redis和redis为什么那么快 4.缓存雪崩.缓存穿透.缓存击穿 5.redis的持久机制 6.redis的过期策略 ...

  5. redis key失效的事件_《分享几道高频 Redis 高频面试题,面试不用愁》

    1.说说 Redis 都有哪些应用场景? 缓存:这应该是 Redis 最主要的功能了,也是大型网站必备机制,合理地使用缓存不仅可以加 快数据的访问速度,而且能够有效地降低后端数据源的压力. 共享Ses ...

  6. Redis高频面试题汇总(下)

    目录 1.Redis中什么是Big Key(大key) 2.Big Key会导致什么问题 3.如何发现 bigkey? 4.为什么redis生产环境慎用keys *命令 5.如何处理大量 key 集中 ...

  7. java高频面试题(2023最新)

    目录 一.java基础 1.八大基础类型 2.java三大特性 3.重载和重写的区别 4.pubilc.protected.(dafault)不写.private修饰符的作用范围 5.==和equal ...

  8. 计算机网络高频面试题最新版

    在秋招过程中看了大量面经,将常见的计算机网络面试题总结如下,并按照面试中提问的频率做了标注(星数越高,面试中提问频率越高),如有帮到你,可以收藏点赞支持哦. 微信搜索公众号路人zhang,回复面试手册 ...

  9. 数据库索引高频面试题(最新版)

    MySQL的索引是面试中的高频题目,将常见的索引面试题目总结了一下,如果有帮到你可以点赞收藏呦. 微信搜索公众号路人zhang,回复面试手册,领取更多高频面试题PDF版及更多面试资料. 面试手册在线版 ...

  10. MySQL高频面试题(最新版)

    MySQL高频面试题,题目后面的星数越高,在面试中越高频 微信搜索公众号路人zhang,回复面试手册,领取更多高频面试题PDF版及更多面试资料. 面试手册在线版: www.mianshi.online ...

最新文章

  1. DongLiORM 第二次更新
  2. 自己发现的数学规律一
  3. 移动端cube界面设计html,滴滴 Web 移动端组件库 cube-ui 开源
  4. 利用TF-IDF提取新闻文章摘要
  5. C语言归并排序(合并排序)
  6. 代码简洁(注意事项)
  7. javascript基础修炼(13)——记一道有趣的JS脑洞练习题
  8. vuedraggable能实现自由拖拽功能吗?_基于 vue.js 仿禅道主页拖拽效果
  9. 近24小时以太坊上的DEX交易量超过150亿美元
  10. python学习之dict的items(),values(),keys()
  11. 【计算机视觉入门案例】手写数字识别:Keras深度学习库
  12. 测试用例的设计方法及例子
  13. 手把手搭建K3cloud插件开发环境
  14. LU分解 LDL分解 Cholesky分解
  15. tbslog乱码转换_tbslog乱码转换
  16. 街头篮球服务器一直维护,《街头篮球》2.8新版本停服维护更新公告
  17. 易语言 文件捆绑机的原理【转载】
  18. 额,我要说一件重要的事+用C++编写一个走迷宫小游戏(1.1版)
  19. 微信JS-SDK实现自定义分享功能,分享给朋友,分享到朋友圈及QQ自定义分享--微信分享
  20. 纯真IP/ZXinc_IPv6数据库镜像及MySQL脚本更新同步更新 for Python 3.x

热门文章

  1. 【概率入门(一)】排列组合?我们再来捋一捋
  2. Forbidden什么意思
  3. 如何使用xbrowser图形化连接centos
  4. Linux下的man指令
  5. 与师生谈人工智能3:精确定义之病
  6. zmud之潜能武学技能计算器。
  7. 【GitHub上传文件夹:bug】 ! [rejected] master - master (non-fast-forward)
  8. 在vue中如何使用umy-ui
  9. Pyqt5的QGraphicsView的使用-选择图片,显示在GUI中
  10. 爱查快递API使用讲解