【面试之redis篇】redis面试

【面试之redis篇】redis面试

一、redis的基本类型以及使用场景

1.String(字符串)

String类型是redis的最基础的数据结构,也是最经常使用到的类型,String 类型的值最大能存储 512MB,这里的String类型可以是简单字符串、 复杂的xml/json的字符串、二进制图像或者音频的字符串、以及可以是数字的字符串。
在这里插入图片描述
在这里插入图片描述

常用命令
#1.	添加值
set key value
#2.	取值
get key
#3.	批量操作
mset key value [key value...]
mget key [key...]
#4.	自增命令(自增1)(应用场景 -> 点赞)
incr key
#5.	自减命令(自减1)
decr key
#6.	自增或自减指定数量
incrby key <increment>
decrby key <increment>
#7.	设置值的同时。指定生存时间(每次向Redis中添加数据时,尽量都设置上生存时间)
setex key <second> value
#8.	设置值,如果当前key不存在的话(如果这个key存在,什么事都不做,如果这个key不存在,和set命令一样)
setnx key value
#9. 在key对应的value后,追加内容
append key value
#10. 查看value字符串的长度
strlen key

常用场景

1.缓存
Redis String 最常用的场景就是缓存。在 Web 应用中,我们经常需要缓存一些数据,以减轻数据库的负担,提高应用的性能。RedisString 提供了 set 和 get 命令,可以方便地将数据存储到 Redis 中,并从 Redis 中获取数据。例如,我们可以将用户的登录信息存储到Redis 中,以便快速验证用户的身份。

2、验证码
Redis String可以用于验证码校验。网站登录中常有验证码,我们可以用此数据类型,手机号作为key,验证码作为value存储在redis中,设置过期时间,后续如果用户输入验证码,我们从redis中取值对比,如果过期则无效。

3.计数器
Redis String 还可以用作计数器。在一些应用中,我们需要统计某个事件的发生次数,例如网站的访问量、文章的阅读量等等。Redis提供了incr 和 decr 命令,可以方便地对 String 类型的数据进行自增和自减操作。例如,我们可以将网站的访问量存储在 Redis 中并使用 incr 命令每次增加 1。

4.分布式锁
Redis String 还可以用作分布式锁。在分布式系统中,多个进程或线程可能同时访问同一个资源,为了避免竞争条件,我们需要使用锁来保证同一时间只有一个进程或线程可以访问该资源。Redis 提供了setnx命令,可以将一个 String 类型的数据作为锁,如果该数据不存在,则创建锁并返回 1,否则返回 0。例如,我们可以使用setnx命令创建一个名为“lock”的锁,如果返回 1,则表示当前进程或线程获得了锁,可以访问资源,否则需要等待。

2.hash(哈希)

Hash 是一个键值对(key-value)集合,其中 value 的形式如: value=[{field1,value1},…{fieldN,valueN}]。Hash 特别适合用于存储对象
在这里插入图片描述
在这里插入图片描述

常用命令
#1.	存储数据
hset key field value
#2.	获取数据
hget key field
#3.	批量操作
hmset key field value [field value ...]
hmget key field [field ...]
#4.	自增(指定自增的值)
hincrby key field increment
#5.	设置值(如果key-field不存在,那么就正常添加,如果存在,什么事都不做)
hsetnx key field value
#6.	检查field是否存在
hexists key field
#7.	删除key对应的某一个field,可以删除多个
hdel key field [field]
#8.	获取当前hash结构中的全部field和value
hgetall key
#9.	获取当前hash结构中的全部field
hkeys key
#10. 获取当前hash结构中的全部value
hvals key
#11. 获取当前hash结构中field的数量
hlen key

常用场景

1.计数器
hash结构可以用来实现计数器,例如网站的访问量统计、文章的阅读量统计等。可以将每篇文章或每个网页对应的键值对存储在一个hash表中,将阅读量作为值存储在对应的键中。每次访问时,将对应键的值加1,就可以实现计数的功能。

