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

Redis学习其二(事务,SpringBoot整合,持久化RDB和AOF)

文章目录

  • 5,事务
    • 5.1Redis 事务不保证原子性的原因
    • 5.2事务操作过程
    • 5.3监控
  • 6,SpringBoot整合Redis
    • 6.1Redis客户端
      • 6.1.1Jedis简单使用
      • 6.1.2Lettuce&Jedis
    • 6.2配置相关
    • 6.3使用
      • 6.3.1使用RedisTemplate
      • 6.3.2Redis工具类
  • 7,持久化RDB
    • 7.1RDB持久化原理
    • 7.2触发机制
      • save命令
      • flushall命令
      • bgsave命令
    • 7.3优缺点
  • 8,持久化AOF
    • 8.1AOF持久化原理
    • 8.2相关配置
    • 8.3aof文件重写
    • 8.3优缺点

5,事务

Redis 事务虽然具备一次性、顺序性、排他性(即事务中的命令会按顺序执行,且执行期间不会被其他命令插入)。Redis的单条命令是保证原子性的,但是redis事务不能保证原子性。

Redis 事务的本质是一组命令的集合,其执行过程可概括为三个阶段:

  1. 开启事务MULTI):标记事务的开始,后续命令会被加入队列而非立即执行。
  2. 命令入队:所有输入的命令(如SETHSET等)会按顺序存入事务队列,等待执行。
  3. 执行事务EXEC):一次性执行队列中的所有命令,执行期间不会被其他客户端的命令干扰。

5.1Redis 事务不保证原子性的原因

原子性的核心是 “要么全部成功,要么全部失败”,但 Redis 事务不满足这一点:

  • 若命令语法错误(如命令不存在):在EXEC执行前,Redis 会检测到错误并拒绝执行事务,此时所有命令都不执行(类似 “全部失败”)。
  • 若命令逻辑错误(如对字符串执行INCR):EXEC会正常执行其他命令,错误命令仅自身失败,不会影响其他命令(即 “部分成功,部分失败”)。

例如:

MULTI
SET key1 "123"   // 成功
INCR key1        // 逻辑错误(字符串无法自增)
SET key2 "456"   // 成功
EXEC

执行后,key1key2都会被创建,仅INCR key1失败,事务并未回滚,因此不满足原子性。

5.2事务操作过程

  • 开启事务(multi
  • 命令入队
  • 执行事务(exec

所以事务中的命令在加入时都没有被执行,直到提交时才会开始执行(Exec)一次性完成。

127.0.0.1:6379> multi # 开启事务
OK
127.0.0.1:6379> set k1 v1 # 命令入队
QUEUED
127.0.0.1:6379> set k2 v2 # ..
QUEUED
127.0.0.1:6379> get k1
QUEUED
127.0.0.1:6379> set k3 v3
QUEUED
127.0.0.1:6379> keys *
QUEUED
127.0.0.1:6379> exec # 事务执行
1) OK
2) OK
3) "v1"
4) OK
5) 1) "k3"2) "k2"3) "k1"

取消事务(discurd)

127.0.0.1:6379> multi
OK
127.0.0.1:6379> set k1 v1
QUEUED
127.0.0.1:6379> set k2 v2
QUEUED
127.0.0.1:6379> DISCARD # 放弃事务
OK
127.0.0.1:6379> EXEC 
(error) ERR EXEC without MULTI # 当前未开启事务
127.0.0.1:6379> get k1 # 被放弃事务中命令并未执行
(nil)

事务错误

代码语法错误(编译时异常)所有的命令都不执行

127.0.0.1:6379> multi
OK
127.0.0.1:6379> set k1 v1
QUEUED
127.0.0.1:6379> set k2 v2
QUEUED
127.0.0.1:6379> error k1 # 这是一条语法错误命令
(error) ERR unknown command `error`, with args beginning with: `k1`, # 会报错但是不影响后续命令入队 
127.0.0.1:6379> get k2
QUEUED
127.0.0.1:6379> EXEC
(error) EXECABORT Transaction discarded because of previous errors. # 执行报错
127.0.0.1:6379> get k1 
(nil) # 其他命令并没有被执行

