Redis

value对象

字符串对象

编码

  • int:整数值。(long长度)
  • embstr:字符串长度小于等于39字节。内存分配和释放次数从raw编码的两次降低为一次,且所有的数据都保存在一块连续的内存里面
  • raw:字符串长度大于39字节。

常用命令

字符串:SET/GET/MSET/MGET/APPEND/SETRANGE/GETRANGE/STRLEN
数值:INCR
bitmap:SETBIT/BITCOUNT/BITPOS/BITOP

使用场景

常常用来做计数器这类自增自减的功能,可用在粉丝数、微博数等。bitmap可以用来记录用户一年中是否登录的数据

列表对象

可以当作栈、队列、数组、阻塞队列

编码

  • ziplist:(1)列表对象保存的所有字符串元素的长度都小于64字节(2)列表对象保存的元素数量小于512个,同时满足上述条件会使用ziplist编码(这两个条件的上限值是可以手动改的)
  • linkedlist:不满足上述两种条件的列表对象使用linkedlist编码

常用命令

LPUSH/RPUSH/LPOP/RPOP/LRANGE/LINDEX/LSET/LREM/LINSERT/LLEN

使用场景

应用场景可以有微博的关注列表、粉丝列表、消息列表等。lrange函数,可以从某个元素开始读取多少个元素,可用来实现分页功能。

哈希对象

编码

  • ziplist:(1)哈希对象保存的所有键值对的键和值的字符串的长度都小于64字节(2)哈希对象保存的键值对数量小于512个,同时满足上述条件会使用ziplist编码(这两个条件的上限值是可以手动改的)
  • hashtable:不满足上述两种条件的列表对象使用hashtable编码

常用命令

HSET/HMSET/HGET/HMGET/HKEYS/HVALS/HGETAL

使用场景

适合存储对象类型信息,例如个人信息、商品信息等。

集合对象

编码

  • intset:(1)集合对象保存的所有元素都是整数值(2)集合对象保存的的元素数量不超过512个,同时满足上述条件会使用ziplist编码(这两个条件的上限值是可以手动改的)
  • hashtable:不满足上述两种条件的列表对象使用hashtable编码

常用命令

SADD/SMEMBERS/SREM/SINTER/SINTERSTORE/SUNION/SDIFF/SRANDMEMBER

使用场景

因为set集合支持交集、并集操作,因此适合做共同好友等功能

有序集合对象

使用跳跃表和字典来实现

编码

ziplist:(1)有序集合保存的元素数量小于128个(2)有序集合保存的所有元素成员的长度都小于64字节,同时满足上述条件会使用ziplist编码(这两个条件的上限值是可以手动改的)
skiplist:不满足上述两种条件的列表对象使用skiplist编码

常用命令

ZADD/ZRANGE/ZREVRANGE/ZSCORE/ZUNIONSTORE

使用场景

游戏排名、微博热点话题等使用场景。

跳跃表

Redis的zset使用了跳跃表+字典作为底层数据结构。

跳跃表(skiplist) 是一种有序数据结构,它通过在每个节点中维持多个指向其他节点的指针,从而达到快速访问节点的目的。
跳跃表支持平均O(logM)、最坏0(N) 复杂度的节点查找,还可以通过顺序性操作来批量处理节点。
在大部分情况下,跳跃表的效率可以和平衡树相媲美,并且因为跳跃表的实现比平衡树要来得更为简单,所以有不少程序都使用跳跃表来代替平衡树。
在这里插入图片描述

Redis中的跳跃表由zSkipList和zSkipListNode两个结构组成

  • zSkipList结构包含四个属性,分别是指向表头节点的指针,指向表尾节点的指针,所有节点中最大的层数(不包括表头节点),跳跃表的长度。
  • zSkipListNode中有层、前进指针、跨度、后退指针、分值、和成员对象等属性。每个节点的层数不固定,在1-32之间随机。它的每一层都有指向表尾节点方向的前进指针和跨度。跨度是用来记录前进指针连接的两个节点之间的距离。后退指针指向上一个节点,分值是个double类型的浮点数,跳跃表中所有节点按分值从小到大排序。成员对象是个指针,指向一个字符串对象,字符串对象保存一个SDS值,跳跃表中可以分值相同,但成员对象需要是唯一的,成员对象较小的节点会排在前面。
    • 层:跳跃表节点的level数组可以包含多个元素,每个元素都包含一个指向其他节点的指针,程序可以通过这些层来加快访问其他节点的速度,一般来说,层的数量越多,访问其他节点的速度就越快。
      每次创建一个新跳跃表节点的时候,程序都根据幂次定律(powerlaw,越大的数出现的概率越小) 随机生成一个介于1和32之间的值作为level数组的大小,这个大小就是层的“高度”。

过期策略

