Skip to content

Redis

INFO

开学!

Redis简介

  Redis是一个开源的内存数据结构存储系统,支持多种数据结构,如字符串、哈希、列表、集合、有序集合等。它被广泛用于缓存、消息队列和实时分析等场景。它是一个键值数据库,属于NOSQL库。

NoSQL/SQL

  SQL(Structured Query Language)(关系型数据库)是一种用于关系型数据库的查询语言,适用于结构化数据。NoSQL(Not Only SQL)是一类非关系型数据库,适用于非结构化或半结构化数据。NoSQL数据库通常具有更高的可扩展性和灵活性,适合处理大规模数据和分布式系统。它们的根本区别在于数据存储的方式、扩展机制以及对事务处理的侧重点。

  1. 数据结构与存储模型
  • SQL:采用二维表格模型。数据被组织成行和列,表与表之间通过外键建立严格的关系关联(如 MySQL, PostgreSQL, Oracle)。
  • NoSQL:采用多样化的数据模型。常见的有键值对(Redis)、文档型(MongoDB,类似 JSON)、列族(HBase)和图结构(Neo4j)。它不需要严格的表格规范。
  1. Schema (数据模式)
  • SQL:结构固定(预定义)。在写入数据之前,必须先建表,定义好每一列的字段名和数据类型。如果后期要增加一个字段,必须修改整个表结构,成本较高。
  • NoSQL:结构灵活(动态)。通常不需要预先定义模式,同一集合(相当于表)下的不同数据(相当于行)可以拥有完全不同的字段。非常适合快速迭代或数据结构不确定的业务。
  1. 扩展方式 (Scaling)
  • SQL:倾向于垂直扩展(Scale-up)。当性能遇到瓶颈时,主要依靠升级单台服务器的硬件配置(加 CPU、内存、磁盘)。虽然也能进行分库分表,但业务代码和运维的改造成本很高。
  • NoSQL:天生为水平扩展设计。当数据量剧增时,只需要在集群里增加更多的廉价服务器节点,通过内部的分片机制把数据分散出去即可,扩展极其顺滑。
  1. SQL查询
  • SQL:使用结构化查询语言(SQL)进行数据操作,支持复杂的查询、连接和事务处理。SQL查询功能强大,适合复杂的数据分析和关系型数据操作。
  • NoSQL:通常提供简单的查询接口,针对特定数据模型进行优化。虽然某些NoSQL数据库(如MongoDB)也支持类似SQL的查询语言,但整体上不如关系型数据库强大,适合简单的查询和高性能读写。
  1. 事务处理 (ACID vs BASE)
  • SQL:严格遵循 ACID 原则(原子性、一致性、隔离性、持久性)。这是它的绝对强项,能够保证金融级别的绝对数据一致性。
  • NoSQL:通常遵循 BASE 理论(基本可用、软状态、最终一致性)。为了追求极致的高并发读写性能和集群高可用性,它往往会牺牲强一致性,只保证数据在“最终”某个时刻会达到一致。
SQLNoSQL
数据结构结构化(Structured)非结构化
数据关联关联的(Relational)无关联的
查询方式SQL查询非SQL
事务特性ACIDBASE
存储方式磁盘内存
扩展性垂直水平
使用场景1) 数据结构固定
2) 相关业务对数据安全性、一致性要求较高
1) 数据结构不固定
2) 对一致性、安全性要求不高
3) 对性能要求

Redis的特点

  1. 键值型,value支持多种不同数据结构,功能丰富
  2. 单线程,每个命令具备原子性
  3. 低延迟,速度快(基于内存、IO多路复用、良好编码)
  4. 支持数据持久化
  5. 支持主从复制,哨兵模式,集群模式等高可用方案