2.分类信息
hash结构可以用来存储分类信息,例如新闻的分类信息、商品的分类信息等。可以将每个分类作为一个键,将该分类下的新闻或商品编号作为值存储在对应的键中。这样就可以快速地查询某个分类下的所有新闻或商品了。

3.对象存储
hash结构可以用来存储对象,例如用户对象、商品对象等。可以将每个对象作为一个hash表,将对象的各个属性作为键值对存储在对应的键中。这样可以方便地查询、修改、删除对象的属性,而不必查询整个对象。

3.List(列表)

Redis 列表是最简单的字符串列表,按照插入的顺序,我们可以添加一个元素到列表的头部或者尾部。一个列表最多可以包含 4294968295(2^32 - 1)个元素。
在这里插入图片描述
在这里插入图片描述

常用命令
#1.	存储数据(从左侧插入数据,从右侧插入数据)
lpush key value [value ...]
rpush key value [value ...]
#2.	存储数据(如果key不存在,什么事都不做,如果key存在,但是不是list结构,什么都不做)
lpushx key value
rpushx key value
#3.	修改数据(在存储数据时,指定好你的索引位置,覆盖之前索引位置的数据,index超出整个列表的长度,也会失败)
lset key index value
#4.	弹栈方式获取数据(左侧弹出数据,从右侧弹出数据)
lpop key
rpop key
#5.	获取指定索引范围的数据(start从0开始,stop输入-1代表最后一个)
lrange key start stop
#6.	获取指定索引位置的数据
lindex kek index
#7.	获取整个列表的长度
llen key
#8.	删除列表中的数据(删除当前列表中的count个value值;count>0,从左侧向右侧删除;count<0,从右侧向左侧删除;count==0,删除列表中全部数据)
lrem key count value
#9.	保留列表中的数据(保留指定索引范围内的数据,超过整个索引范围被移除掉)
ltrim key start stop
#10. 将一个列表中最后的一个数据,插入到另外一个列表的头部位置
rpoplpush list1 list2
常用场景

1、消息队列
如下图所示,Redis的lpush + brpop命令组合即可实现阻塞队列,生产者客户端使用lpush从列表左侧插入元素,多个消费者客户端使用brpop命令阻塞式的争抢列表尾部的元素,多个客户端保证了消费的负载均衡和高可用

在这里插入图片描述
2、最新列表
ist类型的lpush命令和Irange命今能实现最新列表的功能,每次通过lpush命今往列表里插入新的元素,然后通过Irange命令读取最新的元素列表,如朋友圈的点赞列表、评论列表。
但是,并不是所有的最新列表都能用st类型实现,因为对于频繁更新的列表,list类型的分页可能导致列表元索重复或漏掉,举个例子,出前列表里由表头到表尾依次有(E,D,C,B,A)五个元素,每页获取3个元素,用户第一次获取到(ED,C)三个元素,然后表头新增了一个元素F,列表变成了(F,E,D,C,B,A),此时用户取第二页拿到(CB、A),元素C重复了。只有不需要分页(比如每次都只取列表的前5个元素)或者更新频率低(比如每天凌晨更新一次)的列表才适合用/ist类型实现。对于需要分页并且会频整更新的列表需用使用有席集合sorted set类型实现。另外,需要通过时间范围查找的最新列表,it类型也实现不了,也需要通过有集合sorted set类型实现,如以成交时间范围作为条件来查询的订单列表。之后在介绍有序集合sorted set类型的应用场景时会详细介绍sorted set类型如何实现最新列表。