https://blog.csdn.net/qq_38022739/article/details/125822653

内存淘汰策略

Redis一共提供了8种淘汰策略,默认的策略为noeviction

LRU(2种)、随机淘汰(2种)、选择即将要过期的淘汰、LFU(2种)、禁止

  • volatile-lru,针对设置了过期时间的key,使用lru算法进行淘汰。
  • allkeys-lru,针对所有key使用lru算法进行淘汰。
  • volatile-lfu,针对设置了过期时间的key,使用lfu算法进行淘汰。
  • allkeys-lfu,针对所有key使用lfu算法进行淘汰。
  • volatile-random,从所有设置了过期时间的key中使用随机淘汰的方式进行淘汰。
  • allkeys-random,针对所有的key使用随机淘汰机制进行淘汰。
  • volatile-ttl,针对设置了过期时间的key,越早过期的越先被淘汰。
  • noeviction,不会淘汰任何数据,当使用的内存空间超过 maxmemory 值时,再有写请求来时返回错误。

RDB和AOF持久化

简单来说,RDB是保存一份数据快照,数据快照是一份经过压缩的二进制文件。而AOF是保存一份写命令,当要恢复时重新执行写命令。

RDB持久化

RDB持久化将某个时间点上的数据库状态保存到一个RDB文件中,RDB文件是一个经过压缩的二进制文件。有两个Redis命令可以用于生成RDB文件,一个是SAVE,另一个是BGSAVE。SAVE命令会阻塞Redis服务器进程,BGSAVE命令会派生出一个子进程。

Redis允许用户通过设置服务器配置的save选项,让服务器每隔一段时间自动执行一次BGSAVE命令(例如 [save 900 1] 表示服务器在900秒之内,对数据库进行了至少1次修改BGSAVE命令就会被执行)。

服务器中维持着一个dirty计数器,以及一个lastsave属性。

  • dirty计数器记录距离上一次成功执行SAVE命令或者BGSAVE命令之后,服务器对数据库状态(服务器中的所有数据库)进行了多少次修改(包括写入、删除、更新等操作)
  • lastsave属性是一个UNIX时间戳,记录了服务器上一次成功执行SAVE命令或者BGSAVE命令的时间。

AOF持久化

AOF持久化保存数据库状态的方法是将服务器执行的SET、SADD、RPUSH三个命令保存到AOF文件中。AOF持久化功能的实现可以分为命令追加(append)、文件写入、文件同步(sync)三个步骤。

  • 命令追加:服务器在执行完一个写命令之后,会以协议格式将被执行的写命令追加到服务器状态的aof_buf缓冲区的末尾。
  • 文件写入、文件同步(sync):Redis的服务器进程就是一个事件循环。在服务器每次结束一个事件循环之前,它都会调用flushAppendOnlyFile函数,考虑是否需要将aof_buf缓冲区中的内容写入和保存到AOF文件里面。

AOF文件的更新频率通常比RDB文件的更新频率高,服务器会优先使用AOF来还原数据库状态。

AOF重写

Redis提供了AOF文件重写(rewrite)功能。Redis服务器可以创建一个新的AOF文件来替代现有的AOF文件,新旧两个AOF文件所保存的数据库状态相同,但新AOF文件不会包含任何浪费空间的冗余命令,所以新AOF文件的体积通常会比旧AOF文件的体积要小得多。

AOF重写是一个有歧义的名字,该功能是通过读取数据库中的键值对来实现的,程序无须对现有AOF文件进行任何读入、分析或者写入操作。

AOF后台重写

Redis将AOF重写程序放到子进程里执行,这样做可以同时达到两个目的

  • 子进程进行AOF重写期间,服务器进程(父进程)可以继续处理命令请求。
  • 子进程带有服务器进程的数据副本,使用子进程而不是线程,可以在避免使用锁的情况下,保证数据的安全性。

为了解决数据不一致的问题,Redis服务器设置了一个AOF重写缓冲区(另外还有个AOF缓冲区)。在执行BGREWRITEAOF命令时,AOF重写缓冲区会在子进程创建新AOF文件期间,记录服务器执行的所有写命令。当子进程完成创建新AOF文件的工作之后,服务器会将重写缓冲区中的所有内容追加到新AOF文件的末尾,使得新的AOF文件保存的数据库状态与现有的数据库状态一致。最后,服务器用新的AOF文件替换旧的AOF文件,以此来完成AOF文件重写操作。

AOF缓冲区的内容会定期被写入和同步到AOF文件中,这里需要注意区别于AOF重写缓冲区

Reactor线程模式的文件事件处理器