数据结构

  1. String: 字符串,二进制安全,可以存储任何类型的数据,如文本、图片、序列化对象等。
  2. Hash: 哈希表,适合存储对象类型的数据,如用户信息等。
  3. List: 列表,按照插入顺序存储字符串,可以在两端进行快速的插入和删除操作。
  4. Set: 集合,存储唯一的字符串元素,提供丰富的集合操作,如交集、并集、差集等。
  5. Sorted Set: 有序集合,类似于Set,但每个元素都会关联一个分数(score),可以根据分数进行排序,适合排行榜等场景。

以下不怎么常用

  1. Bitmap: 位图,适合存储大量的布尔值数据,如用户签到等。例如:011011011010101。
  2. HyperLogLog: 基数统计,适合统计独立元素的数量,如UV统计等。例如:011011011010101。
  3. GEO: 地理位置,支持存储地理坐标并进行距离计算等操作。例如:{"latitude": 40.7128, "longitude": -74.0060}。

key的结构

  Redis中的key是一个字符串,可以包含任何字符,长度不能超过512MB。为了更好地组织和管理数据,通常会使用冒号(:)作为分隔符来构建具有层次结构的key。例如:user:name:1001表示用户ID为1001的姓名,order:2023:09:15表示2023年9月15日的订单。通过这种方式,可以方便地使用通配符(如user:*)来批量操作相关的key。

  具体的格式为:项目名:业务名:类型:id

  如果Value是一个java对象,可以将对象序列化成字符串进行存储,常用的序列化方式有JSON、XML、Protobuf等。反序列化时需要将字符串转换回对象。

常见命令

  所有的命令都可以通过help @<command>来查看具体的用法和参数,例如help @set。可以使用help @generic来查看通用命令。

通用命令

  1. PING: 测试服务器是否可用。
  2. ECHO <message>: 返回输入的消息。
  3. KEYS <pattern>: 查找所有匹配给定模式的键。不建议在生产环境设备上使用,因为redis单线程,可能会阻塞服务器。
  4. DEL <key>: 删除一个或多个键。
  5. EXISTS <key>: 检查一个或多个键是否存在。
  6. EXPIRE <key> <seconds>: 设置键的过期时间。当seconds为-2时,表示键不存在;当seconds为-1时,表示键永不过期。
  7. TTL <key>: 获取键的剩余生存时间。
  8. FLUSHDB: 删除当前数据库中的所有键。
  9. FLUSHALL: 删除所有数据库中的所有键。
  10. INFO: 获取服务器的各种信息和统计数据。
  11. AUTH <password>: 认证密码。
  12. SELECT <index>: 切换到指定的数据库索引。
  13. DBSIZE: 获取当前数据库中的键数量。
  14. SAVE: 同步保存数据到磁盘。
  15. BGSAVE: 在后台异步保存数据到磁盘。
  16. SHUTDOWN: 关闭服务器。

字符串命令

  字符串类型的键,value是字符串,根据字符串的格式,又可以分为:

  • string: 普通字符串,适合存储文本数据。
  • int:整数,适合存储数字数据,可以进行自增自减等操作。
  • float:浮点数,适合存储带小数的数据,可以进行自增自减等操作。

  不管是哪种格式,底层都是字节数组形式存储,只不过是编码方式不同。字符串类型的最大空间不能超过512m。

  常见命令有:

  1. SET: 设置键值对,可以设置过期时间和条件。
  2. GET: 获取键的值。
  3. MSET: 批量设置多个键值对。
  4. MGET: 批量获取多个键的值。
  5. INCR/DECR: 将键的整数值增加/减少1。
  6. INCRBY/DECRBY: 将键的整数值增加/减少指定的数值。
  7. INCRBYFLOAT/DECRBYFLOAT: 将键的浮点值增加/减少指定的数值。
  8. STRLEN: 获取键的值的长度。
  9. SETNX: 只有在键不存在时才设置键值对,存在的时候直接忽略。
  10. SETEX: 设置键值对并设置过期时间。