3、排行榜
ist类型的lrange命令可以分页查看队列中的数据。可将每隔一段时间计算一次的排行榜存储在list类型中,如京东每日的手机销量排行、学校每次月考学生的成绩排名、斗鱼年终盛典主播排名等,下图是酷狗音乐K歌擂台赛”的昨日打播金曲排行榜,每日计算一次,存储在list类型中,接口访问时,通过page和size分页获取打擂金曲。
但是,并不是所有的排行榜都能用list类型实现,只有定时计算的排行榜才适合使用list类型存储,与定时计算的排行榜相对应的是实时计算的排行榜,list类型不能支持实时计算的排行榜,之后在介绍有序集合sorted set的应用场景时会详细介绍实时计算的排行榜的实现。

4.set(集合)

set是可以自动排重的,当你需要存储一个列表数据,又不希望出现重复数据时,set是一个很好选择,并且set提供了判断某个成员是否在一个set集合内的重要接口,这个也是list所不能提供的。
在这里插入图片描述
在这里插入图片描述

常用命令
#1.	存储数据
sadd key member [member ...]
#2.	获取数据(获取全部数据)
smembers key
#3.	随机获取一个数据(获取的同时,移除数据,count默认为1,代表弹出数据的数量)
spop key [count]
#4. 交集(取多个set集合交集)
sinter set1 set2 ...
#5. 并集(获取全部集合中的数据)
sunion set1 set2 ...
#6.	差集(获取多个集合中不一样的数据)
sdiff set1 set2 ... (获取set1-set2)
sdiff set2 set1 ... (获取set2-set1)
#7.	删除数据
srem key member [member ...]
#8.	查看当前的set集合中是否包含这个值
sismember key member
常用场景

1.去重
使用 Set 可以轻松去重,只需将要去重的数据存储到Set_中即可。Set 会自动去除重复的元素,从而简化去重操作。2.共同好友:在社交场景中,可以使用 Set 来实现共同好友的功能。比如,将每个用户的好友列表存储到 Set 中,然后使用交集运算求出两个用户之间的共同好友。

3.抽奖
在抽奖场景中,可以使用 Set 来存储所有参与抽奖的用户,然后使用随机函数从 Set 中随机选出一个中奖用户。

4.标签系统
在标签系统中,可以使用 Set 来存储每个标签所对应的文章 id 列表。这样,当需要查找某个标签下的所有文章时只需从 Set 中取出对应的文章 id 列表即可。

5.排行榜
在排行榜场景中,可以使用 Set 来存储每个用户的得分,然后使用有序集合来排序,从而实现排行榜功能。

5.zSet(有序集合集合)

Zset是一个没有重复元素的字符串集合。与set类型的不同之处是有序集合的每个成员都关联了一个评分( score) ,这个评分(score)被用来按照从最低分到最高分的方式排序集合中的成员。集合的成员是唯一的,但是评分可以是重复的。因为元素是有序的,所以可以很快的根据评分( score )或者次序( position )来获取一个范围的元素。访问有序集合的中间元素也是非常快的,因此能够使用有序集合作为一个没有重复成员的智能列表。
在这里插入图片描述
在这里插入图片描述

常用命令
#1.	添加数据(score必须是一个数值,member不允许重复)
zadd key score member [socre member ...]
#2. 修改member的分数(如果member是存在于key中的,正常增加分数,如果member不存在,这个命令就相当于zadd)
zincrby key increment member
#3.	查看指定的member的分数
zscore key member
#4.	获取zset中数据的数量
zcard key
#5.	根据score的范围查询member数量
zcount key min max
#6.	删除zset中的成员
zrem key member [member ...]
#7.	根据分数从小到大排序,获取指定范围内的数据(withscore如果添加这个参数,那么会返回member对应的分数)
zrange key start stop [withscores]
#8.	根据分数从大到小排序,获取指定范围内的数据
zrevrange key start stop [withscores]
#9.	根据分数的返回去获取member(withscores代表返回score,添加limit,就和MySQL一样,如果不希望等于min或者max的值被查询出来可以采用‘(分数’相当于 < 但是不等于的方式,最大值和最小值使用+inf和-inf来表示)
zrangebyscore key min max [withscores] [limit offset count]
#10.	根据分数的返回去获取member(withscores代表返回score,添加limit,就和MySQL一个样)
zrangebyscore key max min [withscores] [limit offset count]
使用场景