代码逻辑错误 (运行时异常) **其他命令可以正常执行 ** >>> 所以不保证事务原子性

127.0.0.1:6379> multi
OK
127.0.0.1:6379> set k1 v1
QUEUED
127.0.0.1:6379> set k2 v2
QUEUED
127.0.0.1:6379> INCR k1 # 这条命令逻辑错误(对字符串进行增量)
QUEUED
127.0.0.1:6379> get k2
QUEUED
127.0.0.1:6379> exec
1) OK
2) OK
3) (error) ERR value is not an integer or out of range # 运行时报错
4) "v2" # 其他命令正常执行# 虽然中间有一条命令报错了,但是后面的指令依旧正常执行成功了。
# 所以说Redis单条指令保证原子性,但是Redis事务不能保证原子性。

5.3监控

悲观锁(Pessimistic Locking)

核心思想:总是假设最坏的情况,认为数据随时可能被其他线程修改,因此在访问数据前先加锁,确保只有自己能操作数据。

乐观锁(Optimistic Locking)

核心思想:假设数据一般不会被其他线程修改,因此不上锁,仅在更新数据时检查是否有人在此期间修改过(使用版本号进行检查)。

而Redis使用watch key监控指定数据,相当于乐观锁加锁。监控某一key后,如果key在一个线程/客户端A执行事务时,有另外一个客户端/线程B对key进行修改,则执行A的事务失败(全部失败)。

并且Redis 的 WATCH 是一次性的,且第二次事务没有重新执行 WATCH money

案例一:成功案例

客户端B在客户端A的事务期间执行(multi之后和exec之前)。

-------------客户端A--------------
127.0.0.1:6379[2]> set money 100
OK
127.0.0.1:6379[2]> set use 0
OK
127.0.0.1:6379[2]> watch money
OK
127.0.0.1:6379[2]> multi
OK
127.0.0.1:6379[2]> decrby money 20
QUEUED
127.0.0.1:6379[2]> incrby money 20
QUEUED
127.0.0.1:6379[2]> exec
(nil)
127.0.0.1:6379[2]> get money
"600"
127.0.0.1:6379[2]> get use
"0"
-------------客户端B--------------
127.0.0.1:6379[2]> incrby money 500
(integer) 600

案例二:失败案例

客户端B在客户端A的第二次事务期间执行(multi之后和exec之前)。这就是“Redis 的 WATCH 是一次性的,且第二次事务没有重新执行 WATCH money。”

-------------客户端A--------------
127.0.0.1:6379> set money 100
OK
127.0.0.1:6379> set use 0
OK
127.0.0.1:6379> watch money
OK
127.0.0.1:6379> multi
OK
127.0.0.1:6379> decrby money 20
QUEUED
127.0.0.1:6379> incrby use 20
QUEUED
127.0.0.1:6379> exec
1) (integer) 80
2) (integer) 20
127.0.0.1:6379> get money
"80"
127.0.0.1:6379> get use
"20"
127.0.0.1:6379> multi
OK
127.0.0.1:6379> decrby money 20
QUEUED
127.0.0.1:6379> incrby use 20
QUEUED
127.0.0.1:6379> exec
1) (integer) 560
2) (integer) 40
-------------客户端B--------------
127.0.0.1:6379> incrby money 500
(integer) 580

6,SpringBoot整合Redis

6.1Redis客户端

什么是客户端?在 Spring Boot 应用中,Redis 客户端是指用于连接和操作 Redis 数据库的工具库。spring-boot-starter-data-redis作为官方提供的启动器,默认集成了Lettuce作为 Redis 客户端。

6.1.1Jedis简单使用

