1、redis知识点

351 阅读32分钟

缓存类型有哪些?

缓存的类型分为:本地缓存、分布式缓存和多级缓存。

本地缓存就是在进程的内存中进行缓存,比如我们的JVM堆中,可以用LRUMap来实现,也可以使用Ehcache这样的工具来实现。
本地缓存是内存访问,没有远程交互开销,性能最好,但是受限于单机容量,一般缓存较小且无法扩展。

分布式缓存一般都具有良好的水平扩展能力,对较大数据量的场景也能应付自如。缺点就是需要进行远程请求,性能不如本地缓存。

为了平衡这种情况,实际业务中一般采用多级缓存,本地缓存只保存访问频率最高的部分热点数据,其他的热点数据放在分布式缓存中。

为什么要用redis

Redis采用单线程模式处理请求。这样做的原因有2个:
一个是因为采用了非阻塞的异步事件处理机制;
另一个是缓存数据都是内存操作,IO时间不会太长,单线程可以避免线程上下文切换产生的代价。

Redis支持持久化,所以Redis不仅仅可以用作缓存,也可以用作NoSQL数据库。

Redis还有一个非常大的优势,就是除了K-V之外,还支持多种数据格式,例如list、set、sorted set、hash 等。

Redis提供主从同步机制,以及Cluster集群部署能力,能够提供高可用服务。

查询速度快,并发请求如果比较多,都直接查询数据库的话会把数据库搞挂。

redis的缓存雪崩、缓存穿透和缓存击穿

缓存穿透

产生这个问题的原因可能是外部的恶意攻击,例如,对用户信息进行了缓存,但恶意攻击者使用不存在的用户id频繁请求接口,导致查询缓存不命中,然后穿透DB查询依然不命中。这时会有大量请求穿透缓存访问到DB。

解决的办法如下。
1、对不存在的用户,在缓存中保存一个空对象进行标记,防止相同ID再次访问DB。不过这个方法并不能很好解决问题,可能导致缓存中存储大量无用数据。
2、使用BloomFilter过滤器,BloomFilter的特点是存在性检测,如果BloomFilter中不存在,那么数据一定不存在;如果BloomFilter中存在,实际数据也有可能会不存在。非常适合解决这类的问题。

缓存击穿

就是某个热点数据失效时,大量针对这个数据的请求会穿透到数据源。

解决这个问题有如下办法。
1、可以使用互斥锁更新,保证同一个进程中针对同一个数据不会并发请求到DB,减小DB压力。
2、使用随机退避方式,失效时随机sleep一个很短的时间,再次查询,如果失败再执行更新。
3、针对多个热点key同时失效的问题,可以在缓存时使用固定时间加上一个小的随机数,避免大量热点key同一时刻失效。

缓存雪崩

缓存中数据大批量到过期时间,而查询数据量巨大,引起数据库压力过大甚至down机。和缓存击穿不同的是,缓存击穿指并发查同一条数据,缓存雪崩是不同数据都过期了,很多数据都查不到从而查数据库。

解决方法:
1、避免缓存同时过期,加个随机数。
2、设置热点数据永远不过期。

持久化机制AOF、RDB

Redis提供了RDB和AOF两种持久化方式,RDB是把内存中的数据集以快照形式写入磁盘,实际操作是通过fork 子进程执行,采用二进制压缩存储;AOF是以文本日志的形式记录Redis处理的每一个写入或删除操作。

RDB把整个Redis的数据保存在单一文件中,比较适合用来做灾备,但缺点是快照保存完成之前如果宕机,这段时间的数据将会丢失,另外保存快照时可能导致服务短时间不可用。

AOF对日志文件的写入操作使用的是追加模式,有灵活的同步策略,支持每秒同步、每次修改同步和不同步,缺点就是相同规模的数据集,AOF要大于RDB,AOF在运行效率上往往会慢于RDB。

如果突然机器掉电会怎样?

