Redis相关problem问题

LRU算法

hash一致性算法

目录

https://blog.csdn.net/middleware2018/article/details/80355418

 为啥在项目里要用缓存呢?

1)高性能

10万 QPS

2)高并发

Mysql 不到一万,redis内存存储

常见的缓存问题 

1)缓存与数据库双写不一致

2)缓存雪崩

3)缓存穿透

4)缓存并发竞争

Redis单线程模型

(1)redis和memcached有啥区别

1)Redis支持服务器端的数据操作

4)集群模式

(2)redis的线程模型

1)文件事件处理器

(3)为啥redis单线程模型也能效率这么高?

Redis数据过期策略与内存删除淘汰机制

定期删除+过期删除

淘汰策略

  LRU  LinkedHashMap

 redis都有哪些数据类型?分别在哪些场景下使用比较合适呢?


(1)在项目中缓存是如何使用的?

 

这个,你结合你自己项目的业务来,你如果用了那恭喜你,你如果没用那不好意思,你硬加也得加一个场景吧

 

 为啥在项目里要用缓存呢?

 

用缓存,主要是俩用途,高性能和高并发

 

1)高性能

假设这么个场景,你有个操作,一个请求过来,吭哧吭哧你各种乱七八糟操作mysql,半天查出来一个结果,耗时600ms。但是这个结果可能接下来几个小时都不会变了,或者变了也可以不用立即反馈给用户。那么此时咋办?

 

缓存啊,折腾600ms查出来的结果,扔缓存里,一个key对应一个value,下次再有人查,别走mysql折腾600ms了。直接从缓存里,通过一个key查出来一个value,2ms搞定。性能提升300倍。

 

这就是所谓的高性能。

 

就是把你一些复杂操作耗时查出来的结果,如果确定后面不咋变了,然后但是马上还有很多读请求,那么直接结果放缓存,后面直接读缓存就好了。

 

2)高并发

mysql这么重的数据库,压根儿设计不是让你玩儿高并发的,虽然也可以玩儿,但是天然支持不好。mysql单机支撑到2000qps也开始容易报警了。

 

所以要是你有个系统,高峰期一秒钟过来的请求有1万,那一个mysql单机绝对会死掉。你这个时候就只能上缓存,把很多数据放缓存,别放mysql。缓存功能简单,说白了就是key-value式操作,单机支撑的并发量轻松一秒几万十几万,支撑高并发so easy。单机承载并发量是mysql单机的几十倍。

 

3)所以你要结合这俩场景考虑一下,你为啥要用缓存?

 

一般很多同学项目里没啥高并发场景,那就别折腾了,直接用高性能那个场景吧,就思考有没有可以缓存结果的复杂查询场景,后续可以大幅度提升性能,优化用户体验,有,就说这个理由,没有??那你也得编一个出来吧,不然你不是在搞笑么

 

(3)用了缓存之后会有啥不良的后果?

 

呵呵。。。你要是没考虑过这个问题,那你就尴尬了,面试官会觉得你头脑简单,四肢也不发达。你别光是傻用一个东西,多考虑考虑背后的一些事儿。

 

常见的缓存问题 

 

1)缓存与数据库双写不一致

2)缓存雪崩

3)缓存穿透

4)缓存并发竞争

 

这仨问题是常见面试题,后面我要讲,大家看到后面自然就知道了,但是人要是问你,你至少自己能说出来,并且给出对应的解决方案

Redis单线程模型

redis和memcached有什么区别?redis的线程模型是什么?为什么单线程的redis比多线程的memcached效率要高得多(为什么redis是单线程的但是还可以支撑高并发)?

 

2、面试官心里分析

 

这个是问redis的时候,最基本的问题吧,redis最基本的一个内部原理和特点,就是redis实际上是个单线程工作模型,你要是这个都不知道,那后面玩儿redis的时候,出了问题岂不是什么都不知道?

 

还有可能面试官会问问你redis和memcached的区别,不过说实话,最近这两年,我作为面试官都不太喜欢这么问了,memched是早些年各大互联网公司常用的缓存方案,但是现在近几年基本都是redis,没什么公司用memcached了

 

