Redis中的事务和乐观锁如何实现?

介绍

事务介绍

在传统的关系型数据库中,事务(Transaction)通常需要满足ACID特性(原子性、一致性、隔离性和持久性)。然而,在Redis中,事务的实现有所不同:

1、原子性(Atomicity):

  • Redis的单个命令是原子的,意味着每个命令要么完全执行,要么完全不执行。
  • 但Redis事务中的一组命令并不完全保证原子性。即使事务中的某个命令失败,其余命令仍然会继续执行。

2、一致性(Consistency):

  • Redis事务可以通过将多个命令作为一个原子操作来保持一致性,但由于事务不保证完全的原子性,某些情况下可能会破坏一致性。

3、隔离性(Isolation):

  • Redis事务没有隔离级别的概念。事务中的命令在执行时,不会被其他命令打断,但Redis事务并不提供严格的隔离机制。这意味着在MULTI/EXEC之间的命令会被缓冲,但在EXEC执行之前,其他客户端的写操作可能会影响事务结果。

4、持久性(Durability):

  • Redis使用内存存储数据,持久性依赖于配置的RDB快照和AOF(Append-Only File)日志。事务中的数据变更在提交后,会根据配置持久化到磁盘。

乐观锁介绍

乐观锁是Redis中确保并发安全的一种机制,主要通过WATCH命令来实现。具体实现细节如下:

  1. 监视键(WATCH):在执行事务之前,使用WATCH命令监视一个或多个键。如果在执行EXEC之前这些键被其他命令修改,事务会中止。
  2. 执行事务(MULTI和EXEC):使用MULTI开始事务,然后添加多个命令。最后使用EXEC提交事务。
  3. 重试机制:如果事务由于监视的键被修改而失败,可以捕捉失败并重新尝试事务操作。

注意:没有做好并发控制,就可能导致脏读、幻读和不可重复读等问题。

  • 脏读(Dirty Read):在一个事务读取到另一个未提交事务的修改数据时,就会发生脏读。Redis事务不提供解决脏读的机制。
  • 幻读(Phantom Read):幻读发生在一个事务在两次读取之间看到其他事务插入的新数据时。Redis的事务机制无法直接解决幻读问题。
  • 不可重复读(Non-repeatable Read):当一个事务在读取同一数据时,另一个事务修改了该数据,导致两次读取结果不同。这在Redis中可以通过乐观锁来部分解决。

Spring boot中Redis如何实现事务?

配置Spring Boot项目

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>

配置RedisTemplate

创建一个配置类,用于配置RedisTemplate,并启用事务支持:

package com.example.demo.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.StringRedisSerializer;

/**
 * Redis配置类
 * 该类用于配置Redis模板,以便在应用程序中使用Redis进行数据操作。
 */
@Configuration
public class RedisConfig {

    /**
     * 配置Redis模板
     *
     * @param redisConnectionFactory Redis连接工厂,用于创建Redis连接。
     * @return RedisTemplate实例,用于执行Redis操作。
     *
     * 该方法配置了Redis模板的序列化方式,并启用了事务支持。
     * 使用StringRedisSerializer对键和值进行序列化,确保数据在Redis中的存储格式一致。
     */
    @Bean
    public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
        // 创建一个RedisTemplate实例
        RedisTemplate<String, Object> template = new RedisTemplate<>();
        // 设置Redis连接工厂
        template.setConnectionFactory(redisConnectionFactory);
        // 设置键的序列化方式为StringRedisSerializer
        template.setKeySerializer(new StringRedisSerializer());
        // 设置值的序列化方式为StringRedisSerializer
        template.setValueSerializer(new StringRedisSerializer());
        // 启用事务支持
        template.setEnableTransactionSupport(true);
        // 返回配置好的RedisTemplate实例
        return template;
    }
}

使用Redis事务

在Service层使用Redis事务,如果在事务过程中发生异常,Spring的事务管理器会自动回滚事务:

package com.example.demo.service;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

/**
 * RedisService类提供了对Redis数据库进行操作的方法,特别是在事务环境下。
 */
@Service
public class RedisService {

    /**
     * 自动注入RedisTemplate,用于操作Redis数据库。
     */
    @Autowired
    private RedisTemplate<String, Object> redisTemplate;

