Redis原理及拓展(持久化和集群)

 持久化

  Redis的数据全部在内存里,如果突然宕机,数据就会全部丢失,因此必须有一种机制来保证Redis中的数据不会因为故障而丢失,这种机制就是Redis的持久化机制。

  Redis的持久化机制有两种:

  一、快照RDB

    1、一次全量备份,使用 BGSAVE命令

    2、一个紧凑压缩的二进制文件(保存方式是内存数据的二进制序列化形式,在存储上非常紧凑)

    3、使用操作系统的多进程COW(copy on write)机制来实现持久化,持久化时调用glibc的函数fork(分岔)产生一个子进程,持久化完全交给子进程来处理,父进程继续处理客户端请求

    4、COW机制的数据页面的分离。父进程在对页面的数据进行修改时,会将被共享的页面复制一份分离出来,然后对复制出来的页面进行修改。这时子进程的页面是没有变化的,还是进程产生那一瞬间的数据,所以这种持久化叫做快照的原因。

    5、使用fork子进程,无法实时,宕机会造成数据丢失

 

  二、AOF日志

    1、连续的增量备份,使用appendonly yes开启

    2、存储的是Redis服务器的顺序指令序列,只记录对内存进行修改的指令序列

    3、记录的是内存数据修改的指令记录文本,在长期的运行过程中会变得非常庞大,数据库重启时需要加载AOF日志进行指令重放,比较耗时,所以需要定期进行AOF重写,给AOF日志瘦身

    4、Redis收到客户端修改指令后,进行参数校验、逻辑处理,如果没问题,就立即将该指令写到缓冲区中(使用RESP协议),然后每秒钟调用一次fsync将指令存储到AOF日志中

    5、使用bgrewriteaof指令对AOF日志进行瘦身,即开辟一个子进程对内存进行遍历,转换成一系列Redis的操作指令,序列化到一个新的AOF日志文件中,再将操作期间新增的AOF日志追加到这个新的AOF日志文件中,替代旧的AOF日志文件,完成瘦身。

 

       RDB和AOF还有以下特点:

            1、RDB 采用二进制 + 数据压缩的方式写磁盘,这样文件体积小,加载RDB恢复数据速度也快(远快于AOF的方式)

            2、AOF 记录的是每一次写命令,数据最全,但文件体积大,数据恢复速度慢

 

 

  Redis4.0混合持久化

  实际应用中重启Redis时,很少使用RDB来恢复内存状态,因为会丢失大量数据。所以我们通常使用AOF日志重放,但是重放AOF日志相对于RDB要慢得多。

  混合持久化:将RDB文件的内容和增量的AOF日志文件存在一起。这里的AOF日志不是全量的日志而是自持久化开始到持久化结束的这

        段时间发生的增量AOF日志,通常这部分AOF日志很小。因此重启的时候先加在RDB内容,然后再重放增量AOF日志,替

        代之前的AOF的全量文件重放,重启效率得到大幅度提升。

 

  具体来说,当 AOF rewrite 时,Redis 先以 RDB 格式在 AOF 文件中写入一个数据快照,再把在这期间产生的每一个写命令,追加到 AOF 文件中。因为 RDB 是二进制压缩写入的,这样 AOF 文件体积就变得更小了。

 

 

事务

Redis的事务模型并不严格(不具备原子性,事务的命令如果有执行失败,并不会回滚)

  基本的事务操作都有begin、commit和rollback。begin指示事务的开始,commit指示事务的提交,rollback指示事务的回滚。

  Redis事务的指令也差不多,分别是multi、exec、discard。multi指示事务的开始,exec指示事务的执行,discard指示事务的丢弃。

  Redis的指令在exec之前不执行,而是缓存在服务器的事务队列中,服务器一旦收到exec指令,才开始执行整个事务。因为Redis是单线程,所以在执行队列中的命令时不会被其他指令打搅。

  但是Redis的事务不具备原子性,而仅仅满足了事务的隔离性中的串行化,当前事务执行不会被其他事务干扰。

  优化:Redis事务在每发送一个指令到事务缓存队列都要经过一次网络读写,当一个事务内部的指令较多时,需要的网络IO也会线性增长,所以通常Redis的客户端在执行事务时都会结合pipeline一起使用。

 

