1 Redis数据类型
1.1 Redis有哪几种基本数据类型
Redis有5种基本数据类型,分别是:
1、String(字符串):普通的key、value可以归为此类
2、List(链表):可以用作消息队列,利用List的push操作将任务放在List中,元素可以重复
3、Set(集合):交集可以看共同好友
4、Zset(有序集合):用在排行榜,取TopN操作
4、Hash(哈希):存储对象,比如用户(姓名、性别、爱好)、文章(标题、作者、内容)
1.2 Redis底层数据结构是哪几个
1.2.1 Sring(字符串)底层数据结构-SDS
String(字符串)的底层结构就是SDS(简单动态字符串),其结构如下:
struct sdshdr {
//记录buf数组中已使用字节的数量
//等于SDS所保存字符串的长度
int len;
//记录buf数组中未使用字节的数量,表示声音可用空间
int free;
//字节数组,用于存放字符串
char buf[];
};
为什么要使用SDS,而不直接使用C字符串,两个有什么区别
C字符串 | SDS |
---|---|
获取字符串长度的复杂度为O(N) | 获取字符串长度的复杂度为O(1) |
API是不安全的,可能会造成缓冲区溢出 | API是安全的,不会造成缓冲区溢出 |
A修改字符串长度N次必须执行N次内存重分配 | 修改字符串长度N次最多需要执行N次内存重分配 |
只能保存文本数据 | 可以保存文本或者二进制数据 |
可以使用所有<string.h>库中的函数 | 可以使用一部分<string.h>库中的函数 |
1.2.2 List(链表)底层数据结构
List(链表)的底层数据结构是双向链表和压缩列表(ziplist)
双向链表的结构如下图所示:
有什么特点呢
1)每个链表节点都有一个指向前置节点和后置节点的指针,所以是双向链表
2)因为链表表头节点的前置节点和表尾节点的后置节点都指向NULL,所以Redis的链表实现是无环链表。
压缩列表是Redis为了节约内存而开发的,是由一系列特殊编码的连续内存块组成的顺序性(sequential)数据结构。一个压缩列表可以包含任意多个节点(entry),每个节点可以保存一个字节数组或者一个整数值
下图是压缩列表的各个组成部分:
下图是一个压缩列表示例:
为什么List(链表)要使用双向链表和压缩列表这两种数据结构?什么时候使用双向链表,什么时候使用压缩列表(ziplist)作为底层实现呢?
压缩列表(ziplist)是列表键和哈希键的底层实现之一。当一个列表键只包含少量列表项,并且每个列表项要么就是小整数值,要么就是长度比较短的字符串,那么Redis就会使用压缩列表来做列表键的底层实现。
压缩列表比双端链表更节约内存,并且在元素数量较少时,在内存中以连续块方式保存的压缩列表比起双端链表可以更快被载入到缓存中
随着列表对象包含的元素越来越多,使用压缩列表来保存元素的优势逐渐消失。所以双端链表适合保存大量元素。
更精准的条件是:
1)列表对象保存的所有字符串元素的长度都小于64字节
2)列表对象保存的元素数量小于512个
同时满足这两个条件才会使用压缩列表(ziplist),否则就使用双端链表
1.2.3 Set(集合)底层数据结构
set集合的特点:元素无序、不重复
set底层是由哈希表或整数集合来实现的,当元素是整数时,采用整数集合intset数据结构。当元素不是整数,或者元素个数大于512个时,则使用哈希表的数据结构HashTable
哈希表很好理解,就是java中的HashTable
整数集合(intset)可以保存类型为int16_t、int32_t、int64_t的整数值,并且保证集合中不会出现重复元素。整数集合有序、无重复
当集合对象同时满足以下两个条件时,对象使用整数数组(intset)编码:
1)集合对象保存的所有元素都是整数值;
2)集合对象保存的元素数量不超过512个。
注意:第二个条件的上限值可以改
1. 2.4 Zset(有序集合)底层数据结构
zset底层是由压缩列表和跳表来实现的。压缩列表前面已经讲了,现在着重讲下跳表
当有序集合中的元素小于128个,或者有序集合中所有元素的长度小于64个字节时,采用压缩列表,否则采用跳表。
为什么要使用跳表?因为跳表增加了多级索引,通过多级索引位置的跳转,实现了快速查找元素。
现在用个通俗易懂的例子来讲跳表。如下图所示,有序集合中有元素1、3、5、8、10、12、15、18、20,如果想查找一个元素,只能去遍历,复杂度为O(N),那现在开始建立一级索引,每隔一个元素就取出一个元素作为索引,所以有了一级索引:1、5、10、15、20。如果还觉得查找慢,那就建立二级索引1、10、20,最后建立三级索引1、20。三级索引只有2个元素了,这时候就不用在继续建立索引了。
为什么不使用二叉树和红黑树?因为跳表的范围查找效率更好,而且跳表的实现比二叉树和红黑树要简单的多
1. 2.5 Hash(哈希)底层数据结构
Hash底层是由哈希表和压缩列表来实现的
当哈希对象可以同时满足以下两个条件时,哈希对象使用压缩列表(ziplist)编码:
1)哈希对象保存的所有键值对的键和值的字符串长度都小于64字节
2)哈希对象保存的键值对数量小于512个
不能满足这2个条件的哈希对象,需要使用hashtable编码
注意:这两个条件中的上限值是可以修改的
哈希表可以理解成Java中的HashTable,存储方式一样,键有冲突时,使用头插法的方式插入到单链表中
1. 2.6 总结
String底层是SDS,这个比较简单
Set(集合)和Hash都使用到了哈希表
List(链表)、Zset(有序集合)都使用了压缩列表
2 持久化相关
2.1 缓存穿透、缓存击穿、缓存雪崩
总结:
缓存穿透:缓存中没有,数据库中也没有
缓存击穿:缓存中没有,数据库中有,一般是某个key过期导致
缓存雪崩:大量key过期
2.2 RDB和AOF有什么区别
2.3 AOF有哪几种日志策略
1)AOF_FSYNC_ALWAYS:每执行一次命令就保存一下数据,性能低
2)AOF_FSYNC_NO:不主动将AOF文件的内存从缓存保存到磁盘中,交给操作系统去决定何时保存到磁盘(比如Redis被关闭,AOF功能被关闭的时候)
3)AOF_FSYNC_EVERYSE:每秒保存一次
2.4 AOF重写是什么
背景:由于AOF文件里记录的是写命令,那命令越来越多,AOF文件就会越来越大,数据还原时间就会越来越长。比如我连续rpush key v1-6,执行6次,AOF就需要保存6条命令。
AOF文件重写:读取Redis数据库中的数据,而不是读取现有的AOF文件。比如旧AOF文件记录了6条rpush命令,那么新AOF文件会从数据库中读取键现在的值,然后用一条命令去记录键值对,这样6条命令变成了一条。重新完后新的AOF文件会替换旧AOF文件。但是新AOF文件不会包含浪费空间的冗余命令,所以体积较小,这就解决了AOF文件体积膨胀的问题
特殊情况,比如一个集合键有100个元素,按理说AOF文件中会记录SADD key + 100个元素,实际上,这个命令太长了,执行时会造成客户端缓冲区溢出,所以重写程序在处理列表、哈希表、集合和有序集合这4种键时,会见检查元素量,如果元素超过64个,就会用多条命令记录键的值,比如SADD100个元素,那么分2条命令,一条是SADD 64个,另一条是SADD36个。
2.5 持久化期间,新的写操作怎么办
Redis专门设置了一个AOF重写缓冲区,Redis执行完写命令后,这个写命令会保存到AOF重写缓冲区。当子进程完成AOF重写工作后,父进程会把AOF重写缓冲区中的所有内容写入到新生成的AOF文件中
3 主从复制相关
3.1 主从复制的作用
1)读写分离:master写,slave读,提高服务器的读写负载能力
2)负载均衡:基于主从复制、读写分离,由slave分担master负载,通过多个从节点分担数据读取负载,提高服务器的并发量和吞吐量
3)故障恢复:mater出现问题时,由slave提供服务,实现快速的故障恢复
4)高可用基石:基于主从复制,构建哨兵与集群,实现Redis的高可用方案
3.2 Redis主从复制原理
1)slave成功连接到maser后,会向master发送SYNC命令。
2)mater收到SYNC命令后,执行BGSAVE命令,在后台生成一个RDB文件,并使用一个缓冲区记录从现在开始执行的所有写命令。
3)mater会将BGSAVE命令生成的RDB文件发送给slave,slave接收并载入RDB文件,将自己的数据库状态更新至master执行BGSAVE命令时的数据库状态。
4)mater将记录在缓冲区的所有写命令发送给从slave,slave执行这些写命令,将自己的数据库状态更新至master数据库当前所处的状态。
全量复制:slave在接受到master的RDB文件后,将其存盘并载入到内存中
增量复制:master将写命令发送给从slave执行,避免每次都是全量复制。比如同步完成后,master删除了key,slave没删除,这时主从不一致,master就需要将写操作发送给salve并执行。这就是命令传播
3.3 部分重同步是怎么实现的
部分重同步由以下三个部分构成:
1. 主服务器的复制偏移量和从服务器的复制偏移量
2. 主服务器的复制积压缓冲区
3. 服务器的运行ID
复制偏移量:主服务器每次向从服务器传播N个字节的数据时,就将自己的复制偏移量的值加上N。从服务器每次收到主服务器传播来的N个字节的数据时,就将自己的复制偏移量的值加上N。如果主从服务器处于一致时,那两者的偏移量总是相同的。
复制积压缓冲区:假如从服务器断线了,等连上master后,如何复制呢?是应该执行完整重同步还是部分重同步呢?这就需要复制积压缓冲区。复制积压缓冲区是由主服务器维护的一个固定长度的先进先出(FIFO)队列,默认大小为1MB。当主服务器进行命令传播时,它不仅会将写命令发送给所有从服务器,还会将写命令入队到复制积压缓冲区。因为主服务器的复制积压缓冲区里会保存着一部分最近传播的写命令,并且复制积压缓冲区会为队列中的每个字节记录相应的复制偏移量,如下表所示:
当slave重新连接上master时,slave会通过PSYNC命令将自己的复制偏移量offset发送给master,master会根据这个复制偏移量来决定对slave执行何种同步操作:
如果offer偏移量之后的数据(也即是offset+1开始的数据)仍然存在于复制积压缓冲区里,那么主服务器将对从服务器执行部分重同步。
相反,如果offset偏移量之后的数据已经不存在于复制积压缓冲区,那么主服务器将对从服务器执行完整重同步操作。因为复制积压缓冲区是固定大小,如果offset偏移量之后的数据已经不存在于复制积压缓冲区,说明从服务器已经落后很久了,比如从offset=10086,但是主服务器复制积压缓冲区里的最小的offset=10096,那中间10字节的数据,怎么知道是哪些,所以无法部分重同步。
服务器运行ID:每台服务器都有自己的运行ID,初次复制时,slave会保存master的服务器运行ID。短线重连后,slave会向当前连接的master发送之前保存的master服务器运行id,如果一致,说明连接的是旧master,则执行部分重同步。否则执行完整重同步
3.4 哨兵有什么作用
哨兵在Redis集群架构中是非常重要的一个组件,主要功能如下:
1)集群监控:负责监控master和slave服务是否正常工作
2)消息通知:如果某个Redis实例有故障,那么哨兵负责发送消息作为报警通知给管理员
3)故障转移:如果master挂掉了,会选一个slave来当master
4)配置中心:如果故障转移发生了,会通知client客户端新的master地址
3.5 Redis有几种部署模式,集群和哨兵模式有什么区别
Redis有4种模式:单机、主从模式、哨兵模式、集群模式
3.5.1 单机模式
单机模式就是部署一台Redis机器
优点:部署简单、高性能,单机不需要同步数据
缺点:可靠性无法保证,有宕机风险
3.5.2 主从复制模式
主从复制是指将一台Redis服务器的数据,复制到其它Redis服务器,一个主节点可以挂多个从节点,如何mater宕机,只能人工干预选择一个slave来当主节点
优点:slave帮master分担了读的压力
缺点:一旦master宕机,只能人工干预选一个slave当主节点,同时需要修改其它slave对应的master地址,命令其它slave去复制新的master,全程需要人工干预
主从复制,是可以部署多台主节点的,各个主节点相互独立,互不影响,至于我想把数据存在哪台主节点上,靠自己选择。
3.5.3 哨兵模式
哨兵监控整个Redis集群,可以很好实现故障转移。一个master宕机了,哨兵会选举出一个slave来当master。
哨兵模式下也是允许有多台主节点的,各个主节点也是相互独立,互不影响,至于我想把数据存在哪台主机上,靠自己选择
优点:哨兵模式是基于主从模式的,可以实现自动切换。
缺点:部署更复杂了
3.5.3 集群模式
主从和哨兵都没有解决一个问题:单个节点的存储能力和访问能力是有限的。集群模式把数据进行分片存储。集群的键空间被分割成16384个slots(即hash卡槽),通过hash的方式将数据分到不同的分片上。某个主节点宕机,那这个主节点下的从节点会通过选举产生一个主节点,替换原来的故障节点
集群模式下有多个主节点,各个主节点之后有联系,因为数据是分片存储的,并不是我想存哪台节点上就可以存哪台节点上,是集群自动计算key是属于哪个卡槽,存放在哪台节点上的
所以集群模式和哨兵模式最大的区别就是分片
3.6 主节点宕掉了,哨兵是怎么选取主节点的
待补充
3.7 主节点宕掉了,集群是怎么选取主节点的
待补充
4 内存淘汰策略相关
4.1 键过期了会立马删吗
记住了,不会的,因为立马删掉对CPU不友好
4.2 键的删除策略有几种
Redis使用惰性删除和定期删除两种策略
当服务器运行在复制模式下,从服务器的过期删除动作由主服务器控制:
1. 主服务器在删除一个过期键后,会显式地向所有从服务器发送一个DEL命令,告知从服务器删除这个过期键。
2. 从服务器在执行客户端发送地读命令时,即使碰到过期键也不会将过期键删除,而是继续像处理未过期的键一样来处理过期键。
3. 从服务器只有在接到主服务器发来的DEL命令之后,才会删除过期键。
通过由主服务器来控制从服务器统一地删除过期键,可以保证主从服务器数据的一致性
举例:有个过期键message,客户端向从服务器发送命令GET message,从服务器将发现message键已过期,但是从服务器不会去删它,而是继续将message键的值返回给客户端,就好像message键没过期一样。之后,客户端向主服务器发送命令GET message,主服务器将会发现message键已过期,主服务器会删除message键,向客户端返回空回复,并向从服务器发送DEL message命令,从服务器在接收到主服务器发来的DEL message命令之后,也会从数据库中删除message键。
4.3 内存淘汰策略有几种
记住了,有8种
1、noeviction:不会驱逐任何key
2、allkeys-lru:对所有key使用LRU算法进行删除
3、volatile-lru:对所有设置了过期时间的key使用LRU算法进行删除
4、allkeys-random:对所有key随机删除
5、volatile-random:对所有设置了过期时间的key随机删除
6、volatile-ttl:删除马上要过期的key
7、allkeys-lfu:对所有key使用LFU算法进行删除
8、volatile-lfy:对所有设置了过期时间的key使用LFU算法进行删除
一般采用第2种,LRU算法思想:删除最久的那些没有被使用到的key,注意强调的是时间上最久
LFU:删除使用次数最少得key,注意强调的是使用次数最少
5 Redis锁相关(超高频)
5.1 为什么需要分布式锁
如果给key设置过期时间的话,redis有个方法执行原子操作,既设置key、value,也设置过期时间,要么全部成功,要么全不成功。这个原子操作的方法就是setIfAbsent方法。因为如果不用原子操作,先set值,突然宕机了,还没有给key设置过期时间,那这时候这个key就一直存在,无法删除了。
可是问题据来了,我设置了10秒过期,比如线程1执行了减一之后,还没执行完,正好10秒到了,key就过期了,那高并发下这时线程2来了,线程2也继续执行减1,还没执行完,线程1执行完了,把key给删了,这就导致key失效了,这时候线程3就来了。
使用redisson的依赖,原理如下:
代码中设置的30秒超时,那么会按照三分之一的时间10s来续命。但是这样还是有问题,因为设置key、value是在主机上设置,主机会同步给从机万一还没来得及同步给从机,主机挂了,然后从机变成了主机,那这时候又出问题了,比如线程1读的是50,线程2尽量扣减也是按照50来扣,那最后大家扣了两个49。此时代码是这样的:加锁、续命、解锁。
下面是一个简单的分布式锁
本质上是用原子操作给key设置value,并设置10秒过期时间
判断锁和删除锁不是原子的,会有问题,可以两种方法解决,一是LUA脚本,二是redis事务。
用while循环,不断的去执行,list为空,说明事务执行失败了,说明这个变量被别人改动了,那就尝试进行下一次循环,如果成功了,就不再监控,break跳出循环。
新的问题又来了,前面设置了过期时间,那么redis分布式锁如何续期呢?master宕机了,key和value还没有同步到从机上来,这时咋办呢,这就要用redisson了。
redisson只需要简单的去lock和在finally里面unlock就行,但是简单的unlock,在超高并发下可能会报错,因为超高并发下解锁线程和当前线程不是同一个线程。解决办法如下:
如果还是锁状态,并且锁还是当前线程持有的话,那就释放锁。
reids分布式锁总结:
1、redis分布式锁就是用setnx设置key和value,并设置过期时间,因为不设置 过期时间, 有可能会在设置key之后,宕机了,导致最后key没有删除,无 法解锁,那后面所有线程都无法setnx 成功,无法获取到这个锁。
2、设置过期时间之后还得续期,因为比如过期时间是10s,业务代码却执行了 20s,那这时 候,锁已经过期了,第二个线程就可以获取锁,就可能会跟 第一个线程一起执行扣减的 操作。本质上我第一个线程还没执行完,第二个 线程应该等待,不应该执行业务代码的, 这样就导致了问题。而且第二个 线程进来,可能先走finally,把线程1的锁给删了,反 而没有删自己的 锁,又导致了问题。
3、finally里面得判断下删除的是不是自己设置的那把锁,因为value是uuid+当 前线程的名称。
4、redis集群情况下,直接上redisson。
5.2 Redisson实现Redis分布式锁的底层原理
加锁时使用的命令是SETNX key value,然后设置过期时间EXPIRE key 10(设置10s过期时间)。加锁和设置过期时间是2条命令,如果SETNX执行成了,由于网络问题,或者客户端异常崩溃,或者Redis宕机了,导致EXPIRE命令没有执行,就会导致锁一直存在,会引发死锁
所以Redis扩展了SET命令的参数,把NX/EX集成到了SET命令中,用一条命令实现加锁和设置过期时间的原子操作
SET key value EX 10 NX
新的问题又来了,如果释放了别人的锁怎么办,所以需要检查这把锁是不是自己持有的,是的话才允许释放。怎么解决呢?客户端在加锁的时候,value设置一个仅自己知道的唯一标识进去,可以是自己的线程ID,也可以是uuid。客户端解锁的时候,根据key拿到value,判断value是不是自己的线程id或者uuid,是的话则释放锁
#伪代码
if redis.get("key") == 自己的线程ID或者前面设置的uuid {
redis.del("key")
}
新的问题又来了,GET和DEL是两条命令,如果GET判断锁是自己的,还没来得删除锁,这时刚好锁过期自动释放了,客户端2又获得了这把锁,,那客户端1执行DEL时,删除的却是客户端2的锁。所以这两条命令必须是原子的,这就需要LUA脚本,Redis是单线程的,执行Lua脚本时,其他请求必须等待,所以GET+DEL之间就不会插入其他命令
1、底层命令是hset来设置key-value,然后设置国企时间
2、Redisson是可重入锁,如下图所示,圈起来的1是记录加锁的次数
底层的加锁的代码如下:
加锁过程如下:exists命令看key是否存在,如果不存在则通过hset命令来设置key-value,并且设置过期时间。如果锁存在,并且锁的是当前线程,则加锁次数+1,因为是可重入锁。所以为什么要用hset命令,因为是可重入锁,用hset命令可以设置key-value和加锁次数
锁的释放主要是publish释放锁的信息,然后做校验,一般会判断释放为当前线程,是则释放锁。还有个hincrby递减操作,锁的值大于0说明是可重入锁,那就刷新过期时间,如果值小于0,则删掉key释放锁
注意:加锁、续期、解锁的命令一定要记得,面试会考
Redisson有什么问题呢?
1)当集群模式下的主从同步(比如一主三从)时,master加了锁,还没同步给slave,这时master宕机了,就会从slave中重新选一个当master,此时新的master和slave都没有锁,锁失效。
2)当多个独立的节点时(注意不是集群,也不是主从,节点各自独立,向把key放在哪台机器上,自己决定),比如5个独立的节点,只有一个节点得到锁。并发下有线程来了,查的是其它节点,发现没有锁。这样就无法解决并发的问题
5.3 watch dog的原理是什么
Redisson客户端在获取锁后,会启动一个守护线程,该线程会执行一个定时任务(TimerTask),每隔一段时间(默认是锁的有效期的1/3,即10s)自动判断当前线程是否还持有这把锁,如果持有,则把过期时间重新设置为最初的有效期(默认10s)。这样就保证了在业务代码执行完之间,锁不会失效。
注意:
1)每10s续期一次,每次续期30s
2)这个后台线程是客户端发起的,不是部署Redis机器发起的。所以当客户端崩溃时,watch dog会停止续期。
3)底层hset key value,是通过lua脚本来实现的
4)线程加锁成功后,才会自动启动watch dog锁续期机制
有个面试题:如果CPU被打满了或者GC停顿,导致这个后台线程无法获得资源去执行,从而导致无法续期。该怎么办?
答:看门狗中的定时任务无法执行时,会抛异常,然后会删除
着重看下文章https://juejin.cn/post/7094102614203170824?from=search-suggest
注意,如果加锁的时候设置了过期时间,那Redisson可能就不会开启看门狗机制,要看加锁调的什么方法
问题1:如果线程执行报错抛异常,没有触发inlock,导致看门狗递归续期锁永远无法释放?
答案:不会,unlock写在了finally中,一定会执行
问题2:如果服务器挂了导致主线程挂了,没有执行unlock导致锁一直在怎么办?
答案:不会,服务器都挂了,那Redisson实例同样挂了,定时任务都不复存在,还有谁续期呢。30s后锁就自动释放了。所以不存在问题中描述的情况
注意续期是客户端发起的,不是Redis服务端,所以客户端如果崩溃,watch dog就会停止续期
5.4 红锁(redLock)
背景:主机加了锁,还没同步给从机,这时主机断了,从机没这把锁,怎么办
RedLock方案基于2个前提:
1)不需要部署从库和哨兵机制,只部署主库
2)主库要部署多个,官方推荐至少5个实例,注意这5个主库是各自独立的,不是集群也不是主从。
RedLock如何使用呢?具体流程如下:
1)客户端先获取当前时间戳T1
2)客户端依次向这5个redis实例发起加锁请求(用set命令),且每个请求都会设置超时时间(毫秒级,要远小于锁的有效时间)。如果某一个实例加锁失败,就立即向下一个Redis实例申请加锁
3)如果客户端从>=3个(大多数)以上redis实例加锁成功,则再次获取当前时间戳T2,如果T2-T1 < 锁的过期时间,此时认为客户端加锁成功,否则认为加锁失败
4)加锁成功,去操作共享资源
5)加锁失败,向全部节点发起释放锁的请求。用LUA脚本释放锁
6 IO多路复用
6.1 Redis为什么这么快
1)Redis是完全基于内存操作
2)采用单线程,减少了上下文切换和竞争条件,不存在多进程或多线程的切换而消耗CPU
3)采用多路复用模型,非阻塞IO。
6.2 IO多路复用
待补充
7 场景题
7.1 布隆过滤器
布隆过滤器是一个很长的二进制向量和一系列随机映射函数(哈希函数)。用于检测一个函数是否在集合中,优点是空间效率和查询效率高,缺点是有一定的误识别率和删除困难
优点:增加和查询的时间复杂度低,为O(n),n为哈希函数的个数,通常情况下比较小;保密性强,布隆过滤器不存储元素本身,只存0和1,谁也不知道真实的数据是啥。存储空间小,因为是用二进制数组存储的。
缺点:有一定的误识别率,因为多个数据计算的哈希码可能是相同的。无法获取元素本身,很难删除元素
底层是一个大型的二进制数组+多个无偏hash函数。二进制数组只存0和1,1表示存在。数据以0和1的形式存在数组中
无偏hash函数就是能把元素的hash值计算得比较均匀的hash函数,能使得计算后的元素下标比较均匀得映射到位数组中
误判率设置的越小,所误判的个数也就越少,性能也越差,因为越小计算时间越长。误判率越小,则需要的hash函数越多,计算出的哈希值越多,对应的二进制数据也越多,所占空间越大
布隆过滤器应用于缓存穿透、黑名单啥的
布隆过滤器告诉我们:存在则可能存在,不存在则一定不存在
缺点:不能删除元素。比如两条数据计算哈希值都是2,那我把二进制向量中2的位置设置为1,那能删吗?不能!因为删了,就会影响2条数据
布隆过滤器如何扩容?比如现在数组长度是1亿,随着业务量增长要扩成2亿怎么办?创建一个新的布隆过滤器,重新哈希现有元素,将现有元素通过哈希函数重新计算哈希值,在新布隆过滤器的数组中把这些哈希值的位置设置为1。
7.2 Redis的一致性Hash算法
如下图所示:根据各个服务器IP或者主机名,采用hash算法获取到哈希值,然后对2的32次方取模,确认这个服务器在圆环中的位置。一致性Hash算法,就是将哈希值空间组织成一个虚拟的圆环
来了一条数据,想访问哪台服务器,将数据的key用相同的Hash函数计算出哈希值,确认此数据在环上的位置,然后沿着环顺时针行走,遇到的第一台服务器就是应该定位到的服务器。
哪台机器宕机,受影响的数据只是此服务器和闭环中前一台服务器之间的数据,其他的数据不受影响。如果加一台机器,受影响的也是这台新服务器到它的前一台服务器之间的数据
Hash环的数据倾斜问题。比如服务器太少,大量数据都被分配到同一台上去了,造成不均衡。解决办法就是每个机器节点进行多次哈希,这样每个服务器就会在闭环上有多个虚拟节点,以此削弱数据倾斜
7.3 Redis事务
记住开启事务、执行事务的命令,面试会考,虽然很冷门,我就被问过
7.4 Redis事务是否具有ACID4个特性
原子性:要么全执行,要么一个都不执行。Redis支持原子性,但是不支持事务回滚
一致性:事务执行前后,不管事务是否执行成功,数据库也仍然是一致的
隔离性:各事务之间不会相互干扰。Redis是但线程执行事务的,并且服务器保证在事务执行期间不会对事务进行中断。因此Redis的事务总是串行执行的,具有隔离性
耐久性:指事务执行的结果会保存到磁盘。但是只有在AOF持久化模式下,且appendfsync=always时,才具有耐久性。其他情况下都不具有耐久性
7.5 Watch命令是咋么实现的
Redis数据库保存一个watched_keys字典,字典的键是被WATCH命令监视的数据库键,字典的值是一个链表,记录所有监视这个数据库键的客户端,如下图所示
当键被修改时,会去watched_keys字典中查看是否有客户端正在监视这个键,如果有,则将客户端的REDIS_DIRTY_CAS标识打开,标识该客户端的事务的安全性已经被破坏