    /**
     * 使用Spring事务管理执行Redis事务。
     * 这个方法演示了如何在Redis中执行一组操作作为事务。
     */
    @Transactional
    public void executeTransaction() {
        // 监视"key1"以确保在事务期间没有其他操作修改它
        redisTemplate.watch("key1");

        try {
            // 开始一个Redis事务
            redisTemplate.multi();
            // 在事务中设置"key1"和"key2"的值
            redisTemplate.opsForValue().set("key1", "value1");
            redisTemplate.opsForValue().set("key2", "value2");
            // 执行事务
            redisTemplate.exec();  // 提交事务
        } catch (Exception e) {
            // 如果发生异常,取消事务
            redisTemplate.discard();  // 放弃事务
            throw e;
        }
    }

    /**
     * 使用Spring事务管理执行Redis事务,并展示如何在发生异常时回滚事务。
     * 这个方法与executeTransaction类似,但增加了异常抛出的逻辑来模拟事务回滚。
     */
    @Transactional
    public void executeTransactionWithRollback() {
        // 监视"key1"以确保在事务期间没有其他操作修改它
        redisTemplate.watch("key1");

        try {
            // 开始一个Redis事务
            redisTemplate.multi();
            // 在事务中设置"key1"的值
            redisTemplate.opsForValue().set("key1", "value1");
            // 模拟一个异常,以展示事务回滚
            // 模拟异常,事务将回滚
            if (true) {
                throw new RuntimeException("模拟异常");
            }
            // 如果没有异常,设置"key2"的值并执行事务
            redisTemplate.opsForValue().set("key2", "value2");
            redisTemplate.exec();  // 提交事务
        } catch (Exception e) {
            // 如果发生异常,取消事务
            redisTemplate.discard();  // 放弃事务
            throw e;
        }
    }
}

测试事务

可以编写一个简单的Controller来测试事务功能:

package com.example.demo.controller;

import com.example.demo.service.RedisService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

/**
 * Redis控制器,负责处理与Redis相关的HTTP请求。
 */
@RestController
@RequestMapping("/redis")
public class RedisController {

    /**
     * 自动注入RedisService,用于执行Redis操作。
     */
    @Autowired
    private RedisService redisService;

    /**
     * 测试Redis事务的执行。
     *
     * @return 成功执行事务时返回"事务执行成功",执行失败时返回失败原因。
     */
    @GetMapping("/transaction")
    public String testTransaction() {
        try {
            redisService.executeTransaction();
            return "事务执行成功";
        } catch (Exception e) {
            return "事务执行失败:" + e.getMessage();
        }
    }

    /**
     * 测试在Redis事务中触发回滚的情况。
     *
     * @return 成功执行事务且未触发回滚时返回"事务执行成功,没有回滚",触发回滚时返回回滚原因。
     */
    @GetMapping("/transaction-rollback")
    public String testTransactionWithRollback() {
        try {
            redisService.executeTransactionWithRollback();
            return "事务执行成功,没有回滚";
        } catch (Exception e) {
            return "事务因以下原因回滚:" + e.getMessage();
        }
    }
}

Spring boot中Redis如何实现乐观锁?

在Spring Boot中使用Redis实现乐观锁通常依赖于Redis的WATCH命令。WATCH命令可以监视一个或多个键,当这些键在事务执行之前被修改,事务会被中止。这个机制可以用来实现乐观锁。

配置Redis

确保已经配置好Redis连接和模板。

package com.example.demo.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.StringRedisSerializer;

/**
 * Redis配置类
 * 该类用于配置Redis模板,以便在应用程序中使用Redis进行数据操作。
 */
@Configuration
public class RedisConfig {

    /**
     * 配置Redis模板
     *
     * @param redisConnectionFactory Redis连接工厂,用于创建Redis连接。
     * @return RedisTemplate实例,配置了字符串序列化器用于键和值的序列化。
     *
     * 此方法通过Spring的@Bean注解来定义一个Bean,该Bean是一个配置了特定序列化器的RedisTemplate,
     * 它将被Spring容器管理并供其他组件使用。
     */
    @Bean
    public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
        RedisTemplate<String, Object> template = new RedisTemplate<>();
        template.setConnectionFactory(redisConnectionFactory);
        // 使用StringRedisSerializer对键进行序列化
        template.setKeySerializer(new StringRedisSerializer());
        // 使用StringRedisSerializer对值进行序列化
        template.setValueSerializer(new StringRedisSerializer());
        return template;
    }
}

实现乐观锁的业务逻辑

在Service层中实现乐观锁的逻辑。

package com.example.demo.service;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;

import java.util.List;

@Service
public class RedisOptimisticLockService {

    @Autowired
    private RedisTemplate<String, Object> redisTemplate;