取决于AOF日志sync属性的配置,如果不要求性能,在每条写指令时都sync一下磁盘,就不会丢失数据。但是在高性能的要求下每次都sync是不现实的,一般都使用定时sync,比如一秒一次,这个时候最多就会丢失1s的数据。

AOF、RDB优缺点

RDB

优点:
生成多个数据文件,每个数据文件分别都代表了某一时刻Redis里面的数据,这种方式比较适合做冷备,完整的数据运维设置定时任务,定时同步到远端的服务器,比如阿里的云服务,这样一旦线上挂了,你想恢复多少分钟之前的数据,就去远端拷贝一份之前的数据就好了。

RDB对Redis的性能影响非常小,因为在同步数据时是fork一个子进程去做持久化的,而且在数据恢复的时候速度比AOF快。

缺点:
RDB都是快照文件,都是默认五分钟甚至更久的时间才会生成一次,这意味着你这次同步到下次同步这中间五分钟的数据很可能全部丢失。AOF则最多丢一秒的数据,数据完整性上高下立判。

RDB在生成数据快照时,如果文件很大,客户端可能会暂停几毫秒甚至几秒,会影响性能。

AOF

优点: RDB五分钟一次生成快照,而AOF是一秒一次通过一个后台的线程fsync操作,最多丢失这一秒的数据。

AOF在对日志文件进行操作的时候是以append-only的方式去写的,是以追加的方式写数据,自然就少了很多磁盘寻址的开销了,写入性能惊人,文件也不容易破损。

AOF的日志是通过一个叫非常可读的方式记录的,这样的特性比较适合做灾难性数据误删除的紧急恢复,比如公司的实习生通过flush all清空了所有的数据,只要这个时候后台重写还没发生,可以拷贝一份AOF日志文件,把最后一条flush all命令删了就完事了。

redis为啥那么快

1、基于内存,绝大部分请求是纯粹的内存操作,速度较快。redis读写都直接对内存进行操作,天然地就比硬盘数据库少了到磁盘读取数据的这一步,而这一步恰恰是计算机处理I/O的瓶颈所在;

2、高效的数据结构
Redis的底层数据结构一共有6种,分别是,简单动态字符串,双向链表,压缩列表,哈希表,跳表和整数数组,它们和数据类型的对应关系如下图所示: image.png

3、采用单线程,避免了不必要的上下文切换和竞争条件,也不存在多进程或者多线程切换导致的消耗CPU,不用去考虑各种锁的问题,不存在加锁释放锁操作,没有因为可能出现死锁而导致的性能消耗;

4、使用多路I/O复用模型,非阻塞IO;

redis为什么选择单线程

1、单线程实现可以避免过多的上下文切换开销。程序始终运行在进程中单个线程内,没有多线程切换的场景。

2、避免同步机制的开销:如果Redis选择多线程模型,需要考虑数据同步的问题,则必然会引入某些同步机制,会导致在操作数据过程中带来更多的开销,增加程序复杂度的同时还会降低性能。

3、实现简单,方便维护:如果Redis使用多线程模式,那么所有的底层数据结构的设计都必须考虑线程安全问题,那么Redis的实现将会变得更加复杂。

Redis主从同步

1、当启动一个slave node的时候,它会发送一个PSYNC命令给master node。

2、如果这是slave node初次连接到master node,那么会触发一次full resynchronization全量复制。此时 master会启动一个后台线程,开始生成一份 RDB快照文件,

3、同时还会将从客户端client新收到的所有写命令缓存在内存中。RDB文件生成完毕后,master会将这个RDB发送给slave,slave会先写入本地磁盘,然后再从本地磁盘加载到内存中,

4、接着master会将内存中缓存的写命令发送到slave,slave也会同步这些数据。

5、slave node如果跟master node有网络故障,断开了连接,会自动重连,连接之master node仅会复制给slave 部分缺少的数据。

image.png