Watch(CAS机制):

  多个客户端并发修改Redis中的一条记录。需要先读,再写。为了保证线程安全,一种方式是通过Redis分布式锁的方式,但是Redis分布式锁是悲观锁。Redis提供了watch机制,是一种乐观锁在multi之前监视某个关键变量,若在watch之后被修改了(包含当前事务所在的客户端),如果关键字被修改了,则exec指令就会返回NULL回复告知客户端事务执行失败,这个时候客户端一般会选择重试。

 

 

Redis管道技术Pipeline

Redis管道技术是由客户端提供的,而不是服务端

Redis是一种基于客户端-服务端模型以及请求/响应协议的TCP服务。这意味着通常情况下一个请求会遵循以下步骤:

  1、客户端每发送一个查询请求,并监听Socket返回,通常是以阻塞模式,等待服务端响应

  2、服务端处理命令,并将结果返回给客户端

如果连续执行多条指令,那么会花费多个网络数据包来回的时间

Redis管道技术可以在服务端未响应时,客户端可以继续向服务端发送请求,并最终一次性读取所有的服务端响应。

管道技术显著的提高了Redis的性能,尤其是在大量写操作的情况下。

 

RESP

  REdis Serialization Protocol:Redis序列化协议。Redis服务端与客户端通过RESP协议进行通信,节点交互不使用这个协议。

  有如下特性:是二进制安全的、在TCP层、基于请求-响应的模式

  RESP有五种最小的单元类型,单元结束时统一加上回车换行符【\r\n】

    1、单行字符串:以+符号开头,如:字符串hello world-->+hello world\r\n

    2、多行字符串:以$符号开头,后跟字符串长度,如:多行字符串hello world-->$11\r\nhello world\r\n

    3、整数值:以:符号开头,后跟整数的字符串形式,如:整数12-->:12\r\n

    4、错误信息:以-符号开头,如:-WRONGTYPE\r\n

    5、数组:以*号开头,后跟数组的长度,如:数组[1,2,3]-->*3\r\n:1\r\n:2\r\n:3\r\n

  

  客户端向服务端发送的指令只有一种格式,多行字符串数组。

    比如一个简单的 set 指令set author codehole会被序列化成下面的字符串。*3\r\n$3\r\nset\r\n$6\r\nauthor\r\n$8\r\ncodehole\r\n

  服务端对客户端的响应支持多种数据结构,即以上5种的基本类型的组合。

 

 

 

发布订阅 PubSub

  前面所讲的Redis的消息队列,一个消息只能被一个消费者消费,不支持消息的多播机制。

  消息多播允许生产者只生产一次消息,由中间件负责将消息复制到多个消息队列,每个消息队列由相应的消费组进行消费,是分布式系统常用的一种解耦方式,用于将多个消费组的逻辑进行拆分。支持了消息多播,每个消费组对应的不同子系统可以有不同的逻辑处理。

  在生产环境中,一般将生产者和消费者分离

  消费者可通过listen来阻塞监听消息来进行处理。

  模式订阅:消费者可以同时订阅多个主题的消息,但是如果生产者新增了一个主题,消费者也必须增加一个订阅指令才能收到新增的主题的消息。为了简化这种订阅的繁琐,Redis提供了模式订阅功能Pattern Subscribe,这样就可以一次订阅多个主题,即使生产者新增

