今天做了什么:

  1. 下载 《Microservices with Spring Boot 3 and Spring Cloud》,源码在 github

  2. Redis 事务

Redis事务

原理

Redis 是一个内存数据库,它支持事务操作。Redis 事务是一系列的命令操作,这些命令会被一起执行,要么全部成功,要么全部失败。在事务执行期间,其他客户端提交的命令不会被插入到事务中,确保事务的原子性。

Redis 事务使用以下命令进行管理:

  1. MULTI:标记事务的开始。
  2. EXEC:执行事务中的所有命令。
  3. DISCARD:取消事务,放弃之前的所有命令。
  4. WATCH:监视一个或多个键,如果在事务执行之前有其他客户端对这些键进行了修改,则事务会被中止。
  5. UNWATCH:取消对所有键的监视。

在执行事务之前,先使用 MULTI 命令标记事务的开始,然后按顺序执行多个命令。在所有命令都被添加到事务队列之后,使用 EXEC 命令来执行事务中的所有命令。如果执行成功,事务中的所有命令会被一起执行,然后返回结果。如果在执行事务期间发生错误,事务会被中止,所有的修改都会被回滚。

示例

以下是一个使用 Redis 事务的示例:

#开启事务
MULTI

SET key1 "value1"
SET key2 "value2"
GET key1
GET key2

#执行事务
EXEC

# 取消事务
discard

上述事务包含了两个 SET 命令和两个 GET 命令。在 EXEC 命令执行之后,将会依次执行这些命令,并返回相应的结果。

需要注意的是,Redis 的事务是乐观锁,并不会在执行期间对键进行加锁。因此,在使用事务时要注意并发操作可能引发的竞态条件。

Redis对于命令执行错误处理,有两种解决方式:

  • 语法错误(编译)
  • 执行错误(运行)

语法错误:执行命令的语法不正确

#开启事务
multi

#命令
set name zhangsan
set age
seterror sex male

#执行事务
exec

#获取正确指令数据
get name

此时整个事务队列中,存在一条正确指令,两条语法错误指令, 当执行exec后,会直接返回错误,正确的命令也不会执行。

执行错误:命令在运行过程中出现错误

#开启事务
multi

#命令
set lesson java
rpush lesson eureka feign nacos
set lesson redis

#执行事务
exec

#获取数据
get lesson

通过上面事务执行可以看到,语法本身是没有问题的,所以运行之前redis无法发现错误,但是在执行时出现了错误,因此只会错误的命令不执行, 而正确的命令仍然能够正常执行。

SpringBoot实现事务操作

1)修改RedisConfig配置类,开启事务控制

@Configuration
public class RedisConfig {

  @Autowired
  private RedisConnectionFactory redisConnectionFactory;

  @Bean
  public RedisTemplate<String, Object> redisTemplate() {
      RedisTemplate<String, Object> template = new RedisTemplate<>();
      template.setConnectionFactory(redisConnectionFactory);

      // 设置序列化器等其他配置...
      template.setKeySerializer(new StringRedisSerializer());
      template.setValueSerializer(new GenericJackson2JsonRedisSerializer());


      //开启redis事务控制
      template.setEnableTransactionSupport(true); 

      return template;
  }
}

2)自定义方法,测试事务效果

@Test
public void multiTest(){
    //开启事务
    redisTemplate.multi();
    try{
        redisTemplate.opsForValue().set("lesson","java");
        redisTemplate.opsForSet().add("lesson","eureka","feign","gateway");
        redisTemplate.opsForValue().set("lesson","redis");
        System.out.println(redisTemplate.opsForValue().get("lesson"));
    }catch (Exception e){
        //回滚
        System.out.println("出现异常");
        redisTemplate.discard();
    }finally {
        redisTemplate.exec();
    }
}

FAQ

Redis 事务的原子性是如何保证的?

Redis 事务的原子性是通过 Redis 的单线程执行和乐观锁来保证的。

在 Redis 中,事务的执行是通过将多个命令按顺序放入事务队列中,然后由 Redis 依次执行这些命令。在事务执行期间,Redis 会确保其他客户端提交的命令不会被插入到当前事务中,从而保证事务的原子性。

当执行 EXEC 命令时,Redis 会按照顺序执行事务队列中的所有命令,并返回每个命令的执行结果。如果在执行期间出现错误,例如其中一个命令返回了错误,Redis 会终止事务的执行,并返回相应的错误信息。