哈希命令

  哈希类型的键,value是一个哈希表,适合存储对象类型的数据,如用户信息等。哈希表中的每个字段都是一个字符串,可以通过字段名来访问对应的值。

  常见命令有:

  1. HSET key field value: 设置哈希表中的字段值。
  2. HGET: 获取哈希表中指定字段的值。
  3. HMSET: 批量设置哈希表中的多个字段值。
  4. HMGET: 批量获取哈希表中多个字段的值。
  5. HGETALL: 获取哈希表中所有字段的名称和值。
  6. HKEYS: 获取哈希表中所有字段的名称。
  7. HVALS: 获取哈希表中所有字段的值。
  8. HDEL: 删除哈希表中的一个或多个字段。
  9. HEXISTS: 检查哈希表中是否存在指定字段。
  10. HLEN: 获取哈希表中字段的数量。
  11. HINCRBY: 将哈希表中指定字段的整数值增加指定的数值。
  12. HSETNX: 只有在字段不存在时才设置字段值,存在的时候直接忽略。

List类型

  List类型可以看作是一个双向链表结构,可以在两端进行快速的插入和删除操作。列表中的每个元素都是一个字符串,可以通过索引来访问对应的值。

  特征有:

  • 有序
  • 允许重复元素
  • 支持快速的插入和删除操作
  • 查询速度一般

  可以用来存储需要有顺序的数据,如消息队列、日志、评论等。

  常见命令有:

  1. LPUSH key value [value ...]: 将一个或多个值插入到列表头部。
  2. RPUSH key value [value ...]: 将一个或多个值插入到列表尾部。
  3. LPOP key: 移除并返回列表头部的元素。
  4. RPOP key: 移除并返回列表尾部的元素。
  5. LRANGE key start stop: 获取列表中指定范围内的元素。
  6. LLEN key: 获取列表的长度。
  7. BLPOP 和 BRPOP: 与LPOP和RPOP类似,但如果列表为空,则会阻塞直到有元素可弹出。

Set类型

  Set类型是一个无序的集合,存储唯一的字符串元素。集合中的每个元素都是一个字符串,可以通过集合操作来进行交集、并集、差集等操作。

  特征有:

  • 无序
  • 不允许重复元素
  • 支持丰富的集合操作(交集、并集、差集等)
  • 查询速度较快

  可以用来存储需要去重的数据,如标签、好友列表等。

  常见命令有:

  1. SADD key member [member ...]: 向集合添加一个或多个成员。
  2. SREM key member [member ...]: 从集合中移除一个或多个成员。
  3. SMEMBERS key: 获取集合中的所有成员。
  4. SISMEMBER key member: 检查成员是否是集合的成员。
  5. SCARD key: 获取集合的成员数量。

  Set类型还有计算集合交集、并集、差集的命令:

  1. SINTER key [key ...]: 计算集合的交集。
  2. SUNION key [key ...]: 计算集合的并集。
  3. SDIFF key [key ...]: 计算集合的差集。

Sorted Set类型

  Sorted Set类型是一个有序的集合,类似于Set,但每个元素都会关联一个分数(score),可以根据分数进行排序。集合中的每个元素都是一个字符串,可以通过分数来访问对应的值。底层是一个跳表结构加上一个哈希表结构。

  特征有:

  • 有序
  • 不允许重复元素
  • 支持根据分数进行排序
  • 查询速度较快

  可以用来存储需要排序的数据,如排行榜、优先级队列等。

  常见命令有:

  1. ZADD key score member [score member ...]: 向有序集合添加一个或多个成员,或者更新已存在成员的分数。
  2. ZREM key member [member ...]: 从有序集合中移除一个或多个成员。
  3. ZSCORE key member: 获取有序集合中指定成员的分数。
  4. ZRANK key member: 获取有序集合中指定成员的排名,排名从0开始。
  5. ZCARD key: 获取有序集合的成员数量。
  6. ZINCRBY key increment member: 将有序集合中指定成员的分数增加指定的增量。
  7. ZRANGE key start stop [WITHSCORES]: 获取有序集合中指定范围内的成员,默认按照分数从低到高排序。
  8. ZRANGEBYSCORE key min max [WITHSCORES]: 获取有序集合中指定分数范围内的成员,默认按照分数从低到高排序。
  9. ZCOUNT key min max: 获取有序集合中指定分数范围内的成员数量。
  10. ZDIFF, ZINTER, ZUNION: 计算有序集合的差集、交集、并集,类似于Set类型的命令,但会根据分数进行排序。