加了同模式的主题,消费者也可以立即收到消息。

  如:psubscribe code.*   

  那么所有以"code. " 开头的主题的消息都能订阅到。

  消息结构:

  1、data:消息的内容,一般一个字符串

  2、channel:当前订阅的主题的名称

  3、type:消息的类I型那个,如果是普通的消息,那么类型就是message;如果是控制消息,比如订阅指令的反馈,它的类型就是

    subscibe;如果是模式订阅的反馈,它的类I型那个就是psubscribe;此外还有取消订阅指令的反馈unsubscribe和punsubscribe。

  4、pattern:表示当前消息是使用哪种模式订阅到的。如果是通过subscribe指令订阅到的,这个字段就是None

缺点:

  1、消费者挂掉重连后,在断连期间生产者发送的消息,就丢失了

  2、如果Redis停机重启,PubSub的消息不会持久化,所有的消息都会丢失

 

 

 

小对象压缩存储ziplist

  Redis的所有数据都放在内存中,所以在使用过程中要注意节约内存,否则就可能出现Redis内存不足导致崩溃。如果Redis内部管理

的集合list数据结构的数据很小,则它会使用紧凑存储形式压缩存储。

  Redis的ziplist是一个紧凑的字节数组结构,每个元素之间都是紧挨着的

 

 

 

内存回收机制

  Redis并不总是将空闲内存立即归还给操作系统。

  如果当前Redis内存有10GB,当你删除了1GB的key后,再去观察内存,你会发现内存变化不会太大。这是以为内操作系统是以页为单位回收内存的,这个页上只要还有一个key在使用,那么这个页就不能被回收。Redis虽然删除了1GB的key,但是这些key分散到了很

多的页中,每个页都还有其他的key存在,这就导致了内存不会被立即回收。

  不过,如果你执行flushdb,然后再观察内存,会发现内存确实被回收了。原因是所有的key都被删掉了,大部分之前使用的页都完全空了,就会立即被操作系统回收。

  Redis虽然无法保证立即回收已经删除的key的内存,但是它会重新使用那些尚未回收的空闲内存。

 

内存分配算法:

  内存分配是一个非常复杂的课题,需要适当的算法划分内存页,需要考虑内存碎片,需要平衡性能和效率,Redis将内存分配的细节交给了第三方内存分配库去实现。默认的内存分配库是jemalloc

 

 

 

集群

  分布式系统存储的理论基石——CAP原理:

  C:Consistent,一致性

  A:Availability,可用性

  P:Partition tolerance,分区容错性

  分布式系统的节点往往都是分布在不同的机器上进行网络隔开的,这意味着必然有网络断开的风险,这个网络断开的场景的专业词汇叫做网络分区

  在网络分区发生时,两个分布式节点之间无法进行通信,我们对一个节点的修改操作无法同步到另一个节点,所以数据的一致性将无法满足,因为两个分布式节点的数据不再保持一致,除非牺牲可用性,也就是暂停分布式节点服务,在网络分区发生时,不再提供修改数据的功能,直到网络状况完全恢复正常再继续对外提供服务。即当网络分区发生时,一致性和可用性不可兼得(Redis满足AP)。

 

  主从:当主节点master挂掉的时候,从节点slave接管服务,使服务可以继续。否则主节点需要经过数据恢复和重启,使服务中断很长时间。

  最终一致

  Redis的主从数据是异步同步的,所以分布式的Redis系统并不满足一致性要求。当客户端在Redis的主节点修改了数据以后,立即返回,即使在主从网络断开的情况下,主节点依旧可以正常对外提供修改服务,所以Redis满足可用性

  但是Redis满足最终一致性,从节点会努力追赶主节点,最终从节点的状态会和主节点的状态保持一致。如果网络断开了,主从节点的数据会出现大量的不一致,但一旦网络恢复,从节点会努力追赶主节点,继续尽力与主节点一致。

  所以Redis满足的是CAP理论中的AP

 

