【2020-面试实战】-Redis

1 Redis 数据结构有哪些? zset数据结构?

在这里插入图片描述

  • String
    redis 的 string 可以包含任何数据. 如数字, 字符串, jpg图片或者序列化的对象
    使用方法: get, set, del, incr, decr 等
    实战场景:
  1. 缓存: 经典使用场景, 把常用的信息,字符串,图片等信息放到redis中, redis作为缓存层, mysql做持久化层,降低mysql的读写压力;
  2. 计数器: redis是单线程模型, 一个命令执行完才会执行下一个, 同时数据可以一步落地到其他的数据源;
  3. session: 常见方案spring session + redis 实现 session共享
  • Hash
    是一个Mapmap, 指本身又是一种键值对结构, 如 value={{k1,v1},…{kn,vn}}
    使用方法: 所有hash的命令都是 h 开头的 hget hset hdel 等
    实战场景:
    缓存: 能直观, 相比String 更节省空间的维护缓存信息, 如用户信息等

  • List
    List 说白了就是链表(redis 使用双端链表实现的 List), 是有序的, value可以重复, 可以通过下标取出对应的value值, 左右两边都能进行插入和删除数据;
    在这里插入图片描述
    使用列表的技巧:

    • lpush + lpop = Stack
    • lpush + rpop = Queue
    • lpush + ltrim = Capped Collection(有限集合)
    • lpush + brpop = message Queue(消息队列)

实战场景:

  1. timeline: 例如微博的时间轴, 有人发布微博, 用push 加入时间轴,展示新的列表信息
  • Set
    集合类型也是用来保存多个字符串的元素, 但和列表不同的是 集合中: 1. 不允许有重复的元素 2. 集合中的元素是无序的,不能通过索引下标获取元素 3. 支持集合间的操作,可以取多个集合取交集,并集,差集
    方法: 命令都是以s开头的, sset, srem, scard, smemebers, sismemeber
    实战场景:
  1. 标签(tag) 给用户添加标签, 或者用户给消息添加标签, 这样有同一标签或者类似标签的可以给推荐关注的事或者关注的人;
  2. 点赞, 或点踩, 收藏等
  • zset
    有序集合和集合有这必然的联系, 保留了集合不能有重复成员的特性,区别是,有序集合中的元素是可以排序的,它给每个元素设置一个分数,作为排序的依据(有序集合中的元素不可重复,但是分数可以重复;就想一个班里的同学学号不能重复,但考试成绩可以相同)
    方法: 有序集合的命令都是以 z 开头, zadd, zrange, zscore
    实战场景:
    排行榜: 有序集合经典使用场景; 例如小说视频等网站需要对用户上传的内容做排行榜. 榜单可以按照用户关注数,更新时间,字数等打分,做排行.

    用Sorted Sets来做带权重的队列,比如普通消息的score为1,重要消息的score为2,然后工作线程可以选择按score的倒序来获取工作任务。让重要的任务优先执行。

以下高级部分-----加分内容

  • Bitmap :
    位图是支持按 bit 位来存储信息,可以用来实现 布隆过滤器(BloomFilter);
  • HyperLogLog:
    供不精确的去重计数功能,比较适合用来做大规模数据的去重统计,例如统计 UV;
  • Geospatial:
    可以用来保存地理位置,并作位置距离计算或者根据半径计算位置等。有没有想过用Redis来实现附近的人?或者计算最优地图路径?
    这三个其实也可以算作一种数据结构,不知道还有多少朋友记得,我在梦开始的地方,Redis基础中提到过,你如果只知道五种基础类型那只能拿60分,如果你能讲出高级用法,那就觉得你有点东西。
  • pub/sub:
    功能是订阅发布功能,可以用作简单的消息队列。
  • Pipeline:
    可以批量执行一组指令,一次性返回全部结果,可以减少频繁的请求应答。
  • Lua:
    Redis 支持提交 Lua 脚本来执行一系列的功能。

2 聊聊redis的线程模型, 为啥单线程还能有很高的效率?

1)纯内存操作
2)核心是基于非阻塞的IO多路复用机制
3)单线程反而避免了多线程的频繁上下文切换问题

