Redis综述篇:与面试官彻夜长谈Redis缓存、持久化、淘汰机制、哨兵、集群底层原理!

一、Redis基本概念

  • 面试官心理: 靠!手上活都没干完又叫我过来面试,这不耽误我事么,今儿又得加班补活了........咦,这小伙子简历不错啊,先考考它Redis..........
  • 面试官: 谈谈你对Redis的理解?
  • 我: RedisANSI C语言编写的一个基于内存的高性能键值对(key-value)的NoSQL数据库,一般用于架设在Java程序与数据库之间用作缓存层,为了防止DB磁盘IO效率过低造成的请求阻塞、响应缓慢等问题,用来弥补DB与Java程序之间的性能差距,同时,也可以在DB吞吐跟不上系统并发量时,避免请求直接落入DB从而起到保护DB的作用。
  • Redis一般除了缓存DB数据之外还可以利用它丰富的数据类型及指令来实现一些其他功能,比如:计数器、用户在线状态、排行榜、session存储等,同时Redis的性能也非常可观,通过官方给出的数据显示能够达到10w/s的QPS处理,但是在生产环境的实测结果大概读取QPS在7-9w/s,写入QPS在6-8w/s左右(注:与机器性能也有关),同时Redis也提供事务、持久化、高可用等一些机制的支持。

二、Redis基本数据类型与常用指令

  • 面试官: 刚刚听你提到了可以利用它丰富的数据类型及指令来实现一些其他功能,那你跟我讲讲Redis的一些常用指令。
  • 我: Redis常用的一些命令的话一般是都是对于基本数据类型的操作指令以及一些全局指令.....叭啦叭啦叭......,如下:
命令 作用
keys * 返回所有键(keys还能用来搜索,比如keys h*:搜索所有以h开头的键)
dbsize 返回键数量,如果存在大量键,线上禁止使用此指令
exists key 检查键是否存在,存在返回 1,不存在返回 0
del key 删除键,返回删除键个数,删除不存在键返回 0
ttl key 查看键存活时间,返回键剩余过期时间,不存在返回-1
expire key seconds 设置过期时间(单位:s),成功返回1,失败返回0
expireat key timestamp 设置key在某个时间戳(精确到秒)之后过期
pexpire key milliseconds 设置过期时间(单位:ms),成功返回1,失败返回0
persist key 去掉过期时间
monitor 实时监听并返回Redis服务器接收到的所有请求信息
shutdown 把数据同步保存到磁盘上,并关闭Redis服务
info 查看当前Redis节点信息
....... .......
当然了,一般也是记得一些常用的命令,但是 更多命令参考:Redis命令大全,因为Redis命令和JVM参数一样,只要记得可以这样做就行了,但是具体的可以去参考相关文档资料。
  • 面试官: 嗯嗯,不错,那再接着讲讲Redis的基本数据类型以及你是在项目中怎么使用它们的吧!
  • 我: Redis数据类型在之前是五种,但是现在的版本中存在九种,分别为:字符串(strings/string)、散列(hashes/hash)、列表(lists/list)、集合(sets/set)、有序集合(sorted sets/zset)以及后续的四种数据类型:bitmaps、hyperloglogs、地理空间(geospatial)、消息(Streams),不过无论是哪种数据类型Redis都不会直接将它放在内存中存储,而是转而内部使用RedisObject来存储以及表示所有类型的key-value(说着说着我拿出了纸和笔,给面试官画了一张图):

 


Redis内部使用一个RedisObject对象来表示所有的keyvalueRedisObject最主要的信息如上图所示:type表示一个value对象具体是何种数据类型,encoding是不同数据类型在Redis内部的存储方式。比如:type=string表示value存储的是一个普通字符串,那么encoding可以是raw或者int,而关于其他数据类型的内部编码实现我顿时再拿起笔chua~ chua~ chua

  • 我接着回答: 下面我再简单讲讲Redis的基本数据类型以及它们的应用场景:
类型 描述 特性 场景
string 二进制安全 可以存储任何元素(数字、字符、音视频、图片、对象.....) 计数器、分布式锁、字符缓存、分布式ID生成、session共享、秒杀token、IP限流等
hash 键值对存储,类似于Map集合 适合存储对象,可以将对象属性一个个存储,更新时也可以更新单个属性,操作某一个字段 对象缓存、购物车等
list 双向链表 增删快 栈、队列、有限集合、消息队列、消息推送、阻塞队列等
set 元素不能重复,每次获取无序 添加、删除、查找的复杂度都是O(1),提供了求交集、并集、差集的操作 抽奖活动、朋友圈点赞、用户(微博好友)关注、相关关注、共同关注、好友推荐(可能认识的人)等
sorted set 有序集合,每个元素有一个对应的分数,不允许元素重复 基于分数进行排序,如果分数相等,以key值的 ascii 值进行排序 商品评价标签(好评、中评、差评等)、排行榜等
bitmaps Bitmaps是一个字节由 8 个二进制位组成 在字符串类型上面定义的位操作 在线用户统计、用户访问统计、用户点击统计等
hyperloglog Redis2.8.9版本添加了 HyperLogLog结构。Redis HyperLogLog是用来做基数统计的算法。 用于进行基数统计,不是集合,不保存数据,只记录数量而不是具体数据 统计独立UV等
geospatial Redis3.2版本新增的数据类型:GEO对地理位置的支持 以将用户给定的地理位置信息储存起来, 并对这些信息进行操作 地理位置计算
stream Redis5.0之后新增的数据类型 支持发布订阅,一对多消费 消息队列