总结

  • 当从库和主库建立MS关系后,会向主数据库发送SYNC命令

  • 主库接收到SYNC命令后会开始在后台保存快照(RDB持久化过程),并将期间接收到的写命令缓存起来

  • 当快照完成后,主Redis会将快照文件和所有缓存的写命令发送给从Redis

  • 从Redis接收到后,会载入快照文件并且执行收到的缓存的命令

  • 之后,主Redis每当接收到写命令时就会将命令发送从Redis,从而保证数据的一致

Redis过期键的删除策略

过期策略通常有以下三种:

定时过期:每个设置过期时间的key都需要创建一个定时器,到过期时间就会立即清除。该策略可以立即清除过期的数据,对内存很友好;但是会占用大量的CPU资源去处理过期的数据,从而影响缓存的响应时间和吞吐量。

惰性过期:只有当访问一个key时,才会判断该key是否已过期,过期则清除。该策略可以最大化地节省CPU资源,却对内存非常不友好。极端情况可能出现大量的过期key没有再次被访问,从而不会被清除,占用大量内存。

定期过期:每隔一定的时间,会扫描一定数量的数据库的expires字典中一定数量的key,并清除其中已过期的key。该策略是前两者的一个折中方案。通过调整定时扫描的时间间隔和每次扫描的限定耗时,可以在不同情况下使得CPU和内存资源达到最优的平衡效果。 (expires字典会保存所有设置了过期时间的key的过期时间数据,其中,key是指向键空间中的某个键的指针,value是该键的毫秒精度的UNIX时间戳表示的过期时间。键空间是指该Redis集群中保存的所有键。)

缓存淘汰机制

Redis现在实现了多种key的淘汰机制,按照淘汰key选择的范围可以分成三类:

1、MAXMEMORY_NO_EVICTION在Redis的内存到达上限时,不对key进行淘汰,而是禁止新的写入操作。

2、在Reids的内存到达上限时,会从设置了过期时间的键中,也就是redisDb.expires中,选择key进行淘汰,选择key的策略有四种:
1、MAXMEMORY_VOLATILE_LRU,这种策略会使用近似的LRU策略,选择key进行淘汰。
2、MAXMEMORY_VOLATILE_LFU,这种策略会使用LFU策略,选择key进行淘汰。
3、MAXMEMORY_VOLATILE_TTL,这种策略会选择过期时间最早的key进行淘汰。
4、MAXMEMORY_VOLATILE_RANDOM,这种策略会从redisDb.expires的key中随机选择进行淘汰。

2、在Redis的内存到达上限时,会从整个数据库的键空间中,也就是redisDb.dict中,选择key进行淘汰,选择key的策略有三种:
1、MAXMEMORY_ALLKEYS_LRU,这种策略会使用近似的LRU策略,选择key进行淘汰。
2、MAXMEMORY_ALLKEYS_LFU,这种策略会使用LFU策略,选择key进行淘汰。
3、MAXMEMORY_ALLKEYS_RANDOM,这种策略会从redisDb.dict的key中随机选择进行淘汰。

redis数据类型

String:

String类型是Redis中最常使用的类型,内部的实现是通过SDS(Simple Dynamic String)来存储的。SDS类似于Java中的ArrayList,可以通过预分配冗余空间的方式来减少内存的频繁分配。

这是最简单的类型,就是普通的set和get,做简单的KV缓存。

String的实际应用场景:

缓存功能:String字符串是最常用的数据类型,不仅仅是Redis,各个语言都是最基本类型,因此,利用Redis作为缓存,配合其它数据库作为存储层,利用Redis支持高并发的特点,可以大大加快系统的读写速度、以及降低后端数据库的压力。

计数器:许多系统都会使用Redis作为系统的实时计数器,可以快速实现计数和查询的功能。而且最终的数据结果可以按照特定的时间落地到数据库或者其它存储介质当中进行永久保存。

