当前位置: 首页 > news >正文

【非关系型数据库Redis 】 入门

Redis入门

一、非关系型数据库概述

(一)概念

非关系型数据库(NoSQL,Not Only SQL)是相对于传统的关系型数据库而言的一种数据存储管理系统。它摒弃了关系型数据库中严格的表结构、SQL 语言操作以及复杂的事务等特性,采用更加灵活的数据模型来存储和管理数据,旨在应对一些关系型数据库在特定场景下处理效率不高、扩展性受限等问题,能够更好地适应大数据量、高并发、灵活多变的数据存储需求。

(二)分类及特点

1. 键值对数据库
  • 特点:以简单的键值对形式存储数据,键是唯一标识,值可以是任意类型的数据(如字符串、数字、对象等),数据的存储和检索操作都基于键进行,非常高效直观。例如,在缓存系统中,可以将网页的 URL 作为键,网页内容作为值进行存储,方便快速获取缓存内容。
  • 实现原理(简单示意):从源码角度看,其内部通常通过哈希表(或类似的数据结构)来实现键值的映射存储。以 Redis 为例(后续会重点介绍 Redis),在 dict.c 文件中定义了字典结构(用于实现键值对存储的核心数据结构之一),有相关的哈希函数用于根据键计算出存储位置,插入、查找、删除操作都是围绕这个字典结构进行相应的节点操作(如链表节点的添加、查找、删除等,用于处理哈希冲突情况)来实现高效的键值对管理。
  • 性能:读写性能极高,因为基于键的查找可以在接近常数时间复杂度内完成(理想情况下,忽略哈希冲突等因素),适用于需要快速读写、对数据结构要求不复杂的场景,像缓存、会话管理等。
  • 应用场景:广泛应用于缓存场景,如缓存数据库查询结果、网页内容缓存等,还可用于存储用户配置信息等简单的键值关联数据。
2. 文档数据库
  • 特点:以文档(通常是类似 JSON 格式的数据结构,包含多个键值对以及嵌套结构)为基本单位存储数据,每个文档可以有不同的结构,更贴合实际业务中灵活多变的数据形式,便于开发者直接以文档的形式操作数据。
  • 实现原理(不同产品有差异,以常见思路举例):在存储方面,一般会将文档进行序列化(比如 JSON 序列化)后存储到底层存储介质中,内部会有索引机制来方便根据文档中的特定字段进行查询。例如,MongoDB(一款常见的文档数据库)会对文档中的一些关键字段构建 B 树索引等,以便快速定位到符合条件的文档,其源码中会涉及到索引创建、维护以及文档存储格式定义、解析等相关模块来实现整个文档存储和查询功能。
  • 性能:读取性能取决于索引的构建和使用情况,写入性能在处理复杂文档结构时相对关系型数据库更有优势,因为不需要遵循严格的表结构约束。适合处理半结构化和非结构化的数据,像内容管理系统中存储文章内容(包含标题、正文、作者、标签等不同结构的信息)、电商系统中存储商品详情等场景。
  • 应用场景:适用于内容管理、电商产品数据存储、社交网络中用户动态等需要灵活存储和查询具有一定结构但又不固定的数据场景。
3. 列族数据库
  • 特点:数据按照列族(Column Family)进行组织,一个列族包含多个列,不同列族可以有不同的属性(如存储策略、版本控制等),适合海量数据的存储和分布式处理,尤其在处理稀疏矩阵等数据类型时有优势。
  • 实现原理(以 HBase 为例简单说明,HBase 是典型的列族数据库):在底层存储上,HBase 基于 Hadoop 的 HDFS(分布式文件系统)进行数据存储,通过区域服务器(Region Server)管理数据的读写,每个列族的数据在物理存储上相对独立,有自己的存储文件等配置。源码中涉及到区域划分、数据写入流程(先写入内存的 MemStore,达到一定条件后刷写到磁盘等)、列族相关的元数据管理等诸多模块来保障列族数据的高效存储和访问,比如在 HRegion.java 文件中就有关于区域内数据操作以及与列族相关的关键逻辑实现。
  • 性能:具备良好的扩展性,能轻松应对海量数据存储和高并发读写场景,不过其数据模型相对复杂,对于简单的小规模数据读写操作可能不如键值对数据库等高效。适用于大数据分析、日志存储等需要存储大量数据且对读写性能和扩展性有较高要求的场景。
  • 应用场景:常用于互联网公司的海量日志存储与分析、物联网数据存储、搜索引擎的索引数据存储等场景。

(三)与关系型数据库的区别及适用场景对比

1. 区别
特性关系型数据库非关系型数据库
数据模型基于严格的表结构,通过行和列存储数据,有固定的模式,数据间通过关系(如外键等)关联数据模型多样(键值对、文档、列族等),结构相对灵活,不一定有严格的关系约束
数据一致性强调 ACID 特性(原子性、一致性、隔离性、持久性),通过事务机制保障数据在复杂操作下的一致性部分非关系型数据库弱化了 ACID 特性,更注重可用性和性能,不同类型有不同的数据一致性保障方式(如最终一致性等)
扩展性垂直扩展相对容易(提升单机硬件性能),但水平扩展(增加服务器节点)较复杂,往往需要复杂的分布式架构设计和数据分片等操作大多在设计上就便于水平扩展,能轻松通过添加节点来增加存储容量和处理能力,适应大数据量和高并发场景
读写性能在复杂的关联查询、事务操作场景下有优势,但对于简单的读写操作(尤其是海量数据简单读写)效率可能受限于表结构和事务机制不同类型读写性能各有优劣,总体在简单读写、海量数据读写以及高并发读写的某些场景下性能表现较好,不过在复杂关联查询方面一般较弱
2. 适用场景
  • 关系型数据库适用场景:适用于对数据一致性、完整性要求极高,需要复杂的事务处理和精确的数据分析(如财务系统、订单管理系统等涉及多表关联和严格数据核算的场景),以及数据结构相对固定、变动不大的业务场景。
  • 非关系型数据库适用场景:在需要快速读写、高并发读写、灵活的数据结构以及易于水平扩展的场景中表现出色,如缓存系统、内容推荐系统(存储用户行为数据等灵活结构数据)、大数据存储与分析等场景更倾向于使用非关系型数据库。

