1. 什么是Redis?Redis是用来干什么的?
Redis :是一种非关系型数据库,以键值对的形式进行存储(key-value),主要用来做数据缓存,Redis运行在内存中,数据也保存在内存中,目的在于让数据离浏览器更进,
Redis的它的读写速度非常快,结构简单易拓展,高性能,灵活的数据类型。
2. Redis的基本数据类型
String(字符串)
String是Redis最基础的数据结构类型,它是二进制安全的,可以存储图片或者序列化的对象,值最大存储为512M。
简单使用举例: set key value
、get key
等
内部编码有3种:int(8字节长整型)/embstr(小于等于39字节字符串)/raw(大于39个字节字符串)
应用场景
- 共享session
- 分布式锁
- 计数器、限流
Hash(哈希表)
在Redis中,哈希类型是指v(值)本身又是一个键值对(k-v)结构.
简单使用举例:hset key field value
、hget key field
内部编码:ziplist(压缩列表)
、hashtable(哈希表)
应用场景:缓存用户信息等。
List(列表)
列表(list)类型是用来存储多个有序的字符串
内部编码:ziplist(压缩列表)、linkedlist(链表)
应用场景参考以下:
- lpush+lpop=Stack(栈)
- lpush+rpop=Queue(队列)
- lpsh+ltrim=Capped Collection(有限集合)
- lpush+brpop=Message Queue(消息队列)
Set(集合)
集合(set)类型也是用来保存多个的字符串元素,但是不允许重复元素。
底层内部编码:ziplist(压缩列表)
、skiplist(跳跃表)
应用场景:
- 用户标签
- 生成随机数抽奖、社交需求
ZSet(有序集合)
已排序的字符串集合,同时元素不能重复。
底层内部编码:ziplist(压缩列表)
、skiplist(跳跃表)
应用场景:
- 排行榜
- 社交需求(如用户点赞)
三种特殊数据类型
- Geo:Redis3.2推出的,地理位置定位,用于存储地理位置信息,并对存储的信息进行操作。
- HyperLogLog:用来做基数统计算法的数据结构,如统计网站的UV。
- Bitmaps :用一个比特位来映射某个元素的状态,在Redis中,它的底层是基于字符串类型实现的,可以把bitmaps成作一个以比特位为单位的数组
3. Redis这么快的原因?
-
性能极高(读写速度快)(基于内存实现)
-
丰富的数据类型(list、set、Map等)
-
原子性 (所有的操作都是原子性的)
-
丰富的特性 (支持publish,通知等)
-
合理的线程模型(单线程模型:避免上下文切换;IO多路复用)
-
虚拟内存机制
4. 什么是缓存击穿、缓存穿透和缓存雪崩及其解决方案?
缓存穿透
我们所查询的数据在数据库中没有,查询后也不在缓存中存,每次还是回去访问数据库。
特点:数据库没有,缓存也没有
解决:
-
本来数据库没有,将查询出来的空值放到缓存中。key = null
-
对查询的参数进行验证
缓存击穿
数据库有数据,只是某个热点key在某个时间点上过期了,此时有大量查询请求到达(查询是不加锁),查询缓存没有,一起都向数据库发送请求,导致数据库崩溃。
解决:
-
可以设置热点的key过期时间要把握好 永不过期
-
查询缓存后,访问数据库时,可以加锁 (使用互斥锁方案)
缓存雪崩
大量的热点key过期或者redis服务器故障,导致大量的请求到达数据库。
解决方案:
-
随机设置有效时间,避免同时生效
-
把不同的热点key放在不同的redis服务器上(集群)
-
设置较长的过期时间
-
在java中设置定时任务,去检测key是否过期
5. 什么是热点key问题?如何解决热点key问题?
什么是热Key呢?
在Redis中,我们把访问频率高的key,称为热点key。
如果某一热点key的请求到服务器主机时,由于请求量特别大,可能会导致主机资源不足,甚至宕机,从而影响正常的服务。
热点Key是怎么产生的呢?主要原因有两个:
- 用户消费的数据远大于生产的数据,如秒杀、热点新闻等读多写少的场景。
- 请求分片集中,超过单Redi服务器的性能,比如固定名称key,Hash落入同一台服务器,瞬间访问量极大,超过机器瓶颈,产生热点Key问题。
6. Redis过期策略和内存淘汰策略
key的过期策略
为key设置过期时间,那么时间到了后,Redis如何处理过期的key
立即删除。到期立即执行回调函数,立即释放内存,对redis性能有影响。
惰性删除。到期不会立即删除,到下次使用该键的时候,根据状态(设置时是会记录的),来决定是删除还是继续使用,占用内存)
定期删除。每隔一段时间对所有到期的键进行删除(类似于java中垃圾回收线程)
Redis中同时使用了惰性过期和定期过期两种过期策略。
7. Redis的应用场景
- 缓存:降低数据库的访问压力
- 排行榜:各式各样的排行榜
- 计数器应用
- 共享Session
- 分布式锁
- 社交网络
- 消息队列
- 位操作
8. Redis的持久化机制有哪些及优缺点
Redis是基于内存的非关系型K-V数据库,既然它是基于内存的,如果Redis服务器挂了,数据就会丢失。为了避免数据丢失了,Redis提供了持久化,即把数据保存到磁盘。
Redis提供了RDB和AOF两种持久化机制。
RDB,就是把内存数据以快照的形式保存到磁盘上。
配置触发持久化的机制:
-
save m n save 多少秒内 多少键
-
flushall 命令
-
退出
RDB 的优点
- 适合大规模的数据恢复场景,如备份,全量复制等
RDB缺点
- 没办法做到实时持久化/秒级持久化。
- 新老版本存在RDB格式兼容问题
AOF:
以日志的形式,将写命令存储到文件 set name jim
还原时,将命令逐个执行还原数据
默认是不开启的 appendonly no
同步机制
appendfsync always 每次set记录一次
appendfsyns everysec 每秒记录一次
AOF的优点
- 数据的一致性和完整性更高
AOF的缺点
- AOF记录的内容越多,文件越大,数据恢复变慢。
9. Redis 是实现高可用的?
单点部署一旦宕机,就不可用了。为了实现高可用,通常的做法是,将数据库复制多个副本以部署在不同的服务器上,其中一台挂了也可以继续提供服务。Redis 实现高可用有三种部署模式:主从模式,哨兵模式,集群模式。
主从复制
主从---> 主机和从机(集群架构)
主机负责写数据,将数据同步到从机,一般的读数据从从机查询,
实现读写分离:写命令由主机执行,而读命令由从机执行。
为什么要使用集群?
如果只有一台redis服务,万一服务宕机,所有的请求都会到达mysql,导致mysql宕机,可以搭建多台redis服务器,如果其中一台出现故障,其他服务可以正常使用。
哨兵机制
有一个单独线程,对集群中的多态服务进行监听,给每个服务发请求,如果没有响应,表明出现故障。
比如,主机出现宕机,会在从机随机选取一台当做主机。当原来主机恢复后,又可以当做主机继续使用。
10. 什么是Redis分布锁?有哪些需要注意的?
分布式锁,是控制分布式系统不同进程共同访问共享资源的一种锁的实现。秒杀下单、抢红包等等业务场景,都需要用到分布式锁,我们项目中经常使用Redis作为分布式锁。
Redis分布式锁的几种实现方法:
- 命令setnx + expire分开写
- setnx + value值是过期时间
- set的扩展命令(set ex px nx)
- set ex px nx + 校验唯一随机值,再删除
11. 什么是Redisson及其原理?
分布式锁可能存在锁过期释放,业务没执行完的问题。有些小伙伴认为,稍微把锁过期时间设置长一些就可以啦。其实我们设想一下,是否可以给获得锁的线程,开启一个定时守护线程,每隔一段时间检查锁是否还存在,存在则对锁的过期时间延长,防止锁过期提前释放。
12. 什么是RedLock算法?
- 按顺序向5个master节点请求加锁
- 根据设置的超时时间来判断,是不是要跳过该master节点。
- 如果大于等于三个节点加锁成功,并且使用的时间小于锁的有效期,即可认定加锁成功啦。
- 如果获取锁失败,解锁!
13. Redis的跳跃表?
跳跃表
- 跳跃表是有序集合zset的底层实现之一
- 跳跃表支持平均O(logN),最坏 O(N)复杂度的节点查找,还可以通过顺序性操作批量处理节点。
- 跳跃表实现由zskiplist和zskiplistNode两个结构组成,其中zskiplist用于保存跳跃表信息(如表头节点、表尾节点、长度),而zskiplistNode则用于表示跳跃表节点。
- 跳跃表就是在链表的基础上,增加多级索引提升查找效率。
14. redis和Mysql 如何保证双写一致性?
- 缓存延时双删
- 删除缓存重试机制
- 读取biglog异步删除缓存
什么是延时双删呢?
- 先删除缓存
- 再更新数据库
- 休眠一会(比如1秒),再次删除缓存。
删除缓存重试机制
因为延时双删可能会存在第二步的删除缓存失败,导致的数据不一致问题。
优化:删除失败就多删除几次呀,保证删除缓存成功就可以了,所以可以引入删除缓存重试机制
删除缓存重试流程
- 写请求更新数据库
- 缓存因为某些原因,删除失败
- 把删除失败的key放到消息队列
- 消费消息队列的消息,获取要删除的key
- 重试删除缓存操作
读取biglog异步删除缓存
重试删除缓存机制还可以吧,就是会造成好多业务代码入侵。其实,还可以这样优化:通过数据库的binlog来异步淘汰key。
以mysql为例吧
- 可以使用阿里的canal将binlog日志采集发送到MQ队列里面
- 然后通过ACK机制确认处理这条更新消息,删除缓存,保证数据缓存一致性
15. 为什么Redis 6.0 之后改为多线程?
redis是单线程模式还是多线程模式。
不同版本的区别:
6.x之前是真正意义上的单线程,处理客户端的连接和执行操作的命令,都是由一个线程完成的。
6.x之后引入多线程,处理客户端请求是由专门的线程处理,执行命令还是单线程。
为什么是单线程模式,速度还非常快?
-
数据存储在内存中,读取速度快,cpu不是性能瓶颈
-
结构简单,key-value 底层是哈希结构,查询操作速度是O(1)
-
采用IO多路复用,非阻塞IO模型,提高连接访问效率
-
单线程执行命令,不存在线程切换,节省开销,而且是线程安全的。
16. Redis事物机制?
redis在执行单条命令时,是原子性(单线程的一次只能有一个线程执行命令),有时候,一次操作需要执行多条命令,如何保证多条命令整体执行。
- 开始事务(MULTI)
- 命令入队
- 执行事务(EXEC)、撤销事务(DISCARD )
可以通过redis事务执行:
开启事务:multi
添加命令:... 命令添加进来不会立即执行,添加到一个多列。
执行exec命令时,才会将对列中的多条命令依次执行,执行一个事务多条命令时,其他客户端会被隔离,不会交替执行,但是事务不保证多条命令执行的原子性(假如执行3条命令,其中一条语法错误,那么会将其他两条正确的命令继续执行)
17. Redis的Hash冲突如何解决?
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:
因此,我们要确认一个元素dn是否在集合A里,我们只要算出Hash1(dn)得到的索引下标,只要是0,那就表示这个元素不在集合A,如果索引下标是1呢?那该元素可能是A中的某一个元素。因为你看,d1和d3得到的下标值,都可能是1,还可能是其他别的数映射的,布隆过滤器是存在这个缺点的:会存在hash碰撞导致的假阳性,判断存在误差。
如何减少这种误差呢?
- 搞多几个哈希函数映射,降低哈希碰撞的概率
- 同时增加B数组的bit长度,可以增大hash函数生成的数据的范围,也可以降低哈希碰撞的概率
这样即使存在误差,我们可以发现,布隆过滤器并没有存放完整的数据,它只是运用一系列哈希映射函数计算出位置,然后填充二进制向量。如果数量很大的话,布隆过滤器通过极少的错误率,换取了存储空间的极大节省,还是挺划算的。