3 Redis 的hash(map)底层实现原理, 为什么不能过多?

redis的哈希对象的底层存储可以使用ziplist(压缩列表)和hashtable。当hash对象可以同时满足一下两个条件时,哈希对象使用ziplist编码。

  • 哈希对象保存的所有键值对的键和值的字符串长度都小于64字节
  • 哈希对象保存的键值对数量小于512个

当我们创建一个空的Hashes的时候使用的ziplist编码, 当某个键或某个值的长度大于hash_max_ziplist_value设定的值,会切换的Dict编码,还有一种情况也会切换就是ziplist的entries(节点数)大于hash_max_ziplist_entries。hash_max_ziplist_value和 hash_max_ziplist_entries在redis.conf中设置,默认值分别是512和64。

不能过多的原因猜测,可能是因为Redis是单线程模型, 如果Map过多,在进行读取操作的时候可能会影响性能

4 项目中Redis数据的更新方式

先更新数据库,后删除缓存

4.1 就不会有并发问题了吗?

依然存在,比如:如果先写了库, 在删除缓存前, 写库的线程当机了, 没有删除掉缓存,则也会出现数据不一致情况;
解决方案:
1. 数据库与缓存更新与读取操作进行异步串行化

更新数据的时候,根据数据的唯一标识,将操作路由之后,发送到一个jvm内部的队列中
读取数据的时候,如果发现数据不在缓存中,那么将重新读取数据+更新缓存的操作,根据唯一标识路由之后,也发送同一个jvm内部的队列中

2. 一个队列对应一个工作线程

每个工作线程串行拿到对应的操作,然后一条一条的执行
这样的话,一个数据变更的操作,先执行,删除缓存,然后再去更新数据库,但是还没完成更新
此时如果一个读请求过来,读到了空的缓存,那么可以先将缓存更新的请求发送到队列中,此时会在队列中积压,然后同步等待缓存更新完成(如果发现队列中已经有一个更新缓存的请求了,那么就不用再放个更新请求操作进去了,直接等待前面的更新操作请求完成即可)

待那个队列对应的工作线程完成了上一个操作的数据库的修改之后,才会去执行下一个操作,也就是缓存更新的操作,此时会从数据库中读取最新的值,然后写入缓存中
如果请求还在等待时间范围内,不断轮询发现可以取到值了,那么就直接返回; 如果请求等待的时间超过一定时长,那么这一次直接从数据库中读取当前的旧值

5 redis hash结构的扩容方案和 java hashmap扩容的一个区别,因为扩容阶段,一般是无法进行读写操作的,那redis是如何避免扩容导致的卡顿的(hash)

在这里插入图片描述
redis rehash与hashmap扩容的区别

redis的rehash,是将ht[0]的数据搬到ht[1] (ht[1]是ht[2]两倍),扩容过程中新加入的元素直接往ht[1]里面添加
hashmap的resize,创建一个新的数组,将旧的搬到新的数组中

redis的Hash在扩容时是如何避免卡顿的
rehash操作不是一次性、集中式完成的,而是分多次,渐进式,断续进行的,这样才不会对服务器性能造成影响。

dict中ht[2]中有两个hash表, 我们第一次存储数据的数据时, ht[0]会创建一个最小为4的hash表, 一旦ht[0]中的size和used相等, 则dict中会在ht[1]创建一个size*2大小的hash表, 此时并不会直接将ht[0]中的数据copy进ht[0]中, 执行的是渐进式rehash, 即在以后的操作(find, set, get等)中慢慢的copy进去, 以后新添加的元素会添加进ht[0], 因此在ht[1]被占满的时候定能确保ht[0]中所有的数据全部copy到ht[1]中.

6 持久化策略