文件事件(file event):
Redis服务器通过套接字与客户端(或者其他Redis服务器)进行连接,而文件事件就是服务器对套接字操作的抽象。服务器与客户端(或者其他服务器)的通信会产生相应的文件事件,而服务器则通过监听并处理这些事件来完成一系列网络通信操作。
文件事件是对套接字操作的抽象,每当一个套接字准备好执行连接应答(accept)、写人、读取、关闭等操作时,就会产生一个文件事件。因为一个服务器通常会连接多个套接字,所以多个文件事件有可能会并发地出现。

  • 文件事件处理器使用I/O多路复用( multiplexing )程序来同时监听多个套接字,并根据套接字目前执行的任务来为套接字关联不同的事件处理器。
  • 当被监听的套接字准备好执行连接应答(accept)、读取(read)、写入(write)、关闭(close)等操作时,与操作相对应的文件事件就会产生,这时文件事件处理器就会调用套接字之前关联好的事件处理器来处理这些事件。

文件事件处理器的构成

文件事件处理器由四个部分组成,它们分别是套接字、I/O 多路复用程序、文件事件分派器( dispatcher),以及事件处理器。
I/O多路复用程序负责监听多个套接字,并向文件事件分派器传送那些产生了事件的套接字。
图12-1 156
I/O多路复用程序会将所有产生事件的套接字都放到一个队列里面,然后通过这个队列,以有序( sequentially)、同步( synchronously)、每次一个套接字的方式向文件事件分派器传送套接字。当上一个套接字产生的事件被处理完毕之后(该套接字为事件所关联的事件处理器执行完毕), I/O 多路复用程序才会继续向文件事件分派器传送下一个套接字。

图12-2 156
Redis的I/O多路复用程序的所有功能都是通过包装常见的select、epoll、evport和kqueue这些I/O多路复用函数库来实现的

图12-3 157

事件的类型 12.1.3

I/O多路复用程序可以监听多个套接字的ae.h/AE_READABLE 事件和ae.h/AE_WRITABLE事件,这两类事件和套接字操作之间的对应关系如下:

  • 当套接字变得可读时(客户端对套接字执行write操作,或者执行close操作),或者有新的可应答(acceptable)套接字出现时(客户端对服务器的监听套接字执行connect操作),套接字产生AE_READABLE 事件。
  • 当套接字变得可写时(客户端对套接字执行read操作),套接字产生AE_WRITABLE事件。

I/O多路复用程序允许服务器同时监听套接字的AE_READABLE事件和AE_WRITABLE事件,如果一个套接字同时产生了这两种事件,那么文件事件分派器会优先处理AE_READABLE事件,等到AE_ READABLE 事件处理完之后,才处理AE_WRITABLE 事件。
这也就是说,如果一个套接字又可读又可写的话,那么服务器将先读套接字,后写套接字。

连接应答处理器

这个处理器用于对连接服务器监听套接字的客户端进行应答。
当Redis服务器进行初始化的时候,程序会将这个连接应答处理器和服务器监听套接字的AE_READABLE事件关联起来,当有客户端用sys/socket.h/connect函数连接服务器监听套接字的时候,套接字就会产生AE_READABLE事件,引发连接应答处理器执行。

命令请求处理器

这个处理器负责从套接字中读人客户端发送的命令请求内容。
当一个客户端通过连接应答处理器成功连接到服务器之后,服务器会将客户端套接字的AE_READABLE 事件和命令请求处理器关联起来,当客户端向服务器发送命令请求的时候,套接字就会产生AE_READABLE事件,引发命令请求处理器执行,并执行相应的套接字读入操作。在客户端连接服务器的整个过程中,服务器都会一直为客户端套接字的AE_READABLE事件关联命令请求处理器。

在这里插入图片描述

命令回复处理器

这个处理器负责将服务器执行命令后得到的命令回复通过套接字返回给客户端。
当服务器有命令回复需要传送给客户端的时候,服务器会将客户端套接字的AE_WRITABLE事件和命令回复处理器关联起来,当客户端准备好接收服务器传回的命令回复时,就会产生AE_WRITABLE 事件,引发命令回复处理器执行,并执行相应的套接字写入操作。当命令回复发送完毕之后,服务器就会解除命令回复处理器与客户端套接字的AEWRITABLE事件之间的关联。

图12-6 159