1.排行榜
ZSET 可以用来实现排行榜功能,例如游戏中的积分排行榜。将每个玩家的得分作为元素的值,将玩家的ID作为元素的键,然后将它们加入到ZSET 中。使用ZREVRANGE 命令可以获得排名前几的玩家。

2.任务队列
ZSET 可以用来实现任务队列,例如用户提出的某些任务需要优先处理。将每个任务的处理时间作为元素的值,将任务内容作为元素的键,然后将它们加入到 ZSET 中。使用ZRANGE 命令可以获取处理时间最短的任务。

3.范围搜索
ZSET 可以用来实现范围搜索功能,例如商城中的商品价格筛选。将每个商品的价格作为元素的值,将商品 ID 为元素的键,然后将它们加入到ZSET 中。使用 ZRANGEBYSCORE 命令可以根据价格范围获取商品列表

4.社交网络
ZSET 可以用来实现社交网络功能,例如根据用户的关注度排序。将每个用户的关注度作为元素的值,将用户 ID 作为元素的键,然后将它们加入到 ZSET 中。使用ZREVRANGE 命令可以获得关注度最多的用户。

二、常见面试题

1.缓存穿透

查询一个不存在的数据,mysql查询不到数据也不会直接写入缓存,就会导致每次请求都查数据库
缓存穿透是指查询一个一定不存在的数据,如果从存储层查不到数据则不写
入缓存,这将导致这个不存在的数据每次请求都要到 DB 去查询,可能导致
DB 挂掉。这种情况大概率是遭到了攻击。在这里插入图片描述
解决方案一:缓存空数据,查询返回的数据为空,仍把这个空结果进行缓存

解决方案二:布隆过滤器
在这里插入图片描述

布隆过滤器是一种多哈希函数映射的快速查找算法,通常用于在大数据量场景下快速判断数据存在性。该算法通过牺牲正确性从而在空间和时间上都有不错的效率
当一个元素被加入集合时,通过N个散列函数将这个元素映射成一个位图中的N个点,将它们置为1。判断某个元素是否存在时,通过这些点是不是都是1即可:如果这些点有任何一个0,则目标元素一定不在;如果都是1,则目标元素很可能在。例如,一个集合中只存在一个apple元素,其经过三个哈希函数计算映射在位图中三个位,此时判断orange是否存在于集合中,同样经过三个哈希函数计算,我们发现有一位为0,所以orange一定不存在于集合中。
在这里插入图片描述
布隆过滤器主要是用于检索一个元素是否在一个集合中。我们当时使用的是
redisson实现的布隆过滤器。
它的底层主要是先去初始化一个比较大数组,里面存放的二进制0或1。在一
开始都是0,当一个key来了之后经过3次hash计算,模于数组长度找到数据
的下标然后把数组中原来的0改为1,这样的话,三个数组的位置就能标明一
个key的存在。查找的过程也是一样的。
当然是有缺点的,布隆过滤器有可能会产生一定的误判,我们一般可以设置
这个误判率,大概不会超过5%,其实这个误判是必然存在的,要不就得增
加数组的长度,5%以内的误判率一般的项目也能
接受,不至于高并发下压倒数据库。

2.缓存击穿

给某一个key设置了过期时间,当key过期的时候,恰好这时间点对这个key有大量的并发请求过来,这些并发的请求可能会瞬间把DB压垮
缓存击穿的意思是对于设置了过期时间的key,缓存在某个时间点过期的时候,恰好这时间点对这个Key有大量的并发请求过来,这些请求发现缓存过期一般都会从后端 DB 加载数据并回设到缓存,这个时候大并发的请求可能会瞬间把 DB 压垮。
在这里插入图片描述
解决方案一:互斥锁
​ 锁具有 互斥性,加锁之后线程从原来的 并行 变成了 串行。 第一个线程过来访问,获得锁,只有第一个线程能够去直接访问数据库,然后把数据写入缓存。第二个线程过来,没得到锁,只能不断重试去获得锁,直至第一个线程释放锁,然后第二个线程就能够直接从缓存中获得数据。
在这里插入图片描述