主从架构

 主从同步方式:

  1、增量同步:

  Redis同步的是指令流,主节点会将那些对自己的状态产生修改性影响的指令记录在本地的buffer中,然后异步将buffer中的指令同步到从节点,从节点一边执行同步的指令流来达到和主节点一样的状态,一边向主节点反馈自己同步到哪里了(偏移量)。

  因为内存的buffer是有限的,所以Redis主节点不能将所有的指令都记录在内存buffer中,Redis的复制内存buffer是一个定长的环形数组,如果数据内容满了,就会从头开始覆盖前面的内容。

  如果因为网络状况不好,从节点在短时间内无法和主节点进行同步,那么当网络恢复时,Redis的主节点中那些没有同步的指令在buffer中可能已经被后续的指令覆盖了,从节点将无法直接通过指令流来进行同步,这个时候就需要用到更加复杂的同步机制-快照同步。

 

  2、快照同步:

  快照同步是一个非常耗资源的操作,它首先需要在主节点上进行一次bgsave,将当前内存的数据全部快照到磁盘文件中,然后再将快照文件的内容全部传送到从节点。

  从节点将快照文件接受完毕后,立即执行一次全量加载,加载之前先要将当前内存的数据清空,加载完毕后通知主节点继续进行增量同步。

  在整个快照同步进行的过程中,主节点的复制buffer还在不停地往前移动,如果快照同步的时间过长或者复制buffer太小,都会导致同步期间的增量指令在复制buffer

中被覆盖,这样的话会导致快照完成后无法进行增量复制,然后会再次发起快照同步,如此下去极有可能陷入快照同步的死循环。

    

    同步具体流程:

    ①:slave节点发送sync命令到master节点

    ②:master收到sync后,执行bgsave,生成RDB文件

    ③:master把slave的写命令记录到缓存

    ④:bgsave执行完毕之后,发送RDB文件到slave,slave执行

    ⑤:slave发送缓冲区中的写命令到slave,slave执行

 

  增加从节点:当从节点刚刚加入集群中时,它必须先进行一次快照同步,同步完成后再继续进行增量同步。

  无盘复制:主节点在进行快照同步时,会进行很耗时的文件IO操作,在非SSD的磁盘存储时,快照同步会对系统的负载产生较大的影响。特别是当系统正在进行AOF的fsync操作时(将AOF日志强制从内核缓存刷到磁盘),如果发生快照同步,fsync将会被推迟执行,这就会严重影响主节点的服务效率。从Redis2.8.18开始,Redis支持无盘复制。所谓的无盘复制是指主服务器通过套接字将快照内容发送到从节点,生成快照是一个遍历的过程,主节点一边遍历内存,一边将序列化的内容发送到从节点,从节点还是跟之前一样,先将接收到的内容存储到磁盘文件中,再进行一次性加载。

  

一、哨兵Sentinel

  如果主节点突发宕机那么如何自动主从切换?Redis Sentinel哨兵就是一种抵抗结点故障的高可用方案。

  可以将Redis Sentinel集群看成是一个zookeeper集群,它是集群高可用的核心。一般由3-5个节点组成,这样即使个别节点挂了,集群还可以正常运转。

  Sentinel负责持续监控主从节点的健康,当主节点挂掉时,自动选择一个最优的从节点切换成主节点。

  客户端来连接集群时会首先连接Sentinel,通过Sentinel来查询主节点的地址,然后再连接主节点进行数据交互。主节点发生故障时,客户

端会重新向Sentinel获取新的主节点的地址,如此应用程序将无需重启即可自动完成节点切换。

  如果主节点挂掉了,原先的主从复制也断开了,客户端和损坏的主节点也断开了。一个从节点被提升为新的主节点,其他从节点开始和新

的主节点建立复制关系。客户端通过新的主节点继续进行交互。Sentinel会持续监控已经挂掉了的前主节点,待它恢复后,变成从节点和