Redis的JAVA客户端

  常用的Java客户端有Jedis、Lettuce、lettuce。Jedis是一个基于阻塞IO的客户端,适合单线程环境;Lettuce是一个基于Netty的异步非阻塞IO客户端,适合多线程环境。选择哪个客户端取决于具体的应用场景和性能需求。

  • Jedis: 以Redis命令作为方法名称,学习成本低,简单实用。但是线程不安全,需要使用连接池来保证线程安全。
  • Lettuce: 基于Netty实现,支持异步和反应式编程模型,性能更高,适合高并发场景。线程安全,可以在多线程环境中共享一个连接实例。支持Redis的哨兵模式和集群模式,提供了更好的高可用性和扩展性。
  • Redisson: 基于Lettuce实现,提供了丰富的分布式数据结构和工具类,如分布式锁、分布式集合、分布式队列等,适合需要使用Redis作为分布式协调工具的场景。线程安全,可以在多线程环境中共享一个连接实例。支持Redis的哨兵模式和集群模式,提供了更好的高可用性和扩展性。
  • Spring Data Redis:Spring Data Redis是Spring框架提供的一个模块,简化了与Redis的集成和使用。它提供了一个高级抽象层,使得开发者可以更方便地使用Redis进行数据访问和操作。Spring Data Redis支持多种Redis客户端,如Jedis、Lettuce等,并且提供了丰富的功能,如RedisTemplate、RedisRepository等,适合在Spring应用中使用Redis作为缓存、消息队列等组件。

Jedis

  用起来非常简单,直接调用方法即可,例如:

java
@SpringBootTest
public class JedisTest {
  private Jedis jedis;

  @BeforeEach
  public void setUp() {
    jedis = new Jedis("192.168.1.160", 6379);
    jedis.auth("123321");
    jedis.select(0);
  }

  @Test
  public void testJedis() {
    jedis.set("name", "duke");
    String name = jedis.get("name");
    System.out.println(name);
  }

  @AfterEach
  public void tearDown() {
    if (jedis != null) {
      jedis.close();
    }
  }
}

连接池

  Jedis连接池可以通过JedisPool类来创建和管理连接池。使用连接池可以提高性能,避免频繁创建和销毁连接的开销。以下是一个使用Jedis连接池的示例:

java
public class JedisConnectionFactory {
  private static final JedisPool jedisPool;

  static {
    JedisPoolConfig poolConfig = new JedisPoolConfig();
    poolConfig.setMaxTotal(128); // 最大连接数
    poolConfig.setMaxIdle(128); // 最大空闲连接数
    poolConfig.setMinIdle(16); // 最小空闲连接数
    poolConfig.setMaxWaitMillis(10000); // 获取连接的最大等待时间

    jedisPool = new JedisPool(poolConfig, "192.168.1.160", 6379, 2000, "123321");
  }

  public static Jedis getJedis() {
    return jedisPool.getResource();
  }
}

什么叫做线程安全?为什么Jedis不是线程安全的?

  线程安全是指在多线程环境下,多个线程同时访问同一个资源时,不会发生数据不一致或程序崩溃等问题。

  Jedis不是线程安全的。在底层,一个 Jedis 对象其实就是你的 Java 程序和 Redis 服务器之间建立的唯一一条物理网络通道(TCP 连接)。它里面封装了输入流(读数据)和输出流(写数据)。为了追求极致的单线程执行速度,Jedis 在设计时没有给这些读写操作加锁(synchronized)。

  假设在多线程环境下,线程 A 和 线程 B 共享了同一个 Jedis 实例:

  • 线程 A 想发送命令:SET name Jack
  • 线程 B 想发送命令:GET age

  如果这两个线程在同一微秒向这个 Jedis 实例写入数据,由于没有锁的保护,这条唯一的 TCP 管道里流动的数据就会变成乱码交叠:发送到 Redis 的数据可能变成了:SGET ET n aagme Jack。

  结果会导致两种崩溃:

  1. 协议解析错误(Protocol Error): Redis 服务器收到了一堆乱码,根本看不懂,直接抛出异常断开连接。
  2. 数据串线(最可怕的 bug): 假如命令侥幸完整发过去了,Redis 处理完后把结果按顺序返回。结果线程 A 满心欢喜地读取返回值,却拿到了线程 B 请求的 age 的数据。

