目录
面试题:redis的过期策略?内存淘汰机制都有哪些?说一下LRU代码实现?
现在的项目是如何解决Redis缓存穿透、 缓存雪崩、 缓存击穿的?
面试题:redis 的线程模型是什么?为什么 redis 单线程却能支撑高并发?
在使用Redis做为缓存层的时候是怎么通过Java操作Redis的呢?
面试题:项目中为什么要使用缓存?
缓存的应用场景是读多写少。秒杀场景中,读的流量是远远大于写流量的。
缓存主要是解决读的问题。写还是每次都要走数据库。但是读是走缓存。
所以,缓存解决了读这一部分的问题。
1. 提升性能
1.数据库里包含复杂功能关键字的sql,都很慢。
2.数据库是磁盘,缓存是内存。所以数据库很慢。
3.数据库索引的数据结构是b+树(速度是logN),redis是map(速度是1)。所以数据库很慢。
2. 缓解数据库压力
当用户请求增多时,数据库的压力将大大增加,通过缓存能够大大降低数据库的压力。所有的socket连接都很耗资源,不管是本地socket,还是网络socket。
3. 使用缓存后会产生什么样的问题?
-
缓存与数据库双写不一致
-
缓存雪崩、缓存穿透
-
缓存并发竞争
面试题:redis的过期策略?内存淘汰机制都有哪些?说一下LRU代码实现?
Redis 选择【惰性删除+定期删除】这两种策略配和使用,以求在合理使用 CPU 时间和避免内存浪费之间取得平衡。
定期删除:每隔一段时间,扫描Redis中过期key字典,并清除部分过期的key。该策略还可以通过调整定时扫描的时间间隔和每次扫描的限定耗时,在不同情况下使得CPU和内存资源达到最优的平衡效果。但是问题是,定期删除可能会导致很多过期 key 到了时间并没有被删除掉,所以还要用到惰性删除。
惰性删除:获取 key 的时候,如果此时 key 已经过期,就删除,不会返回任何东西。该策略能最大限度地节省CPU资源,但是对内存却十分不友好。有一种极端的情况是可能出现大量的过期key没有被再次访问,因此不会被清除,导致占用了大量的内存。
Redis结合了定期删除和惰性删除,基本上能很好的处理过期数据的清理,但是实际上还是有点问题的,如果过期key较多,定期删除漏掉了一部分,而且也没有及时去查,即没有走惰性删除,那么就会有大量的过期key堆积在内存中,导致redis内存耗尽,此时又应该咋么办呢?
答案是:走内存淘汰机制。
内存淘汰机制:五六个呢(LFU等),最常用的就是LRU
(注意和本地缓存的过期策略区分,本地缓存的过期策略是LRU)
面试题:如何保证缓存与数据库的双写一致性?
引入rocketmq,利用rocketmq的事务消息最终解决数据的最终一致性
采用异步消息队列(rocketmq)的方式,将异步扣减的消息同步给消息的consumer端,并由消息的consunmer端完成数据库扣减的操作:
(1)活动发布同步库存进缓存
(2)下单交易减缓存库存
(3)异步消息扣减数据库内存
弱一致性(最终一致性)
最终一致性是能忍受一定时间内的数据不一致性的,只要求最后的数据是一致的即可。缓存一般是设有失效时间的,失效之后数据也会保证一致性,或者是下次修改时,没有并发,也会让数据回到一致性等等;本项目中缓存的失效时间是10分钟。
具体的实现过程为:
-
如果秒杀商品库存尚有,则生成一条秒杀消息发送到消息队列中(信息中含有用户信息与商品id);
-
消息的消费者收到秒杀消息后,从数据库中读取用户是否已经完成秒杀,如果没有,则减库存,下订单,写入订单信息到数据库中。
为什么要使用RocketMQ?
答: 为了redis挂的时候不会丢数据
引入RocketMQ:一可以解决redis和数据库一致性的问题,二是减少库存行锁竞争,先执行creatOrder事务,再异步执行减库存,这样可以减少事务持锁时间减少行锁竞争;异步处理简化秒杀请求中的业务流程,提升系统的性能
引入事务型消息RocketMQ是为了解决redis和数据库最终一致性的问题,但是还是会存在消息回滚,数据库扣减失败,redis和数据库不一致的问题,那么为什么要引入事务性消息呢?
防止redis挂了以后数据库有问题,redis挂了就系统不可用,因为无法确保数据库的数据和redis是同步的
第一阶段中,redis减库存成功而下单db操作失败了,最终数据库的库存是不会减的,这时候redis和数据库库存不是不一致吗?如果不一致,那么和不使用事务消息的方案不是没有什么区别吗?
redis如果扣减成功了,下单失败会导致redis库存无法回滚,这种情况下业务是可以接受的,除非redis也使用事务型操作,否则没办法和下单请求共享事务;但是用了事务性能会降低,因此这里假定redis扣减成功后下单失败的概率近乎很小,因为所有的验证等操作都提前做完了,除非db挂了。
产品展示中的库存数是用redis中的还是数据库中的?
关于展示问题,按照redis中取,取不出来再取数据库的,若数据库内数据更新,比如下单成功,则发送异步消息去清除redis数据,这样下次过来就可以走数据库拿到正确的数据了,当然也会有扣减库存没有清redis快,但业务上对库存还剩多少件展示层面没必要那么实时。
目前redis和provider消息是符合一致性了,但如果消息consumer处理失败,依旧无法保证redis和数据库最终事务一致?
consumer处理失败分为两种情况:
-
程序退出或网络问题等,这种mq不会被标记为消费成功,mq会重试直到成功为止
-
无论重试多少次都不能consumer成功的消息,目前情况下业务是先扣redis的,因此不会有这种情况
但是对于本项目而言:数据库和redis之间没有办法保证绝对的强一致,所以宁可少卖不要超卖
面试题: 缓存穿透、 缓存雪崩、 缓存击穿
缓存穿透
当用户访问的数据,既不在缓存中,也不在数据库中,导致请求在访问缓存时,发现缓存缺失,再去访问数据库时,发现数据库中也没有要访问的数据,没办法构建缓存数据,来服务后续的请求。那么当有大量这样的请求到来时,数据库的压力骤增,这就是缓存穿透的问题。
缓存穿透的发生一般有这两种情况:
-
业务误操作,缓存中的数据和数据库中的数据都被误删除了,所以导致缓存和数据库中都没有数据;
-
黑客恶意攻击,故意大量访问某些读取不存在数据的业务;
应对缓存穿透的方案,常见的方案有两种。
-
第一种方案,非法请求的限制;
-
第二种方案,缓存空值或者默认不存在的值;
1.非法请求的限制
当有大量恶意请求访问不存在的数据的时候,也会发生缓存穿透,因此在 API 入口处我们要判断求请求参数是否合理,请求参数是否含有非法值、请求字段是否存在,如果判断出是恶意请求就直接返回错误,避免进一步访问缓存和数据库。
2. 缓存空值或者默认不存在的值
缓存穿透是指查询一个一定不存在的数据,由于缓存是不命中,将去查询数据库,可以将空结果(null)或者默认不存在的值存入到缓存中进行防止。
注: 尽量不要使用null做防止击穿的手段,因为如果用null代码判断不出来是真的不存在还是就是存了null,这时候必须要用一个默认不存在的值;这个默认不存在值需要在应用层做好判断。
缓存雪崩
缓存雪崩造成的原因是因为我们在做缓存时为了保证内存利用率以及为了保证缓存中的数据与数据库中的数据一致性,一般在写入数据时都会给定一个过期时间,而就是因为过期时间的设置有可能导致大量的key在同一时间内全部失效,此时来了大量请求访问这些key,而Redis中却没有这些数据,从而导致所有请求直接落入数据库查询,造成数据库宕机,从而形成一系列连锁反应,造成整个系统崩溃。
可以看到,发生缓存雪崩有两个原因:
-
大量数据同时过期;
-
Redis 故障宕机;
不同的诱因,应对的策略也会不同。
大量数据同时过期
针对大量数据同时过期而引发的缓存雪崩问题,常见的应对方法有下面这几种:
-
均匀设置过期时间;
-
互斥锁;
-
设置热点数据永不过期;
1. 均匀设置过期时间
如果要给缓存数据设置过期时间,应该避免将大量的数据设置成同一个过期时间。我们可以在对缓存数据设置过期时间时,给这些数据的过期时间加上一个随机数,这样就保证数据不会在同一时间过期。
2. 互斥锁
当业务线程在处理用户请求时,如果发现访问的数据不在 Redis 里,就加个互斥锁,保证同一时间内只有一个请求来构建缓存(从数据库读取数据,再将数据更新到 Redis 里),当缓存构建完成后,再释放锁。未能获取互斥锁的请求,等待锁释放后重新读取缓存。(性能会受到很大的影响,不建议,因为使用缓存就是为了性能 )
3.设置热点数据永不过期:
设置热点数据永不过期,避免热点数据的失效导致大量的相同请求落入DB; 缓存数据不设置有效期,并不是意味着数据一直能在内存里,因为当系统内存紧张的时候,有些缓存数据会被“淘汰” 。
Redis 故障宕机
针对 Redis 故障宕机而引发的缓存雪崩问题,常见的应对方法有下面这几种:
-
服务熔断或请求限流机制;
-
构建 Redis 缓存高可靠集群;
注:也可以从恢复角度出发:
恢复角度:Redis 的 RDB+AOF组合持久化策略,方便redis宕机后及时恢复数据
缓存击穿
缓存击穿和缓存雪崩有点类似,都是由于请求的key过期导致的问题,但是不同点在于失效key的数量,对于雪崩而言指的是大量的key失效导致大量请求落入数据库,而对于击穿而言,指的是某一个热点key突然过期,而这个时候又突然又大量的请求来查询此热点key,但是在Redis中却并没有查询到结果从而导致所有请求全部进入数据库,导致在这个时刻数据库直接被打穿。
应对缓存击穿可以采取前面说到两种方案:
-
互斥锁方案,保证同一时间只有一个业务线程更新缓存,未能获取互斥锁的请求,等待锁释放后重新读取缓存。
-
设置热点数据永不过期,避免热点数据的失效导致大量的相同请求落入DB; 缓存数据不设置有效期,并不是意味着数据一直能在内存里,因为当系统内存紧张的时候,有些缓存数据会被“淘汰” 。
现在的项目是如何解决Redis缓存穿透、 缓存雪崩、 缓存击穿的?
本地缓存 + 限流 + 合理的缓存失效时间,足以抗住整个秒杀环节;对于redis集群高可用、持久化策略还没有在项目中实现。
Redis中所有对于数据库的存或者取都需要经过网络的IO达到对应的redisService上(I/0开销是耗时的主要原因:因为redis的数据存在内存上),所以使用本地热点缓存;本地热点缓存只有热点数据才可以进入,热点数据每秒钟的访问量是非常大的,这样可以减少服务端到redis中取数据对应的网络开销,也可以减少redisService的压力。
设置合理的失效时间避免Redis缓存和本地缓存同时回源。
面试题:Rdeis线程安全问题
关于取数据判断是否为null的逻辑上面,如果在高并发场景下,不加锁,是否会有线程安全问题,是否该加个双重检查锁?
答: 无需加锁,因为用缓存本身就没有必要保证一定不能脏读,加了锁反而影响性能,就体现不出来做缓存的任何意义
《【面试突击】— Redis篇》–Redis的线程模型了解吗?为啥单线程效率还这么高?
面试题:谈谈你对Redis的理解?
-
Redis是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的基本数据类型以及它们的应用场景:
类型
|
描述
|
特性
|
场景
|
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之后新增的数据类型
|
支持发布订阅,一对多消费
|
消息队列
|
面试题:redis内存淘汰机制
制(
通过maxmemory配置)
,如
果达到
限制
就会触发内存淘汰机制
,则使用对应的算法去处理需要删除的key。
当内存已使用率到达,则开始清理缓存)。
在5.0之前为我们提供了六种淘汰策略,而5.0为我们提供了八种,但是大体上来说这些
lru、lfu、random、ttl
四种类型,如下:
策略
|
概述
|
volatile-lru
|
从已设置过期时间的key中挑选最近最少使用的淘汰,没有设置过期时间的key不会被淘汰,这样就可以在增加内存空间的同时保证需要持久化的数据不会丢失。
|
volatile-ttl
|
从已设置过期时间的key中挑选将要过期的数据淘汰,ttl值越大越优先被淘汰。
|
volatile-random
|
从已设置过期时间的key中任意选择数据淘汰
|
volatile-lfu
|
从已设置过期时间的key挑选使用频率最低的数据淘汰
|
allkeys-lru
|
从key中挑选最近最少使用的数据淘汰,该策略要淘汰的key面向的是全体key集合,而非过期的key集合(应用最广泛的策略)。
|
allkeys-lfu
|
从key中挑选使用频率最低的数据淘汰
|
allkeys-random
|
从key中随机选择数据淘汰
|
no-enviction
|
是当内存不足以容纳新入数据时,新写入操作就会报错,请求可以继续进行,线上任务也不能持续进行,采用no-enviction策略可以保证数据不被丢失;默认策略。
|
面试题:Redis持久化机制
RDB
持久化把内存中当前进程的数据生成快照(
.rdb)文件保存到硬盘的过程,有手动触发和自动触发;
阻塞当前
Redis
,直到
RDB持久化过程完成为止,若内存实例比较大 会造成长时间阻塞,线上环境不建议用它
save的优化,在执行
Redis-cli shutdown
关闭
Redis服务时或执行
flushall
命令时,如果没有开启
AOF
持久化,自动执行
bgsave
-
优点:使用单独子进程来进行持久化,主进程不会进行任何 IO 操作,保证了 Redis 的高性能;而且RDB文件存储的是压缩的二进制文件,适用于备份、全量复制,可用于灾难备份,同时RDB文件的加载速度远超于AOF文件。
-
缺点:RDB是间隔一段时间进行持久化,如果持久化之间的时间内发生故障,会出现数据丢失。所以这种方式更适合数据要求不严谨的时候,因为RDB无法做到实时持久化,而且每次都要创建子进程,频繁创建成本过高;备份时占用内存,因为Redis 在备份时会独立创建一个子进程,将数据写入到一个临时文件(需要的内存是原本的两倍);还有一点,RDB文件保存的二进制文件存在新老版本不兼容的问题。
AOF
持久化方式能很好的解决
RDB持久化方式造成的数据丢失,
AOF持久化到硬盘中的并不是内存中的数据快照,而是记录写入命令(和
MySQL的
binlog(归档日志)日志一样)
-
优点:可以保证数据丢失风险降到最低,数据能够
保证是最新的,持久化是后台线程在处理,所以对于处理客户端请求的线程并不影响。 -
缺点:文件体积由于保存的是所有命令会比RDB大上很多,而且数据恢复时也需要重新执行指令,在重启时恢复数据的时间往往会慢很多。虽然持久化并不是共用处理客户端请求线程的资源来处理的,但是这两个线程还是在共享同一台机器的资源,所以在高并发场景下也会一定受到影响。
面试题:Redis写时复制思想的体现:
父子进程共享的空间粒度是页,共享的内存才会以页为单位进行拷贝,父进程会保留原有的物理空间,而子进程会使用拷贝后的新物理空间;
这样,如果当子进程运行期间,父子进程都没有修改数据,这也就避免了大量拷贝内存而带来的
时间消耗和空间占用问题。
Redis持久化时如何判断数据过期?
RDB
AOF
从内存数据库持久化数据到AOF文件:
AOF重写:
项目中Redis的持久化机制
项目中考虑到了
Redis
中仅仅只是用来做缓存
,虽然
RDB
因为并不是实时的持久化,会出现数据丢失,但项目的场景是可以容忍一定的数据丢失的。
面试题:Redis为什么快呢?
Redis到底有多快?
-
一、Redis完全基于内存
-
二、Redis整个结构类似于HashMap,查找和操作复杂度为O(1),不需要和MySQL查找数据一样需要产生随机磁盘IO或者全表查询
-
三、Redis对于客户端的请求处理是单线程的,采用单线程,避免了不必要的上下文切换和竞争条件,也不存在多进程或者多线程导致的 切换而消耗 CPU,不用去考虑各种锁的问题,不存在加锁释放锁操作,没有因为可能出现死锁而导致的性能消耗;
-
四、底层采用select/epoll多路复用的高效非阻塞IO模型
-
五、客户端通信协议采用RESP,简单易读,避免了复杂请求的解析开销
面试题:redis 的线程模型是什么?为什么 redis 单线程却能支撑高并发?
多路 I/O 复用模型
非阻塞 IO 意味着线程在读写 IO 时可以不必再阻塞了,读写可以瞬间完成然后线程可以继续干别的事了。且 Redis 在内存中操作数据的速度非常快,也就是说内存内的操作不会成为影响Redis性能的瓶颈,主要由以上几点造就了 Redis 具有很高的吞吐量。
为什么Redis是单线程的?
但是,我们使用单线程的方式是无法发挥多核CPU 性能,不过我们可以通过在单机开多个Redis 实例来完善!
从Redis 4.0版本开始会支持多线程的方式,但是,只是在某一些网络数据读写等操作上进行多线程的操作!
Redis的线程模型
-
多个 socket
-
IO 多路复用程序
-
文件事件分派器
-
事件处理器(连接应答处理器、命令请求处理器、命令回复处理器)
巨人的肩膀
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/110888.html