解决方案二:逻辑过期
像双十一这种特殊时期,就可以往数据库里添加带有逻辑过期时间的数据,即有一个字段是添加的时间加上有效时间,后续查询出这条数据的时候判断是否超过逻辑过期时间,没有过期就返回数据对象;过期了就重复互斥锁的步骤重建缓存。当双十一过后,就可以把插入的数据缓存给清除掉,也避免了插入缓存过多压力大的问题。
在这里插入图片描述

3.缓存雪崩

缓存雪崩是指大量的请求无法在缓存中处理,从而将请求转移到数据库中,导致数据压力倍增。一个Redis实例可以支持万级别的并发请求,而单个数据库只能支持千级别的并发请求。两者处理请求并发能力相差十倍,数据库会由于压力过大而导致雪崩。
  原因一:缓存中大量的数据同时过期
一般设置缓存数据会设置缓存时间,在某一时刻,大量的缓存同时过期,此时如果有请求访问这些数据的话,缓存不存在,会将请求转移到数据库,如果这些的请求量比较大的,导致数据库的压力增大,严重会导致数据库崩溃。
 原因二:redis服务挂了
redis服务发生宕机,无法处理请求,这就会导致全部转移到数据库去,发生雪崩。
在这里插入图片描述

解决方案一:保持缓存层的高可用性
使用Redis 哨兵模式或者Redis 集群部署方式,即便个别Redis 节点下线,整个缓存层依然可以使用。除此之外,还可以在多个机房部署 Redis,这样即便是机房死机,依然可以实现缓存层的高可用。

解决方案二:限流降级组件

无论是缓存层还是存储层都会有出错的概率,可以将它们视为资源。作为并发量较大的分布式系统,假如有一个资源不可用,可能会造成所有线程在获取这个资源时异常,造成整个系统不可用。降级在高并发系统中是非常正常的,比如推荐服务中,如果个性化推荐服务不可用,可以降级补充热点数据,不至于造成整个推荐服务不可用。常见的限流降级组件如 Hystrix、Sentinel 等。

解决方案三:缓存不过期

Redis 中保存的 key 永不失效,这样就不会出现大量缓存同时失效的问题,但是随之而来的就是Redis 需要更多的存储空间。

解决方案四:优化缓存过期时间

设计缓存时,为每一个 key 选择合适的过期时间,避免大量的 key 在同一时刻同时失效,造成缓存雪崩。

解决方案五:使用互斥锁重建缓存

在高并发场景下,为了避免大量的请求同时到达存储层查询数据、重建缓存,可以使用互斥锁控制,如根据 key 去缓存层查询数据,当缓存层为命中时,对 key 加锁,然后从存储层查询数据,将数据写入缓存层,最后释放锁。若其他线程发现获取锁失败,则让线程休眠一段时间后重试。对于锁的类型,如果是在单机环境下可以使用 Java 并发包下的 Lock,如果是在分布式环境下,可以使用分布式锁(Redis 中的 SETNX 方法)。

分布式环境下使用Redis 分布式锁实现缓存重建,优点是设计思路简单,对数据一致性有保障;缺点是代码复杂度增加,有可能会造成用户等待。假设在高并发下,缓存重建期间 key 是锁着的,如果当前并发 1000 个请求,其中 999 个都在阻塞,会导致 999 个用户请求阻塞而等待。

解决方案六:异步重建缓存