一次完整的客户端和服务端连接事件示例

  • 假设一个Redis服务器正在运作,那么这个服务器的监听套接字AE_READABLE事件应该正处于监听状态之下,而该事件所对应的处理器为连接应答处理器。
  • 如果这时有一个Redis客户端向服务器发起连接,那么监听套接字将产生AE_READABLE事件,触发连接应答处理器执行。处理器会对客户端的连接请求进行应答,然后创建客户端套接字,以及客户端状态,并将客户端套接字的AE_READABLE 事件与命令请求处理器进行关联,使得客户端可以向主服务器发送命令请求。
  • 之后,假设客户端向主服务器发送一个命令请求,那么客户端套接字将产生AE_READABLE事件,引发命令请求处理器执行,处理器读取客户端的命令内容,然后传给相关程序去执行。
  • 执行命令将产生相应的命令回复,为了将这些命令回复传送回客户端,服务器会将客户端套接字的AE_WRITABLE事件与命令回复处理器进行关联。当客户端尝试读取命令回复的时候,客户端套接字将产生AE_WRITABLE 事件,触发命令回复处理器执行,当命令回复处理器将命令回复全部写人到套接字之后,服务器就会解除客户端套接字的AE_WRITABLE 事件与命令回复处理器之间的关联。
    图12-7 160

主从同步

PSYNC命令具有完整重同步(full resynchronization)和部分重同步(partial resynchronization)两种模式:

  • 其中完整重同步用于处理初次复制情况:完整重同步的执行步骤是通过让主服务器创建并发送RDB文件,以及向从服务器发送保存在缓冲区里面的写命令来进行同步。在这里插入图片描述

  • 而部分重同步则用于处理断线后重复制情况:当从服务器在断线后重新连接主服务器时,如果条件允许,主服务器可以将主从服务器连接断开期间执行的写命令发送给从服务器,从服务器只要接收并执行这些写命令,就可以将数据库更新至主服务器当前所处的状态。

部分重同步

部分重同步功能由以下三个部分构成:

  • 主服务器的复制偏移量(replication offset)和从服务器的复制偏移量。
  • 主服务器的复制积压缓冲区( replication backlog )。
  • 服务器的运行ID(run ID)。.

复制偏移量

执行复制的双方——主服务器和从服务器会分别维护一个复制偏移量:

  • 主服务器每次向从服务器传播N个字节的数据时,就将自己的复制偏移量的值加上N。
  • 从服务器每次收到主服务器传播来的N个字节的数据时,就将自己的复制偏移量的值加上N。

通过对比主从服务器的复制偏移量,程序可以很容易地知道主从服务器是否处于一致状态:

  • 如果主从服务器处于一致状态,那么主从服务器两者的偏移量总是相同的。
  • 相反,如果主从服务器两者的偏移量并不相同,那么说明主从服务器并未处于一致状态。
    在这里插入图片描述

复制积压缓冲区

复制积压缓冲区是由主服务器维护的一个固定长度(fixed-size)先进先出(FIFO)队列,默认大小为1MB。

当主服务器进行命令传播时,它不仅会将写命令发送给所有从服务器,还会将写命令入队到复制积压缓冲区里面。在这里插入图片描述
主服务器的复制积压缓冲区里面会保存着一部分最近传播的写命令,并且复制积压缓冲区会为队列中的每个字节记录相应的复制偏移量。
在这里插入图片描述
当从服务器重新连上主服务器时,从服务器会通过PSYNC命令将自己的复制偏移量offset发送给主服务器,主服务器会根据这个复制偏移量来决定对从服务器执行何种同步操作:

  • 如果offset偏移量之后的数据(也即是偏移量offset+1开始的数据)仍然存在于复制积压缓冲区里面,那么主服务器将对从服务器执行部分重同步操作。
  • 相反,如果offset偏移量之后的数据已经不存在于复制积压缓冲区,那么主服务器将对从服务器执行完整重同步操作。

服务器运行ID

除了复制偏移量和复制积压缓冲区之外,实现部分重同步还需要用到服务器运行ID( run ID):

  • 每个Redis服务器,不论主服务器还是从服务,都会有自己的运行ID。
  • 运行ID在服务器启动时自动生成,由40个随机的十六进制字符组成,例如53b9b28df8042fdc9ab5e3fcbbbabffld5dce2b3。

当从服务器对主服务器进行初次复制时,主服务器会将自己的运行ID传送给从服务器,而从服务器则会将这个运行ID保存起来。

当从服务器断线并重新连上一个主服务器时,从服务器将向当前连接的主服务器发送之前保存的运行ID:

  • 如果从服务器保存的运行ID和当前连接的主服务器的运行ID相同,那么说明从服务器断线之前复制的就是当前连接的这个主服务器,主服务器可以继续尝试执行部分重同步操作。
  • 相反地,如果从服务器保存的运行ID和当前连接的主服务器的运行ID并不相同,那么说明从服务器断线之前复制的主服务器并不是当前连接的这个主服务器,主服务器将对从服务器执行完整重同步操作。

复制

通过向从服务器发送SLAVEOF命令,我们可以让一个从服务器去复制一个主服务器:
SLAVEOF < master_ ip> < master_ port>

详细步骤

步骤1 :设置主服务器的地址和端口

从服务器首先要做的就是将客户端给定的主服务器IP地址以及端口保存到服务器状态的masterhost属性和masterport属性里面:
在这里插入图片描述