3、额外的友情提示

 

同学,你要是现在还不知道redis和memcached是啥?那你赶紧百度一下redis入门和memcahced入门,简单启动一下,然后试一下几个简单操作,先感受一下。接着回来继续听课,我觉得1小时以内你就搞定了。

 

另外一个友情提示,要听明白redis的线程模型,你需要了解socket网络相关的基本知识,如果不懂。。。那我觉得你java没学好吧。初学者都该学习java的socket网络通信相关知识的。。。

 

4、面试题剖析

 

(1)redis和memcached有啥区别

 

这个事儿吧,你可以比较出N多个区别来,但是我还是采取redis作者给出的几个比较吧

 

1)Redis支持服务器端的数据操作

Redis相比Memcached来说,拥有更多的数据结构和并支持更丰富的数据操作,通常在Memcached里,你需要将数据拿到客户端来进行类似的修改再set回去。这大大增加了网络IO的次数和数据体积。在Redis中,这些复杂的操作通常和一般的GET/SET一样高效。所以,如果需要缓存能够支持更复杂的结构和操作,那么Redis会是不错的选择。

 

2)内存使用效率对比:使用简单的key-value存储的话,Memcached的内存利用率更高,而如果Redis采用hash结构来做key-value存储,由于其组合式的压缩,其内存利用率会高于Memcached。

 

3)性能对比:由于Redis只使用单核,而Memcached可以使用多核,所以平均每一个核上Redis在存储小数据时比Memcached性能更高。而在100k以上的数据中,Memcached性能要高于Redis,虽然Redis最近也在存储大数据的性能上进行优化,但是比起Memcached,还是稍有逊色。

 

4)集群模式

memcached没有原生的集群模式,需要依靠客户端来实现往集群中分片写入数据;但是redis目前是原生支持cluster模式的,redis官方就是支持redis cluster集群模式的,比memcached来说要更好

 

(2)redis的线程模型

1)文件事件处理器


redis基于reactor模式开发了网络事件处理器,这个处理器叫做文件事件处理器,file event handler。这个文件事件处理器,是单线程的,redis才叫做单线程的模型,采用IO多路复用机制同时监听多个socket,根据socket上的事件来选择对应的事件处理器来处理这个事件。

 

如果被监听的socket准备好执行accept、read、write、close等操作的时候,跟操作对应的文件事件就会产生,这个时候文件事件处理器就会调用之前关联好的事件处理器来处理这个事件。

 

文件事件处理器是单线程模式运行的,但是通过IO多路复用机制监听多个socket,可以实现高性能的网络通信模型,又可以跟内部其他单线程的模块进行对接,保证了redis内部的线程模型的简单性。

 

文件事件处理器的结构包含4个部分:多个socket,IO多路复用程序,文件事件分派器,事件处理器(命令请求处理器、命令回复处理器、连接应答处理器,等等)。

 

多个socket可能并发的产生不同的操作,每个操作对应不同的文件事件,但是IO多路复用程序会监听多个socket,但是会将socket放入一个队列中排队,每次从队列中取出一个socket给事件分派器,事件分派器把socket给对应的事件处理器。

 

然后一个socket的事件处理完之后,IO多路复用程序才会将队列中的下一个socket给事件分派器。文件事件分派器会根据每个socket当前产生的事件,来选择对应的事件处理器来处理。

 

2)文件事件

 

当socket变得可读时(比如客户端对redis执行write操作,或者close操作),或者有新的可以应答的sccket出现时(客户端对redis执行connect操作),socket就会产生一个AE_READABLE事件。

 

当socket变得可写的时候(客户端对redis执行read操作),socket会产生一个AE_WRITABLE事件。

 

IO多路复用程序可以同时监听AE_REABLE和AE_WRITABLE两种事件,要是一个socket同时产生了AE_READABLE和AE_WRITABLE两种事件,那么文件事件分派器优先处理AE_REABLE事件,然后才是AE_WRITABLE事件。

 

3)文件事件处理器