共享用户Session:用户重新刷新一次界面,可能需要访问一下数据进行重新登录,或者访问页面缓存Cookie,但是可以利用Redis将用户的Session集中管理,在这种模式只需要保证Redis的高可用,每次用户Session的更新和获取都可以快速完成。大大提高效率。

Hash:

这个是类似Map的一种结构,这个一般就是可以将结构化的数据,比如一个对象(前提是这个对象没嵌套其他的对象)给缓存在Redis里,然后每次读写缓存的时候,可以就操作Hash里的某个字段。

但是hash的应用场景还是比较单一,因为现在很多对象都是比较复杂的,比如商品对象里面可能包含了很多属性,其中也有对象。

List:

List是有序列表,这个还是可以玩儿出很多花样的。

比如可以通过List存储一些列表型的数据结构,类似粉丝列表、文章的评论列表之类的东西。

比如可以通过lrange命令,读取某个闭区间内的元素,可以基于List实现分页查询,这个是很棒的一个功能,基于Redis实现简单的高性能分页,可以做类似微博那种下拉不断分页的东西,性能高,就一页一页走。

比如可以搞个简单的消息队列,从List头怼进去,从List尾部那里弄出来。

List本身就是我们在开发过程中比较常用的数据结构了,热点数据更不用说了。

消息队列:Redis的链表结构,可以轻松实现阻塞队列,可以使用左进右出的命令组成来完成队列的设计。比如:数据的生产者可以通过Lpush命令从左边插入数据,多个数据消费者,可以使用Brpop命令阻塞的“抢”列表尾部的数据。

文章列表或者数据分页展示的应用。

比如,我们常用的博客网站的文章列表,当用户量越来越多时,而且每一个用户都有自己的文章列表,而且当文章多时,都需要分页展示,这时可以考虑使用Redis的列表,列表不但有序同时还支持按照范围内获取元素,可以完美解决分页查询功能。大大提高查询效率。

Set:

Set是无序集合,会自动去重的那种。

直接基于Set将系统里需要去重的数据扔进去,自动就给去重了,如果你需要对一些数据进行快速的全局去重,你当然也可以基于JVM内存里的HashSet进行去重,但是如果你的某个系统部署在多台机器上呢?得基于Redis进行全局的Set去重。

可以基于Set玩儿交集、并集、差集的操作,比如交集吧,我们可以把两个人的好友列表整一个交集,看看俩人的共同好友是谁?对吧。

反正这些场景比较多,因为对比很快,操作也简单,两个查询一个Set搞定。

Sorted Set:

Sorted set是排序的Set,去重但可以排序,写进去的时候给一个分数,自动根据分数排序。

有序集合的使用场景与集合类似,但是set集合不是自动有序的,而Sorted set可以利用分数进行成员间的排序,而且是插入时就排序好。所以当你需要一个有序且不重复的集合列表时,就可以选择Sorted set数据结构作为选择方案。

排行榜:有序集合经典使用场景。例如视频网站需要对用户上传的视频做排行榜,榜单维护可能是多方面:按照时间、按照播放量、按照获得的赞数等。

用Sorted Sets来做带权重的队列,比如普通消息的score为1,重要消息的score为2,然后工作线程可以选择按score的倒序来获取工作任务。让重要的任务优先执行。

微博热搜榜,就是有个后面的热度值,前面就是名称

redis数据结构的底层存储

String:Redis没有使用C语言内置的字符数组,而是将字符数组封装为简单动态字符串(SDS),虽然SDS比C语言原生字符数组更费内存,但通过空间换时间,可以将很多C语言字符数组时间复杂度为O(n)的操作转为O(1),提高处理速度,比如strlen命令获取String长度时,对于C语言的字符数组,需要遍历数组才能知道,时间复杂度为O(n),而简单动态字符串已经保存了字符串的长度,因此可以直接获取,时间复杂度为O(1)。