持久化策略:master节点开启RDB和AOF写到本地磁盘

  • RDB持久化机制

    RDB在做持久化操作的时候,是从redis主进程中fork出一个子进程,让子进程执行磁盘IO操作来进行RDB的持久化即可.因此它可以让Redis保持高性能;
    RDB会生成多个数据文件,每个数据文件都代表了某一个时刻中redis的数据,这种多个数据文件的形式,非常适合做冷备;
    相对于AOF持久化机制来说, 直接基于RDB数据文件来重启和恢复redis进程,更加快速

    缺点:
    (1) 如果想要在redis故障时,尽可能少的丢失数据,那么RDB没有AOF好, 一般来说RDB数据快照文件,都是每隔5分钟或者更长时间生成一次. 这个时候就得接受一旦redis进程宕机,那么会丢失最近5分钟的数据;
    (2) RDB每次在fork子进程来执行RDB快照数据文件生成的时候,如果数据文件特别大, 可能会导致对客户端提供的服务暂停数毫秒,甚至数秒

  • AOF持久化机制

    AOF可以更好的保护数据不丢失, 一般AOF会每隔1秒, 通过一个后台线程执行一次 fsync 操作,最多丢失1秒钟的数据;
    AOF日志文件以append-only模式写入,所以没有任何磁盘寻址的开销, 写入性能非常高, 而且文件不容易破损,即使文件尾部破损,也很容易修复;
    Redis可以在 AOF 文件体积变得过大的时候,自动的在后台对AOF进行重写;
    AOF日志文件的命令通过非常可读的方式进行记录,这个特性非常适合做灾难性的误删除的紧急恢复, 比如不小心用flushall命令清空了所有数据,只要这个时候后台rewrite还没发生,那么就可以立即拷贝AOF文件,将最后一条flushall命令删掉, 然后再将该AOF文件放回去,就可以通过恢复机制,自动恢复所有数据;

    缺点:
    对同一份数据来说,AOF日志文件通常比RDB数据快照文件更大
    AOF开启后, 支持的写QPS会比RDB支持的写QPS低, 因为AOF会配置成每秒fsync一次日志文件,当然每秒一次fsync性能也还是很高的

RDB和AOF持久化机制该如何取舍

  • 不要仅仅使用RDB,因为那样会导致你丢失很多数据
  • 也不要仅仅使用AOF,因为那样有两个问题:第一,通过AOF做冷备,没有RDB做冷备,来的恢复速度更快;第二,RDB每次简单粗暴生成数据快照,更加健壮,可以避免AOF这种复杂的备份和恢复机制的bug
  • 结合使用AOF和RDB两种持久化机制,用AOF来保证数据不丢失,作为数据恢复的第一选择;用RDB来做不同程度的冷备,在AOF文件丢失或损坏不可用的时候,还可以使用RDB来进行快速的数据恢复

7 谈谈redis缓存雪崩、穿透 出现的场景以及防止手段?都有哪些监控措施?

  • 缓存雪崩
    现象:

    假设 系统A 其后台数据架构是 缓存+数据库; 该系统每天高峰期每秒有5000个请求,正常情况下,该系统的缓存高峰期可以抗每秒4000请求,数据库高峰期可抗每秒1000个请求;
    而此时缓存由于某些原因宕机, 高峰的请求每秒5000个,直接落到数据库上, 数据库无法扛起每秒5000的请求,因此数据库直接被打死;就算重启数据库也会立马被新的流量给打死, 这就是缓存雪崩

    解决:
    事前: redis 高可用(主从+哨兵, redis cluster(集群)) 避免全盘崩溃
    事中: 本地再添加一个 ehcache 缓存+Hystrix限流&降级, 避免 MySQL数据库被打死
    事后: redis持久化, 快速恢复缓存数据

  • 缓存穿透:
    现象:

    还是以上面的系统A为例, 假设某天的高峰的5000个请求,只有1000个是正常的普通用户发出,剩余4000都是恶意请求,因此这4000个请求不可能在缓存中寻得数据,也不可能在数据库中查到数据从而反写到缓存中,而数据库根本扛不住4000每秒的并发量而被打死;

    解决:

    解决方式其实不难, 就是只要从数据库中没查到,就在数据库中set一个空值,或具有特殊意义的固定值;

8 redis你是如何压测的,瓶颈在哪?为什么?

9 Redis中key的过期机制?

(1) 手动设置过期时间
当过期时间到了,Redis会通过定期删除+惰性删除的方式,进行这批过期缓存数据的删除