步骤2 :建立套接字连接

在这里插入图片描述

步骤3:发送PING命令

虽然主从服务器成功建立起了套接字连接,但双方并未使用该套接字进行过任何通信,通过发送PING命令可以检查套接字的读写状态是否正常。
因为复制工作接下来的几个步骤都必须在主服务器可以正常处理命令请求的状态下才能进行,通过发送PING命令可以检查主服务器能否正常处理命令请求。
在这里插入图片描述

步骤4:身份验证

在这里插入图片描述

步骤5:发送端口信息

在身份验证步骤之后,从服务器将执行命令向主服务器发送从服务器的监听端口号。
在这里插入图片描述

步骤6:同步

在这一步,从服务器将向主服务器发送PSYNC命令,执行同步操作,并将自己的数据库更新至主服务器数据库当前所处的状态。

步骤7:命令传播

当完成了同步之后,主从服务器就会进入命令传播阶段,这时主服务器只要一直将自己执行的写命令发送给从服务器,而从服务器只要一直接收并执行主服务器发来的写命令,就可以保证主从服务器一直保持一致了。

心跳检测

在命令传播阶段,从服务器默认会以每秒一次的频率,向主服务器发送命令:
REPLCONF ACK < replication offset>
其中replication_ offset 是从服务器当前的复制偏移量。

发送REPLCONF ACK命令对于主从服务器有三个作用:

  • 检测主从服务器的网络连接状态。
  • 辅助实现min-slaves选项。
  • 检测命令丢失。

哨兵机制

Sentinel (哨岗、哨兵)是Redis的高可用性( high availability) 解决方案:由一个或多个Sentinel实例( instance)组成的Sentinel系统( systemn)可以监视任意多个主服务器,以及这些主服务器属下的所有从服务器,并在被监视的主服务器进入下线状态时,自动将下线主服务器属下的某个从服务器升级为新的主服务器,然后由新的主服务器代替已下线的主服务器继续处理命令请求。

启动并初始化Sentinel

Sentinel本质上只是一个运行在特殊模式下的Redis服务器。当一个Sentinel启动时,它需要执行以下步骤:

  1. 初始化服务器。
  2. 将普通Redis服务器使用的代码替换成Sentinel专用代码。
  3. 初始化Sentinel状态。
  4. 根据给定的配置文件,初始化Sentinel的监视主服务器列表。
  5. 创建连向主服务器的网络连接。

获取主服务器信息

Sentinel默认会以每十秒一次的频率,通过命令连接向被监视的主服务器发送INFO命令,并通过分析INFO命令的回复来获取主服务器的当前信息。
在这里插入图片描述

获取从服务器信息

当Sentinel发现主服务器有新的从服务器出现时,Sentinel除了会为这个新的从服务器创建相应的实例结构之外,Sentinel还会创建连接到从服务器的命令连接和订阅连接。

在这里插入图片描述

向主服务器和从服务器发送信息

在默认情况下,Sentinel会以每两秒一次的频率,通过命令连接向所有被监视的主服务器和从服务器发送命令。命令中包含了Sentinel和主服务器有关的参数信息。

接收来自主服务器和从服务器的频道信息

当Sentinel与一个主服务器或者从服务器建立起订阅连接之后,Sentinel就会通过订阅连接,向服务器发送命令。
对于每个与Sentinel连接的服务器,Sentinel既通过命令连接向服务器的频道发送信息,又通过订阅连接从服务器的频道接收信息。
在这里插入图片描述
对于监视同一个服务器的多个Sentinel来说,一个Sentinel发送的信息会被其他Sentinel接收到,这些信息会被用于更新其他Sentinel对发送信息Sentinel的认知,也会被用于更新其他Sentinel对被监视服务器的认知。
在这里插入图片描述

检测主观下线状态

在默认情况下,Sentinel 会以每秒一次的频率向所有与它创建了命令连接的实例(包括主服务器、从服务器、其他Sentinel在内)发送PING命令,并通过实例返回的PING命令回复来判断实例是否在线。
Sentinel配置文件中的down-after-milliseconds选项指定了Sentinel判断实例进人主观下线所需的时间长度:如果一个实例在down-after-milliseconds毫秒内,连续向Sentinel返回无效回复,那么Sentinel会修改这个实例所对应的实例结构,在结构的flags属性中打开SRI_S_DOWN标识,以此来表示这个实例已经进人主观下线状态。

检查客观下线状态

当Sentinel将一个主服务器判断为主观下线之后,为了确认这个主服务器是否真的下线了,它会向同样监视这一主服务器的其他Sentinel进行询问,看它们是否也认为主服务器已经进入了下线状态(可以是主观下线或者客观下线)。当Sentinel从其他Sentinel那里接收到足够数量(自定义配置)的已下线判断之后,Sentinel就会将从服务器判定为客观下线,并对主服务器执行故障转移操作。