Hash:在数据量还不多的情况下,Hash类型使用压缩列表(ZipList)保存元素,因为元素不多,所以查找也比较快,如果数据增长到设定的值,就改为哈希表,而哈希表查找元素的时间复杂度为O(1),所以也是相当快的。

List:List的底层数据结构为双向链表和压缩列表组合而成的,也称为快速链表(QuickList),因此List类型非常适合头尾插入弹出的操作,因此如果要把Redis作为队列的话,选择List是非常高效的。

Sorted Set:当元素数量不多时,Sorted Set使用的是压缩列表,只有当元素超过设定值时,才使用跳表和哈希表。

Set:在元素的数值都是整数时,Set使用整数数组保存数据,如果元素数量达到设置的值,则改为哈希表。

Redis是一个Key-Value健值对数据库,我们上面介绍的五种类型是指Key-Value中的Value,对于所有KeyValue之间的映射,Redis也是采用哈希表,这个哈希表也叫全局哈希表,如下图所示:

image.png

redis压缩链表zipList

ziplist结构

image.png zlbytes: ziplist的长度(单位: 字节),是一个32位无符号整数。
zltail: ziplist最后一个节点的偏移量,反向遍历ziplist或者pop尾部节点的时候有用。
zllen: ziplist的节点(entry)个数。
entry: ziplist中间储存的节点。
zlend: 标记ziplist结尾。

entry节点的结构

image.png previous-entry_length:用来存储上一个节点的长度, 当前以节点的长度小于254字节时, 本身的长度为1字节, 当前一节点的长度大于等于254时,自身长度为5字节。
encoding:编码,不同类型的编码代表这个节点存储的内容和长度是什么。
content:内容,具体存储数据的地方。

添加或者删除引起的连锁更新

image.png

image.png

连锁更新造成的影响:
1、在最坏情况下连锁更新对ziplist执行了N次重新分配。
2、每次重新分配需要O(N)的时间, 那么就消耗了O(N^2)的时间。
当然连锁更新出现的概率很低, 并且当节点较少时, 出现连锁更新对性能影响不大

redis快速列表quicklist

quicklist其实现也是依赖于ziplist和linkedlist来实现的,它是两个结构的结合。它将ziplist来进行分段存储,也就是分成一个个的quicklistNode节点来进行存储。每个quicklistNode指向一个ziplist,然后quicklistNode之间是通过双向指针来进行连接的。

为什么要把底层数据结构优化成quicklist?

image.png

ziplist这个结构,它内部的数据存储是一段连续的空间,这样的话,就要求很大一块内存空间。就比如说,我们想存储很多的数据,但是内存中并没有符合要求的连续的存储空间,而是存在很多不连续的小空间(加起来可以符合要求)。
再来说说linkedlist这个结构,它数据存储不要求连续,就可以避免上面的弊端,不过这样以来,每个节点都分配一个一块内存,那就有可能造成大量的内存碎片。

redis跳跃表

跳表的时间复杂度为log n

跳表具有如下性质:

(1) 由很多层结构组成

(2) 每一层都是一个有序的链表

(3) 最底层(Level 1)的链表包含所有元素

(4) 如果一个元素出现在Level i的链表中,则它在Level i之下的链表也都会出现。

(5) 每个节点包含两个指针,一个指向同一链表中的下一个元素,一个指向下面一层的元素。

image.png

跳表层数,类似掷硬币,第一层概率P,第二层P的平方。

一个节点的平均层数(也即包含的平均指针数目),计算如下: image.png

缓存一致性问题

首先,我们要明确一点,缓存不是更新,而应该是删除。
删除缓存有两种方式:
1、先删除缓存,再更新数据库。解决方案是使用延迟双删。
2、先更新数据库,再删除缓存。解决方案是消息队列或者其他binlog同步,引入消息队列会带来更多的问题,并不推荐直接使用。
针对缓存一致性要求不是很高的场景,那么只通过设置超时时间就可以了。