(四)Redis 在相关场景中的应用优势

1. 缓存场景
  • 性能优势:Redis 作为键值对数据库,读写速度极快,其基于内存存储数据(部分持久化机制后面会介绍,但主要读写操作在内存中),可以在极短的时间内完成数据的读取和写入操作,能大大减少后端数据库(如 MySQL 等关系型数据库)的压力,提升整个系统的响应速度。例如在电商系统中,热门商品信息、分类列表等频繁查询的数据缓存到 Redis 中后,用户再次查询时几乎可以瞬间获取到数据,降低了数据库的查询负载。
  • 功能优势:支持丰富的数据过期策略,通过设置不同的过期时间,可以自动清理不再需要的缓存数据,避免缓存数据无限增长占用过多内存。同时,它还可以方便地进行缓存更新策略的实现,如通过缓存击穿、缓存穿透等问题的应对机制(如使用互斥锁、布隆过滤器等结合 Redis 实现),保证缓存系统的稳定性和有效性。
2. 分布式锁场景
  • 实现原理优势(源码角度):Redis 通过 SETNX (SET if Not eXists)命令(底层源码实现涉及到对键值对的原子操作判断和设置逻辑,在 set.c 等相关文件中有具体实现细节)可以实现简单的分布式锁功能,当多个进程或线程需要竞争同一资源时,通过尝试使用 SETNX 设置一个特定的锁键,如果返回成功,表示获取到锁,可以对资源进行操作,操作完成后再通过 DEL 命令删除锁键释放锁。在此基础上,结合 EXPIRE 命令可以设置锁的过期时间,避免因为进程崩溃等原因导致锁无法释放的情况,保证了分布式锁的可靠性。例如在分布式系统中的定时任务调度场景,多个节点可能同时竞争执行某个定时任务,通过 Redis 分布式锁可以确保同一时刻只有一个节点能执行该任务,避免重复执行造成数据不一致等问题。
  • 性能和易用性优势:Redis 的分布式锁操作简单高效,性能上由于基于内存操作以及自身高效的命令执行机制,获取和释放锁的延迟很低,能够快速响应分布式环境下的资源竞争需求,而且相比于一些基于数据库表实现的分布式锁或者复杂的分布式协调框架(如 ZooKeeper 等),Redis 的使用门槛更低,更容易集成到现有的系统中,方便开发者快速实现分布式锁功能。

二、Redis 的安装与配置

(一)安装过程

Linux 环境下安装(以常见的源码编译安装为例):