    public boolean updateValueWithOptimisticLock(String key, String value) {
        boolean transactionStarted = false;
        try {
            // 监视键
            redisTemplate.watch(key);

            // 获取当前值
            String currentValue = (String) redisTemplate.opsForValue().get(key);

            // 开启事务
            redisTemplate.multi();
            transactionStarted = true;

            // 更新值
            redisTemplate.opsForValue().set(key, value);

            // 执行事务
            List<Object> result = redisTemplate.exec();

            if (result == null || result.isEmpty()) {
                // 事务失败,表示键在事务执行前被修改,乐观锁失败
                return false;
            }

            // 事务成功,表示键在事务执行前没有被修改,乐观锁成功
            return true;
        } catch (Exception e) {
            if (transactionStarted) {
                redisTemplate.discard();  // 放弃事务
            }
            throw e;  // 重新抛出异常
        } finally {
            redisTemplate.unwatch();  // 取消监视
        }
    }
}

创建控制器

创建一个控制器来调用乐观锁业务逻辑。

package com.example.demo.controller;

import com.example.demo.service.RedisOptimisticLockService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

/**
 * 控制器类,处理与Redis相关的乐观锁操作。
 * 使用@RestController注解,表明这是一个处理HTTP请求的控制器。
 * @RequestMapping("/redis")注解指定了处理请求的根路径为/redis。
 */
@RestController
@RequestMapping("/redis")
public class RedisOptimisticLockController {

    /**
     * 自动注入RedisOptimisticLockService,用于执行乐观锁更新操作。
     */
    @Autowired
    private RedisOptimisticLockService redisOptimisticLockService;

    /**
     * 自动注入RedisTemplate,用于直接操作Redis数据库。
     */
    @Autowired
    private RedisTemplate<String, Object> redisTemplate;

    /**
     * 初始化Redis中的键值对。
     * @param key Redis中的键。
     * @param value Redis中的值。
     * @return 初始化结果提示信息。
     */
    @GetMapping("/key")
    public String initKeyValue(@RequestParam String key, @RequestParam String value) {
        redisTemplate.opsForValue().set(key, value);
        return "键值对初始化成功";
    }

    /**
     * 使用乐观锁更新Redis中的值。
     * @param key 要更新的键。
     * @param value 更新后的值。
     * @return 更新操作的结果提示信息。
     */
    @GetMapping("/lock")
    public String updateValueWithOptimisticLock(@RequestParam String key, @RequestParam String value) {
        try {
            boolean success = redisOptimisticLockService.updateValueWithOptimisticLock(key, value);
            if (success) {
                return "乐观锁更新成功";
            } else {
                return "乐观锁更新失败,键值已被修改";
            }
        } catch (Exception e) {
            return "乐观锁更新失败:" + e.getMessage();
        }
    }
}

乐观锁和悲观锁的区别

悲观锁

悲观锁的基本思想是,在整个数据处理过程中,认为数据会被其他事务修改,因此在整个数据处理过程中将数据锁定,防止其他事务访问和修改。主要特点如下:

  • 阻塞式锁定:悲观锁在读取数据时会将数据进行加锁,其他事务想要读取或修改数据时会被阻塞,直到当前事务释放锁。
  • 性能开销大:因为需要持有锁的时间较长,可能导致其他事务需要等待,从而引起性能下降。
  • 适用场景:适用于并发写入较多的场景,如高并发更新同一行数据的情况。

乐观锁

乐观锁的基本思想是,认为在数据处理过程中,数据不会被其他事务修改,直到真正更新数据时才会检查是否被修改。主要特点如下:

  • 非阻塞式:乐观锁不会使用加锁机制,而是通过版本号或时间戳等方式记录数据的状态,在更新时检查数据的状态是否发生变化。
  • 适用性强:适用于读操作多于写操作的场景,可以避免因为加锁而带来的性能损失。
  • 处理冲突:如果在更新时发现数据已被修改,则根据具体情况选择重试或者放弃更新。

总结区别

  • 性能开销:悲观锁由于需要持有锁较长时间,因此性能开销大;而乐观锁在正常情况下不会造成阻塞,性能开销较小。
  • 并发控制策略:悲观锁是通过阻塞其他事务来保证数据一致性,而乐观锁是在更新时进行冲突检测来保证数据的正确性。
  • 适用场景:悲观锁适合写操作频繁的场景,乐观锁适合读操作多、写操作少、冲突少的场景。
  • 20
    点赞
  • 21
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

正在奋斗的程序猿

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值