本系列会零碎的整顿MySQL,Redis,SSM框架,算法,计网等面试常问技术栈的面试题,本文次要是整顿分享了Redis相干的面试题,MySQL、Spring、JVM之前曾经更新了,须要的同学也能够去看一下,心愿对正在筹备秋招的你们有所帮忙!
当然集体整顿的所有面试题都无偿分享,只求大伙一个点赞关注转发三连,这些文档都放在文末了,须要的同学能够自取
1. 什么是Redis?它次要用来什么的?
Redis,英文全称是Remote Dictionary Server(近程字典服务),是一个开源的应用ANSI C语言编写、反对网络、可基于内存亦可长久化的日志型、Key-Value数据库,并提供多种语言的API。
与MySQL数据库不同的是,Redis的数据是存在内存中的。它的读写速度十分快,每秒能够解决超过10万次读写操作。因而redis被广泛应用于缓存,另外,Redis也常常用来做分布式锁。除此之外,Redis反对事务、长久化、LUA 脚本、LRU 驱动事件、多种集群计划。
2.说说Redis的根本数据结构类型
大多数小伙伴都晓得,Redis有以下这五种根本类型:
- String(字符串)
- Hash(哈希)
- List(列表)
- Set(汇合)
- zset(有序汇合)
它还有三种非凡的数据结构类型
- Geospatial
- Hyperloglog
- Bitmap
2.1 Redis 的五种根本数据类型
String(字符串)
- 简介:String是Redis最根底的数据结构类型,它是二进制平安的,能够存储图片或者序列化的对象,值最大存储为512M
- 简略应用举例: set key value、get key等
- 利用场景:共享session、分布式锁,计数器、限流。
- 外部编码有3种,int(8字节长整型)/embstr(小于等于39字节字符串)/raw(大于39个字节字符串)
C语言的字符串是char[]实现的,而Redis应用SDS(simple dynamic string) 封装,sds源码如下:
struct sdshdr{ unsigned int len; // 标记buf的长度 unsigned int free; //标记buf中未应用的元素个数 char buf[]; // 寄存元素的坑}
SDS 结构图如下:
Redis为什么抉择SDS构造,而C语言原生的char[]不香吗?
举例其中一点,SDS中,O(1)工夫复杂度,就能够获取字符串长度;而C 字符串,须要遍历整个字符串,工夫复杂度为O(n)
Hash(哈希)
- 简介:在Redis中,哈希类型是指v(值)自身又是一个键值对(k-v)构造
- 简略应用举例:hset key field value 、hget key field
- 外部编码:ziplist(压缩列表) 、hashtable(哈希表)
- 利用场景:缓存用户信息等。
- 留神点:如果开发应用hgetall,哈希元素比拟多的话,可能导致Redis阻塞,能够应用hscan。而如果只是获取局部field,倡议应用hmget。
字符串和哈希类型对比方下图:
List(列表)
- 简介:列表(list)类型是用来存储多个有序的字符串,一个列表最多能够存储2^32-1个元素。
- 简略实用举例:lpush key value [value ...] 、lrange key start end
- 外部编码:ziplist(压缩列表)、linkedlist(链表)
- 利用场景:音讯队列,文章列表,
一图看懂list类型的插入与弹出:
list利用场景参考以下:
lpush+lpop=Stack(栈)
lpush+rpop=Queue(队列)
lpsh+ltrim=Capped Collection(无限汇合)
lpush+brpop=Message Queue(音讯队列)
Set(汇合)
- 简介:汇合(set)类型也是用来保留多个的字符串元素,然而不容许反复元素
- 简略应用举例:sadd key element [element ...]、smembers key
- 外部编码:intset(整数汇合)、hashtable(哈希表)
- 留神点:smembers和lrange、hgetall都属于比拟重的命令,如果元素过多存在阻塞Redis的可能性,能够应用sscan来实现。
- 利用场景:用户标签,生成随机数抽奖、社交需要。
有序汇合(zset)
- 简介:已排序的字符串汇合,同时元素不能反复
- 简略格局举例:zadd key score member [score member ...],zrank key member
- 底层外部编码:ziplist(压缩列表)、skiplist(跳跃表)
- 利用场景:排行榜,社交需要(如用户点赞)。
2.2 Redis 的三种非凡数据类型
- Geo:Redis3.2推出的,地理位置定位,用于存储地理位置信息,并对存储的信息进行操作。
- HyperLogLog:用来做基数统计算法的数据结构,如统计网站的UV。
- Bitmaps :用一个比特位来映射某个元素的状态,在Redis中,它的底层是基于字符串类型实现的,能够把bitmaps成作一个以比特位为单位的数组
3. Redis为什么这么快?
3.1 基于内存存储实现
咱们都晓得内存读写是比在磁盘快很多的,Redis基于内存存储实现的数据库,绝对于数据存在磁盘的MySQL数据库,省去磁盘I/O的耗费。
3.2 高效的数据结构
咱们晓得,Mysql索引为了提高效率,抉择了B+树的数据结构。其实正当的数据结构,就是能够让你的利用/程序更快。先看下Redis的数据结构&外部编码图:
[外链图片转存失败,源站可能有防盗链机制,倡议将图片保留下来间接上传(img-bQuqNANR-1657354189274)(https://upload-images.jianshu...)]
SDS简略动静字符串
字符串长度解决:Redis获取字符串长度,工夫复杂度为O(1),而C语言中,须要从头开始遍历,复杂度为O(n);
空间预调配:字符串批改越频繁的话,内存调配越频繁,就会耗费性能,而SDS批改和空间裁减,会额定调配未应用的空间,缩小性能损耗。
惰性空间开释:SDS 缩短时,不是回收多余的内存空间,而是free记录下多余的空间,后续有变更,间接应用free中记录的空间,缩小调配。
二进制平安:Redis能够存储一些二进制数据,在C语言中字符串遇到'\0'会完结,而 SDS中标记字符串完结的是len属性。
字典
Redis 作为 K-V 型内存数据库,所有的键值就是用字典来存储。字典就是哈希表,比方HashMap,通过key就能够间接获取到对应的value。而哈希表的个性,在O(1)工夫复杂度就能够取得对应的值。
跳跃表
跳跃表是Redis特有的数据结构,就是在链表的根底上,减少多级索引晋升查找效率。
跳跃表反对均匀 O(logN),最坏 O(N)复杂度的节点查找,还能够通过程序性操作批量解决节点。
3.3 正当的数据编码
Redis 反对多种数据数据类型,每种根本类型,可能对多种数据结构。什么时候,应用什么样数据结构,应用什么样编码,是redis设计者总结优化的后果。
String:如果存储数字的话,是用int类型的编码;如果存储非数字,小于等于39字节的字符串,是embstr;大于39个字节,则是raw编码。
List:如果列表的元素个数小于512个,列表每个元素的值都小于64字节(默认),应用ziplist编码,否则应用linkedlist编码
Hash:哈希类型元素个数小于512个,所有值小于64字节的话,应用ziplist编码,否则应用hashtable编码。
Set:如果汇合中的元素都是整数且元素个数小于512个,应用intset编码,否则应用hashtable编码。
Zset:当有序汇合的元素个数小于128个,每个元素的值小于64字节时,应用ziplist编码,否则应用skiplist(跳跃表)编码
3.4 正当的线程模型
I/O 多路复用
I/O 多路复用
多路I/O复用技术能够让单个线程高效的解决多个连贯申请,而Redis应用用epoll作为I/O多路复用技术的实现。并且,Redis本身的事件处理模型将epoll中的连贯、读写、敞开都转换为事件,不在网络I/O上节约过多的工夫。
什么是I/O多路复用?
I/O :网络 I/O
多路 :多个网络连接
复用:复用同一个线程。
IO多路复用其实就是一种同步IO模型,它实现了一个线程能够监督多个文件句柄;一旦某个文件句柄就绪,就可能告诉应用程序进行相应的读写操作;而没有文件句柄就绪时,就会阻塞应用程序,交出cpu。
单线程模型
- Redis是单线程模型的,而单线程防止了CPU不必要的上下文切换和竞争锁的耗费。也正因为是单线程,如果某个命令执行过长(如hgetall命令),会造成阻塞。Redis是面向疾速执行场景的数据库。,所以要慎用如smembers和lrange、hgetall等命令。
- Redis 6.0 引入了多线程提速,它的执行命令操作内存的依然是个单线程。
3.5 虚拟内存机制
Redis间接本人构建了VM机制 ,不会像个别的零碎会调用零碎函数解决,会节约肯定的工夫去挪动和申请。
Redis的虚拟内存机制是啥呢?
虚拟内存机制就是临时把不常常拜访的数据(冷数据)从内存替换到磁盘中,从而腾出贵重的内存空间用于其它须要拜访的数据(热数据)。通过VM性能能够实现冷热数据拆散,使热数据仍在内存中、冷数据保留到磁盘。这样就能够防止因为内存不足而造成访问速度降落的问题。
4. 什么是缓存击穿、缓存穿透、缓存雪崩?
4.1 缓存穿透问题
先来看一个常见的缓存应用形式:读申请来了,先查下缓存,缓存有值命中,就间接返回;缓存没命中,就去查数据库,而后把数据库的值更新到缓存,再返回。
缓存穿透:指查问一个肯定不存在的数据,因为缓存是不命中时须要从数据库查问,查不到数据则不写入缓存,这将导致这个不存在的数据每次申请都要到数据库去查问,进而给数据库带来压力。
艰深点说,读申请拜访时,缓存和数据库都没有某个值,这样就会导致每次对这个值的查问申请都会穿透到数据库,这就是缓存穿透。
缓存穿透个别都是这几种状况产生的:
- 业务不合理的设计,比方大多数用户都没开守护,然而你的每个申请都去缓存,查问某个userid查问有没有守护。
- 业务/运维/开发失误的操作,比方缓存和数据库的数据都被误删除了。
- 黑客非法申请攻打,比方黑客成心捏造大量非法申请,以读取不存在的业务数据。
如何防止缓存穿透呢? 个别有三种办法。
- 1.如果是非法申请,咱们在API入口,对参数进行校验,过滤非法值。
- 2.如果查询数据库为空,咱们能够给缓存设置个空值,或者默认值。然而如有有写申请进来的话,须要更新缓存哈,以保障缓存一致性,同时,最初给缓存设置适当的过期工夫。(业务上比拟罕用,简略无效)
- 3.应用布隆过滤器疾速判断数据是否存在。即一个查问申请过去时,先通过布隆过滤器判断值是否存在,存在才持续往下查。
布隆过滤器原理:它由初始值为0的位图数组和N个哈希函数组成。一个对一个key进行N个hash算法获取N个值,在比特数组中将这N个值散列后设定为1,而后查的时候如果特定的这几个地位都为1,那么布隆过滤器判断该key存在。
4.2 缓存雪奔问题
缓存雪奔: 指缓存中数据大批量到过期工夫,而查问数据量微小,申请都间接拜访数据库,引起数据库压力过大甚至down机。
- 缓存雪奔个别是因为大量数据同时过期造成的,对于这个起因,可通过平均设置过期工夫解决,即让过期工夫绝对离散一点。如采纳一个较大固定值+一个较小的随机值,5小时+0到1800秒酱紫。
- Redis 故障宕机也可能引起缓存雪奔。这就须要结构Redis高可用集群啦。
4.3 缓存击穿问题
缓存击穿: 指热点key在某个工夫点过期的时候,而恰好在这个工夫点对这个Key有大量的并发申请过去,从而大量的申请打到db。
缓存击穿看着有点像,其实它两区别是,缓存雪奔是指数据库压力过大甚至down机,缓存击穿只是大量并发申请到了DB数据库层面。能够认为击穿是缓存雪奔的一个子集吧。有些文章认为它俩区别,是区别在于击穿针对某一热点key缓存,雪奔则是很多key。
解决方案就有两种:
- 1.应用互斥锁计划。缓存生效时,不是立刻去加载db数据,而是先应用某些带胜利返回的原子操作命令,如(Redis的setnx)去操作,胜利的时候,再去加载db数据库数据和设置缓存。否则就去重试获取缓存。
- 2. “永不过期”,是指没有设置过期工夫,然而热点数据快要过期时,异步线程去更新和设置过期工夫。
5. 什么是热Key问题,如何解决热key问题
什么是热Key呢?在Redis中,咱们把拜访频率高的key,称为热点key。
如果某一热点key的申请到服务器主机时,因为申请量特地大,可能会导致主机资源有余,甚至宕机,从而影响失常的服务。
而热点Key是怎么产生的呢?次要起因有两个:
用户生产的数据远大于生产的数据,如秒杀、热点新闻等读多写少的场景。
申请分片集中,超过单Redi服务器的性能,比方固定名称key,Hash落入同一台服务器,霎时访问量极大,超过机器瓶颈,产生热点Key问题。
那么在日常开发中,如何辨认到热点key呢?
凭教训判断哪些是热Key;
客户端统计上报;
服务代理层上报
如何解决热key问题?
Redis集群扩容:减少分片正本,平衡读流量;
将热key扩散到不同的服务器中;
应用二级缓存,即JVM本地缓存,缩小Redis的读申请。
6. Redis 过期策略和内存淘汰策略
6.1 Redis的过期策略
咱们在set key的时候,能够给它设置一个过期工夫,比方expire key 60。指定这key60s后过期,60s后,redis是如何解决的嘛?咱们先来介绍几种过期策略:
定时过期
每个设置过期工夫的key都须要创立一个定时器,到过期工夫就会立刻对key进行革除。该策略能够立刻革除过期的数据,对内存很敌对;然而会占用大量的CPU资源去解决过期的数据,从而影响缓存的响应工夫和吞吐量。
惰性过期
只有当拜访一个key时,才会判断该key是否已过期,过期则革除。该策略能够最大化地节俭CPU资源,却对内存十分不敌对。极其状况可能呈现大量的过期key没有再次被拜访,从而不会被革除,占用大量内存。
定期过期
每隔肯定的工夫,会扫描肯定数量的数据库的expires字典中肯定数量的key,并革除其中已过期的key。该策略是前两者的一个折中计划。通过调整定时扫描的工夫距离和每次扫描的限定耗时,能够在不同状况下使得CPU和内存资源达到最优的均衡成果。
expires字典会保留所有设置了过期工夫的key的过期工夫数据,其中,key是指向键空间中的某个键的指针,value是该键的毫秒精度的UNIX工夫戳示意的过期工夫。键空间是指该Redis集群中保留的所有键。
Redis中同时应用了惰性过期和定期过期两种过期策略。
- 假如Redis以后寄存30万个key,并且都设置了过期工夫,如果你每隔100ms就去查看这全副的key,CPU负载会特地高,最初可能会挂掉。
- 因而,redis采取的是定期过期,每隔100ms就随机抽取肯定数量的key来检查和删除的。
- 然而呢,最初可能会有很多曾经过期的key没被删除。这时候,redis采纳惰性删除。在你获取某个key的时候,redis会检查一下,这个key如果设置了过期工夫并且曾经过期了,此时就会删除。
然而呀,如果定期删除漏掉了很多过期的key,而后也没走惰性删除。就会有很多过期key积在内存内存,间接会导致内存爆的。或者有些时候,业务量大起来了,redis的key被大量应用,内存间接不够了,运维小哥哥也遗记加大内存了。难道redis间接这样挂掉?不会的!Redis用8种内存淘汰策略爱护本人~
6.2 Redis 内存淘汰策略
volatile-lru:当内存不足以包容新写入数据时,从设置了过期工夫的key中应用LRU(最近起码应用)算法进行淘汰;
allkeys-lru:当内存不足以包容新写入数据时,从所有key中应用LRU(最近起码应用)算法进行淘汰。
volatile-lfu:4.0版本新增,当内存不足以包容新写入数据时,在过期的key中,应用LFU算法进行删除key。
allkeys-lfu:4.0版本新增,当内存不足以包容新写入数据时,从所有key中应用LFU算法进行淘汰;
volatile-random:当内存不足以包容新写入数据时,从设置了过期工夫的key中,随机淘汰数据;。
allkeys-random:当内存不足以包容新写入数据时,从所有key中随机淘汰数据。
volatile-ttl:当内存不足以包容新写入数据时,在设置了过期工夫的key中,依据过期工夫进行淘汰,越早过期的优先被淘汰;
noeviction:默认策略,当内存不足以包容新写入数据时,新写入操作会报错。
7.说说Redis的罕用利用场景
- 缓存
- 排行榜
- 计数器利用
- 共享Session
- 分布式锁
- 社交网络
- 音讯队列
- 位操作
7.1 缓存
咱们一提到redis,自然而然就想到缓存,国内外中大型的网站都离不开缓存。正当的利用缓存,比方缓存热点数据,不仅能够晋升网站的访问速度,还能够升高数据库DB的压力。并且,Redis相比于memcached,还提供了丰盛的数据结构,并且提供RDB和AOF等长久化机制,强的一批。
7.2 排行榜
当今互联网利用,有各种各样的排行榜,如电商网站的月度销量排行榜、社交APP的礼物排行榜、小程序的投票排行榜等等。Redis提供的zset数据类型可能实现这些简单的排行榜。
比方,用户每天上传视频,取得点赞的排行榜能够这样设计:
- 1.用户Jay上传一个视频,取得6个赞,能够酱紫:
zadd user:ranking:2021-03-03 Jay 3
- 2.过了一段时间,再取得一个赞,能够这样:
zincrby user:ranking:2021-03-03 Jay 1
- 3.如果某个用户John舞弊,须要删除该用户:
zrem user:ranking:2021-03-03 John
- 4.展现获取赞数最多的3个用户
zrevrangebyrank user:ranking:2021-03-03 0 2
7.3 计数器利用
各大网站、APP利用常常须要计数器的性能,如短视频的播放数、电商网站的浏览数。这些播放数、浏览数个别要求实时的,每一次播放和浏览都要做加1的操作,如果并发量很大对于传统关系型数据的性能是一种挑战。Redis人造反对计数性能而且计数的性能也十分好,能够说是计数器零碎的重要抉择。
7.4 共享Session
如果一个分布式Web服务将用户的Session信息保留在各自服务器,用户刷新一次可能就须要从新登录了,这样显然有问题。实际上,能够应用Redis将用户的Session进行集中管理,每次用户更新或者查问登录信息都间接从Redis中集中获取。
7.5 分布式锁
简直每个互联网公司中都应用了分布式部署,分布式服务下,就会遇到对同一个资源的并发拜访的技术难题,如秒杀、下单减库存等场景。
- 用synchronize或者reentrantlock本地锁必定是不行的。
- 如果是并发量不大话,应用数据库的乐观锁、乐观锁来实现没啥问题。
- 然而在并发量高的场合中,利用数据库锁来管制资源的并发拜访,会影响数据库的性能。
- 实际上,能够用Redis的setnx来实现分布式的锁。
7.6 社交网络
赞/踩、粉丝、独特好友/爱好、推送、下拉刷新等是社交网站的必备性能,因为社交网站访问量通常比拟大,而且传统的关系型数据不太适保留 这种类型的数据,Redis提供的数据结构能够绝对比拟容易地实现这些性能。
7.7 音讯队列
音讯队列是大型网站必用中间件,如ActiveMQ、RabbitMQ、Kafka等风行的音讯队列中间件,次要用于业务解耦、流量削峰及异步解决实时性低的业务。Redis提供了公布/订阅及阻塞队列性能,能实现一个简略的音讯队列零碎。另外,这个不能和业余的消息中间件相比。
7.8 位操作
用于数据量上亿的场景下,例如几亿用户零碎的签到,去重登录次数统计,某用户是否在线状态等等。腾讯10亿用户,要几个毫秒内查问到某个用户是否在线,能怎么做?千万别说给每个用户建设一个key,而后挨个记(你能够算一下须要的内存会很恐怖,而且这种相似的需要很多。这里要用到位操作——应用setbit、getbit、bitcount命令。原理是:redis内构建一个足够长的数组,每个数组元素只能是0和1两个值,而后这个数组的下标index用来示意用户id(必须是数字哈),那么很显然,这个几亿长的大数组就能通过下标和元素值(0和1)来构建一个记忆系统。
8. Redis 的长久化机制有哪些?优缺点说说
Redis是基于内存的非关系型K-V数据库,既然它是基于内存的,如果Redis服务器挂了,数据就会失落。为了防止数据失落了,Redis提供了长久化,即把数据保留到磁盘。
Redis提供了RDB和AOF两种长久化机制,它长久化文件加载流程如下:
8.1 RDB
RDB,就是把内存数据以快照的模式保留到磁盘上。
什么是快照?能够这样了解,给以后时刻的数据,拍一张照片,而后保留下来。
RDB长久化,是指在指定的工夫距离内,执行指定次数的写操作,将内存中的数据集快照写入磁盘中,它是Redis默认的长久化形式。执行完操作后,在指定目录下会生成一个dump.rdb文件,Redis 重启的时候,通过加载dump.rdb文件来复原数据。RDB触发机制次要有以下几种:
RDB 的长处
- 适宜大规模的数据恢复场景,如备份,全量复制等
RDB毛病
- 没方法做到实时长久化/秒级长久化。
- 新老版本存在RDB格局兼容问题
AOF
AOF(append only file) 长久化,采纳日志的模式来记录每个写操作,追加到文件中,重启时再从新执行AOF文件中的命令来复原数据。它次要解决数据长久化的实时性问题。默认是不开启的。
AOF的工作流程如下:
AOF的长处
- 数据的一致性和完整性更高
AOF的毛病
- AOF记录的内容越多,文件越大,数据恢复变慢。
9.怎么实现Redis的高可用?
咱们在我的项目中应用Redis,必定不会是单点部署Redis服务的。因为,单点部署一旦宕机,就不可用了。为了实现高可用,通常的做法是,将数据库复制多个正本以部署在不同的服务器上,其中一台挂了也能够持续提供服务。Redis 实现高可用有三种部署模式:主从模式,哨兵模式,集群模式。
9.1 主从模式
主从模式中,Redis部署了多台机器,有主节点,负责读写操作,有从节点,只负责读操作。从节点的数据来自主节点,实现原理就是主从复制机制
主从复制包含全量复制,增量复制两种。个别当slave第一次启动连贯master,或者认为是第一次连贯,就采纳全量复制,全量复制流程如下:
- 1.slave发送sync命令到master。
- 2.master接管到SYNC命令后,执行bgsave命令,生成RDB全量文件。
- 3.master应用缓冲区,记录RDB快照生成期间的所有写命令。
- 4.master执行完bgsave后,向所有slave发送RDB快照文件。
- 5.slave收到RDB快照文件后,载入、解析收到的快照。
- 6.master应用缓冲区,记录RDB同步期间生成的所有写的命令。
- 7.master快照发送结束后,开始向slave发送缓冲区中的写命令;
- 8.salve接受命令申请,并执行来自master缓冲区的写命令
redis2.8版本之后,曾经应用psync来代替sync,因为sync命令十分耗费系统资源,psync的效率更高。
slave与master全量同步之后,master上的数据,如果再次发生更新,就会触发增量复制。
当master节点产生数据增减时,就会触发replicationFeedSalves()函数,接下来在 Master节点上调用的每一个命令会应用replicationFeedSlaves()来同步到Slave节点。执行此函数之前呢,master节点会判断用户执行的命令是否有数据更新,如果有数据更新的话,并且slave节点不为空,就会执行此函数。这个函数作用就是:把用户执行的命令发送到所有的slave节点,让slave节点执行。流程如下:
9.2 哨兵模式
主从模式中,一旦主节点因为故障不能提供服务,须要人工将从节点降职为主节点,同时还要告诉利用方更新主节点地址。显然,少数业务场景都不能承受这种故障解决形式。Redis从2.8开始正式提供了Redis Sentinel(哨兵)架构来解决这个问题。
哨兵模式,由一个或多个Sentinel实例组成的Sentinel零碎,它能够监督所有的Redis主节点和从节点,并在被监督的主节点进入下线状态时,主动将下线主服务器属下的某个从节点降级为新的主节点。然而呢,一个哨兵过程对Redis节点进行监控,就可能会呈现问题(单点问题),因而,能够应用多个哨兵来进行监控Redis节点,并且各个哨兵之间还会进行监控。
Sentinel哨兵模式
简略来说,哨兵模式就三个作用:
- 发送命令,期待Redis服务器(包含主服务器和从服务器)返回监控其运行状态;
- 哨兵监测到主节点宕机,会主动将从节点切换成主节点,而后通过公布订阅模式告诉其余的从节点,批改配置文件,让它们切换主机;
- 哨兵之间还会互相监控,从而达到高可用。
故障切换的过程是怎么的呢
假如主服务器宕机,哨兵1先检测到这个后果,零碎并不会马上进行 failover 过程,仅仅是哨兵1主观的认为主服务器不可用,这个景象成为主观下线。当前面的哨兵也检测到主服务器不可用,并且数量达到肯定值时,那么哨兵之间就会进行一次投票,投票的后果由一个哨兵发动,进行 failover 操作。切换胜利后,就会通过公布订阅模式,让各个哨兵把本人监控的从服务器实现切换主机,这个过程称为主观下线。这样对于客户端而言,一切都是通明的。
哨兵的工作模式如下:
- 每个Sentinel以每秒钟一次的频率向它所知的Master,Slave以及其余Sentinel实例发送一个 PING命令。
- 如果一个实例(instance)间隔最初一次无效回复 PING 命令的工夫超过 down-after-milliseconds 选项所指定的值, 则这个实例会被 Sentinel标记为主观下线。
- 如果一个Master被标记为主观下线,则正在监督这个Master的所有 Sentinel 要以每秒一次的频率确认Master确实进入了主观下线状态。
- 当有足够数量的 Sentinel(大于等于配置文件指定的值)在指定的工夫范畴内确认Master确实进入了主观下线状态, 则Master会被标记为主观下线。
- 在个别状况下, 每个 Sentinel 会以每10秒一次的频率向它已知的所有Master,Slave发送 INFO 命令。
- 当Master被 Sentinel 标记为主观下线时,Sentinel 向下线的 Master 的所有 Slave 发送 INFO 命令的频率会从 10 秒一次改为每秒一次
- 若没有足够数量的 Sentinel批准Master曾经下线, Master的主观下线状态就会被移除;若Master 从新向 Sentinel 的 PING 命令返回无效回复, Master 的主观下线状态就会被移除。
9.3 Cluster集群模式
哨兵模式基于主从模式,实现读写拆散,它还能够主动切换,零碎可用性更高。然而它每个节点存储的数据是一样的,节约内存,并且不好在线扩容。因而,Cluster集群应运而生,它在Redis3.0退出的,实现了Redis的分布式存储。对数据进行分片,也就是说每台Redis节点上存储不同的内容,来解决在线扩容的问题。并且,它也提供复制和故障转移的性能。
Cluster集群节点的通信
一个Redis集群由多个节点组成,各个节点之间是怎么通信的呢?通过Gossip协定!
Redis Cluster集群通过Gossip协定进行通信,节点之前一直替换信息,替换的信息内容包含节点呈现故障、新节点退出、主从节点变更信息、slot信息等等。罕用的Gossip音讯分为4种,别离是:ping、pong、meet、fail。
meet音讯:告诉新节点退出。音讯发送者告诉接收者退出到以后集群,meet音讯通信失常实现后,接管节点会退出到集群中并进行周期性的ping、pong音讯替换。
ping音讯:集群内替换最频繁的音讯,集群内每个节点每秒向多个其余节点发送ping音讯,用于检测节点是否在线和替换彼此状态信息。
pong音讯:当接管到ping、meet音讯时,作为响应音讯回复给发送方确认音讯失常通信。pong音讯外部封装了本身状态数据。节点也能够向集群内播送本身的pong音讯来告诉整个集群对本身状态进行更新。
fail音讯:当节点断定集群内另一个节点下线时,会向集群内播送一个fail音讯,其余节点接管到fail音讯之后把对应节点更新为下线状态。
特地的,每个节点是通过集群总线(cluster bus) 与其余的节点进行通信的。通信时,应用非凡的端口号,即对外服务端口号加10000。例如如果某个node的端口号是6379,那么它与其它nodes通信的端口号是 16379。nodes 之间的通信采纳非凡的二进制协定。
Hash Slot插槽算法
既然是分布式存储,Cluster集群应用的分布式算法是一致性Hash嘛?并不是,而是Hash Slot插槽算法。
插槽算法把整个数据库被分为16384个slot(槽),每个进入Redis的键值对,依据key进行散列,调配到这16384插槽中的一个。应用的哈希映射也比较简单,用CRC16算法计算出一个16 位的值,再对16384取模。数据库中的每个键都属于这16384个槽的其中一个,集群中的每个节点都能够解决这16384个槽。
集群中的每个节点负责一部分的hash槽,比方以后集群有A、B、C个节点,每个节点上的哈希槽数 =16384/3,那么就有:
- 节点A负责0~5460号哈希槽
- 节点B负责5461~10922号哈希槽
- 节点C负责10923~16383号哈希槽
Redis Cluster集群
Redis Cluster集群中,须要确保16384个槽对应的node都失常工作,如果某个node呈现故障,它负责的slot也会生效,整个集群将不能工作。
因而为了保障高可用,Cluster集群引入了主从复制,一个主节点对应一个或者多个从节点。当其它主节点 ping 一个主节点 A 时,如果半数以上的主节点与 A 通信超时,那么认为主节点 A 宕机了。如果主节点宕机时,就会启用从节点。
在Redis的每一个节点上,都有两个玩意,一个是插槽(slot),它的取值范畴是0~16383。另外一个是cluster,能够了解为一个集群治理的插件。当咱们存取的key达到时,Redis 会依据CRC16算法得出一个16 bit的值,而后把后果对16384取模。酱紫每个key都会对应一个编号在 0~16383 之间的哈希槽,通过这个值,去找到对应的插槽所对应的节点,而后间接主动跳转到这个对应的节点上进行存取操作。
尽管数据是离开存储在不同节点上的,然而对客户端来说,整个集群Cluster,被看做一个整体。客户端端连贯任意一个node,看起来跟操作单实例的Redis一样。当客户端操作的key没有被调配到正确的node节点时,Redis会返回转向指令,最初指向正确的node,这就有点像浏览器页面的302 重定向跳转。
[外链图片转存失败,源站可能有防盗链机制,倡议将图片保留下来间接上传(img-nqlbjyMn-1657354189285)(https://upload-images.jianshu...)]
故障转移
Redis集群实现了高可用,当集群内节点呈现故障时,通过故障转移,以保障集群失常对外提供服务。
redis集群通过ping/pong音讯,实现故障发现。这个环境包含主观下线和主观下线。
主观下线: 某个节点认为另一个节点不可用,即下线状态,这个状态并不是最终的故障断定,只能代表一个节点的意见,可能存在误判状况。
主观下线
主观下线: 指标记一个节点真正的下线,集群内多个节点都认为该节点不可用,从而达成共识的后果。如果是持有槽的主节点故障,须要为该节点进行故障转移。
- 如果节点A标记节点B为主观下线,一段时间后,节点A通过音讯把节点B的状态发到其它节点,当节点C承受到音讯并解析出音讯体时,如果发现节点B的pfail状态时,会触发主观下线流程;
- 当下线为主节点时,此时Redis Cluster集群为统计持有槽的主节点投票,看投票数是否达到一半,当下线报告统计数大于一半时,被标记为主观下线状态。
流程如下:
主观下线
故障复原:故障发现后,如果下线节点的是主节点,则须要在它的从节点当选一个替换它,以保障集群的高可用。流程如下:
- 资格查看:查看从节点是否具备替换故障主节点的条件。
- 筹备选举工夫:资格查看通过后,更新触发故障选举工夫。
- 发动选举:到了故障选举工夫,进行选举。
- 选举投票:只有持有槽的主节点才有票,从节点收集到足够的选票(大于一半),触发替换主节点操作
10. 应用过Redis分布式锁嘛?有哪些留神点呢?
分布式锁,是管制分布式系统不同过程独特访问共享资源的一种锁的实现。秒杀下单、抢红包等等业务场景,都须要用到分布式锁,咱们我的项目中常常应用Redis作为分布式锁。
选了Redis分布式锁的几种实现办法,大家来探讨下,看有没有啥问题哈。
- 命令setnx + expire离开写
- setnx + value值是过期工夫
- set的扩大命令(set ex px nx)
- set ex px nx + 校验惟一随机值,再删除
10.1 命令setnx + expire离开写
if(jedis.setnx(key,lock_value) == 1){ //加锁 expire(key,100); //设置过期工夫 try { do something //业务申请 }catch(){ } finally { jedis.del(key); //开释锁 }}
如果执行完setnx加锁,正要执行expire设置过期工夫时,过程crash掉或者要重启保护了,那这个锁就“长生不老”了,别的线程永远获取不到锁啦,所以分布式锁不能这么实现。
10.2 setnx + value值是过期工夫
long expires = System.currentTimeMillis() + expireTime; //零碎工夫+设置的过期工夫String expiresStr = String.valueOf(expires);// 如果以后锁不存在,返回加锁胜利if (jedis.setnx(key, expiresStr) == 1) { return true;} // 如果锁曾经存在,获取锁的过期工夫String currentValueStr = jedis.get(key);// 如果获取到的过期工夫,小于零碎以后工夫,示意曾经过期if (currentValueStr != null && Long.parseLong(currentValueStr) < System.currentTimeMillis()) { // 锁已过期,获取上一个锁的过期工夫,并设置当初锁的过期工夫(不理解redis的getSet命令的小伙伴,能够去官网看下哈) String oldValueStr = jedis.getSet(key_resource_id, expiresStr); if (oldValueStr != null && oldValueStr.equals(currentValueStr)) { // 思考多线程并发的状况,只有一个线程的设置值和以后值雷同,它才能够加锁 return true; }}//其余状况,均返回加锁失败return false;}
笔者看过有开发小伙伴是这么实现分布式锁的,然而这种计划也有这些毛病:
- 过期工夫是客户端本人生成的,分布式环境下,每个客户端的工夫必须同步。
- 没有保留持有者的惟一标识,可能被别的客户端开释/解锁。
- 锁过期的时候,并发多个客户端同时申请过去,都执行了jedis.getSet(),最终只能有一个客户端加锁胜利,然而该客户端锁的过期工夫,可能被别的客户端笼罩。
10.3:set的扩大命令(set ex px nx)(留神可能存在的问题)
if(jedis.set(key, lock_value, "NX", "EX", 100s) == 1){ //加锁 try { do something //业务解决 }catch(){ } finally { jedis.del(key); //开释锁 }}
这个计划可能存在这样的问题:
- 锁过期开释了,业务还没执行完。
- 锁被别的线程误删。
10.4 set ex px nx + 校验惟一随机值,再删除
if(jedis.set(key, uni_request_id, "NX", "EX", 100s) == 1){ //加锁 try { do something //业务解决 }catch(){ } finally { //判断是不是以后线程加的锁,是才开释 if (uni_request_id.equals(jedis.get(key))) { jedis.del(key); //开释锁 } }}
在这里,判断以后线程加的锁和开释锁是不是一个原子操作。如果调用jedis.del()开释锁的时候,可能这把锁曾经不属于以后客户端,会解除别人加的锁。
个别也是用lua脚本代替。lua脚本如下:
if redis.call('get',KEYS[1]) == ARGV[1] then return redis.call('del',KEYS[1]) else return 0end;
这种形式比拟不错了,个别状况下,曾经能够应用这种实现形式。然而存在锁过期开释了,业务还没执行完的问题(实际上,估算个业务解决的工夫,个别没啥问题了)。
11. 应用过Redisson嘛?说说它的原理
分布式锁可能存在锁过期开释,业务没执行完的问题。有些小伙伴认为,略微把锁过期工夫设置长一些就能够啦。其实咱们构想一下,是否能够给取得锁的线程,开启一个定时守护线程,每隔一段时间查看锁是否还存在,存在则对锁的过期工夫缩短,避免锁过期提前开释。
以后开源框架Redisson就解决了这个分布式锁问题。咱们一起来看下Redisson底层原理是怎么的吧:
只有线程一加锁胜利,就会启动一个watch dog看门狗,它是一个后盾线程,会每隔10秒检查一下,如果线程1还持有锁,那么就会一直的缩短锁key的生存工夫。因而,Redisson就是应用Redisson解决了锁过期开释,业务没执行完问题。
12. 什么是Redlock算法
Redis个别都是集群部署的,假如数据在主从同步过程,主节点挂了,Redis分布式锁可能会有哪些问题呢?一起来看些这个流程图:
如果线程一在Redis的master节点上拿到了锁,然而加锁的key还没同步到slave节点。恰好这时,master节点产生故障,一个slave节点就会降级为master节点。线程二就能够获取同个key的锁啦,但线程一也曾经拿到锁了,锁的安全性就没了。
为了解决这个问题,Redis作者 antirez提出一种高级的分布式锁算法:Redlock。Redlock核心思想是这样的:
搞多个Redis master部署,以保障它们不会同时宕掉。并且这些master节点是齐全互相独立的,相互之间不存在数据同步。同时,须要确保在这多个master实例上,是与在Redis单实例,应用雷同办法来获取和开释锁。
咱们假如以后有5个Redis master节点,在5台服务器下面运行这些Redis实例。
RedLock的实现步骤:如下
1.获取以后工夫,以毫秒为单位。
2.按程序向5个master节点申请加锁。客户端设置网络连接和响应超时工夫,并且超时工夫要小于锁的生效工夫。(假如锁主动生效工夫为10秒,则超时工夫个别在5-50毫秒之间,咱们就假如超时工夫是50ms吧)。如果超时,跳过该master节点,尽快去尝试下一个master节点。
3.客户端应用以后工夫减去开始获取锁工夫(即步骤1记录的工夫),失去获取锁应用的工夫。当且仅当超过一半(N/2+1,这里是5/2+1=3个节点)的Redis master节点都取得锁,并且应用的工夫小于锁生效工夫时,锁才算获取胜利。(如上图,10s> 30ms+40ms+50ms+4m0s+50ms)
如果取到了锁,key的真正无效工夫就变啦,须要减去获取锁所应用的工夫。
如果获取锁失败(没有在至多N/2+1个master实例取到锁,有或者获取锁工夫曾经超过了无效工夫),客户端要在所有的master节点上解锁(即使有些master节点基本就没有加锁胜利,也须要解锁,以避免有些漏网之鱼)。
简化下步骤就是:
- 按程序向5个master节点申请加锁
- 依据设置的超时工夫来判断,是不是要跳过该master节点。
- 如果大于等于三个节点加锁胜利,并且应用的工夫小于锁的有效期,即可认定加锁胜利啦。
- 如果获取锁失败,解锁!
13. Redis的跳跃表
跳跃表
- 跳跃表是有序汇合zset的底层实现之一
- 跳跃表反对均匀O(logN),最坏 O(N)复杂度的节点查找,还能够通过程序性操作批量解决节点。
- 跳跃表实现由zskiplist和zskiplistNode两个构造组成,其中zskiplist用于保留跳跃表信息(如表头节点、表尾节点、长度),而zskiplistNode则用于示意跳跃表节点。
- 跳跃表就是在链表的根底上,减少多级索引晋升查找效率。
14. MySQL与Redis 如何保障双写一致性
- 缓存延时双删
- 删除缓存重试机制
- 读取biglog异步删除缓存
14.1 延时双删?
什么是延时双删呢?流程图如下:
延时双删流程
- 先删除缓存
- 再更新数据库
- 休眠一会(比方1秒),再次删除缓存。
这个休眠一会,个别多久呢?都是1秒?
这个休眠工夫 = 读业务逻辑数据的耗时 + 几百毫秒。为了确保读申请完结,写申请能够删除读申请可能带来的缓存脏数据。
这种计划还算能够,只有休眠那一会(比方就那1秒),可能有脏数据,个别业务也会承受的。然而如果第二次删除缓存失败呢?缓存和数据库的数据还是可能不统一,对吧?给Key设置一个天然的expire过期工夫,让它主动过期怎么?那业务要承受过期工夫内,数据的不统一咯?还是有其余更佳计划呢?
14.2 删除缓存重试机制
因为延时双删可能会存在第二步的删除缓存失败,导致的数据不统一问题。能够应用这个计划优化:删除失败就多删除几次呀,保障删除缓存胜利就能够了呀~ 所以能够引入删除缓存重试机制
删除缓存重试流程
- 写申请更新数据库
- 缓存因为某些起因,删除失败
- 把删除失败的key放到音讯队列
- 生产音讯队列的音讯,获取要删除的key
- 重试删除缓存操作
14.3 读取biglog异步删除缓存
重试删除缓存机制还能够吧,就是会造成好多业务代码入侵。其实,还能够这样优化:通过数据库的binlog来异步淘汰key。
以mysql为例吧
- 能够应用阿里的canal将binlog日志采集发送到MQ队列外面
- 而后通过ACK机制确认解决这条更新音讯,删除缓存,保证数据缓存一致性
15. 为什么Redis 6.0 之后改多线程呢?
- Redis6.0之前,Redis在解决客户端的申请时,包含读socket、解析、执行、写socket等都由一个程序串行的主线程解决,这就是所谓的“单线程”。
- Redis6.0之前为什么始终不应用多线程?应用Redis时,简直不存在CPU成为瓶颈的状况, Redis次要受限于内存和网络。例如在一个一般的Linux零碎上,Redis通过应用pipelining每秒能够解决100万个申请,所以如果应用程序次要应用O(N)或O(log(N))的命令,它简直不会占用太多CPU。
redis应用多线程并非是齐全摒弃单线程,redis还是应用单线程模型来解决客户端的申请,只是应用多线程来解决数据的读写和协定解析,执行命令还是应用单线程。
这样做的目标是因为redis的性能瓶颈在于网络IO而非CPU,应用多线程能晋升IO读写的效率,从而整体进步redis的性能。
16. 聊聊Redis 事务机制
Redis通过MULTI、EXEC、WATCH等一组命令汇合,来实现事务机制。事务反对一次执行多个命令,一个事务中所有命令都会被序列化。在事务执行过程,会依照程序串行化执行队列中的命令,其余客户端提交的命令申请不会插入到事务执行命令序列中。
简言之,Redis事务就是程序性、一次性、排他性的执行一个队列中的一系列命令。
Redis执行事务的流程如下:
- 开始事务(MULTI)
- 命令入队
- 执行事务(EXEC)、撤销事务(DISCARD )
17. Redis的Hash 抵触怎么办
Redis 作为一个K-V的内存数据库,它应用用一张全局的哈希来保留所有的键值对。这张哈希表,有多个哈希桶组成,哈希桶中的entry元素保留了key和value指针,其中key指向了理论的键,value指向了理论的值。
哈希表查找速率很快的,有点相似于Java中的HashMap,它让咱们在O(1) 的工夫复杂度疾速找到键值对。首先通过key计算哈希值,找到对应的哈希桶地位,而后定位到entry,在entry找到对应的数据。
什么是哈希抵触?
哈希抵触:通过不同的key,计算出一样的哈希值,导致落在同一个哈希桶中。
Redis为了解决哈希抵触,采纳了链式哈希。链式哈希是指同一个哈希桶中,多个元素用一个链表来保留,它们之间顺次用指针连贯。
有些读者可能还会有疑难:哈希抵触链上的元素只能通过指针逐个查找再操作。当往哈希表插入数据很多,抵触也会越多,抵触链表就会越长,那查问效率就会升高了。
为了放弃高效,Redis 会对哈希表做rehash操作,也就是减少哈希桶,缩小抵触。为了rehash更高效,Redis还默认应用了两个全局哈希表,一个用于以后应用,称为主哈希表,一个用于扩容,称为备用哈希表。
18. 在生成 RDB期间,Redis 能够同时解决写申请么?
能够的,Redis提供两个指令生成RDB,别离是save和bgsave。
- 如果是save指令,会阻塞,因为是主线程执行的。
- 如果是bgsave指令,是fork一个子过程来写入RDB文件的,快照长久化齐全交给子过程来解决,父过程则能够持续解决客户端的申请。
19. Redis底层,应用的什么协定?
RESP,英文全称是Redis Serialization Protocol,它是专门为redis设计的一套序列化协定. 这个协定其实在redis的1.2版本时就曾经呈现了,然而到了redis2.0才最终成为redis通信协定的规范。
RESP次要有实现简略、解析速度快、可读性好等长处。
20. 布隆过滤器
应答缓存穿透问题,咱们能够应用布隆过滤器。布隆过滤器是什么呢?
布隆过滤器是一种占用空间很小的数据结构,它由一个很长的二进制向量和一组Hash映射函数组成,它用于检索一个元素是否在一个汇合中,空间效率和查问工夫都比个别的算法要好的多,毛病是有肯定的误识别率和删除艰难。
布隆过滤器原理是?假如咱们有个汇合A,A中有n个元素。利用k个哈希散列函数,将A中的每个元素映射到一个长度为a位的数组B中的不同地位上,这些地位上的二进制数均设置为1。如果待查看的元素,通过这k个哈希散列函数的映射后,发现其k个地位上的二进制数全副为1,这个元素很可能属于汇合A,反之,肯定不属于汇合A。
来看个简略例子吧,假如汇合A有3个元素,别离为{d1,d2,d3}。有1个哈希函数,为Hash1。当初将A的每个元素映射到长度为16位数组B。
咱们当初把d1映射过去,假如Hash1(d1)= 2,咱们就把数组B中,下标为2的格子改成1,如下:
咱们当初把d2也映射过去,假如Hash1(d2)= 5,咱们把数组B中,下标为5的格子也改成1,如下:
接着咱们把d3也映射过去,假如Hash1(d3)也等于 2,它也是把下标为2的格子标1:
[外链图片转存失败,源站可能有防盗链机制,倡议将图片保留下来间接上传(img-YMDN3Slg-1657354189295)(https://upload-images.jianshu...)]
因而,咱们要确认一个元素dn是否在汇合A里,咱们只有算出Hash1(dn)失去的索引下标,只有是0,那就示意这个元素不在汇合A,如果索引下标是1呢?那该元素可能是A中的某一个元素。因为你看,d1和d3失去的下标值,都可能是1,还可能是其余别的数映射的,布隆过滤器是存在这个毛病的:会存在hash碰撞导致的假阳性,判断存在误差。
如何缩小这种误差呢?
- 搞多几个哈希函数映射,升高哈希碰撞的概率
- 同时减少B数组的bit长度,能够增大hash函数生成的数据的范畴,也能够升高哈希碰撞的概率
咱们又减少一个Hash2哈希映射函数,假如Hash2(d1)=6,Hash2(d3)=8,它俩不就不抵触了嘛,如下:
即便存在误差,咱们能够发现,布隆过滤器并没有寄存残缺的数据,它只是使用一系列哈希映射函数计算出地位,而后填充二进制向量。如果数量很大的话,布隆过滤器通过极少的错误率,换取了存储空间的极大节俭,还是挺划算的。
目前布隆过滤器曾经有相应实现的开源类库啦,如Google的Guava类库,Twitter的 Algebird 类库,信手拈来即可,或者基于Redis自带的Bitmaps自行实现设计也是能够的。
材料支付
本文就先写到这里,面试中常问的一些题目我都有整顿的,前面会继续更新,须要PDF的好兄弟能够点赞本文+关注后【点击此处】即可支付