如果是客户端要连接redis,那么会为socket关联连接应答处理器

如果是客户端要写数据到redis,那么会为socket关联命令请求处理器

如果是客户端要从redis读数据,那么会为socket关联命令回复处理器

 

4)客户端与redis通信的一次流程

 

在redis启动初始化的时候,redis会将连接应答处理器跟AE_READABLE事件关联起来,接着如果一个客户端跟redis发起连接,此时会产生一个AE_READABLE事件,然后由连接应答处理器来处理跟客户端建立连接,创建客户端对应的socket,同时将这个socket的AE_READABLE事件跟命令请求处理器关联起来。

 

当客户端向redis发起请求的时候(不管是读请求还是写请求,都一样),首先就会在socket产生一个AE_READABLE事件,然后由对应的命令请求处理器来处理。这个命令请求处理器就会从socket中读取请求相关数据,然后进行执行和处理。

接着redis这边准备好了给客户端的响应数据之后,就会将socket的AE_WRITABLE事件跟命令回复处理器关联起来,当客户端这边准备好读取响应数据时,就会在socket上产生一个AE_WRITABLE事件,会由对应的命令回复处理器来处理,就是将准备好的响应数据写入socket,供客户端来读取。

 

命令回复处理器写完之后,就会删除这个socket的AE_WRITABLE事件和命令回复处理器的关联关系。

 

(3)为啥redis单线程模型也能效率这么高?

 

1)纯内存操作

2)核心是基于非阻塞的IO多路复用机制

3)单线程反而避免了多线程的频繁上下文切换问题(百度)

Redis数据过期策略与内存删除淘汰机制

https://blog.csdn.net/qq_28018283/article/details/80764518

啥叫缓存?用内存当缓存。内存是无限的吗,内存是很宝贵而且是有限的,磁盘是廉价而且是大量的。可能一台机器就几十个G的内存,但是可以有几个T的硬盘空间。redis主要是基于内存来进行高性能、高并发的读写操作的。

 

那既然内存是有限的,比如redis就只能用10个G,你要是往里面写了20个G的数据,会咋办?当然会干掉10个G的数据,然后就保留10个G的数据了。那干掉哪些数据?保留哪些数据?当然是干掉不常用的数据,保留常用的数据了。

 

所以说,这是缓存的一个最基本的概念,数据是会过期的,要么是你自己设置个过期时间,要么是redis自己给干掉。

 

set key value 过期时间(1小时)

set进去的key,1小时之后就没了,就失效了

 

2)老师,我的数据明明都过期了,怎么还占用着内存啊?

 

还有一种就是如果你设置好了一个过期时间,你知道redis是怎么给你弄成过期的吗?什么时候删除掉?如果你不知道,之前有个学员就问了,为啥好多数据明明应该过期了,结果发现redis内存占用还是很高?那是因为你不知道redis是怎么删除那些过期key的。

 

redis 内存一共是10g,你现在往里面写了5g的数据,结果这些数据明明你都设置了过期时间,要求这些数据1小时之后都会过期,结果1小时之后,你回来一看,redis机器,怎么内存占用还是50%呢?5g数据过期了,我从redis里查,是查不到了,结果过期的数据还占用着redis的内存。

 

如果你连这个问题都不知道,上来就懵了,回答不出来,那线上你写代码的时候,想当然的认为写进redis的数据就一定会存在,后面导致系统各种漏洞和bug,谁来负责?

 

3、面试题剖析

 

(1)设置过期时间

 

我们set key的时候,都可以给一个expire time,就是过期时间,指定这个key比如说只能存活1个小时?10分钟?这个很有用,我们自己可以指定缓存到期就失效。

 

如果假设你设置一个一批key只能存活1个小时,那么接下来1小时后,redis是怎么对这批key进行删除的?

 

答案是:定期删除+惰性删除

定期删除