新的主节点建立复制关系。

 

  哨兵工作核心:

  哨兵进程启动后后会和master建立两条链接

    1.用来获取其他同样在监控着此redis系统的哨兵信息
    2.发送一个info命令来获取此redis系统master本身的信息


  当和master完成链接建立后,该哨兵就会定时的做以下三件事情

    1.每10秒会向master和slave发送info命令,info命令可以让哨兵获取到当前数据库的信息如id,复制信息等,从而实现新节点的自动发现

    2.每2秒会向master和slave发送自己的信息

    3.每1秒会向master,slave以及其他同样在监控着此redis系统的哨兵发送ping命令

 

  故障后master的选举:

    哨兵集群自身的选举和zk的选举一致。

    1、

 

 

 

消息丢失:

  Redis主从采用异步复制,意味着当主节点挂掉时,从节点可能还未收到全部的同步消息,这部分未同步的消息就丢失了。如果主从延

迟特别大,那么丢失的数据就可能会特别多。Sentinel无法保证消息完全不丢失,但是也尽量保证消息少丢失。有下面两个选项避免主从延迟过大:

  min-slaves-to-write 1  表示主节点必须至少有一个从节点在进行正常复制,否则就对外停止写服务,丧失可用性

  min-slaves-max-lag 10  单位秒,表示如果在10s内没有收到从节点的反馈,就意味着从节点同步不正常。

Sentinel的默认端口是26379,不同于Redis的默认端口6379,通过Sentinel对象的discover_xxx方法可以发现主从地址,主地址只有一个,从地址可以有多个。

通过master_for 或者 slave_for方法可以从连接池中获取主节点或者从节点的连接实例。因为从地址有多个,所以Redis客户端对从地址采用RoundRobin轮询方案。

 

 

二、集群Codis

  在大数据高并发情况下,单个Redis实例往往会显得捉襟见肘。

  首先体现在内存上,单个Redis的内存不宜过大,内存太大会导致rdb文件过大,进一步导致主从同步时全量同步时间过长,在实例重启恢复

时也会消耗很长的数据加载时间。

  其次体现在CPU的利用率上,单个Redis实例只能利用单个核心,这单个核心要完成海量数据的存取和管理工作,压力非常大。

所以Redis集群应运而生。它可以将众多小内存的Redis实例整合起来,将分布在多台机器上的众多CPU核心的计算能力聚集在一起,完成海

量数据存储和高并发读写操作。

Codis是一个代理中间件,和Redis一样也使用Redis协议对外提供服务,当客户端向Codis发送指令时,Codis负责将指令转发到后面的Redis

实例来执行,并将返回结果再转回给客户端。

Codis上挂载的所有Redis实例构成一个Redis集群,当集群空间不足时,可以通过动态增加Redis实例来实现扩容需求。

因为Codis是无状态的,它只是一个转发代理中间件,这意味着我们可以启动多个Codis实例,供客户端使用,每个Codis节点都是对等的。因

为单个Codis代理能支撑的QPS比较有限,通过启动多个Codis代理可以显著增加整体的QPS需求,还能起到容灾功能,挂掉一个Codis代理实

例没有关系,还有很多的Codis代理实例可以提供服务。

Codis分片原理:

  Codis负责将特定的key转发到特定的Redis实例,这种对应关系Codis是如何管理的呢?Codis默认将所有的key划分为1024个槽位(slot),

  如果集群节点比较多,也可以手动设置大一些,如2048;

  它首先对客户端传来的key进行crc32运算计算hash值,再将hash后的整数值对1024这个整数进行取模得到一个余数,这个余数就是对应

  的key的槽位。而每个槽位都会唯一映射到后面的多个Redis实例中的一个。Codis会在内存中维护槽位和Redis实例的映射关系,这样有了

  key对应的槽位,将这个key转发到那个Redis实例就很明确了。

 不同的Codis实例之间槽位关系如何同步:

  如果Codis的槽位映射关系只存储在内存里,那么不同的Codis实例之间的映射关系就无法得到同步。所以Codis还需要一个分布式配置存储

  数据库专门用来持久化槽位关系,Codis支持zookeeper和etcd。

  Codis将槽位关系存储在zookeeper中,并且提供了一个Dashboard可以用来观察和修改槽位关系,当槽位关系变化时,Codis Proxy会监听

  到变化并重新同步槽位关系,从而实现多个Codis Proxy之间共享槽位关系配置。

 

 