布隆过滤器

布隆过滤器(Bloom Filter)是1970年由布隆提出的。它实际上是一个很长的二进制向量和一系列随机映射函数。布隆过滤器可以用于检索一个元素是否在一个集合中。它的优点是空间效率和查询时间都比一般的算法要好的多,缺点是有一定的误识别率和删除困难

布隆过滤器数据结构

在初始状态时,对于长度为m的位数组,它的所有位都被置为0,如下图所示:
image.png

当有变量被加入集合时,通过K个映射函数将这个变量映射成位图中的K个点,把它们置为1(假定有两个变量都通过 3个映射函数)。

image.png

查询某个变量的时候我们只要看看这些点是不是都是1就可以大概率知道集合中有没有它了。

如果这些点有任何一个0,则被查询变量一定不在;
如果都是1,则被查询变量很可能存在。
为什么说是可能存在,而不是一定存在呢?那是因为映射函数本身就是散列函数,散列函数是会有碰撞的。

一个元素如果判断结果为存在的时候元素不一定存在,但是判断结果为不存在的时候则一定不存在。 布隆过滤器可以添加元素,但是不能删除元素。因为删掉元素会导致误判率增加。

添加元素

将要添加的元素给k个哈希函数
得到对应于位数组上的k个位置
将这k个位置设为1

查询元素

将要查询的元素给k个哈希函数
得到对应于位数组上的k个位置
如果k个位置有一个为0,则肯定不在集合中
如果k个位置全部为1,则可能在集合中

哨兵机制

Sentinel(哨兵)是用于监控redis集群中Master状态的工具,是Redis的高可用性解决方案,sentinel哨兵模式已经被集成在redis2.4之后的版本中。sentinel系统可以监视一个或者多个redis master服务,以及这些master服务的所有从服务;当某个master服务下线时,自动将该master下的某个从服务升级为master服务替代已下线的master服务继续处理请求。

sentinel可以让redis实现主从复制,当一个集群中的master失效之后,sentinel可以选举出一个新的master自动接替master的工作,集群中的其他redis服务器自动指向新的master同步数据。一般建议sentinel采取奇数台,防止某一台sentinel无法连接到master导致误切换。

Redis Sentinel作用

Sentinel系统用于管理多个Redis服务器(instance),该系统执行以下三个任务:

1、监控(Monitoring):Sentinel会不断地检查主服务器和从服务器是否运作正常。
2、提醒(Notification):当被监控的某个Redis服务器出现问题时,Sentinel可以通过API向管理员或者其他应用程序发送通知。
3、自动故障迁移(Automatic failover): 当一个主服务器不能正常工作时,Sentinel会开始一次自动故障迁移操作,它会将失效主服务器的其中一个从服务器升级为新的主服务器,并让失效主服务器的其他从服务器改为复制新的主服务器;当客户端试图连接失效的主服务器时,集群也会向客户端返回新主服务器的地址, 使得集群可以使用新主服务器代替失效服务器。

Sentinel的工作方式

1、每个Sentinel以每秒钟一次的频率向它所知的Master,Slave以及其他Sentinel实例发送一个PING 命令。
2、如果一个实例(instance)距离最后一次有效回复 PING 命令的时间超过 down-after-milliseconds选项所指定的值,则这个实例会被Sentinel标记为主观下线。
3、如果一个Master被标记为主观下线,则正在监视这个Master的所有Sentinel要以每秒一次的频率确认 Master的确进入了主观下线状态。
4、当有足够数量的Sentinel(大于等于配置文件指定的值)在指定的时间范围内确认Master的确进入了主观下线状态,则Master会被标记为客观下线。
5、在一般情况下,每个Sentinel会以每 10 秒一次的频率向它已知的所有Master,Slave发送 INFO 命令。
6、当Master被Sentinel标记为客观下线时,Sentinel 向下线的Master的所有Slave发送INFO命令的频率会从10秒一次改为每秒一次。
7、若没有足够数量的Sentinel同意Master已经下线,Master的客观下线状态就会被移除。
8、若Master重新向Sentinel的PING命令返回有效回复,Master的主观下线状态就会被移除。