使用Java来操作Redis,Jedis是Redis官方推荐使用的Java连接redis的客户端。

  1. 导入依赖

    <!--导入jredis的包-->
    <dependency><groupId>redis.clients</groupId><artifactId>jedis</artifactId><version>3.2.0</version>
    </dependency>
    <!--fastjson-->
    <dependency><groupId>com.alibaba</groupId><artifactId>fastjson</artifactId><version>1.2.70</version>
    </dependency>
    
  2. 这里直接在springboot的测试类里测试了

    @SpringBootTest
    public class One {@Testpublic void test() {Jedis jedis = new Jedis("127.0.0.1", 6379);String pong = jedis.ping();System.out.println(pong);}@Testpublic void test2() {Jedis jedis = new Jedis("127.0.0.1", 6379);jedis.select(2);JSONObject jsonObject = new JSONObject();jsonObject.put("hello", "world");jsonObject.put("name", "Selena");// 开启事务Transaction multi = jedis.multi();String result = jsonObject.toJSONString();try {multi.set("user1", result);multi.set("user2", result);// 执行事务multi.exec();}catch (Exception e){// 放弃事务multi.discard();} finally {// 关闭连接System.out.println(jedis.get("user1"));System.out.println(jedis.get("user2"));jedis.close();}}
    }
    

6.1.2Lettuce&Jedis

在 Spring Boot 应用中,spring-boot-starter-data-redis默认使用 Lettuce 作为 Redis 客户端,因为其依赖中包含lettuce-core包。若要切换为 Jedis,需排除 Lettuce 并引入 Jedis 依赖。

特性LettuceJedis
连接模型基于 Netty 的响应式、非阻塞 I/O,支持异步和多路复用基于传统 BIO(阻塞 I/O),每次操作需新建连接
线程安全性线程安全,可多线程共享连接实例线程不安全,需通过连接池管理连接
资源消耗使用 Netty 事件循环,资源消耗少,适合长连接频繁创建 / 销毁连接,资源消耗大,依赖连接池降开销
功能特性支持响应式编程(Reactive Redis API)、集群和哨兵模式提供传统同步 API,适合简单业务场景
适用场景高并发、异步操作、响应式应用(如 Spring WebFlux)简单同步操作、传统 Servlet 应用

6.2配置相关

RedisProperties 是 Spring Boot 中用于配置 Redis 连接信息的核心配置类,位于 org.springframework.boot.autoconfigure.data.redis 包下。它通过 @ConfigurationProperties 注解绑定 spring.redis 前缀的配置项,让你可以在 application.propertiesapplication.yml 中轻松配置 Redis 连接参数。

此外,RedisAutoConfiguration这个类,顾名思义就是Redis的自动化配置。在这个类中,会引入LettuceConnectionConfiguration 和 JedisConnectionConfiguration 两个配置类,分别对应lettuce和jedis两个客户端。而这个两个类上都是用了ConditionalOnClass注解来进行判断是否加载。

而由于我们的项目自动引入了lettuce-core,而没有引入jedis相关依赖,所以LettuceConnectionConfiguration这个类的判断成立会被加载,而Jedis的判断不成立,所以不会加载。进而lettuce的配置生效,所以我们在使用的使用, 默认就是lettuce的客户端。

配置文件(基础配置):

server: # 服务器配置port: 8080servlet:context-path: /
spring:# redis 配置redis:# 地址host: localhost# 端口,默认为6379port: 6379# 数据库索引database: 0# 密码password:# 连接超时时间timeout: 10s

但是有的时候我们想要给我们的redis客户端配置上连接池。就像我们连接mysql的时候,也会配置连接池一样,目的就是增加对于数据连接的管理,提升访问的效率,也保证了对资源的合理利用。

如果使用的是jedis,就把lettuce换成jedis(同时要注意依赖也是要换的)。

spring:# redis 配置redis:# 地址host: localhost# 端口,默认为6379port: 6379# 数据库索引database: 0# 密码password:# 连接超时时间timeout: 10slettuce:pool:# 连接池中的最小空闲连接min-idle: 0# 连接池中的最大空闲连接max-idle: 8# 连接池的最大数据库连接数max-active: 8# #连接池最大阻塞等待时间(使用负值表示没有限制)max-wait: -1ms

但是仅仅这在配置文件中加入,其实连接池是不会生效的。还少了最关键的一步,就是要导入一个依赖,不导入的话,这么配置也没有用。

<dependency><groupId>org.apache.commons</groupId><artifactId>commons-pool2</artifactId>
</dependency>

之后,连接池才会生效。我们可以做一个对比。 在导包前后,观察RedisTemplate对象的值就可以看出来。

添加依赖前:
在这里插入图片描述

添加依赖后:

在这里插入图片描述

6.3使用

6.3.1使用RedisTemplate

简单案例:

@SpringBootTest
public class RedisTemplateTest {@Autowiredprivate RedisTemplate redisTemplate;@Testpublic void testRedisTemplate1() {redisTemplate.opsForValue().set("name", "saber");}
}

在 Spring 的RedisTemplate中,opsForValue()是用于获取操作 Redis 字符串(String)类型数据的操作接口,通过它能执行一系列针对字符串的操作。除了opsForValue()外,RedisTemplate还提供了opsForHash()(操作哈希类型数据)、opsForList()(操作列表类型数据 )、opsForSet()(操作集合类型数据 )和opsForZSet()(操作有序集合类型数据)等方法,方便开发者针对不同的 Redis 数据类型进行操作。

拿opsForValue()举例,其常用方法

  • set(String key, Object value):将键值对存储到 Redis 中。如果对应的 key 已经存在,新值会覆盖旧值。
  • get(String key):根据给定的 key,从 Redis 中获取对应的字符串值,如果 key 不存在,则返回null
  • set(String key, Object value, long timeout, TimeUnit unit):存储键值对,并设置该键值对的过期时间。
  • increment(String key, long delta):对存储在 Redis 中的数字类型的 key 进行自增操作,delta表示自增的步长,返回自增后的值。若 key 不存在,会将其初始化为delta的值。
  • decrement(String key, long delta):对存储在 Redis 中的数字类型的 key 进行自减操作,delta表示自减的步长,返回自减后的值。

6.3.2Redis工具类

Redis工具类-CSDN博客

7,持久化RDB

7.1RDB持久化原理

RDB 是 Redis 默认的持久化方式,其核心机制是:

  • 定期执行:Redis 在指定时间间隔内(如 5 分钟),将内存中的数据快照保存为二进制文件(默认名为dump.rdb可以通过配置文件修改)。
  • fork 子进程:执行快照时,Redis 主进程会fork一个子进程,由子进程负责将数据写入磁盘,主进程继续处理客户端请求。
  • 全量复制:RDB 文件包含某一时刻 Redis 的全部数据,恢复时直接加载整个文件。

fork是操作系统提供的一个系统调用,用于创建一个与父进程几乎完全相同的子进程。RDB 持久化需要将内存中的全量数据写入磁盘,如果由主线程直接执行,会导致长时间阻塞(尤其数据量大时),影响 Redis 的响应性能。

在进行 RDB 的时候,redis 的主线程是不会做 io 操作的,主线程会 fork 一个子线程来完成该操作;

  1. Redis 调用forks。同时拥有父进程和子进程。
  2. 子进程将数据集写入到一个临时 RDB 文件中。
  3. 当子进程完成对新 RDB 文件的写入时,Redis 用新 RDB 文件替换原来的 RDB 文件,并删除旧的 RDB 文件。

这种工作方式使得 Redis 可以从写时复制(copy-on-write)机制中获益(因为是使用子进程进行写操作,而父进程依然可以接收来自客户端的请求。)

1,写时复制(Copy-On-Write):初始时,父子进程共享同一块物理内存;当父进程或子进程修改数据时,才会复制被修改的内存页,避免了全量复制的性能消耗。

2,什么是 “临时的 RDB 文件”?

在 RDB 持久化过程中,子进程并非直接写入最终的 RDB 文件(如默认的dump.rdb),而是先将内存数据写入一个临时文件(例如命名为temp-xxx.rdb)。

临时文件的作用是:

  • 保证数据完整性:如果子进程在写入过程中意外崩溃(如磁盘满、进程被 kill),临时文件会被丢弃,不会影响原有的 RDB 文件(原文件仍可用于恢复数据)。
  • 避免部分写入:如果直接写入目标文件,中途失败会导致目标文件损坏,而临时文件可确保只有当全量数据写入完成后,才会成为有效的 RDB 文件。

3,替换过程是怎样的?

当子进程成功将全量数据写入临时文件后,Redis 会执行原子性的文件替换操作

  1. 子进程完成写入后,通知主进程 “临时文件已就绪”。
  2. 主进程通过操作系统的rename(或类似)系统调用,将临时文件重命名为目标 RDB 文件名(如dump.rdb)。
    • 例如:temp-xxx.rdbdump.rdb
  3. 替换完成后,删除旧的dump.rdb文件(如果存在)。这意味着旧的数据会被新的数据覆盖。

7.2触发机制

  1. save的规则(配置文件中设值,在快照模块,如下)满足的情况下,会自动触发rdb原则
  2. 执行flushall命令,也会触发我们的rdb原则
  3. 退出redis,也会自动产生rdb文件

配置文件相关内容

# 当至少1个key被修改且时间超过900秒(15分钟)时,执行一次快照
save 900 1
# 当至少10个key被修改且时间超过300秒(5分钟)时,执行一次快照
save 300 10
# 当至少10000个key被修改且时间超过60秒时,执行一次快照
save 60 10000# RDB文件保存路径
dir ./# RDB文件名
dbfilename dump.rdb# 启用压缩(可能影响性能)
rdbcompression yes

save命令

使用 save 命令,会立刻对当前内存中的数据进行持久化 ,但是会阻塞,也就是不接受其他操作了;

由于 save 命令是同步命令,会占用Redis的主进程。若Redis数据非常多时,save命令执行速度会非常慢,阻塞所有客户端的请求。

flushall命令

flushall 命令也会触发持久化 ;

bgsave命令

bgsave 是异步进行,进行持久化的时候,redis 还可以将继续响应客户端请求 ;配置文件中的save规则本质上也是bgsave,异步执行数据持久化。
在这里插入图片描述

bgsave和save对比

命令savebgsave
IO类型同步异步
阻塞?是(阻塞发生在fock(),通常非常快)
复杂度O(n)O(n)
优点不会消耗额外的内存不阻塞客户端命令
缺点阻塞客户端命令需要fock子进程,消耗内存

7.3优缺点

优点:

  1. 适合大规模的数据恢复,相比AOF恢复速度快。
  2. 对数据的完整性要求不高

缺点:

  1. 需要一定的时间间隔进行操作,如果redis意外宕机了,这个最后一次修改的数据就没有了,破快数据完整性。
  2. fork进程的时候,会占用一定的内容空间。

8,持久化AOF

8.1AOF持久化原理

Append Only File

将我们所有的命令都记录下来,恢复的时候就把这个文件全部再执行一遍。

AOF 通过追加写命令到日志文件的方式实现持久化:

  1. 命令实时记录:Redis 执行写命令后,会将命令追加到 AOF 缓冲区(内存)。
  2. 缓冲区同步到磁盘:根据配置的同步策略(fsync),将缓冲区中的命令写入磁盘 AOF 文件。
  3. 文件重写(AOF Rewrite):定期通过BGREWRITEAOF命令对 AOF 文件进行瘦身,去除冗余命令。

8.2相关配置

如果要使用AOF,需要修改配置文件:

在这里插入图片描述

appendonly no yes则表示启用AOF

默认是不开启的,我们需要手动配置,然后重启redis,就可以生效了!

相关配置:

appendonly yes  # 默认是不开启aof模式的,默认是使用rdb方式持久化的,在大部分的情况下,rdb完全够用
appendfilename "appendonly.aof"# appendfsync always # 每次修改都会sync 消耗性能
appendfsync everysec # 每秒执行一次 sync 可能会丢失这一秒的数据
# appendfsync no # 不执行 sync ,这时候操作系统自己同步数据,速度最快# AOF文件重写触发条件
auto-aof-rewrite-percentage 100  # 当前AOF文件大小超过上次重写后大小的100%时触发
auto-aof-rewrite-min-size 64mb   # AOF文件最小达到64MB时才考虑重写

8.3aof文件重写

重写的作用:通过生成一个只包含当前数据库最新状态的 AOF 文件,替代旧文件,大幅减小文件体积。随着 Redis 运行,AOF 文件会不断追加写命令,导致文件体积膨胀,例如:对同一 key 多次修改(如SET k 1 → SET k 2 → SET k 3),旧命令变得冗余。就是舍去大量的中间状态,并记录当前数据库最新的状态。

触发方式

  1. 自动触发:当满足以下两个条件时,Redis 自动触发重写:

    auto-aof-rewrite-percentage 100  # 当前AOF文件大小超过上次重写后大小的100%
    auto-aof-rewrite-min-size 64mb   # AOF文件最小达到64MB才考虑重写
    
  2. 手动触发:执行命令:

    redis-cli BGREWRITEAOF
    

重写执行的流程。

  1. 主线程 fork 子进程
    • 主线程通过fork()创建子进程,子进程获得当前内存数据的副本。
    • 关键特性:写时复制(Copy-On-Write),父子进程共享内存,仅在修改数据时复制内存页,避免全量内存拷贝。
  2. 子进程生成新 AOF 文件
    • 子进程遍历内存中的所有键值对,生成对应的写命令序列(如SET k vHSET hash f v)。
    • 新 AOF 文件仅包含重建当前数据库所需的最小命令集,不包含任何冗余命令。
  3. 处理增量写命令
    • 在子进程重写期间,主线程继续处理客户端请求,并将新的写命令同时追加到:
      • 旧 AOF 文件:确保当前持久化过程不受影响。
      • AOF 重写缓冲区:保存重写期间的增量命令。
  4. 替换旧文件
    • 子进程完成重写后,向主线程发送信号。
    • 主线程将 AOF 重写缓冲区中的增量命令追加到新 AOF 文件中。
    • 主线程使用原子性的rename()系统调用,将新 AOF 文件替换旧文件。

8.3优缺点

优点

  1. 每一次修改都会同步,文件的完整性会更加好
  2. 每秒同步一次,可能会丢失一秒的数据
  3. 从不同步,效率最高

缺点

  1. 相对于数据文件来说,aof远远大于rdb,修复速度比rdb慢!
  2. Aof运行效率也要比rdb慢,所以我们redis默认的配置就是rdb持久化
http://www.lryc.cn/news/592800.html

相关文章:

  • java基础——面向对象04(继承)
  • 通俗易懂:什么是决策树?
  • STM32-第七节-TIM定时器-3(输入捕获)
  • STL—— list迭代器封装的底层讲解
  • 小白学Python,网络爬虫篇(2)——selenium库
  • 2025年Flutter开发主流技术栈
  • Windows发现可疑的svchost程序
  • 怎么自己搭建云手机
  • Hive 向量化执行引擎 Vectorized Execution 常见 NPE 报错分析及解决
  • 域名WHOIS信息查询免费API使用指南
  • HIVE实战处理(二十四)留存用户数
  • 专题:2025智能体研究报告|附70份报告PDF、原数据表汇总下载
  • 线程控制:互斥与同步
  • math.h函数
  • 深度学习零基础入门(3)-图像与神经网络
  • 需求变更频繁?构建动态估算机制四大要点
  • 短视频矩阵系统:选择与开发的全面指南
  • nastools继任者?极空间部署影视自动化订阅系统『MediaMaster』
  • 代理模式及优化
  • 解锁时序数据库选型密码,为何国产开源时序数据库IoTDB脱颖而出?
  • 脉冲神经网络(Spiking Neural Network, SNN)与知识蒸馏(Knowledge Distillation, KD)
  • Vue3 Anime.js超级炫酷的网页动画库详解
  • Kubernetes (k8s)、Rancher 和 Podman 的异同点分析
  • Jmeter系列(6)-测试计划
  • 网关-微服务网关实现
  • Postman/Apipost中使用Post URL编码发送含换行符参数的问题分析
  • vue2 面试题及详细答案150道(101 - 120)
  • 智慧后厨检测算法构建智能厨房防护网
  • Redis学习其三(订阅发布,主从复制,哨兵模式)
  • 【大模型:知识图谱】--6.Neo4j DeskTop安装+使用