选举领头Sentinel

当一个主服务器被判断为客观下线时,监视这个下线主服务器的各个Sentinel会进行协商,选举出一个领头Sentinel,并由领头Sentinel对下线主服务器执行故障转移操作。

以下是Redis选举领头Sentinel 的规则和方法:

  1. 所有在线的Sentinel都有被选为领头Sentinel 的资格,换句话说,监视同一个主服务器的多个在线Sentinel中的任意一个都有可能成为领头Sentinel。
  2. 每次进行领头Sentinel选举之后,不论选举是否成功,所有Sentinel的配置纪元( configuration epoch)的值都会自增一次。配置纪元实际上就是一个计数器,并没有什么特别的。
  3. 在一个配置纪元里面,所有Sentinel都有一次将某个Sentinel设置为局部领头Sentinel的机会,并且局部领头一旦设置,在这个配置纪元里面就不能再更改。
  4. 每个发现主服务器进人客观下线的Sentinel都会要求其他Sentinel将自已设置为局部领头Sentinel。
  5. 当一个Sentinel (源Sentinel) 向另一个Sentinel (目标Sentinel) 发送SENTINEL is-master-down-by-addr命令,并且命令中的runid参数不是*符号而是源Sentinel的运行ID时,这表示源Sentinel要求目标Sentinel将前者设置为后者的局部领头Sentinel。
  6. Sentinel设置局部领头Sentinel的规则是先到先得:最先向目标Sentinel发送设置要求的源Sentinel将成为目标Sentinel的局部领头Sentinel,而之后接收到的所有设置要求都会被目标Sentinel拒绝。
  7. 目标Sentinel在接收到SENTINEL is-master-down-by-addr命令之后,将向源Sentinel返回一条命令回复,回复中的leader_ runid 参数和leader_ epoch参数分别记录了目标Sentinel的局部领头Sentinel的运行ID和配置纪元。
  8. 源Sentinel在接收到目标Sentinel返回的命令回复之后,会检查回复中leader_epoch参数的值和自己的配置纪元是否相同,如果相同的话,那么源Sentinel继续取出回复中的leader_ runid 参数,如果leader_ runid 参数的值和源Sentinel的运行ID一致,那么表示目标Sentinel将源Sentinel 设置成了局部领头Sentinel。
  9. 如果有某个Sentinel被半数以上的Sentinel设置成了局部领头Sentinel,那么这个Sentinel成为领头Sentinel。举个例子,在一个由10 个Sentinel组成的Sentinel系统里面,只要有大于等于10/2+1=6个Sentinel将某个Sentinel设置为局部领头Sentinel,那么被设置的那个Sentinel 就会成为领头Sentinel。
  10. 因为领头Sentinel的产生需要半数以上Sentinel的支持,并且每个Sentinel在每个配置纪元里面只能设置一次局部领头Sentinel,所以在一个配置纪元里面,只会出现一个领头Sentinel。
  11. 如果在给定时限内,没有一个Sentinel被选举为领头Sentinel, 那么各个Sentinel将在一段时间之后再次进行选举,直到选出领头Sentinel 为止。

故障转移

在选举产生出领头Sentinel之后,领头Sentinel将对已下线的主服务器执行故障转移操作,该操作包含以下三个步骤:

  1. 在已下线主服务器属下的所有从服务器里面,挑选出一一个从服务器,并将其转换为主服务器。
  2. 让已下线主服务器属下的所有从服务器改为复制新的主服务器。
  3. 将已下线主服务器设置为新的主服务器的从服务器,当这个旧的主服务器重新上线时,它就会成为新的主服务器的从服务器。

新的主服务器是怎样挑选出来的

领头Sentinel会将已下线主服务器的所有从服务器保存到一个列表里面,然后按照以下规则,一项一项地对列表进行过滤:

  1. 删除列表中所有处于下线或者断线状态的从服务器,这可以保证列表中剩余的从服务器都是正常在线的。
  2. 删除列表中所有最近五秒内没有回复过领头Sentinel的INFO命令的从服务器,这可以保证列表中剩余的从服务器都是最近成功进行过通信的。
  3. 删除所有与已下线主服务器连接断开超过down-after-milliseconds * 10
    毫秒的从服务器: down-after-milliseconds选项指定了判断主服务器下线所需的时间,而删除断开时长超过down-after -milliseconds * 10 毫秒的从服务器,则可以保证列表中剩余的从服务器都没有过早地与主服务器断开连接,换句话说,列表中剩余的从服务器保存的数据都是比较新的。
  4. 之后,领头Sentinel将根据从服务器的优先级,对列表中剩余的从服务器进行排序,并选出其中优先级最高的从服务器。如果有多个具有相同最高优先级的从服务器,那么领头Sentinel将按照从服务器的复制偏移量,对具有相同最高优先级的所有从服务器进行排序,并选出其中偏移量最大的从服务器(复制偏移量最大的从服务器就是保存着最新数据的从服务器)。
  5. 最后,如果有多个优先级最高、复制偏移量最大的从服务器,那么领头Sentinel 将按照运行ID对这些从服务器进行排序,并选出其中运行ID最小的从服务器。