在这种方案下构建缓存采取异步策略,会从线程池中获取线程来异步构建缓存,从而不会让所有的请求直接到达存储层,该方案中每个Redis key 维护逻辑超时时间,当逻辑超时时间小于当前时间时,则说明当前缓存已经失效,应当进行缓存更新,否则说明当前缓存未失效,直接返回缓存中的 value 值。如在Redis 中将 key 的过期时间设置为 60 min,在对应的 value 中设置逻辑过期时间为 30 min。这样当 key 到了 30 min 的逻辑过期时间,就可以异步更新这个 key 的缓存,但是在更新缓存的这段时间内,旧的缓存依然可用。这种异步重建缓存的方式可以有效避免大量的 key 同时失效。

4.redis做为缓存,mysql的数据如何与redis进行同步呢?(双写一致性)

采用redisson实现的读写锁,在读的时候添加共享锁,可以保证读读不互斥,读写互斥。当我们更新数据的时候,添加排他锁,它是读写,读读都互斥,这样就能保证在写数据的同时是不会让其他线程读数据的,避免
了脏数据。这里面需要注意的是读方法和写方法上需要使用同一把锁才行。

5.Redis的数据过期策略

第一种是惰性删除,在设置该key过期时间后,我们不去管它,当需要该key时,我们在检查其是否过期,如果过期,我们就删掉它,反之返回该key。
第二种是 定期删除,就是说每隔一段时间,我们就对一些key进行检查,删除里面过期的key
定期清理的两种模式
SLOW模式是定时任务,执行频率默认为10hz,每次不超过25ms,以通过修改配置文件redis.conf 的 hz 选项来调整这个次数FAST模式执行频率不固定,每次事件循环会尝试执行,但两次间隔不低于2ms,每次耗时不超过1ms
Redis的过期删除策略:惰性删除 + 定期删除两种策略进行配合使用。

6.Redis的数据淘汰策略

在redis中提供了很多种,默认是noeviction,不删除任何数据,内部不足直接报错是可以在redis的配置文件中进行设置的,里面有两个非常重要的概念,一个是LRU,另外一个是LFULRU的意思就是最少最近使用,用当前时间减去最后一次访问时间,这个值越大则淘汰优先级越高。LFU的意思是最少频率使用。会统计每个key的访问频率,值越小淘汰优先级越高,我们在项目设置的allkeys-lru,挑选最近最少使用的数据淘汰,把一些经常访问的key留在redis中。

7.保证Redis中的数据都是热点数据

可以使用 allkeys-lru (挑选最近最少使用的数据淘汰)淘汰策略,那留下来的都是经常访问的热点数据.。

8.Redis分布式锁

redis中提供了一个命令setnx(SET if not exists),由于redis的单线程的,用了命令之后,只能有一个客户端对某一个key设置值,在没有过期或删除key的时候是其他客户端是不能设置这个key的。

9.Redis做异步队列

一般使用list结构作为队列,rpush生产消息,lpop消费消息。当lpop没有消息的时候,要适当sleep一会再重试。
list还有个指令叫blpop,在没有消息的时候,它会阻塞住直到消息到来。使用pub/sub主题订阅者模式,可以实现1:N的消息队列。在消费者下线的情况下,生产的消息会丢失,得使用专业的消息队列如rabbitmq等。

10.redis 事务

Redis 通过 MULTI、EXEC、WATCH 等命令来实现事务(transaction)功能。事务提供了一种将多个命令请求打包,然后一次性、按顺序地执行多个命令的机制,并且在事务执行期间,服务器不会中断事务而改去执行其他客户端的命令请求,它会将事务中的所有命令都执行完毕,然后才去处理其他客户端的命令请求。在传统的关系式数据库中,常常用 ACID 性质来检验事务功能的可靠性和安全性。在 Redis 中,事务总是具有原子性(Atomicity)、一致性(Consistency)和隔离性(Isolation),并且当 Redis 运行在某种特定的持久化模式下时,事务也具有持久性(Durability)。

  • 0
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值