所谓定期删除,指的是redis默认是每隔100ms就随机抽取一些设置了过期时间的key,检查其是否过期,如果过期就删除。假设redis里放了10万个key,都设置了过期时间,你每隔几百毫秒,就检查10万个key,那redis基本上就死了,cpu负载会很高的,消耗在你的检查过期key上了。注意,这里可不是每隔100ms就遍历所有的设置过期时间的key,那样就是一场性能上的灾难。实际上redis是每隔100ms随机抽取一些key来检查和删除的。

惰性删除

但是问题是,定期删除可能会导致很多过期key到了时间并没有被删除掉,那咋整呢?所以就是惰性删除了。这就是说,在你获取某个key的时候,redis会检查一下 ,这个key如果设置了过期时间那么是否过期了?如果过期了此时就会删除,不会给你返回任何东西。

 

并不是key到时间就被删除掉,而是你查询这个key的时候,redis再懒惰的检查一下

 

通过上述两种手段结合起来,保证过期的key一定会被干掉。

 

很简单,就是说,你的过期key,靠定期删除没有被删除掉,还停留在内存里,占用着你的内存呢,除非你的系统去查一下那个key,才会被redis给删除掉。

 

但是实际上这还是有问题的,如果定期删除漏掉了很多过期key,然后你也没及时去查,也就没走惰性删除,此时会怎么样?如果大量过期key堆积在内存里,导致redis内存块耗尽了,咋整?

惰性删除-》过期key存在,但是内存超了

答案是:走内存淘汰机制。

 

(2)内存淘汰

 

如果redis的内存占用过多的时候,此时会进行内存淘汰,有如下一些策略:

 

redis 10个key,现在已经满了,redis需要删除掉5个key

 

1个key,最近1分钟被查询了100次

1个key,最近10分钟被查询了50次

1个key,最近1个小时倍查询了1次

淘汰策略

1)noeviction:当内存不足以容纳新写入数据时,新写入操作会报错,这个一般没人用吧,实在是太恶心了

2)allkeys-lru:当内存不足以容纳新写入数据时,在键空间中,移除最近最少使用的key(这个是最常用的)

3)allkeys-random:当内存不足以容纳新写入数据时,在键空间中,随机移除某个key,这个一般没人用吧,为啥要随机,肯定是把最近最少使用的key给干掉啊

4)volatile-lru:当内存不足以容纳新写入数据时,在设置了过期时间的键空间中,移除最近最少使用的key(这个一般不太合适)

5)volatile-random:当内存不足以容纳新写入数据时,在设置了过期时间的键空间中,随机移除某个key

6)volatile-ttl:当内存不足以容纳新写入数据时,在设置了过期时间的键空间中,有更早过期时间的key优先移除

 

 

noeviction:默认策略,不淘汰,如果内存已满,添加数据是报错。

allkeys-lru:在所有键中,选取最近最少使用的数据抛弃。

volatile-lru:在设置了过期时间的所有键中,选取最近最少使用的数据抛弃。

allkeys-random: 在所有键中,随机抛弃。

volatile-random: 在设置了过期时间的所有键,随机抛弃。

volatile-ttl:在设置了过期时间的所有键,抛弃存活时间最短的数据

百度,问题啊,网上鱼龙混杂

 

如果百度一些api操作,入门的知识,ok的,随便找一个博客都可以

 

一些高级别的,redis单线程模型

 

很简单,你写的数据太多,内存满了,或者触发了什么条件,redis lru,自动给你清理掉了一些最近很少使用的数据


(3)要不你手写一个LRU算法?

 

我确实有时会问这个,因为有些候选人如果确实过五关斩六将,前面的问题都答的很好,那么其实让他写一下LRU算法,可以考察一下编码功底

 

你可以现场手写最原始的LRU算法,那个代码量太大了,我觉得不太现实

 

public class LRUCache<K, V> extends LinkedHashMap<K, V> {

    

private final int CACHE_SIZE;

 

    // 这里就是传递进来最多能缓存多少数据

    public LRUCache(int cacheSize) {

        super((int) Math.ceil(cacheSize / 0.75) + 1, 0.75f, true); // 这块就是设置一个hashmap的初始大小,同时最后一个true指的是让linkedhashmap按照访问顺序来进行排序,最近访问的放在头,最老访问的就在尾

        CACHE_SIZE = cacheSize;

    }

 