PS:HyperLogLog的优点是,在输入元素的数量或者体积非常非常大时,计算基数所需的空间总是固定 的、并且是很小的。在 Redis 里面,每个HyperLogLog键只需要花费12 KB内存,就可以计算接近2^64个不同元素的基数。这和计算基数时,元素越多耗费内存就越多的集合形成鲜明对比。但是,因为HyperLogLog只会根据输入元素来计算基数,而不会储存输入元素本身,所以HyperLogLog不能像集合那样,返回输入的各个元素(核心是基数估算算法,最终数值存在一定误差误差范围:基数估计的结果是一个带有0.81%标准错误的近似值,耗空间极小,每个hyperloglog key占用了12K的内存用于标记基数,pfadd命令不是一次性分配12K内存使用,会随着基数的增加内存逐渐增大,Pfmerge命令合并后占用的存储空间为12K,无论合并之前数据量多少)

三、Redis缓存及一致性、雪崩、击穿与穿透问题

  • 面试官提问: 那么你们在使用Redis做为缓存层的时候是怎么通过Java操作Redis的呢?

  • 我的心理: 这问题不是送命题吗.....

  • 我: Java操作Redis的客户端有很多,比如springData中的RedisTemplate,也有SpringCache集成Redis后的注解形式,当然也会有一些Jedis、Lettuce、Redisson等等,而我们使用的是Lettuce以及Redisson........

  • 面试官提问: 那你们在使用Redis作为缓存的时候有没有遇到什么问题呢?

  • 我: 咳咳,是的,确实遇到了以及考虑到了一些问题,比如缓存一致性、雪崩、穿透与击穿,关于RedisMySQL之间的数据一致性问题其实也考虑过很多方案,比如先删后改,延时双删等等很多方案,但是在高并发情况下还是会造成数据的不一致性,所以关于DB与缓存之间的强一致性一定要保证的话那么就对于这部分数据不要做缓存,操作直接走DB,但是如果这个数据比较热点的话那么还是会给DB造成很大的压力,所以在我们的项目中还是采用先删再改+过期的方案来做的,虽然也会存在数据的不一致,但是勉强也能接受,因为毕竟使用缓存访问快的同时也能减轻DB压力,而且本身采用缓存就需要接受一定的数据延迟性和短暂的不一致性,我们只能采取合适的策略来降低缓存和数据库间数据不一致的概率,而无法保证两者间的强一致性。合适的策略包括合适的缓存更新策略,合适的缓存淘汰策略,更新数据库后及时更新缓存、缓存失败时增加重试机制等。

  • 面试官话锋一转: 打断一下,你刚刚提到了使用缓存能让访问变快,那么你能不能讲讲Redis为什么快呢?

  • 我的心理: 好家伙,这一手来的我猝不及防......

  • 硬着头发回答: Redis快的原因嘛其实可以从多个维度来看待:

    • 一、Redis完全基于内存
    • 二、Redis整个结构类似于HashMap,查找和操作复杂度为O(1),不需要和MySQL查找数据一样需要产生随机磁盘IO或者全表
    • 三、Redis对于客户端的处理是单线程的,采用单线程处理所有客户端请求,避免了多线程的上下文切换和线程竞争造成的开销
    • 四、底层采用select/epoll多路复用的高效非阻塞IO模型
    • 五、客户端通信协议采用RESP,简单易读,避免了复杂请求的解析开销
  • 面试官露出姨父般的慈笑: 嗯嗯,还不错,那你继续谈谈刚刚的缓存雪崩、穿透与击穿的问题吧

  • 我: 好的,先说缓存雪崩吧,缓存雪崩造成的原因是因为我们在做缓存时为了保证内存利用率,一般在写入数据时都会给定一个过期时间,而就是因为过期时间的设置有可能导致大量的热点key在同一时间内全部失效,此时来了大量请求访问这些key,而Redis中却没有这些数据,从而导致所有请求直接落入DB查询,造成DB出现瓶颈或者直接被打宕导致雪崩情况的发生。关于解决方案的的话也可以从多个维度来考虑:

    • 一、设置热点数据永不过期,避免热点数据的失效导致大量的相同请求落入DB
    • 二、错开过期时间的设置,根据业务以及线上情况合理的设置失效时间
    • 三、使用分布式锁或者MQ队列使得请求串行化,从而避免同一时间请求大量落入DB(性能会受到很大的影响)
  • 面试官: 那缓存穿透呢?指的是什么?又该怎么解决?

  • 我喝了口水接着回答: 缓存穿透这个问题是由于请求参数不合理导致的,比如对外暴露了一个接口getUser?userID=xxx,而数据库中的userID是从1开始的,当有黑客通过这个接口携带不存在的ID请求时,比如:getUser?userID=-1,请求会先来到Redis中查询缓存,但是发现没有对应的数据从而转向DB查询,但是DB中也无此值, 所以也无法写入数据到缓存,而黑客就通过这一点利用“肉鸡”等手段疯狂请求这个接口,导致出现大量Redis不存在数据的请求落入DB,从而导致DB出现瓶颈或者直接被打宕机,整个系统陷入瘫痪。

  • 面试官: 嗯,那又该如果避免这种情况呢?

  • 我: 解决方案也有好几种呢:

    • 一、做IP限流与黑名单,避免同一IP一瞬间发送大量请求
    • 二、对于请求做非法校验,对于携带非法参数的请求直接过滤
    • 三、对于DB中查询不存在的数据写入Redis“Not Data”并设置短暂的过期时间,下次请求能够直接被拦截在Redis而不会落入DB
    • 四、布隆过滤器
  • 面试官: 那接下来的缓存击穿呢?又是怎么回事?怎么解决?

  • 我: 这个简单,缓存击穿和缓存雪崩有点

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值