集群

节点

一个Redis集群通常由多个节点(node)组成,在刚开始的时候,每个节点都是相互独立的,它们都处于一个只包含自己的集群当中,要组建一个真正可工作的集群,我们必须将各个独立的节点连接起来,构成一个包含多个节点的集群。

连接各个节点的工作可以使用CLUSTER MEET命令来完成:

CLUSTER MEET

Redis服务器在启动时会根据cluster-enabled配置选项是否为yes来决定是否开始服务器的集群模式,为yes则开启服务器的集群模式成为一个节点,为no则开启服务器的单机(stand alone)模式成为一个普通Redis服务器。

槽指派

Redis集群通过分片的方式来保存数据库中的键值对:集群的整个数据库被分为16384(2^14)个槽(slot),数据库中的每个键都属于这16384个槽的其中一个,集群中的每个节点可以处理0个或最多16384个槽。

当数据库中的16384个槽都有节点在处理时,集群处于上线状态(ok);相反地,如果数据库中有任何一个槽没有得到处理,那么集群处于下线状态(fail)。

通过向节点发送CLUSTER ADDSLOTS命令,可以将一个或多个槽指派(assign)给节点负责:

CLUSTER ADDSLOTS [slot …]

节点会将自己的slot数据通过消息发送给集群中的其他节点,以此来告知其他节点自己目前负责处理哪些槽。

在集群中执行命令

当客户端向节点发送与数据库键有关的命令时,接收命令的节点会计算出命令要处理数据库键属于哪个槽,并检查这个槽是否指派给了自己:

  • 如果键所在的槽正好就指派给了当前节点,那么节点直接执行这个命令。
  • 如果键所在的槽并没有指派给当前节点,那么节点会向客户端返回一个MOVED错误,指引客户端转向(redirect)至正确的节点,并再次发送之前想要执行的命令。

Redis图263 17-18

重新分片

Redis集群的重新分片操作可以将任意数量已经指派给某个节点(源节点)的槽改为指派给另一个节点(目标节点),并且相关槽所属的键值对也会从源节点被移动到目标节点。

重新分片操作可以在线(online)进行,在重新分片的过程中,集群不需要下线,并且源节点和目标节点都可以继续处理命令请求。

Redis图271 17-25

ASK错误

在进行重新分片期间,源节点向目标节点迁移一个槽的过程中,可能会出现这样一种情况:属于被迁移槽的一部分键值对保存在源节点里面,而另一部分键值对则保存在目标节点里面。当客户端向源节点发送一个与数据库键有关的命令,并且命令要处理的数据库键恰好就属于正在被迁移的槽时:

  • 源节点会现在自己的数据库里面查找指定的键,如果找到的话,就直接执行客户端发送的命令。
  • 如果源节点没能在自己的数据库里面找到指定的键,那么这个键有可能已经被迁移到了目标节点,源节点将向客户端返回一个ASK错误,指引客户端转向正在导入槽的目标节点,并再次发送之前想要执行的命令。

Redis图271 17-26

ASK错误和MOVED错误的区别

ASK错误和MOVED错误都会导致客户端转向,它们的区别在于:

  • MOVED错误代表槽的负责权已经从一个节点转移到了另一个节点:在客户端收到关于槽i的MOVED错误之后,客户端每次遇到关于槽i的命令请求时,都可以直接将命令请求发送至MOVED错误所指向的节点,因为该节点就是目前负责槽i的节点。
  • 与此相反,ASK错误只是两个节点在迁移槽的过程中使用的一种临时措施:在客户端收到关于槽i的ASK错误之后,客户端只会在接下来的一次命令请求中将关于槽i的命令请求发送至ASK错误所指示的节点,但这种转向不会对客户端今后发送关于槽i的命令请求产生任何影响,客户端仍然会将关于槽i的命令请求发送至目前负责处理槽i的节点,除非ASK错误再次出现。

复制和故障转移

Redis集群中的节点分为主节点(master)和从节点(slave),其中主节点用于处理槽,而从节点则用于复制某个主节点,并在被复制的主节点下线时,代替下线主节点继续处理命令请求。

消息