    @Override

    protected boolean removeEldestEntry(Map.Entry eldest) {

        return size() > CACHE_SIZE; // 这个意思就是说当map中的数据量大于指定的缓存个数的时候,就自动删除最老的数据

    }

 

}


我给你看上面的代码,是告诉你最起码你也得写出来上面那种代码,不求自己纯手工从底层开始打造出自己的LRU,但是起码知道如何利用已有的jdk数据结构实现一个java版的LRU

 

 

 redis都有哪些数据类型?分别在哪些场景下使用比较合适呢?

 

第一,看看你到底有没有全面的了解redis有哪些功能,一般怎么来用,啥场景用什么,就怕你别就会最简单的kv操作

 

第二,看看你在实际项目里都怎么玩儿过redis

 

要是你回答的不好,没说出几种数据类型,也没说什么场景,你完了,面试官对你印象肯定不好,觉得你平时就是做个简单的set和get。

 

3、面试题剖析

 

(1)string

 

这是最基本的类型了,没啥可说的,就是普通的set和get,做简单的kv缓存

 

(2)hash

 

这个是类似map的一种结构,这个一般就是可以将结构化的数据,比如一个对象(前提是这个对象没嵌套其他的对象)给缓存在redis里,然后每次读写缓存的时候,可以就操作hash里的某个字段。

 

key=150

 

value={

  “id”: 150,

  “name”: “zhangsan”,

  “age”: 20

}

 

hash类的数据结构,主要是用来存放一些对象,把一些简单的对象给缓存起来,后续操作的时候,你可以直接仅仅修改这个对象中的某个字段的值

 

value={

  “id”: 150,

  “name”: “zhangsan”,

  “age”: 21

}

 

 

(3)list

 

有序列表,这个是可以玩儿出很多花样的

 

微博,某个大v的粉丝,就可以以list的格式放在redis里去缓存

 

key=某大v

 

value=[zhangsan, lisi, wangwu]

 

比如可以通过list存储一些列表型的数据结构,类似粉丝列表了、文章的评论列表了之类的东西

 

比如可以通过lrange命令,就是从某个元素开始读取多少个元素,可以基于list实现分页查询,这个很棒的一个功能,基于redis实现简单的高性能分页,可以做类似微博那种下拉不断分页的东西,性能高,就一页一页走

 

比如可以搞个简单的消息队列,从list头怼进去,从list尾巴那里弄出来

 

(4)set

 

无序集合,自动去重

 

直接基于set将系统里需要去重的数据扔进去,自动就给去重了,如果你需要对一些数据进行快速的全局去重,你当然也可以基于jvm内存里的HashSet进行去重,但是如果你的某个系统部署在多台机器上呢?

 

得基于redis进行全局的set去重

 

可以基于set玩儿交集、并集、差集的操作,比如交集吧,可以把两个人的粉丝列表整一个交集,看看俩人的共同好友是谁?对吧

 

把两个大v的粉丝都放在两个set中,对两个set做交集

 

(5)sorted set

 

排序的set,去重但是可以排序,写进去的时候给一个分数,自动根据分数排序,这个可以玩儿很多的花样,最大的特点是有个分数可以自定义排序规则

 

比如说你要是想根据时间对数据排序,那么可以写入进去的时候用某个时间作为分数,人家自动给你按照时间排序了

 

排行榜:将每个用户以及其对应的什么分数写入进去,zadd board score username,接着zrevrange board 0 99,就可以获取排名前100的用户;zrank board username,可以看到用户在排行榜里的排名

 

zadd board 85 zhangsan

zadd board 72 wangwu

zadd board 96 lisi

zadd board 62 zhaoliu

 

96 lisi

85 zhangsan

72 wangwu

62 zhaoliu

 

zrevrange board 0 3

 

获取排名前3的用户

 

96 lisi

85 zhangsan

72 wangwu

 

zrank board zhaoliu

4

 

 

 

 

 

 

猜你喜欢

转载自blog.csdn.net/ssllkkyyaa/article/details/84099240