redis与Memcache的对比

1、Redis不仅仅支持简单的k/v类型的数据,同时还提供list,set,hash等数据结构的存储。
2、Redis支持数据的备份,即master-slave模式的数据备份。
3、Redis支持数据的持久化,可以将内存中的数据保持在磁盘中,重启的时候可以再次加载进行使用。

key不能超过250个字节;
value不能超过1M字节;
key的最大失效时间是30天;
只支持K-V结构,不提供持久化和主从同步功能。

redis事务

概念

Redis事务的本质是通过MULTI、EXEC、WATCH等一组命令的集合。事务支持一次执行多个命令,一个事务中所有命令都会被序列化。在事务执行过程,会按照顺序串行化执行队列中的命令,其他客户端提交的命令请求不会插入到事务执行命令序列中。

总结说:redis事务就是一次性、顺序性、排他性的执行一个队列中的一系列命令。

Redis事务的三个阶段

1、事务开始MULTI
2、命令入队
3、事务执行EXEC

redis 不支持回滚
Redis在事务失败时不进行回滚,而是继续执行余下的命令, 所以Redis的内部可以保持简单且快速。
如果在一个事务中的命令出现错误,那么所有的命令都不会执行
如果在一个事务中出现运行错误,那么正确的命令会被执行

WATCH命令是一个乐观锁,可以为Redis事务提供check-and-set(CAS)行为。可以监控一个或多个键,一旦其中有一个键被修改(或删除),之后的事务就不会执行,监控一直持续到EXEC命令。 MULTI命令用于开启一个事务,它总是返回OK。 MULTI执行之后,客户端可以继续向服务器发送任意多条命令,这些命令不会立即被执行,而是被放到一个队列中,当EXEC命令被调用时,所有队列中的命令才会被执行。 EXEC:执行所有事务块内的命令。返回事务块内所有命令的返回值,按命令执行的先后顺序排列。 当操作被打断时,返回空值nil。 通过调用DISCARD,客户端可以清空事务队列,并放弃执行事务, 并且客户端会从事务状态中退出。 UNWATCH命令可以取消watch对所有key的监控。

redis实现分布式锁

setNX命令

如果set以后挂了怎么办?给锁加一个过期时间。

过期时间内无法执行完怎么办?看门狗机制,给锁续期。

解锁的client必须是加锁的client,给一个ID。

使用过Redis做异步队列么,你是怎么用的?

一般使用list结构作为队列,rpush生产消息,lpop消费消息。当lpop没有消息的时候,要适当sleep一会再重试。
list还有个指令叫blpop,在没有消息的时候,它会阻塞住直到消息到来。

使用pub/sub主题订阅者模式,可以实现 1:N 的消息队列。

Redis IO多路复用技术

Redis的选举算法和流程是怎样的

Raft选举流程

采用心跳机制触发Leader选举。系统启动后,全部节点初始化为Follower,term为0.节点如果收到了RequestVote或者AppendEntries,就会保持自己的Follower身份。如果一段时间内没收到AppendEntries消息直到选举超时,说明在该节点的超时时间内还没发现Leader,Follower就会转换成Candidate,自己开始竞选Leader。一旦转化为Candidate,该节点立即开始下面几件事情:

1、增加自己的term。
2、启动一个新的定时器。
3、给自己投一票。
4、向所有其他节点发送RequestVote,并等待其他节点的回复。
如果在这过程中收到了其他节点发送的AppendEntries,就说明已经有Leader产生,自己就转换成Follower,选举结束。

如果在计时器超时前,节点收到多数节点的同意投票,就转换成Leader。同时向所有其他节点发送AppendEntries,告知自己成为了Leader。