首先,从 Redis 官方网站(https://redis.io/download)下载最新的稳定版本源码包,例如 redis-x.y.z.tar.gz(x.y.z 为具体版本号)。

解压源码包:

tar -zxvf redis-x.y.z.tar.gz。
进入解压后的目录:
cd redis-x.y.z。
执行编译命令:
make

这一步会根据当前系统环境编译 Redis 的源码,生成可执行文件等相关二进制文件(编译过程中会涉及到众多源码文件的编译链接,比如 src 目录下的各个 .c 文件,分别实现了 Redis 的不同功能模块,像网络通信、命令执行、数据存储等功能对应的源码编译后整合在一起)。

安装到指定目录(一般需要 root 权限):
make install PREFIX=/usr/local/redis

将 Redis 安装到 /usr/local/redis 目录下,可以根据实际需求修改安装目录。

配置文件相关:

Redis 安装目录下会有默认的配置文件 redis.conf,可以根据实际使用场景对配置文件进行修改,比如设置监听的 IP 地址、端口号、内存限制、持久化相关参数等内容(后续会详细介绍配置文件中一些关键参数的作用和调整方法)。

(二)基本数据类型及操作

1. 字符串(String)
操作方法(命令行示例):
  • 设置键值:使用 SET 命令,例如 SET mykey “Hello Redis”,会在 Redis 中创建一个名为 mykey 的键,对应的值为 “Hello Redis”。从源码角度看,在 set.c 文件中的 setCommand 函数实现了 SET 命令的具体逻辑,它会调用相关的数据存储模块(基于前面提到的字典结构等)将键值对存储到内存中,同时处理一些可选的参数(如设置过期时间等相关逻辑)。
  • 获取值:通过 GET 命令,如 GET mykey 会返回对应键的值 “Hello Redis”,其实现是在 get.c 文件中的 getCommand 函数,通过键查找对应的存储位置,从字典结构中获取值并返回给客户端。
  • 自增 / 自减操作:可以使用 INCR(自增 1)、DECR(自减 1)命令,例如 INCR numkey(假设 numkey 存储的是一个整数类型的值)会将对应键的值自增 1,其源码实现涉及到对数值类型的解析、原子性操作的保障(通过 Redis 的单线程执行模型以及相关的底层操作原语保证操作的原子性,在 intset.c 等文件中有部分相关实现细节,用于处理整数集合相关操作)等逻辑,确保在高并发环境下数据的准确性。
  • 应用场景:常用于存储简单的字符串类型数据,如缓存网页内容、用户的登录令牌、计数器(如网站访问量计数、点赞数计数等)等场景,利用其快速读写和简单操作的特点。
2. 列表(List)
操作方法(命令行示例):
  • 添加元素:使用 LPUSH(从列表头部添加元素)或 RPUSH(从列表尾部添加元素)命令,例如 LPUSH mylist “item1” 会在名为 mylist 的列表头部添加元素 “item1”,其源码实现(在 list.c 文件中有相关命令实现函数)是基于 Redis 内部的链表结构(双向链表实现,方便在头部和尾部进行快速操作)进行节点插入操作,维护列表的数据结构。
  • 获取元素:通过 LRANGE 命令可以获取列表中的部分或全部元素,如 LRANGE mylist 0 -1 会返回整个 mylist 的元素列表,实现过程涉及到链表的遍历逻辑(从指定的起始节点到结束节点,按照链表指针依次获取节点中的元素值并返回),确保正确返回符合范围要求的元素。
  • 弹出元素:使用 LPOP(从列表头部弹出元素)或 RPOP(从列表尾部弹出元素)命令,例如 LPOP mylist 会移除并返回列表头部的元素,源码中在执行弹出操作时,会同时调整链表的指针,保证链表结构的完整性以及数据的一致性。
  • 应用场景:适合实现消息队列(生产者将消息 RPUSH 到队列列表,消费者从队列头部 LPOP 消息进行消费)、文章评论列表存储(按时间顺序将评论 LPUSH 或 RPUSH 到列表中展示)、最近浏览记录列表(将浏览的商品 ID 等信息 LPUSH 到列表,限制列表长度实现最近浏览记录的存储和展示)等场景,利用其有序且支持两端操作的特点。
3. 哈希(Hash)
操作方法(命令行示例):
  • 设置字段值:使用 HSET 命令,例如 HSET user:1 name “John” age 30 会在名为 user:1 的哈希表中设置 name 字段的值为 “John”,age 字段的值为 30,其源码实现(在 hash.c 文件中有对应的命令实现函数)基于 Redis 内部的哈希表结构(与前面提到的整体存储键值对的字典结构类似,但这里是用于存储哈希表内部的字段和值)进行字段值的插入和更新操作,通过计算字段的哈希值来定位存储位置等。
  • 获取字段值:通过 HGET 命令,如 HGET user:1 name 会返回 user:1 哈希表中 name 字段的值 “John”,实现原理是根据哈希表结构查找对应字段的存储位置并获取值返回。
  • 获取所有字段和值:使用 HGETALL 命令,如 HGETALL user:1 会返回 user:1 哈希表中所有的字段和值,源码中会遍历哈希表的所有桶(根据哈希冲突处理机制形成的存储单元),获取每个桶中的字段和值并组装成结果返回给客户端。
    应用场景:常用于存储对象的属性信息,比如用户对象(将用户的各个属性如用户名、密码、年龄等以字段值的形式存储在一个哈希表中,通过用户 ID 等作为键)、商品详情(商品的价格、库存、描述等属性作为哈希表的字段值存储)等场景,方便对对象的部分属性进行单独操作和获取,减少数据冗余。
4. 集合(Set)
操作方法(命令行示例):
  • 添加元素:使用 SADD 命令,例如 SADD myset “elem1” “elem2” 会向名为 myset 的集合中添加元素 “elem1” 和 “elem2”,其源码实现(在 set.c 文件中有对应命令实现函数)基于 Redis 内部的集合数据结构(可能是哈希表或者整数集合等实现方式,根据元素类型等情况选择,在源码中有相应的判断和切换逻辑)进行元素的添加操作,确保元素的唯一性(会自动去除重复元素),通过哈希计算等方式判断元素是否已存在。
  • 获取集合成员:通过 SMEMBERS 命令,如 SMEMBERS myset 会返回集合 myset 的所有成员元素,实现涉及到对集合结构的遍历(如果是哈希表实现的集合,遍历桶获取元素;如果是整数集合则按照相应的顺序获取元素等),将所有元素返回给客户端。
  • 判断元素是否在集合中:使用 SISMEMBER 命令,如 SISMEMBER myset “elem1” 会返回 1 表示元素 “elem1” 在集合中,返回 0 表示不在,其源码实现根据元素查找逻辑快速定位元素并判断其是否存在于集合当中,在哈希表实现的集合中会通过计算元素的哈希值来查找对应的桶位置,然后确认元素是否存在于该桶的链表(处理哈希冲突时形成的链表结构)中;若是整数集合实现方式,则通过简单的顺序查找或基于整数集合自身有序特点的高效查找算法来判断元素是否存在,以此来准确返回相应的判断结果。
  • 应用场景:常用于去重场景,比如统计网站的独立访客 IP(将每个访客的 IP 地址用 SADD 命令添加到一个集合中,集合会自动去除重复的 IP,通过 SCARD 命令可获取集合元素个数即独立访客数量),还可用于实现标签功能(如文章有多个标签,将文章的所有标签以 SADD 命令添加到对应文章的标签集合中,方便后续查询具有特定标签的文章集合等操作),利用其元素唯一性以及快速判断元素是否存在的特性,高效处理相关业务逻辑。
  1. 有序集合(Sorted Set)
    操作方法(命令行示例):
  • 添加元素及设置分值:使用 ZADD 命令,例如 ZADD myzset 10 “elem1” 20 “elem2” 会向名为 myzset 的有序集合中添加元素 “elem1” 分值为 10,元素 “elem2” 分值为 20,其源码实现(在 zset.c 文件中有对应命令实现函数)基于 Redis 内部的跳跃表(Skip List)与哈希表结合的数据结构来实现(跳跃表用于高效的排序和范围查找,哈希表用于快速定位元素,两者协同保障有序集合功能的实现),添加元素时会先通过哈希表判断元素是否已存在,若不存在则插入到跳跃表合适的位置(根据分值大小确定顺序),同时更新哈希表中的对应关系,确保有序集合的有序性以及元素操作的高效性。
  • 获取指定范围元素:通过 ZRANGE 命令,如 ZRANGE myzset 0 -1 会按照分值从小到大的顺序返回 myzset 的所有元素(可指定 WITHSCORES 参数一同返回元素对应的分值),实现过程是基于跳跃表的有序遍历逻辑(从跳跃表的最底层最左侧节点开始,按照每层的指针以及分值大小顺序依次访问节点获取元素,直到遍历完指定范围的节点),从而准确返回符合要求的有序元素列表。
  • 获取元素分值:使用 ZSCORE 命令,如 ZSCORE myzset “elem1” 会返回元素 “elem1” 在有序集合中的分值 10,源码中会先通过哈希表快速定位到元素所在位置(找到对应的跳跃表节点),然后获取该节点记录的分值信息并返回给客户端。
  • 应用场景:常用于排行榜功能实现,比如游戏中的玩家积分排行榜(将玩家的 ID 作为元素,积分作为分值用 ZADD 命令添加到有序集合中,通过 ZRANGE 命令可方便地获取排名前几名的玩家信息),也可用于实现按照时间顺序排序的任务队列(将任务的唯一标识作为元素,任务的执行时间戳作为分值添加到有序集合,按时间顺序执行任务)等场景,充分利用其能根据分值排序以及快速获取指定范围元素的特性,灵活应对各类需要排序的数据处理情况。

(三)使用 Redis 实现简单的缓存功能示例

以下是一个简单的使用 Java 结合 Redis 实现缓存功能的示例代码(以 Jedis 客户端为例,Jedis 是常用的 Java 操作 Redis 的客户端库):

import redis.clients.jedis.Jedis;public class RedisCacheExample {private static final String REDIS_HOST = "localhost";private static final int REDIS_PORT = 6379;private Jedis jedis;public RedisCacheExample() {// 连接 Redis 服务器jedis = new Jedis(REDIS_HOST, REDIS_PORT);}public String getFromCache(String key) {// 先从 Redis 缓存中获取数据String value = jedis.get(key);if (value!= null) {System.out.println("从缓存中获取到数据: " + value);return value;}// 如果缓存中没有,这里假设从数据库(此处省略实际的数据库查询操作)获取数据String dataFromDb = "模拟从数据库获取的数据";// 将从数据库获取的数据存入缓存,并设置过期时间(这里设置为 60 秒,可根据实际情况调整)jedis.setex(key, 60, dataFromDb);System.out.println("将数据存入缓存,键: " + key + ", 值: " + dataFromDb);return dataFromDb;}public static void main(String[] args) {RedisCacheExample cacheExample = new RedisCacheExample();// 模拟获取缓存数据的操作cacheExample.getFromCache("user:1");}
}

在上述代码中,首先通过 Jedis 客户端建立与 Redis 服务器的连接,然后在 getFromCache 方法中,先尝试从 Redis 缓存中通过键获取对应的值,如果获取到了就直接返回(即命中缓存),如果缓存中没有值,就模拟从数据库获取数据(实际应用中这里会是真实的数据库查询操作,比如通过 JDBC 连接 MySQL 等数据库进行查询),获取到数据后再使用 setex 命令(SET 命令的扩展,用于设置值并同时设置过期时间)将数据存入 Redis 缓存,并设置一个过期时间,这样下次再查询相同的键时就能直接从缓存中获取数据,从而实现简单的缓存功能,减少对后端数据库的频繁访问,提升系统整体性能。

三、Redis 的持久化机制

(一)RDB 持久化

原理:
  • RDB(Redis DataBase)持久化是通过对 Redis 数据库在某个时间点上的数据进行快照(Snapshot)的方式来实现数据持久化保存的。其核心原理是 Redis 会按照一定的时间间隔(由配置文件中的 save 配置参数决定,例如 save 900 1 表示在 900 秒内如果有至少 1 个键发生了更改就进行一次快照保存,有多个类似的配置项可以组合设置不同的触发条件)或者满足特定的手动触发条件(通过 SAVE 或 BGSAVE 命令,SAVE 命令会阻塞 Redis 服务,直到快照保存完成;BGSAVE 命令则会在后台异步执行快照保存操作,不影响 Redis 正常服务,在源码中,bgsave.c 文件里有 BGSAVE 命令执行的相关函数实现,涉及到子进程创建、数据复制等关键逻辑),启动一个子进程,这个子进程会将此时 Redis 内存中的所有数据以二进制的形式写入到磁盘上的 RDB 文件(默认文件名为 dump.rdb)中,完成数据的持久化。在子进程进行数据复制和写入 RDB 文件过程中,主进程依然可以正常处理客户端的读写请求,只是会对一些数据结构进行写时复制(Copy-On-Write,COW)操作(在 rdb.c 等相关文件中有关于 COW 操作的实现逻辑,涉及到内存页面的管理以及数据共享和复制机制),避免子进程复制的数据被主进程的新写入操作干扰,保证数据的一致性和快照的准确性。
优点:
  • 文件体积小:RDB 文件是经过压缩的二进制文件,相比于 AOF(后面会介绍的另一种持久化方式)文件,相同数据量下其文件体积更小,更便于进行数据备份、迁移等操作,例如在将 Redis 数据迁移到新的服务器上时,传输 RDB 文件相对更快,占用的网络带宽更少。
  • 恢复速度快:在 Redis 重启或者需要恢复数据时,直接加载 RDB 文件到内存中即可快速恢复到上次快照时的数据状态,因为其是一次性加载整个数据快照,不需要像 AOF 那样重放大量的日志命令,所以恢复过程相对简单高效,能让 Redis 快速回到可用状态,尤其适用于对恢复时间要求较高的场景,比如生产环境下的 Redis 服务故障后快速重启恢复数据服务。
缺点:
  • 数据丢失风险:由于 RDB 是按照一定时间间隔或者特定触发条件进行快照保存的,如果 Redis 在两次快照之间发生故障,那么这期间修改的数据就会丢失,无法进行持久化保存,数据的丢失量取决于两次快照的时间间隔以及这段时间内的数据更新情况,所以对于数据不能有任何丢失风险的场景(如关键的金融交易数据等),单独使用 RDB 持久化可能不太合适。
  • 资源消耗问题:执行 BGSAVE 命令时,虽然是在后台异步进行,但会消耗一定的系统资源(如 CPU、内存等),因为需要创建子进程来复制数据并进行文件写入操作,在数据量较大或者服务器资源紧张的情况下,可能会对 Redis 服务的性能以及服务器上其他应用的运行产生一定影响,尤其是频繁触发 BGSAVE 操作时(比如设置的 save 触发条件过于宽松),资源消耗问题会更加明显。

(二)AOF 持久化

原理:
  • AOF(Append Only File)持久化的原理是将 Redis 执行的每一条写命令(如 SET、SADD 等命令)以追加的方式记录到一个日志文件(默认文件名为 appendonly.aof)中,只要 Redis 执行了写操作,对应的命令就会立即被记录到 AOF 文件中,确保了数据的修改操作都有记录可查。在 Redis 重启时,会通过重新执行 AOF 文件中的所有写命令(这个过程称为 “重放”,在 aof.c 文件中有关于 AOF 文件加载和命令重放的相关函数实现逻辑,涉及到命令解析、执行环境构建等关键操作),来将数据恢复到故障前的状态,从而实现数据的持久化。同时,为了避免 AOF 文件无限增大(因为持续追加写命令),Redis 提供了多种 AOF 文件重写机制,例如当 AOF 文件的大小超过一定阈值(由配置文件中的相关参数决定,如 auto-aof-rewrite-percentage 和 auto-aof-rewrite-min-size 等参数共同控制,前者表示相对于上次重写后 AOF 文件大小的增长百分比达到多少时触发重写,后者表示 AOF 文件的最小体积达到多少时触发重写),Redis 会启动一个后台进程,根据当前内存中的数据重新生成一个新的 AOF 文件,新文件中只包含能够恢复当前数据状态的最少的必要命令,去除了一些冗余的、对最终数据状态没有影响的命令(比如重复的对同一个键的多次写操作,最终只保留最后一次有效的写命令),在重写过程中,同样会采用写时复制等技术来保证主进程的正常读写操作不受影响,重写完成后会替换原来的 AOF 文件,实现 AOF 文件的优化和精简。
优点:
  • 数据安全性高:因为每一条写命令都被记录下来,所以即使 Redis 在运行过程中出现故障,最多只会丢失最后一次执行 BGREWRITEAOF(后台重写 AOF 文件的命令)或者系统崩溃时正在执行的那条写命令的数据,相比于 RDB 持久化,数据丢失的可能性更小,更能保障数据的完整性,适用于对数据准确性要求极高的场景,如金融系统中的账户余额记录等关键数据存储。
  • 灵活性与可读性:AOF 文件记录的是可读的 Redis 命令文本(虽然是以追加方式记录,但命令本身是文本格式),对于开发者来说,如果需要查看数据的修改历史或者进行一些数据审计工作,相对更容易理解和分析 AOF 文件中的内容,而且可以通过修改 AOF 文件(在一定规则下,且需要谨慎操作)来手动调整 Redis 的数据状态(不过这种情况比较少见,一般用于数据恢复等特殊场景),体现了一定的灵活性。
缺点:
  • 文件体积大:由于是不断追加写命令,AOF 文件随着 Redis 运行时间的增长以及写操作的频繁进行,文件体积会不断增大,即使有重写机制,在一些高并发写操作场景下,文件体积依然可能较大,这不仅占用大量的磁盘空间,而且在 Redis 重启时,加载和重放较大的 AOF 文件需要花费较长的时间,影响 Redis 的恢复速度,例如在一个写入非常频繁的大数据量 Redis 应用场景中,AOF 文件可能很快达到几个 GB 甚至更大,重启时重放命令的过程会比较耗时。
  • 性能影响:每执行一次写命令都要追加记录到 AOF 文件中,这会对 Redis 的写性能产生一定的影响,虽然 Redis 内部采用了一些优化措施(如缓冲区机制,先将写命令暂存到缓冲区,达到一定条件后再批量写入 AOF 文件等),但相比于不开启 AOF 持久化的情况,还是会有一定的性能损耗,尤其是在对性能要求极高的高并发场景下,这种影响可能需要综合权衡,不过对于大多数应用场景来说,这种性能损耗通常是在可接受范围内的。

(三)持久化方式选择依据

在实际应用中,需要根据不同的应用需求来选择合适的 Redis 持久化方式或者结合使用两种持久化方式,以下是一些常见的选择依据:

  1. 数据安全性要求
    如果对数据安全性要求极高,不能容忍数据有较大丢失风险,比如金融支付系统、订单处理系统等核心业务场景,建议优先选择 AOF 持久化或者采用 RDB + AOF 的组合方式(同时开启两种持久化,这样即使 RDB 快照之间出现故障,AOF 文件也能最大限度地保证数据的完整性),利用 AOF 对写命令全记录的特点来保障数据的准确性;而对于一些对数据丢失有一定容忍度,更注重快速恢复和备份便捷性的场景,如缓存系统(缓存数据即使丢失一部分也可以重新从后端数据库获取补充),可以选择 RDB 持久化方式。
  2. 性能与资源考虑
    如果应用场景对 Redis 的写性能要求很高,且服务器资源(如磁盘 I/O、CPU 等)相对紧张,单独使用 AOF 持久化可能会因为频繁的写命令记录对性能产生较大影响,此时可以考虑 RDB 持久化或者适当调整 AOF 的相关参数(如增大缓冲区大小、合理设置重写触发条件等)来降低性能损耗;反之,如果服务器资源充足,且能够接受一定程度的性能损耗来换取更高的数据安全性,那么 AOF 持久化或者组合持久化方式是更好的选择,尤其是在应对高并发写操作场景下,合理配置 AOF 持久化可以在保障数据安全的同时,将性能影响控制在可接受范围内。
  3. 恢复时间要求
    对于一些需要快速重启恢复数据的应用场景,如生产环境中的 Redis 服务意外故障后需要尽快恢复上线提供服务,RDB 持久化方式由于其恢复速度快的优势,可以单独使用或者作为主要的持久化手段(结合 AOF 时,先通过 RDB 文件快速恢复大部分数据,再利用 AOF 文件补充丢失的数据部分);而如果对恢复时间没有特别严格的要求,更在意数据的完整性和准确性,那么 AOF 持久化或者组合持久化方式可以根据具体的业务情况进行选用。

四、Redis 的集群模式

(一)主从复制模式

  • 配置主节点:在 Redis 的配置文件(redis.conf)中,一般不需要做特殊的主从相关配置改动(默认就是可以作为主节点运行的状态),只需设置好基本的服务参数(如监听端口、密码等,若有需要),启动主节点服务即可。
  • 配置从节点:在从节点的配置文件中,需要通过 replicaof (在旧版本中是 slaveof )命令来指定主节点的 IP 地址和端口号,例如 replicaof 123.45.67.89 6379(这里假设主节点的 IP 是 123.45.67.89,端口是 6379),表示该从节点要复制这个主节点的数据,同时可以根据实际需求设置其他相关参数(如是否只读等,从节点默认是只读的,只能进行读操作,不能执行写操作,可通过配置文件中的 replica-read-only 参数进行调整),配置完成后启动从节点服务。启动后,从节点会主动向主节点发起连接请求,建立主从复制关系。
工作原理(源码角度):
  • 连接建立与数据同步阶段:从节点启动后,通过网络通信模块(在 Redis 源码中,anet.c 等文件涉及到网络连接相关的底层函数实现,负责创建套接字、建立 TCP 连接等操作)向主节点发送 SYNC 信号(在 replication.c 文件中有关于 SYNC 命令及相关同步逻辑的实现函数),请求进行数据同步。主节点收到请求后,会执行 BGSAVE 命令(后台保存数据快照,前面介绍 RDB 持久化时有提到其源码实现逻辑)生成 RDB 文件,同时在生成 RDB 文件期间,主节点会将新接收到的写命令缓存到一个缓冲区(称为复制缓冲区,在源码中有相应的结构和管理逻辑来保证写命令的临时存储和后续传递)中。当 RDB 文件生成完成后,主节点会将 RDB 文件发送给从节点(通过网络连接进行文件传输,涉及到文件读取和网络发送的相关操作,确保数据准确传输到从节点),从节点接收到 RDB 文件后,会将其加载到内存中,恢复到主节点当时的数据状态。接着,主节点会将复制缓冲区中缓存的在生成 RDB 文件期间的写命令依次发送给从节点,从节点接收到这些命令后,按照顺序在本地重新执行一遍,这样就能保证从节点的数据与主节点在执行完 BGSAVE 命令那一刻的数据完全一致,完成首次的全量数据同步过程。
  • 命令传播阶段:在全量数据同步完成后,主节点后续每接收到客户端发来的写命令并执行时,都会实时地将这些写命令以传播的方式发送给所有连接的从节点(通过网络通信模块再次进行命令的发送操作,在源码中有相关的消息队列以及发送逻辑确保命令能准确无误且及时地传递给各个从节点),从节点收到命令后同样会在本地执行,从而保证从节点的数据能实时跟上主节点的数据变化,实现数据的实时同步。在这个阶段,如果网络出现短暂中断或者其他异常情况导致从节点与主节点的连接断开,从节点会自动尝试重新连接主节点,并根据主节点的状态以及自身已同步的数据情况,判断是进行部分数据的增量同步(如果断开期间主节点变化的数据量不大,通过主节点记录的一些偏移量等信息来确定需要同步的部分数据,从节点获取并执行相应命令即可恢复同步)还是重新进行全量数据同步(如果断开时间较长等情况),以此来维持主从数据的一致性。
  • 性能特点:主从复制模式可以实现数据的读写分离,将读操作分散到多个从节点上,减轻主节点的读压力,提升整体的读性能,适用于读多写少的应用场景,比如一些内容管理系统中对文章、图片等数据的频繁读取以及相对较少的写入操作(文章发布、图片更新等属于写操作)场景下,通过配置多个从节点,可以让大量的读请求(如用户浏览文章等)在从节点上完成,而主节点专注于处理写请求,保障数据的一致性更新。不过,主从复制模式存在单点故障问题,即如果主节点出现故障,整个系统的写操作将无法正常进行(虽然从节点可以继续提供读服务,但不能进行新的写操作),需要人工干预进行主节点的恢复或者切换等操作,对系统的可用性有一定影响,而且在进行全量数据同步时,尤其是数据量较大的情况下,会占用一定的网络带宽以及主从节点的系统资源(如磁盘 I/O、CPU 等用于生成和传输 RDB 文件、执行命令等操作),可能会对业务产生短暂的性能影响。
应用场景:
  • 常用于对数据一致性有一定要求,同时读请求量远大于写请求量的场景,像缓存系统中缓存数据的同步(主节点负责更新缓存数据,从节点提供缓存查询服务)、电商系统中商品信息的读取(大量用户浏览商品详情等读操作分配到从节点)与商品信息更新(由主节点处理如商品价格调整、库存更新等写操作)等场景,利用其读写分离提升读性能以及简单的主从数据同步保障数据一致性的特点,优化系统整体性能和可用性。

(二)哨兵模式

  • 配置哨兵节点:首先需要创建哨兵的配置文件(通常命名为 sentinel.conf),在配置文件中主要配置要监控的主节点信息以及一些哨兵自身的参数。例如,通过 sentinel monitor 语句来指定要监控的主节点名称(自定义的名称,方便识别和管理)、主节点的 IP 地址、主节点的端口号以及法定人数(quorum,表示认定主节点不可用所需要的哨兵节点数量,一般设置为大于等于 1 的整数,根据实际部署的哨兵节点数量以及对故障判断的严格程度等来合理设置)。还可以配置如 sentinel down-after-milliseconds (用于设置哨兵判断主节点或从节点主观下线的时间,即节点在多长时间内没有响应哨兵的心跳检测等就认定为下线)、sentinel failover-timeout (用于设定故障转移操作的超时时间,确保故障转移过程能在合理时间内完成)等其他相关参数,配置完成后启动哨兵节点服务(通过执行 redis-sentinel sentinel.conf 命令或者 redis-server sentinel.conf --sentinel 命令启动,这两个启动方式在源码实现上最终都会调用到相关的初始化及运行逻辑,在 sentinel.c 文件中有部分关键启动和运行相关的函数实现)。
  • 结合主从节点:哨兵模式是建立在主从复制基础上的,所以需要先按照前面介绍的主从复制模式搭建好主节点和若干从节点,然后启动的哨兵节点会自动去发现并连接对应的主从节点,开始对它们进行监控以及后续的故障转移等相关操作。
工作原理(源码角度):
  • 监控阶段:哨兵节点会定期(由配置的心跳检测时间间隔决定,通过源码中的定时器相关机制实现,在 ae.c 等文件中有涉及到定时器的创建、触发等底层函数实现逻辑,用于定期执行心跳检测等任务)向主节点、从节点发送心跳检测命令(如 PING 等简单的命令用于判断节点是否存活且可响应),如果在规定的 down-after-milliseconds 时间内没有收到节点的有效回复,哨兵节点就会认为该节点主观下线(在 sentinel.c 文件中的相关判断逻辑函数会根据返回结果以及时间等条件进行主观下线的判定操作)。当有多个哨兵节点都判定主节点主观下线后(达到法定人数 quorum 的要求),就会认为主节点客观下线,此时就会触发后续的故障转移流程。
  • 故障转移阶段:在确定主节点客观下线后,哨兵节点之间会通过选举机制(在源码中有基于 Raft 协议或者类似的分布式选举算法实现逻辑,涉及到节点之间消息传递、投票等操作,用于选出一个哨兵节点作为领导者来执行故障转移操作,不同版本的 Redis 可能采用的具体算法略有差异)选出一个领导者哨兵节点,这个领导者哨兵节点会开始进行故障转移操作。首先,它会从可用的从节点中选择一个合适的从节点(选择标准通常考虑从节点的优先级,可通过配置文件设置,优先级高的优先被选;如果优先级相同,则选择复制偏移量最大的从节点,即数据最接近主节点的从节点,在源码中有相应的比较和筛选逻辑函数实现)作为新的主节点,然后向这个新主节点发送 SLAVEOF NO ONE 命令(在 replication.c 文件中有关于该命令及相关操作的函数实现),使其停止作为从节点并开始独立作为主节点接受写操作。接着,领导者哨兵节点会向其他从节点发送 SLAVEOF 命令,让它们指向新的主节点,重新建立主从复制关系,从而完成整个故障转移过程,使得系统能在主节点出现故障后快速恢复正常的读写服务,保障系统的可用性。
  • 通知阶段:在故障转移完成后,哨兵节点还会向客户端(通过发布 / 订阅机制实现通知功能,在 Redis 源码中,pubsub.c 文件里有关于发布 / 订阅相关的底层函数实现,用于消息的发布、订阅以及传递)以及其他相关的系统组件(如连接到 Redis 的应用程序等)发送通知消息,告知它们主节点已经发生了变更,以便客户端等能及时更新连接的主节点信息,继续正常地进行读写操作。
  • 性能特点:哨兵模式解决了主从复制模式中主节点单点故障的问题,通过自动的故障检测和转移机制,能够在主节点出现故障时快速地选择新的主节点,恢复系统的写操作能力,提升了系统的可用性和可靠性。不过,哨兵模式在进行故障转移过程中,尤其是选举阶段和重新配置主从关系阶段,会涉及到多个节点之间的通信、命令执行等操作,虽然时间相对较短,但在这个短暂的过程中可能会对系统的读写性能产生一定影响,而且如果哨兵节点自身出现故障(虽然可以部署多个哨兵节点来降低这种风险),也可能会影响整个故障转移机制的正常运行,需要合理地部署和配置哨兵节点数量以及相关参数,确保系统的稳定性和性能的平衡。
应用场景:
  • 适用于对系统可用性要求较高,需要自动处理主节点故障情况的场景,比如在企业级的缓存集群、电商系统的核心数据存储(如订单数据、用户数据等)等场景中,即使主节点出现意外故障,也能依靠哨兵模式快速恢复服务,减少因故障导致的业务中断时间,保障业务的正常运转。

(三)Cluster 模式

  • 配置节点信息:在每个节点(Redis 集群中的每个实例都可看作一个节点)的配置文件(redis.conf)中,需要开启集群模式相关配置,一般通过设置 cluster-enabled yes 来启用集群功能,同时可以配置 cluster-config-file (指定集群配置文件的名称和路径,用于存储集群节点的相关信息,如节点的 IP 地址、端口号、槽位分配等信息,默认文件名通常为 nodes.conf)、cluster-node-timeout (用于设定节点之间通信超时时间,如果一个节点在这个时间内没有响应其他节点的消息,就可能被认为出现故障等情况,根据网络环境以及集群规模等合理设置该参数,单位是毫秒)等参数。配置完成后,分别启动各个节点服务,启动过程中,节点会根据配置信息尝试与其他节点进行连接和通信,开始构建集群。
  • 槽位分配与集群初始化:Redis Cluster 采用了槽位(Slot)的概念来管理数据分布,总共有 16384 个槽位,集群启动后,需要将这些槽位分配到各个节点上,最初可以通过命令行工具(如 redis-cli )或者在节点启动时指定相关参数来进行手动分配(不过在实际应用中,更多是通过集群内部的自动分配机制来完成)。例如,可以使用 CLUSTER ADDSLOTS 命令(在 cluster.c 文件中有关于该命令及相关操作的函数实现,涉及到槽位分配的逻辑以及与其他节点的通信告知等操作)将一定范围的槽位分配给某个节点,各个节点分配到槽位后,就明确了各自负责存储和处理的数据范围,完成集群的初步初始化工作,之后集群就可以正常接收客户端的读写请求了。
工作原理(源码角度):
  • 数据分布与槽位管理:每个节点负责一部分槽位的数据存储和读写操作,当客户端发送一个写命令或者读命令时,Redis 会先根据键(通过对键进行 CRC16 算法计算,在 crc16.c 文件中有关于 CRC16 算法的实现函数,将键转换为一个 16 位的哈希值,然后对 16384 取模,得到该键对应的槽位编号)计算出这个键所属的槽位,然后查找负责这个槽位的节点(通过节点之间的通信以及每个节点维护的槽位分配表来确定,在源码中,每个节点都有相应的结构体来存储槽位信息以及与其他节点关于槽位的关联信息,方便查询和管理),并将命令转发到该节点进行处理。如果在运行过程中某个节点出现故障或者需要进行节点的添加、删除等操作(如扩容或缩容场景),集群会通过内部的重新分片(Resharding)机制(涉及到槽位的迁移、数据的移动以及节点之间的协调沟通等复杂操作,在 cluster.c 文件中有大量关于重新分片相关的函数实现逻辑)来重新分配槽位,保证数据的合理分布和集群的正常运行。
  • 节点通信与故障检测:集群中的节点会定期互相发送消息(通过 Gossip 协议实现节点之间的信息传播和状态更新,在 cluster.c 文件中有关于 Gossip 协议的消息发送、接收以及处理等函数实现,利用随机选择部分节点进行消息传递的方式,高效且异步地让每个节点了解整个集群的大致状态)来进行节点状态的更新和故障检测,比如检测某个节点是否下线等情况。如果一个节点在 cluster-node-timeout 时间内没有响应其他节点的消息,其他节点会先将其标记为疑似下线(PFAIL,Possible Failure)状态,然后随着更多节点反馈该节点的疑似下线情况,经过一定的判断机制(根据集群的配置以及节点之间的通信反馈等综合判断),如果达到一定条件,就会将其判定为已下线(FAIL)状态,并触发相应的故障处理流程(如槽位的迁移等操作,确保数据的可用性和集群的正常运行)。
  • 故障恢复与数据可用性保障:当一个节点被判定为已下线后,集群会根据节点之间的槽位分配以及数据复制情况(部分节点之间可能存在数据的备份复制关系,用于提高数据的冗余和可用性)来进行故障恢复操作,比如将该节点负责的槽位数据迁移到其他正常的节点上(通过重新分片等机制实现),或者启用备份的数据副本(如果有)来继续提供对相应槽位数据的读写服务,保证在节点出现故障的情况下,客户端依然能够正常访问到自己需要的数据,最大限度地保障了数据的可用性和集群的整体稳定性。
  • 性能特点:Redis Cluster 模式具有良好的扩展性,可以方便地通过添加节点来扩展集群的存储容量和处理能力,适合处理海量数据以及高并发读写的场景,而且通过槽位管理和自动的故障处理机制,能够在一定程度上保障数据的可用性和集群的稳定性。然而,集群模式由于涉及到多个节点之间的复杂通信、槽位计算和管理以及数据的迁移等操作,在数据读写性能上相对单机的 Redis 或者简单的主从复制模式可能会有一定的损耗,尤其是在进行大规模的数据迁移或者集群状态频繁变化(如频繁添加、删除节点等)时,对性能的影响会更加明显,需要合理规划集群的规模、槽位分配以及节点配置等,以平衡性能和扩展性、可用性之间的关系。
应用场景:
  • 广泛应用于大数据量、高并发的互联网应用场景中,比如大型的社交网络平台(存储海量用户信息、用户动态等数据以及应对高并发的读写请求)、电商平台的商品数据存储与读写(大量商品信息的管理以及频繁的用户浏览、下单等操作)、在线游戏的玩家数据管理(众多玩家的游戏数据存储以及实时的读写操作)等场景,通过构建 Redis Cluster 集群,能够有效地应对业务增长带来的数据量和并发量的挑战,保障系统的高效稳定运行。

总之,Redis 的不同集群模式各有其特点、性能表现以及适用场景,在实际应用中,需要根据具体的业务需求、数据规模、对可用性和性能的要求等因素综合考虑,选择合适的集群模式或者组合使用多种模式,来充分发挥 Redis 在非关系型数据库领域的优势,为系统提供高效、可靠的数据存储和处理服务。

相关资料已更新
关注公众号:搜 架构研究站,回复:资料领取,即可获取全部面试题以及1000+份学习资料
在这里插入图片描述


http://www.mrgr.cn/news/82174.html

相关文章:

  • 域名都有哪些?
  • LINUXC 时间相关操作
  • Java Web开发基础——Java Web项目的结构与组织
  • 智能商业分析 Quick BI
  • springboot3 ThreadPoolTaskExecutor 和 Executors 线程池优化
  • 【新方法】通过清华镜像源加速 PyTorch GPU 2.5安装及 CUDA 版本选择指南
  • primevue的<Menu>组件
  • 基于 Node.js 的 ORM(对象关系映射)工具——Sequelize介绍与使用,并举案例分析
  • 【循环神经网络】RNN介绍
  • 【容器化技术 Docker 与微服务部署】详解
  • 基于WSL在Windows下安装Ubuntu
  • C++ 设计模式:组合模式(Composite Pattern)
  • 智能工厂的设计软件 应用场景的一个例子:为AI聊天工具添加一个知识系统 之12 方案再探之3:特定于领域的模板 之2 首次尝试和遗留问题解决
  • 「Mac畅玩鸿蒙与硬件50」UI互动应用篇27 - 水果掉落小游戏
  • libvirt学习
  • 【Compose multiplatform教程24】导航与路由
  • 智能工厂的设计软件 应用场景的一个例子:为AI聊天工具添加一个知识系统 之11 方案再探之2 项目文件(修改稿1)
  • Linux2.4.20顶层Makefile文件分析
  • 我的Java-Web进阶--Spring
  • CPT203 Software Engineering 软件工程 Pt.2 敏捷方法和需求工程(中英双语)
  • 计算机的错误计算(一百九十七)
  • Kubernetes第二天
  • Vue演练场基础知识(一)
  • Wireshark和科来网络分析系统
  • 掌握大数据处理利器:Flink 知识点全面总结【上】
  • C++ 设计模式:职责链模式(Chain of Responsibility)