所谓定期删除,就是redis默认是每隔100ms就随机抽取一些设置了过期时间的key,检查其是否过期,如果过期就删除;
所谓惰性删除,就是说,当你获取某个key时,redis会检查这个key如果设置了过期时间是否已经到点了?如果过期了就删除,并且不会返回给你任何东西

(2) 内存淘汰机制
上面定期删除+惰性删除并没有全部删除掉实际上已经过期的key,这个时候就会有大批量的无用的缓存数据占据着你的内存,这样是不能够被允许的,该怎么办呢? 走内存淘汰机制,其淘汰策略如下:

  • noeviction: 当内存不足以容纳新写入数据时, 新写入操作会报错;(太恶心)
  • allkeys-lru: 当内存不足以容纳新写入数据时, 在键空间中,移除最少使用的key(最常用)
  • allkeys-random: 当内存不足以容纳新写入数据时,在键空间中,随机移除某个key(一般没人用)
  • volatile-lru: 当内存不足以容纳新写入数据时, 在设置了过期时间的键空间中,移除最近最少使用的key
  • volatile-random: 当内存不足以容纳新写入数据时, 在设置了过期时间的键空间中, 随机移除某个key
  • volatile-ttl: 当内存不足以容纳新写入数据时, 在设置了过期时间的键空间中, 有更早过期时间的key优先移除

10 Redis 主从复制方式 ,复制步骤

(1) 当启动一个slave node的时候,它会发送一个PSYNC命令给master node

(2) 如果这是slave node重新连接master node,那么master node仅仅会复制给slave部分缺少的数据; 否则如果是slave node第一次连接master node,那么会触发一次full resynchronization

(3) 开始full resynchronization的时候,master会启动一个后台线程,开始生成一份RDB快照文件,同时还会将从客户端收到的所有写命令缓存在内存中。RDB文件生成完毕之后,master会将这个RDB发送给slave,slave会先写入本地磁盘,然后再从本地磁盘加载到内存中。然后master会将内存中缓存的写命令发送给slave,slave也会同步这些数据。

(4) slave node如果跟master node有网络故障,断开了连接,会自动重连。master如果发现有多个slave node都来重新连接,仅仅会启动一个rdb save操作,用一份数据服务所有slave node。

11 Redis数据丢失的两种场景

  • 主备切换的过程,可能会导致数据丢失

    因为 mater -> slave 的复制是异步的, 所以可能有部分数据还没有复制到 slave, master 就宕机了,此时这部分数据就丢失了

  • 脑裂会导致数据丢失

    脑裂产生的时候,虽然某个slave被切换成了master, 但是可能client还没来得及切换到新的master, 此时还是在往旧的master上写数据. 但是我们知道, 脑裂情况下,当旧的master再次恢复的时候,会被作为一个slave挂到新的master上去,自己的数据会清空, 选择重新从新的master复制数据;
    因此, 这一部分的数据会丢失;

解决数据丢失的问题
min-slaves-to-write 1
min-slaves-max-lag 10

表示:
要求至少有1个slave,数据复制和同步的延迟不能超过10秒

  • 减少异步复制的数据丢失

    有了 min-slaves-max-lag 这个配置,就可以确保说,一旦slave复制数据和ack(回执)延时太长,就认为可能master宕机后损失的数据太多了, 那么就拒绝客户端写请求,这样可以把master宕机时由于部分数据未同步到slave导致的数据丢失降低的可控范围内;

  • 减少脑裂的数据丢失

    如果一个master出现了脑裂,跟其他slave丢了连接,那么上面两个配置可以确保说,如果不能继续给指定数量的slave 发送数据,而且slave超过10秒没有给自己ack消息,那么就直接拒绝客户端的写请求;
    上面的配置就确保了,如果任何一个slave丢了连接, 在10秒后发现没有slave给自己ack, 那么就拒绝新的写请求; 因此在脑裂场景下, 最多就丢失10秒的数据

12 Redis实现分布式锁

可以解决Redis并发竞争问题

13 Redis集群

哨兵+cluster

14 redis如何通过读写分离来承载读请求QPS超过10万+?

redis主从架构 -> 读写分离架构 -> 可支持水平扩展的读高并发架构

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值