Spring Data Redis

  Spring Data Redis提供了一个高级抽象层,使得开发者可以更方便地使用Redis进行数据访问和操作。它支持多种Redis客户端,如Jedis、Lettuce等,并且提供了丰富的功能,如RedisTemplate、RedisRepository等,适合在Spring应用中使用Redis作为缓存、消息队列等组件。

  其特点:

  • 提供了对不同Redis客户端的支持,如Jedis、Lettuce等,开发者可以根据需要选择合适的客户端。
  • 提供了RedisTemplate类,封装了对Redis的常用操作,简化了开发者的使用。
  • 支持Redis的发布/订阅功能,方便实现消息队列等功能。
  • 支持Redis哨兵和Redis集群,提供了更好的高可用性和扩展性。
  • 支持基于Lettuce的反应式编程模型,适合在响应式应用中使用Redis。
  • 支持基于JDK、JSON、XML等多种序列化方式,方便存储和读取复杂对象。
  • 支持基于Redis的JDKCollection实现

RedisTemplate

  RedisTemplate是Spring Data Redis提供的一个核心类,封装了对Redis的常用操作,简化了开发者的使用。它提供了丰富的方法来操作Redis中的数据结构,如字符串、哈希、列表、集合、有序集合等。

API返回值类型说明
redisTemplate.opsForValue()ValueOperations操作String类型数据
redisTemplate.opsForHash()HashOperations操作Hash类型数据
redisTemplate.opsForList()ListOperations操作List类型数据
redisTemplate.opsForSet()SetOperations操作Set类型数据
redisTemplate.opsForZSet()ZSetOperations操作SortedSet类型数据
redisTemplate通用的命令

配置文件

  在Spring Boot项目中,可以通过在application.propertiesapplication.yml文件中配置Redis的连接信息来使用Spring Data Redis。例如:

yaml
spring:
  redis:
    host: 192.168.1.160
    port: 6379
    password: 123321
    lettuce: 
      pool:
        max-active: 8
        max-idle: 8
        min-idle: 0
        max-wait: -1ms

使用

  使用RedisTemplate进行数据操作非常简单。例如:

java
@SpringBootTest
public class RedisTest {
  
  @Autowired
  private RedisTemplate redisTemplate;

  @Test
  public void test() {
    redisTemplate.opsForValue().set("name", "zhangsan");
    String name = (String) redisTemplate.opsForValue().get("name");
    System.out.println(name);
  }
}

  实际上,使用redisTemplate存入的数据是经过序列化的,默认使用JdkSerializationRedisSerializer进行序列化和反序列化,即使是字符串,也会被序列化,可读性差的同时占用内存大。

  可以通过配置来改变默认的序列化方式,推荐使用StringRedisSerializer来序列化字符串类型的数据,这样存储在Redis中的数据就具有可读性,并且占用内存较小;使用GenericJackson2JsonRedisSerializer来序列化对象类型的数据,这样存储在Redis中的数据就具有可读性,并且占用内存较小。使用如下方式修改默认的序列化方式:

java
@Bean
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
    RedisTemplate<String, Object> template = new RedisTemplate<>();
    template.setConnectionFactory(redisConnectionFactory);
    
    //设置序列化工具
    GenericJackson2JsonRedisSerializer jsonRedisSerializer = new GenericJackson2JsonRedisSerializer();

    //key和hashKey使用StringRedisSerializer进行序列化
    template.setKeySerializer(RedisSerializer.string());
    template.setHashKeySerializer(RedisSerializer.string());

    //value和hashValue使用GenericJackson2JsonRedisSerializer进行序列化
    template.setValueSerializer(jsonRedisSerializer);
    template.setHashValueSerializer(jsonRedisSerializer);
    return template;
}

  实际上,在这种情况存储的时候,redis里面会额外存储一个@class字段来记录对象的类型信息,这样在反序列化的时候就可以根据这个字段来确定要反序列化成什么类型的对象。虽然这种方式增加了存储空间的开销,但它提供了更好的可读性和灵活性,适合在开发和调试阶段使用。为了节省空间,存储value的时候也适用StringRedisSerializer来序列化,这样存储在Redis中的数据就具有可读性,并且占用内存较小。与此同时,我们得手动完成对象的序列化和反序列化工作,例如:

java
public class User {
  private String name;
  private int age;

  // getters and setters
}

# 核心实战:Spring Boot + Redis 手动序列化与反序列化 (企业级标准玩法)

**核心痛点:** 为了避免将 Java 类名(`@class`)存入 Redis 导致极其浪费内存以及后期代码重构引发的灾难,企业级开发中通常使用 `StringRedisTemplate` 配合 JSON 工具(如 Jackson)进行纯手动转换。

## 核心代码演示

