“只有当你复习的时候,你才发现,其实自己了解的知识都很片面。”
01
—
为啥使用redis?
因为项目刚上线的时候,有很多请求并发的访问首页,每次访问都去查询数据库查找数据,结果把数据库崩了,所以我们引入了缓存中间件,结合市场上常用的缓存中间件有redis和memcache,考虑了一下他们之间的区别,选择了redis。
redis和memcache的区别:
redis是单线程模式处理请求的,这样做的原因有两个:一个是采用了非阻塞的异步事件处理机制;另一个是缓存数据都是内存操作,IO时间不会太长,而且单线程可以避免线程切换上下文的代价,而且最新的redis6.0版本,引入了多线程,不过多线程部分用来处理网络数据的读写和协议解析,执行命令仍然是单线程部分,不用担心并发安全问题。
redis支持持久化,memcache不支持持久化
redis数据类型多,memcache数据类型单一
redis支持主从同步机制和集群部署能力,可以提供高可用服务。memcache不支持主从同步
02
—
redis数据结构
StringHash有序set,list,set
使用场景:
String:使用最多的就是计数器,进行原子性的自增/自减
hash:可以做存储用户数据
sortset:一般使用排序的时候用的比较多,比如排行榜,热搜榜
list:一般存储商品列表,或者粉丝列表比较多,还可以支持范围获取元素,可以分页
set:一般去重的时候用,也可以进行交集,并集,差集操作,操作简单
差集操作sdiffset1set2
交集操作sinterset1set2
并集操作sunionset1set2
两个集合的差集,交集,并集存入另一个集合使用
sdiffstoreset3set1set2
sinterstoreset3set1set2
sunionstoreset3set1set2
都是把set1和set2的差集,交集,并集的结果并入到set3中
高级用法:
geospatial:用来保存地理位置
bitmap:位图是支持bit位来存储信息,可以用来实现布隆过滤器
hyperloglog:一般用于统计,例如页面pv/uv等数据
03
—
redis数据持久化
RDB优点:
会生成多个数据文件,每个数据文件都代表某一时刻的redis里面的数据
数据恢复的速度比AOF快
缺点:
RDB都是快照文件,默认是5分钟甚至更久时间才会生成一次,意味着这五分钟到下一个五分钟的数据可能会丢失
RDB如果生成数据快照的时候,如果文件很大,客户端可能会停顿几毫秒甚至几秒
AOF优点:
AOF是一秒一次通过后台的线程fsync操作,那最多丢失一秒的数据
AOF操作日志文件的时候,是以append-only的方式去写的,它只是追加的方式写数据,少了很多磁盘寻址的开销,写入性能极高,文件不容易破损
AOF文件即使过大的时候,出现后台重写,也不会影响客户端的读写
AOF日志文件的命令通过非常可读的方式进行记录,这非常适合做灾难性的误删除的紧急恢复。比如某人清空了缓存,只要rewrite还没有发生,就可以拷贝出AOF文件,把清空缓存的命令删除,然后把AOF放回去,通过恢复机制,自动恢复所有数据
缺点:
AOF文件比RDB还要大
04
—
redis内存淘汰策略
定期删除:就是每过多少毫秒就随机抽取设置了过期时间的key,过期了就删除(注意:是随机抽取,不是全表扫描)
惰性删除:就是我不主动删除,如果有人来查询,我在看看你过期没,过期了就删了
内存淘汰机制:
面试问题:
1.如果有大量的key需要设置同一时间过期,需要注意什么?
注意在过期的那一瞬间,可能会出现短暂的卡顿,严重的可能会出现缓存雪崩,一般在时间上加一个随机值,是的过期时间分散一些。
1.1什么是缓存雪崩?
同一时间,有大量的用户请求涌入,但是刚好那时候redis大面积的key过期了,这些请求直接就落到了数据库上面,数据库承受不住挂了,这就是我理解的缓存雪崩。
1.2那你知道怎么解决吗?
可以给key加上一个随机的过期时间,令过期时间分散开来,这样可以保证数据不会在同一时间大面积失效
也可以给热点数据设置永不过期,只要有更新缓存的操作就行了。
也可以使用失败的熔断策略,减少DB瞬间压力。
1.3那你知道缓存穿透跟缓存击穿吗?他们怎么解决呢?
缓存穿透,就是大量的请求,请求缓存中不存在的数据,导致这些请求直接到数据库去查询,导致数据库挂了。
缓存击穿,就是同一时间,大量的请求一直并发请求一个热点数据,在热点数据过期的那一瞬间,这些持续的大并发请求直接打到了数据库上。
缓存穿透解决办法,可以在接口层做参数校验,不合法的参数直接return,比如你请求id等于-1的东西,我在后台做了校验请求id必须大于1,否则直接返回。
也可以把请求不存在的数据做个缓存,value为null,只要设置一个过期时间30s就可以了,这样也可以解决
也可以用nginx设置用户ip每秒访问量超过多少直接拉黑
也可以用布隆过滤器,他的原理就是利用高效的数据结构和算法判断出你这个key在数据库中是否存在,不存在直接return,存在你在去查db刷新缓存然后return
缓存击穿的解决办法,可以设置热点永不过期,有更新缓存的操作就可以了,也可以加互斥锁
2.那你使用过redis分布式锁么,它是怎么回事?
先拿setnx争抢锁,抢到之后,给锁加一个过期时间,防止忘记释放锁
3.如果在setnx之后,执行过期时间之前,redis要重启维护了怎么办?
set有一个复杂的参数可以把setnx和过期时间合并成一条指令
4.假如redis里面有1亿个key,从里面找出10万个已知前缀相同的key,怎么全部找出来?
可以使用keys指令扫描指定模式的key列表,不过redis是单线程的,可能会导致线程阻塞一段时间,线程服务会停顿
也可以使用scan指令无阻塞的取出指定模式的key列表,不过有一定概率会重复,需要自己手动去重,整体花费时间比keys长
5.使用过redis做异步队列么,你是怎么用的?
一般使用list结构作为队列,rpush生产消息,lpop消费消息。但lpop没有消息的时候,可以适当sleep一会再重试
也可以使用blpop,在没有消息的时候,阻塞住直到消息到来
但是使用blpop也有问题,在阻塞过程中,如果中途网络出现问题或者某些问题导致连接池失效,就永远接收不到消息,虽然redis有连接状态检查功能,但是因为程序是阻塞的,redis连接状态功能不能用。
解决办法,在blpop后面加一个超时时间,每几分钟就断开,检查一下,在重新连接上。
6.redis如何实现延迟队列?
使用sortedset,拿时间戳作为score,消息内容作为key调用zadd生产消息,消费者用zrangebyscore指令获取N秒之前的数据轮询进行处理
7.redis是怎么持久化的?服务主从数据是怎么交互的?
RDB做全量持久化,AOF做增量持久化。因为RDB会耗费较长的时间,不够实时,在停机的时候会导致大量的丢失数据,所以需要AOF来配合使用,但redis实例重启时,使用RDB持久化文件重构内存,在使用AOF持久化文件重放近期操作指令来实现完整恢复重启之前的状态。
8.如果机器突然掉电怎么办?
取决于AOF日志的sync属性,如果不要求性能,每次写指令都sync一下磁盘就不会丢失数据,不过这样在高性能的情况下是不现实的,一般使用定时sync,比如1s一次,这样一般最多丢失一秒的数据。
9.RDB的原理是什么?
redis的master会fork出一个子线程,然后有子线程去执行RDB操作,子进程创建后,父子进程共享数据段,父进程继续提供读写服务,写脏的页面数据会逐渐和子进程分离开来。
10.为什么要用pipeline?有什么好处?
可以将多次IO往返的时间缩减为一次,前提是pipeline执行的指令之间没有因果相关性。
pipeline批次指令的数目是影响redis的QPS的一个重要因素。
11.redis同步机制了解吗?
redis有主从同步,从从同步。第一次同步时,主节点做一次bgsave,并同时将后续的修改操作记录到内存buffer,待完成后将RDB全量同步到复制节点,待复制节点接受完成后把RDB镜像加载到内存,加载完成后,在通知主节点将期间修改的操作记录同步到复制节点进行重放就完成了同步操作,后续的增量操作,通过AOF日志同步即可。
12.是否使用过redis集群?集群的高可用怎么保证?
主从配置可以保证,但主节点宕机时会自动把从节点提升为主节点,继续服务。也可以用哨兵+主从。
13.如何解决数据库和缓存双写,导致数据一致性的问题?
可以先更新数据库,然后在删除缓存,如果删除失败可以重试。
14.你了解最经典的缓存+数据库读写模式吗?
读的时候先读缓存,缓存没有再去读数据库,取出数据库后放入缓存,同时返回响应。
更新的时候,先更新数据库,然后在删除缓存。
为啥要删除缓存而不是更新缓存呢?
因为如果更新缓存,如果缓存里存的是数据设计到操作其他表的操作,那还要去查询其他表的数据库,进行计算才可以更新缓存,代价高,而且你也不知道该缓存会不会被频繁访问到?如果删除缓存的话,只是重新计算了一次而已。
15.redis的线程模式了解吗?
redis内部使用文件事件处理器,这个文件事件处理器是单线程的,所以redis是单线程的模型。它采用IO多路复用机制同时监听多个Socket,跟Socket上的事件来选择对应的事件处理器进行处理。
文件事件处理器包含4个部分:
1.多个Socket
2.IO多路复用程序
3.文件事件分派器
4.事件处理器(连接应答处理器,命令请求处理器,命令回复处理器)
多个Socket可能会并发产生不同的操作,每个操作对应不同的一个文件事件,但是IO多路复用程序会监听多个Socket,会将多个Socket产生的事件放入到队列中排队,文件事件分派器会依次从队列中取出一个事件,将该事件交给对应的事件处理器处理。
16.redis为啥那么快?
完全基于内存,绝大部分请求时存粹的内存操作,非常快。
是单线程,避免了不必要的上下文切换和竞争条件。
数据结构简单,对数据操作也简单。
使用多路IO复用模型,非阻塞IO。
注:IO多路复用,是指一个或一组线程处理多个tcp连接,可以减少系统的开销,不必创建过多的线程,也不必维护这些线程。
17.redis主从同步,主从之间的数据是怎么同步的?
你启动一个slave的时候,它会向master发送一个psync命令,如果这个slave是第一次连接到master,它会触发一个全量复制。master就会启动一个线程,生成RDB快照,还会把新的写请求缓存到内存中,RDB生成后,master会将RDB发送给slave,slave拿到之后第一时间会写进本地的磁盘,然后加载进内存,然后master会把缓存到内存中新的写请求发送给slave。之后的请求,就是master把AOF日志增量同步给slave服务就好了。