此外,Redis 使用乐观锁来保证事务的原子性。在事务执行期间,Redis 不会对事务中的键进行加锁。相反,Redis 会在执行事务之前使用 WATCH 命令监视一个或多个键。如果在事务执行期间有其他客户端对这些被监视的键进行了修改,Redis 会中止事务的执行,并返回一个错误。通过使用乐观锁,Redis 确保了事务在执行期间不会被其他客户端的修改所干扰。

需要注意的是,Redis 的事务并不是隔离的,即事务执行期间其他客户端提交的命令仍然可以修改事务中的键。因此,在使用 Redis 事务时,需要注意并发操作可能引发的竞态条件。如果需要更严格的隔离性,可以使用 Redis 的 WATCH 和 MULTI 命令结合 Lua 脚本来实现更复杂的事务逻辑。

Redis 事务的乐观锁是如何工作的?

Redis 的事务乐观锁机制是通过 WATCH 命令和检测事务执行期间键的变化来实现的。

当调用 WATCH 命令时,Redis 会监视一个或多个键。在事务执行期间,如果任何被监视的键发生了变化(被其他客户端修改),Redis 将中止事务的执行。

以下是乐观锁在 Redis 事务中的工作原理:

  1. 客户端调用 WATCH 命令,指定要监视的键。
  2. Redis 开始监视这些键,并记录它们的当前值。
  3. 客户端开始执行事务中的命令。
  4. 在执行 EXEC 命令之前,Redis 检查被监视的键是否发生了变化。
  5. 如果任何被监视的键发生了变化,Redis 中止事务的执行,并返回一个错误。
  6. 如果被监视的键没有发生变化,则 Redis 执行事务中的命令,并返回结果。

乐观锁的关键在于在执行 EXEC 命令之前检查键的变化。如果在事务执行期间,有其他客户端修改了被监视的键,那么 Redis 会中止事务的执行,因为它无法保证事务的原子性。

需要注意的是,乐观锁并不会对被监视的键进行加锁。它只是在事务执行之前检查键的变化情况。这意味着其他客户端仍然可以修改被监视的键,但是当事务执行时,如果键发生了变化,事务将被中止。

乐观锁在 Redis 事务中的应用场景是保证在执行事务期间被监视的键没有被其他客户端修改。如果需要更严格的隔离性,可以结合使用 WATCH 和 MULTI 命令以及 Lua 脚本来实现更复杂的事务逻辑。

Redis 事务是否支持嵌套?

在 Redis 中,事务是不支持嵌套的。也就是说,你不能在一个事务中开启另一个事务。

当你调用 MULTI 命令开始一个事务后,后续的命令都会被添加到当前事务队列中,直到调用 EXEC 命令执行事务。事务中的命令会按照顺序执行,要么全部成功,要么全部失败。

虽然 Redis 提供了 MULTIEXEC 命令来支持事务,但这并不意味着事务的嵌套。如果在一个事务中调用 MULTI 命令开启另一个事务,实际上仍然是在当前事务中执行了一个 MULTI 命令,而不是启动了一个新的嵌套事务。因此,嵌套事务的概念在 Redis 中并不存在。

如果你需要在 Redis 中进行复杂的操作,可以使用 Lua 脚本来实现。Lua 脚本可以在一个原子性操作中执行多个 Redis 命令,并且支持条件判断和流程控制,以满足更复杂的业务需求。

Redis 是否支持回滚事务?

在 Redis 中,事务是支持回滚的。当事务执行过程中发生错误或者通过客户端显式地调用 DISCARD 命令时,Redis 将会回滚事务中的所有命令,将数据恢复到事务执行之前的状态。

以下是 Redis 中回滚事务的情况:

  1. 当事务执行过程中发生错误时,比如其中一个命令返回了错误,Redis 将自动中止事务的执行,并丢弃事务队列中尚未执行的命令。这样可以确保事务的原子性,未执行的命令不会对数据产生影响。
  2. 客户端可以显式地调用 DISCARD 命令来取消事务,放弃之前的所有命令。这将导致 Redis 丢弃事务队列中的所有命令,并将数据恢复到事务开始之前的状态。

需要注意的是,Redis 的事务回滚只会影响当前连接的事务,不会对其他客户端产生影响。如果一个客户端在执行事务期间发生错误或者回滚,其他客户端的操作不会受到影响,它们仍然可以正常地操作和访问 Redis 数据库。

另外,需要注意的是 Redis 的事务并不是隔离的,即事务执行期间其他客户端提交的命令仍然可以修改事务中的键。因此,在使用 Redis 事务时,需要注意并发操作可能引发的竞态条件。如果需要更严格的隔离性,可以结合使用 WATCH 和 MULTI 命令以及 Lua 脚本来实现更复杂的事务逻辑和控制。