三、集群Cluster

  Redis Cluster 是Redis的作者自己提供的Redis集群化方案。

  与Codis不同,Redis Cluster是去中心化的,该集群由三个Redis节点(都是master节点,每个master节点有一或多个slave节点)组成,每个节点负责整个集群的一部分数据,每个节点负责的数据多少可能不一样,节点组成一个对等的集群,它们之间通过一种特殊的二进制协议交互集群信息。

 

  Redis Cluster将所有数据划分为16384个槽位,它比Codis的1024个槽位划分的更为精细,每个节点负责其中一部分槽位。槽位的信息存储于每个节点中,不像Codis,不需要另外的分布式存储空间来存储节点信息。

  当Redis Cluster的客户端来连接集群时,也会得到一份集群的槽位配置信息。这样当客户端要查找某个key时,可以直接定位到目标节点。这一点于Codis也不同,Coids需要通过Proxy来定位目标节点,Redis Cluster则是直接定位。

  客户端为了直接定位某个具体的key所在的节点,需要缓存槽位的相关信息,这样才可以准确快速地定位到相应的节点。同时因为可能会存在客户端与服务端存储槽位的信息不一致的情况,还需要纠正机制来实现槽位信息的校验调整。另外,Redis Cluster的每个节点会将集群的配置信息持久化到配置文件中,所以必须确保配置文件是可写的,而且尽量不要依靠人工修改配置文件。

 

 槽位定位算法:

  Redis Cluster默认会对key值使用crc16算法进行hash,得到 一个整数值,然后用这个整数值对16384进行取模来得到槽位。

 

 容错(故障转移):

  Redis Cluster可以为每个主节点设置若干个从节点,当主节点发生故障时,集群会自动将其中同步数据最新的从节点提升为主节点。并接管下线节点的slot。

  如果某个主节点没有从节点,那么当它发生故障时,集群将完全处于不可用状态

 

  集群中每个主节点都会定时发送信息到其他主节点进行同步,如果其他主节点在规定时间内响应了发送消息的主节点,则发送消息的主节点认为响应了消息的主节点正常,反之则认为响应消息的主节点疑似下线,则发送消息的主节点在其节点上将其标记『疑似下线』。

  当集群中超过一半以上的节点认为某个主节点被标记为『疑似下线』,那么这个疑似下线的主节点将被标记为下线状态,并向集群广播一条下线消息,当下线节点对应的从节点接收到该消息时,则在从节点中选举出一个节点作为主节点继续对望提供服务。

 

 

 

Info指令-问题诊断

在使用Redis时,时长会遇到很多问题需要诊断,在诊断之前需要了解Redis的运行状态,通过强大的Info指令,可以清晰地知道Redis内部一系列运行

参数。Info指令显示的信息繁多,分为9大块,每个块都有非常多的参数

  1、Server  服务器运行的环境参数

  2、Clients  客户端相关信息

  3、Memory  服务器运行内存统计数据

  4、Persistence  持久化信息

  5、Stats  通用统计数据

  6、Replication  主从复制相关信息

  7、CPU  CPU使用情况

  8、Cluster   集群信息

  9、KeySpace  键值对统计数量信息

  Info stats|grep ops  每秒操作数

  moniter  哪些key被访问得比较频繁

  Info clients  连接了多少客户端

  Info memory  Redis占用了多少内存

 

END.

 

posted @   杨岂  阅读(422)  评论(0编辑  收藏  举报
编辑推荐:
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
阅读排行:
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· 阿里巴巴 QwQ-32B真的超越了 DeepSeek R-1吗?
· 【译】Visual Studio 中新的强大生产力特性
· 【设计模式】告别冗长if-else语句:使用策略模式优化代码结构
· AI与.NET技术实操系列(六):基于图像分类模型对图像进行分类
点击右上角即可分享
微信分享提示