集群中的各个节点通过发送和接收消息(message)来进行通信,我们称发送消息的节点为发送者( sender),接收消息的节点为接收者( receiver )。
节点发送的消息主要有以下五种:

  • MEET消息:CLUSTER MEET命令
  • PING消息:ping消息
  • PONG消息:回复MEET或PING消息
  • FAIL消息:当一个主节点A判断另一个主节点B已经进入FAIL状态时,节点A会向集群广播一条关于节点B的FAIL消息,所有收到这条消息的节点都会立即将节点B标记为已下线。
  • PUBLISH消息:当节点接收到一个PUBLISH命令时,节点会执行这个命令,并向集群广”播一条PUBLISH消息,所有接收到这条PUBLISH消息的节点都会执行相同的PUBLISH命令。

缓存雪崩、缓存击穿、缓存穿透、脑裂

  • 缓存雪崩:指的是缓存中大量key在同一时间失效,或者缓存服务器宕机导致的大量请求直接访问数据库。

    使用redis集群+队列限流+随机过期时间

  • 缓存击穿:热点key过期,大量访问请求到数据库。

    设置热点key永不过期+限流

  • 缓存穿透:请求不存在的key,会导致请求一定需要去访问数据库。

    对key进行有效性判断,返回key-null保存在缓存中

  • 脑裂:在主从集群中,同时有两个主节点他们都能接收写请求。而脑裂最直接影响的就是客户端不知道往哪个主节点写入数据,结果就是不同的客户端往不同的主节点写入数据。而且严重的会导致数据丢失。

    产生脑裂的原因:https://blog.csdn.net/Drftyytf/article/details/122778395图

    redis提供了两个配置项来限制主库的请求处理,分别是min-slaves-to-write 和 min-slaves-max-lag
    min-slaves-to-write:这个配置项设置了主库能进行数据同步的最少从库数量
    min-slaves-max-lag:这个配置项设置了主从库间进行数据复制时,从库给主库发送的ACK消息的最大延迟。

    简单地说,设置参数少于n个从服务器,或从服务器延迟都大于n秒则拒绝执行新的写命令。

    有了这两个配置就可以轻松解决脑裂问题了,首先既然原主库是假故障,它在假故障期间是无法响应哨兵心跳的,也不能和从库进行同步,自然也就无法和从库进行 ACK 确认了。这样一来,min-slaves-to-write 和 min-slaves-max-lag 的组合要求就无法得到满足,原主库就会被限制接收客户端请求,客户端也就不能在原主库中写入新数据了。等到新主库上线时,就只有新主库能接收和处理客户端请求,此时,新写的数据会被直接写到新主库中。而原主库会被哨兵降为从库,即使它的数据被清空了,也不会有新数据丢失。

Redis 双写一致性

延时双删

延时双删策略最终将redis删除掉,等下一次查询的时候会把最新的数据缓存到redis中,虽然解决了缓存不一致性,但是牺牲了性能,
在这里插入图片描述

先更新数据库再删缓存

先更新数据库再删缓存可能会导致以下问题
https://www.jianshu.com/p/04bffb205afc?u_atoken=189f044c-7dad-4e97-a267-05878b1c1f76&u_asession=01dAmRIdaE7G_vBZhGxI2su_fjbTve_ig-2eEg1DOKDXzPJ-1w8_1m-W-Hmp8T-HnBX0KNBwm7Lovlpxjd_P_q4JsKWYrT3W_NKPr8w6oU7K8iQWLaHD-cHvyj_T1PrWjCPpcarp92QKzyJKyYjREPlmBkFo3NEHBv0PZUm6pbxQU&u_asig=05cyib6BOG8D9V4r7YUMQ1OICmDIBT_4rJtdFYVL23P12vnadNZ_UvY-HmNYX6GRhsN5gQAYMQjW7x2o42kOpiVhNK00BUJQQ4GU1HGecCNLYooGvCmPOJjaemXqZukJL1175BV-7lOvkThQYvUQeuit_bgEIsMQpyY1gdHRbFHyX9JS7q8ZD7Xtz2Ly-b0kmuyAKRFSVJkkdwVUnyHAIJzbil4Fcfe97Pr4CQQrVryhLRyxBP-trUKkcjOIaB-mjfWPRPQyB_SKrj-61LB_f61u3h9VXwMyh6PgyDIVSG1W-3Gp3AXLpYtkpSwl1XbrvWHANw4_8BpU31TkZPiPBinkUizuAXCQvuY-SNsi3juMjRGlb4RUGH2PLCDlvKNmXdmWspDxyAEEo4kbsryBKb9Q&u_aref=%2FkftN%2BlCAGeOi2%2BQOak88ttbic0%3D
但由于读的效率通常比写的效率快很多,所以大部分是以下情况。能保证双写一致性在这里插入图片描述

事务(不常用)

MULTIEXECDISCARDWATCH 是 Redis 事务相关的命令。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值