```java
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.data.redis.core.StringRedisTemplate;

@SpringBootTest
class RedisStringTest {

    // 注入只能操作纯字符串的 StringRedisTemplate
    @Autowired
    private StringRedisTemplate stringRedisTemplate;

    // 注入 Spring Boot 默认自带的 JSON 处理工具 Jackson
    @Autowired
    private ObjectMapper mapper;

    @Test
    void testSaveUser() throws JsonProcessingException {
        // ==========================================
        // 第一阶段:存入数据 (Java对象 -> JSON字符串 -> Redis)
        // ==========================================
        
        // 1. 创建对象:大厨炒好一盘菜(普通的 Java POJO 对象)
        User user = new User("虎哥", 21);
        
        // 2. 手动序列化:将 Java 对象转换成极其干净的 JSON 字符串
        // 转换结果类似:{"name":"虎哥","age":21} ,绝不包含任何多余的类名信息
        String json = mapper.writeValueAsString(user);
        
        // 3. 写入数据:将纯净的 JSON 字符串存入 Redis
        // 这里存入的 key 是 "user:200",value 是刚才转好的 json 字符串
        stringRedisTemplate.opsForValue().set("user:200", json);


        // ==========================================
        // 第二阶段:取出数据 (Redis -> JSON字符串 -> Java对象)
        // ==========================================
        
        // 4. 获取数据:从 Redis 中根据 Key 捞出那串纯文本 JSON
        String jsonUser = stringRedisTemplate.opsForValue().get("user:200");
        
        // 5. 手动反序列化(最致命的一步):将 JSON 字符串复活成 Java 对象
        // 因为 JSON 里没有类名,Spring 是“脸盲”的,所以必须手动传入 `User.class` 这个“DNA 图谱”,
        // 告诉 mapper 照着这个图谱把字符串还原成 User 对象!
        User user1 = mapper.readValue(jsonUser, User.class);
        
        // 6. 打印结果:验证是否完美复活
        System.out.println("user1 = " + user1);
    }
}

单点redis问题

  1. 数据丢失问题:实现redis数据持久化。
  2. 并发能力问题:搭建主从集群,实现读写分离。
  3. 存储能力问题
  4. 故障恢复问题:利用Redis哨兵,实现健康检查和自动故障转移。

RDB

  RDB(Redis Database Backup file),是Redis的一种数据持久化方式。它会在指定的时间间隔内将内存中的数据快照保存到磁盘上。当Redis重启时,可以通过加载RDB文件来恢复数据。RDB文件是一个二进制文件,包含了Redis数据库的完整状态,包括所有键值对和相关的元数据。RDB持久化方式适合于需要快速恢复数据的场景,但可能会导致数据丢失,因为它只在指定的时间间隔内进行快照保存。

  直接在redis-cli中使用SAVE命令可以同步保存数据到磁盘,但这是在主进程执行,会阻塞其他行为。

  可以使用BGSAVE命令可以在后台异步保存数据到磁盘。RDB文件默认保存在Redis的工作目录下,文件名为dump.rdb。bgsave开始时会fork主进程得到子进程,子进程共享主进程的内存数据。完成fork后,子进程会将数据写入磁盘,而主进程继续处理客户端请求。为了防止读写数据不一致,采用copy-on-write机制,即在子进程写入数据时,如果主进程对数据进行修改,操作系统会将修改的数据复制一份给子进程,确保子进程写入的数据是完整的。

  Redis内部有触发RDB持久化的机制,可以修改redis.conf配置,如下:

txt
save 900 1
save 300 10
save 60 10000

  上述配置中,900秒内有1次修改,300秒内有10次修改,60秒内有10000次修改,就会触发RDB持久化。

  其他配置包括:

txt
rdbcompression yes # 是否压缩RDB文件
dbfilename dump.rdb # RDB文件名
dir /var/lib/redis # RDB文件保存目录

AOF

  AOF(Append Only File),是Redis的一种数据持久化方式。它会将每个写操作以日志的形式追加到AOF文件中。当Redis重启时,可以通过重放AOF文件中的命令来恢复数据。AOF文件是一个文本文件,包含了所有对Redis进行修改的命令。AOF持久化方式适合于需要最大程度保证数据不丢失的场景,但可能会导致恢复数据的速度较慢,因为需要重放所有的命令。

  AOF文件默认保存在Redis的工作目录下,文件名为appendonly.aof。需要修改redis.conf来开启AOF,如下:

txt
appendonly yes # 开启AOF功能,默认是no

appendfilename appendonly.aof # AOF文件名

appendfsync always # 每次有新的写命令时,立即将命令追加到AOF文件中,并且强制将数据同步到磁盘。这种策略提供了最高的数据安全性,但性能较差,因为每次写操作都需要等待磁盘同步完成。
appendfsync everysec # 写命令执行完先放入AOF缓冲区,并且每秒钟将数据同步到磁盘一次。这种策略在性能和数据安全性之间提供了一个平衡,适合大多数应用场景。**是默认方案**
appendfsync no # 将命令追加到AOF缓冲区,由操作系统决定何时写回磁盘。这种策略提供了较好的性能,但可能会导致数据丢失,适合对性能要求较高且可以接受一定程度数据丢失的场景。

  显而易见,当我们对同一个值进行多次修改时,AOF持久会将每次修改的命令都追加到AOF文件中,这样就会导致AOF文件变得非常大,恢复数据的速度也会变慢。为了解决这个问题,Redis提供了AOF重写功能,可以通过BGREWRITEAOF命令来触发AOF重写,例如:

txt
set num 123
set name duke
set num 456
set num 789

# 会变成

mset num 789 name duke

  也可以在redis.conf中配置自动触发AOF重写的条件,例如:

txt
auto-aof-rewrite-percentage 100 # 当AOF文件大小达到上次重写后大小的100%时,自动触发AOF重写
auto-aof-rewrite-min-size 64mb # 当AOF文件大小达到64MB时,自动触发AOF重写
对比项RDBAOF
持久化方式定时对整个内存做快照记录每一次执行的命令
数据完整性不完整,两次备份之间会丢失相对完整,取决于刷盘策略
文件大小会有压缩,文件体积小记录命令,文件体积很大
宕机恢复速度很快
数据恢复优先级低,因为数据完整性不如AOF高,因为数据完整性更高
系统资源占用高,大量CPU和内存消耗低,主要是磁盘IO资源
但AOF重写时会占用大量CPU和内存资源
使用场景可以容忍数分钟的数据丢失,追求更快的启动速度对数据安全性要求较高常见