每个节点在一个term内只能投一票,采取先到先得的策略,Candidate前面说到已经投给了自己,Follower会投给第一个收到RequestVote的节点。每个Follower有一个计时器,在计时器超时时仍然没有接受到来自Leader的心跳RPC, 则自己转换为Candidate, 开始请求投票,就是上面的的竞选Leader步骤。

如果多个Candidate发起投票,每个Candidate都没拿到多数的投票(Split Vote),那么就会等到计时器超时后重新成为Candidate,重复前面竞选Leader步骤。

Raft协议的定时器采取随机超时时间,这是选举Leader的关键。每个节点定时器的超时时间随机设置,随机选取配置时间的1倍到2倍之间。由于随机配置,所以各个Follower同时转成Candidate的时间一般不一样,在同一个term内,先转为Candidate的节点会先发起投票,从而获得多数票。多个节点同时转换为Candidate的可能性很小。即使几个Candidate同时发起投票,在该term内有几个节点获得一样高的票数,只是这个term无法选出Leader。由于各个节点定时器的超时时间随机生成,那么最先进入下一个term的节点,将更有机会成为Leader。连续多次发生在一个term内节点获得一样高票数在理论上几率很小,实际上可以认为完全不可能发生。一般1-2个term类,Leader就会被选出来。

Sentinel的选举流程

Sentinel集群正常运行的时候每个节点epoch相同,当需要故障转移的时候会在集群中选出Leader执行故障转移操作。Sentinel采用了Raft协议实现了Sentinel间选举Leader的算法,不过也不完全跟论文描述的步骤一致。Sentinel集群运行过程中故障转移完成,所有Sentinel又会恢复平等。Leader仅仅是故障转移操作出现的角色。

选举流程

1、某个Sentinel认定master客观下线的节点后,该Sentinel会先看看自己有没有投过票,如果自己已经投过票给其他Sentinel了,在2倍故障转移的超时时间自己就不会成为Leader。相当于它是一个Follower。
2、如果该Sentinel还没投过票,那么它就成为Candidate。
3、和Raft协议描述的一样,成为Candidate,Sentinel需要完成几件事情
1)更新故障转移状态为start
2)当前epoch加1,相当于进入一个新term,在Sentinel中epoch就是Raft协议中的term。
3)更新自己的超时时间为当前时间随机加上一段时间,随机时间为1s内的随机毫秒数。
4)向其他节点发送is-master-down-by-addr命令请求投票。命令会带上自己的epoch。
5)给自己投一票,在Sentinel中,投票的方式是把自己master结构体里的leader和leader_epoch改成投给的Sentinel和它的epoch。
4、其他Sentinel会收到Candidate的is-master-down-by-addr命令。如果Sentinel当前epoch和Candidate传给他的epoch一样,说明他已经把自己master结构体里的leader和leader_epoch改成其他Candidate,相当于把票投给了其他Candidate。投过票给别的Sentinel后,在当前epoch内自己就只能成为Follower。
5、Candidate会不断的统计自己的票数,直到他发现认同他成为Leader的票数超过一半而且超过它配置的quorum(quorum可以参考《redis sentinel设计与实现》)。Sentinel比Raft协议增加了quorum,这样一个Sentinel能否当选Leader还取决于它配置的quorum。
6、如果在一个选举时间内,Candidate没有获得超过一半且超过它配置的quorum的票数,自己的这次选举就失败了。
7、如果在一个epoch内,没有一个Candidate获得更多的票数。那么等待超过2倍故障转移的超时时间后,Candidate增加epoch重新投票。
8、如果某个Candidate获得超过一半且超过它配置的quorum的票数,那么它就成为了Leader。
9、与Raft协议不同,Leader并不会把自己成为Leader的消息发给其他Sentinel。其他Sentinel等待Leader从slave选出master后,检测到新的master正常工作后,就会去掉客观下线的标识,从而不需要进入故障转移流程。