Redis
-
Redis数据结构
String字符串,list链表,hash键值对,set集合,sortedset有序集合,BloomFilter布隆过滤器
布隆过滤器原理:当一个元素被加入到集合中时,通过K个散列函数将元素分布到一个位数组上的K个点,查询该元素的时候,如果hash出来的这个K个点都为1,则说明元素可能存在,如果有一个为0,则说明元素一定不存在。优点:可用于防止缓存穿透,数据库的元素先散列到布隆数组中,查询时先通过布隆过滤器判断是否存在再查询。缺点:牺牲了准确性和便利性,查询出来位数组上的点全为1也不一定存在。删除时也不能修改位数组,否则容易影响其他数据的查询
-
Redis数据结构的底层实现
- String:
struct sdshdr{ //记录buf数组中已使用字节的数量 //等于 SDS 保存字符串的长度 int len; //记录 buf 数组中未使用字节的数量 int free; //字节数组,用于保存字符串 char buf[]; }
优点:不会有字符串变更造成的内存溢出问题;获取字符串长度的时间复杂度为1;空间预分配,惰性空间释放free字段,会默认留一定的空间放置多次重分配内存
- Hash:数组+链表的基础上,进行了一些rehash的优化,
- Redis的hash采用链地址法来处理冲突,没有使用红黑树
- hash表节点采用单链表结构
- rehash优化:分治思想,将庞大的迁移工作量分到每一次curd中,避免服务繁忙
- List:双向链表
- set:内部实现是value为null的Hashmap,通过计算hash的方式来排重。
- zset:使用hashmap和跳跃表(skiplist)来保证数据的存储和有序,Hashmap存放的是成员到score的映射,跳跃表中存放的是所有成员,排序依据是score,使用跳跃表机构可以获得比较高的查询效率,并且实现上简单。跳跃表:每个节点维持多个指向其他节点的指针,从而达到快速访问节点的目的
- String:
-
Redis有事务吗?
- multi开启事务
- exec执行事务内命令
- discard取消事务
- watch监视一个或多个key,key被改动时事务将会被打断
-
Redis单线程为什么执行这么快
- 纯内存操作,读写的时候不会受到磁盘IO的限制
- 单线程操作,避免了切换上下文和竞争条件,也避免了多进程或线程切换的CPU损耗,也不存在死锁的问题
- 采用了IO多路复用机制
-
缓存问题:雪崩,穿透,击穿
雪崩:同一时间内大量的缓存失效导致DB压力过大。
这种情况可以对缓存的key在默认值上加上一些随机时间,让缓存失效的时间分散开来
穿透:指缓存和数据库中都没有的数据,而用户不断发起请求
这种情况可以对一些查询出来不存在的数据缓存一个null,失效时间设置短一些防止数据库新增了这个元素
还可以使用布隆过滤器,在查询之前先通过布隆过滤器判断是否存在
击穿:这个和穿透有点像,是指某个热点key在大量访问的时候突然失效了,导致请求直接来到数据库。
热点key设置为永不过期,或者定时对这个key进行更新
-
Redis出现热点key造成集群访问量倾斜的解决办法?
- 使用本地缓存
- 利用分片算法的特性,对key进行打散处理,给hotkey加上前缀或后缀,把一个hotkey的数量变成redis实例个数的倍数
-
Redis里面有1亿个key,其中有10w个key是以某个固定的已知的前缀开头的,如何将它们全部找出来
redis中有个指令是keys,可以指定模式查询对应的key列表
----如果这个redis正在给线上的业务提供服务,那使用keys指令会有什么问题?
redis是单线程的,keys指令会阻塞一段时间,查询完成才能恢复。可以使用scan指令替代,scan是无阻塞的指令,不过scan可能会查出重复的数据,这个可以在客户端做一些去重。
-
有使用过redis做异步队列吗?
redis的list就可以做消息队列,通过rpush插入,lpop弹出。
----能否生产一次,消费多次?
redis有pub/sub模型,可以实现一对多的消费,不过在消费者掉线的时候会有消息丢失的问题,这类情况还是使用专业的消息中间件比较好
----那有用过redis做延时队列吗?
可以使用sortedset,拿时间戳作为score,消息内容作为key调用zadd来生产消息,消费者用zrangebyscore指令获取N秒之前的数据轮询进行处理。
-
redis怎么做持久化的?
redis有两种持久化的方式,AOF和RDB,其中AOF是增量持久化,RDB是全量持久化,所有一般RDB会比较耗时,需要配合AOF使用,一般redis重启时,会通过RDB文件重新构建内存,然后再使用AOF重新对近期数据做处理进行完整恢复,不过一般redis在AOF开启时,是优先加载AOF,AOF文件找不到才使用RDB,AOF是日志型文件,每个指令都会存储,而RDB会优化指令,保证数据完整即可。
-
机器突然断电,丢失的数据怎么办?
开启了AOF之后,取决于AOF的sync设置,如果设置1s一次的话,会丢失1s内的数据,也可以设置成每条指令都sync一下,不过会导致磁盘io增多性能降低。
-
RDB的原理?
redis在开启RDB时,会定时fork一个子进程,子进程会根据父进程内存生成临时快照文件,完成后对RDB文件进行替换。
RDB相当于是redis某个时间段的内存快照,是十分紧凑的二进制文件,加载RDB文件比AOF文件快
AOF相当于对redis的操作指令做复制,所有的写入命令会追加到aof_buf(缓冲区)中,根据对应的策略向硬盘做同步操作,然后定期对AOF文件进行重写,此外,AOF会对命令进行优化,简化一些操作命令
-
redis集群的同步原理?
redis的主从同步分为全同步和修改同步,全同步一般是在启动的时候通过主机的bgsave生成RDB文件,从机通过RDB文件进行恢复,后续的同步都是修改同步,修改同步是通过propagate函数完成将一个操作记录到aof文件中或者扩散到其他slave中,在该函数中通过调用feedAppendOnlyFile()将操作记录到aof中,通过调用replicationFeedSlaves()将操作扩散到各slave中
-
redis集群模式的性能优化该怎么做?
- Master不做任何持久化操作
- 数据如果比较重要,某个slave开启aof备份,策略为每秒一次
- master和slave在同一个局域网内
- 避免在压力很大的主库上增加从库
-
redis怎么保证高可用
redis中有哨兵模式,在主机宕机的时候,通过哨兵选举出新的master,哨兵必须存在三个以上,否则无法选举
1. 领导者选举
作用:选举出一个sentenel节点作为领导者去进行故障转移操作。
过程:
1). 每个做主观下线的sentinel节点向其他sentinel节点发送vote命令,要求将它设置为领导者。
2). 收到命令的sentinel节点如果还没有同意过其他的sentinel发送的命令(还未投过票),那么就会同意,否则拒绝。
3). 如果该sentinel节点发现自己的票数已经过半且达到了quorum的值,就会成为领导者。
4). 如果这个过程出现多个sentinel成为领导者,则会等待一段时间重新选举。
2. 选出新的master节点
redis sentinel会选一个合适的slave来升级为master,那么,如何选择一个合适的slave呢?顺序如下:
1). 选择slave-priority最高的slave节点(默认是相同)。
2). 选择复制偏移量最大的节点。
3). 如果以上两个条件都不满足,选runId最小的(启动最早的)。
3. 更改slave节点的master节点
当选举出新的master节点后,会将其余的节点变更为新的master节点的slave节点,如果原有的master节点重新上线,成为新的master节点的slave节点
-
redis的哨兵sentinel怎么保证自身的高可用呢?使用了什么协议?
使用了Raft协议来保证高可用
Raft协议中的相关概念:
1、角色定义
Leader:
主节点,整个集群只有一个Leader,所有的写请求都通过Leader发送给Follower;
Follower:
从节点(服从角色);
Candidate:
在Leader消息发送失败或宕机,整集群没有Leader时,此时Follower接收Leader的心跳包失败,则Follwer开始竞选Leader时,它们的身份是Candidate。Candidate只是个中间状态,不会长期存在。
2、Term(任期)定义
在每一个Leader的任期期间,都有唯一表示该任期的一个Term;
3、基本操作
1、Raft将state machine replication划分为三个子问题
1、Leader Election
2、Log Replication
3、Safety
2、Leader Election步骤
集群启动或Leader的心跳包消息无法发送给Follower时,触发 Leader Election——选主 操作。
3、Log Replication步骤
1、所有的写请求都要经过Leader;
2、Leader将写请求携带在心跳包中发送给Follower;
3、当Leader收到多数派回复的消息后,则先自己提交写操作,同时发送Commit请求给Follower;
4、Safety保证
1、Leader宕机感知:
a、Raft通过TimeOut来保证Follower能正确感知Leader宕机或消息丢失的事件,并触发Follower竞选Leader;
b、Leader需要给Follower发送心跳包(heartbeats),数据也是携带在心跳包中发送给Follower的;
2、选主平票情况
Leader Election时平票情况下,则两个Candidates会产生一个随机的Timewait,继续发送下一个竞选消息。
3、脑裂(大小集群)情况:
小集群由于没有得到多数派的回复,写操作失败;
大集群会发生重新选主的过程,且新Leader拥有自己新的Term(任期),写操作成功;
当小集群回到大集群时,由于小集群的Term小于新集群的Term,则同步新集群的信息。
-
有没有考虑过,如果多个系统同时操作(并发)Redis带来的数据问题
可以基于分布式锁操作。每个系统通过 Redis获取分布式锁,确保同一时间,只能有一个系统实例在操作某个 Key,别人都不允许读和写。
你要写入缓存的数据,都是从 MySQL 里查出来的,都得写入 MySQL 中,写入 MySQL 中的时候必须保存一个时间戳,从 MySQL 查出来的时候,时间戳也查出来。
每次要写之前,先判断一下当前这个 Value 的时间戳是否比缓存里的 Value 的时间戳要新。如果是的话,那么可以写,否则,就不能用旧的数据覆盖新的数据。
-
如何保证缓存与数据库的双写一致性?
先更新数据库,再删除缓存,将删除缓存的操作放在更新数据库的同一个事务中,如果删除缓存失败,抛出异常使当前事务回滚
-
redis线程模型了解吗?
Redis 基于 Reactor 模式开发了自己的网络事件处理器: 这个处理器被称为文件事件处理器(file event handler)。文件事件处理器使用 I/O 多路复用(multiplexing)程序来同时监听多个套接字, 并根据套接字目前执行的任务来为套接字关联不同的事件处理器。文件处理器包括连接处理器,命令请求处理器,命令回复处理器,文件事件处理器以单线程方式运行, 但通过使用 I/O 多路复用程序来监听多个套接字, 文件事件处理器既实现了高性能的网络通信模型, 又可以很好地与 redis 服务器中其他同样以单线程方式运行的模块进行对接, 这保持了 Redis 内部单线程设计的简单性
-
如何实现分布式锁?
两种实现方式,redis和zookeeper,其中redis是通过lua表达式实现
-
说到zookeeper,了解ZAB协议吗?
zookeeper依赖ZAB协议来实现分布式数据一致性,ZAB协议全称为原子消息广播协议,ZAB协议可以说是在Paxos算法基础上进行了扩展和改造的。
在ZooKeeper中所有的事务请求都由一个主服务器也就是Leader来处理,其他服务器为Follower,Leader将客户端的事务请求转换为事务Proposal,并且将Proposal分发给集群中其他所有的Follower,然 后Leader等待Follwer反馈,当有 过半数(>=N/2+1) 的Follower反馈信息后,Leader将再次向集群内Follower广播Commit信息,Commit为将之前的Proposal提交;
ZAB协议中节点存在三种状态:
Looking :系统刚启动时或者Leader崩溃后正处于选举状态
Following :Follower节点所处的状态,Follower与Leader处于数据同步阶段;
Leading :Leader所处状态,当前集群中有一个Leader为主进程;
Zookeeper刚启动时,所有节点都处于Looking状态,这时集群会选举出一个Leader,Leader会处于Leading状态,其余节点发现存在主节点后,处于Following状态,并且与Leader保持同步,当从节点发现 Leader失去联系后,会开始新一轮选举。Zookeeper整个生命周期内都处于这样的循环
ZAB协议定义了选举(election)、发现(discovery)、同步(sync)、广播(Broadcast)四个阶段。
选举时,节点都会发送(myID,ZXID)到其余节点,通过判断ZXID(事务ID)的大小来决定哪个节点为leader,如果有多个节点都有LastZXID,则由myId决定,id小的为leader,id小表明该节点先加入集群。当投票数过半时,leader就产生了。然后Leader会以自身的lastZXID为基准,将follower同步。
客户端提交事务请求时Leader节点为每一个请求生成一个事务Proposal,将其发送给集群中所有的Follower节点,收到过半Follower的反馈后开始对事务进行提交,ZAB协议使用了原子广播协议;在ZAB协议中只需要得到过半的Follower节点反馈Ack就可以对事务进行提交,这也导致了Leader几点崩溃后可能会出现数据不一致的情况,ZAB使用了崩溃恢复来处理数字不一致问题;消息广播使用了TCP协议进行通讯所有保证了接受和发送事务的顺序性。广播消息时Leader节点为每个事务Proposal分配一个全局递增的ZXID(事务ID),每个事务Proposal都按照ZXID顺序来处理;
Leader节点为每一个Follower节点分配一个队列按事务ZXID顺序放入到队列中,且根据队列的规则FIFO来进行事务的发送。Follower节点收到事务Proposal后会将该事务以事务日志方式写入到本地磁盘中,成功后反馈Ack消息给Leader节点,Leader在接收到过半Follower节点的Ack反馈后就会进行事务的提交,以此同时向所有的Follower节点广播Commit消息,Follower节点收到Commit后开始对事务进行提交;
-
一致性算法除了ZAB还有哪些?
Base-Paxos
paxos——Chubby(Google首次运用Multi Paxos算法到工程领域)
Multi-Paxos
zab协议——Zookeeper,raft协议——redis哨兵
Paxos中的角色分类
- Proposer 提案者:可以有多个,用于发起一个议案(由client向Proposer发出),议案包括议案编号、议案内容
- Acceptor 接收者、批准者:用于接收Proposer提出的议案,并决定采纳哪一个议案
- Learner 学习者、记录者:在最终决定采纳某个议案后,进行记录,不做其他事
其中Proposer在Base-Paxos中可以存在多个,而Multi-Paxos中仅有一个,别名leader。因此,Base-paxos存在活锁问题:
三种算法的区别:
Raft:
追加日志的操作必须是连续的(Multi-Paxos中是并发)
只有拥有最新、最全日志的节点才能够当选Leader(Multi-Paxos中任意节点都可以写日志,无限制)
zab:
Raft每一次leader的任期叫做term,ZAB中叫做epoch
Raft由leader向follower发送心跳,ZAB相反
选主投票方式不同
事务操作方式不同
-
一致性就代表了正确性吗?
一致性并不代表完全正确性,一致性需要客户端和共识算法(Consensus)来共同保证。
Client Request操作的三个可能结果:成功、失败、unknown(Timeout)
理解unknown(Timeout)
场景:Client写请求,Leader向Follower同步日志,此时集群中有3个节点失败,2个节点存活,结果是? 假设节点为:S1、S2、S3、S4、S5(Leader)
假设S5和S4存活,Client发起 第N次写请求 为 操作I 时,由于Leader没有得到多数派的回复,操作I只被发送到了S4中,此时Leader即会返回Client unknown,因为Leader不知道后面会不会成功将该条日志写入多数派中。
结果1:假设Leader在返回客户端后,宕机的Follower:S1、S2、S3恢复正常,Leader再次发送 第N次写请求——操作I,且得到了多数派的回复,则提交日志,写操作最终结果为成功;
结果2:假设Leader在返回客户端后,此时S5和S4宕机,且S1、S2、S3恢复正常,此时S1、S2、S3触发选主操作,且集群恢复可用,如果此时Client发起
第N+1次请求 为 操作I+1 ,且Client操作成功后 S5、S4恢复正常,则保存在S5、S4中的 操作I 会被删除,S5、S4同步最新的
操作I+1 到本地。则 第N次写请求—操作I 失败;