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

news2024/9/9 0:48:49

介绍

事务介绍

在传统的关系型数据库中,事务(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();
        }
    }
}

乐观锁和悲观锁的区别

悲观锁

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

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

乐观锁

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

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

总结区别

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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1958598.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Debian12 安装Docker 用 Docker Compose 部署WordPress

服务器准备&#xff1a; 以root账号登录&#xff0c;如果不是root&#xff0c;后面指令需要加sudo apt update apt install apt-transport-https ca-certificates curl gnupg lsb-release添加GPG密钥&#xff0c;推荐国内源 curl -fsSL https://mirrors.aliyun.com/docker…

ArchLinux部署waydroid

在Arch Linux系统上部署Waydroid运行Android APP 文章目录 在Arch Linux系统上部署Waydroid运行Android APP1. 安装要求2. 本机环境3. 安装 Waydroid4. 网络配置5.注册Google设备6. 运行效果图 Waydroid是Anbox配合Haliun技术开发的LXC Android容器&#xff0c;可在GUN/Linux系…

C语言中的指针基础

文章目录 &#x1f34a;自我介绍&#x1f34a;地址&#x1f34a;C语言中的指针 你的点赞评论就是对博主最大的鼓励 当然喜欢的小伙伴可以&#xff1a;点赞关注评论收藏&#xff08;一键四连&#xff09;哦~ &#x1f34a;自我介绍 Hello,大家好&#xff0c;我是小珑也要变强&am…

Spring Boot 3 + Resilience4j 简单入门 + Redis Cache 整合

1. 项目结构 2. Maven依赖 <parent><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-parent</artifactId><version>3.1.2</version><relativePath/> <!-- lookup parent from repository --&…

CSRF Token 原理

CSRF 攻击 CSRF 攻击成功的关键是&#xff0c;恶意网站让浏览器自动发起一个请求&#xff0c;这个请求会自动携带 cookie &#xff0c;正常网站拿到 cookie 后会认为这是正常用户&#xff0c;就允许请求。 防范 如果在请求中加一个字段&#xff08;CSRF Token&#xff09;&am…

C++笔记之指针基础

函数重载:(C++特性) 定义: C++允许函数重名,但是参数列表要有区分 在相同的作用域定义同名的函数,但是它们的参数要有所区分,这样的多个函数构成重载关系 objdump -d test.exe >log.txt 将test.exe反汇编并将结果重定向到log.txt 文件中 ,然后在 log.txt中找到定…

学习网络安全 为什么Linux首择Kali Linux? 以及如何正确的使用Kali Linux

1.什么是kali linux&#xff1f; Kali Linux是一款基于Debian的Linux发行版&#xff0c;主要用于网络安全测试和渗透测试。它由全球顶尖的安全专家和黑客社区维护开发&#xff0c;提供了丰富的工具和资源&#xff0c;用于测试安全性、漏洞利用和渗透测试。此外&#xff0c;Kal…

MySQL 性能调优

文章目录 一. MySQL调优金字塔1. 架构调优2. MySQL调优3. 硬件和OS调优4. 小结 二. 查询性能调优三. 慢查询1. 概念2. 优化数据访问3. 请求了不需要数据&#xff0c;怎么做4. 是否在扫描额外的记录5. 慢查询相关配置 & 日志位置6. 小结 四. 查询优化器五. 实现调优手段 一.…

24、Python之面向对象:责任与自由,私有属性真的有必要吗

引言 前面我们进一步介绍了类定义中属性的使用&#xff0c;今天我们对中关于属性私有化的话题稍微展开聊一下&#xff0c;顺便稍微理解一下Python设计的相关理念。 访问级别 在其他编程语言中&#xff0c;比如Java&#xff0c;关于类中的属性和方法通过关键字定义明确的访问级…

1、仓颉工程基础操作 cjpm

文章目录 1. 仓颉工程创建方式2. cjpm2.1 init 初始化工程2.2 run 运行工程 1. 仓颉工程创建方式 使用 cangjie studio 通过cangjie studio 创建 使用vscode插件 通过 VSCode 命令面板创建仓颉工程通过可视化界面创建仓颉工程 cjpm 注&#xff1a;具体使用参考官方文档&#…

探索分布式光伏运维系统的组成 需要几步呢?

前言 随着光伏发电的不断发展&#xff0c;对于光伏发电监控系统的需求也日益迫切&#xff0c;“互联网”时代&#xff0c;“互联网”的理念已经转化为科技生产的动力&#xff0c;促进了产业的升级发展&#xff0c;本文结合“互联网”技术提出了一种针对分散光伏发电站运行数据…

浅谈Devops

1.什么是Devops DevopsDev&#xff08;Development&#xff09;Ops&#xff08;Operation&#xff09; DevOps&#xff08;Development和Operations的混合词&#xff09;是一种重视“软件开发人员&#xff08;Dev&#xff09;”和“IT运维技术人员&#xff08;Ops&#xff09;”…

asp.net mvc 三层架构开发商城系统需要前台页面代完善

一般会后端开发&#xff0c;都不太想写前台界面&#xff0c;这套系统做完本来想开源&#xff0c;需要前台界面&#xff0c;后台已开发&#xff0c;有需求的朋友&#xff0c;可以开发个前端界面完善一下&#xff0c;有的话可以私聊发给我啊

The Llama 3 Herd of Models 第6部分推理部分全文

第1,2,3部分 介绍,概览和预训练 第4部分 后训练 第5部分 结果 6 Inference 推理 我们研究了两种主要技术来提高Llama 3405b模型的推理效率:(1)管道并行化和(2)FP8量化。我们已经公开发布了FP8量化的实现。 6.1 Pipeline Parallelism 管道并行 当使用BF16数字表示模型参数时…

VirtualBox创建共享磁盘

VirtualBox创建共享磁盘 目录 VirtualBox创建共享磁盘1、划分共享磁盘1.1、【管理】->【工具】->【虚拟介质管理】1.2、【创建】->【VDI&#xff08;VirtualBox 磁盘映像&#xff09;】->【下一步】1.3、【预先分配全部空间】->【下一步】1.4、【分配大小】->…

5、springboot3 vue3开发平台-后端- satoken 整合

文章目录 1. 为什么使用sa-token2. 依赖导入jichu2.1 基础依赖引入2.2 redis整合2.3 redis 配置&#xff0c; 使redis能支持中文存储 3. 配置4. 配置使用4.1 权限加载接口实现&#xff0c; 登录实现4.2 配置全局过滤器4.3 登录异常处理 5. 登录测试6. 用户session的获取 1. 为什…

MySQL索引与存储引擎、事物

数据库索引 是一个排序的列表&#xff0c;存储着索引值和这个值所对应的物理地址 无须对整个表进行扫描&#xff0c;通过物理地址就可以找到所需数据 是表中一列或者若干列值排序的方法 需要额外的磁盘空间 类型 普通索引 最基本的索引类型&#xff0c;没有唯一性之类的限制 创…

图不连通怎么办?

目录 1.问题 2.连通的相关概念 3.解决方案 C语言示例实现&#xff1a; 1.问题 无论是图的深度还是广度遍历都是从图的某一条边往下走&#xff0c;那么被孤立的结点怎么被遍历到呢&#xff1f; 2.连通的相关概念 连通&#xff1a;如果从V到W存在一条&#xff08;无向&#…

3D魔方游戏制作lua迷你世界

--3D魔方 --星空露珠工作室 --核心脚本来自负负 --1:xy 2:yx 3:xz 4:zx 5:yz 6:zy --4000,0-3 3995-0,3 local trn{ {{5,2},{3,1},{1,2},{1,3},{4,0},{2,2}}, {{3,0},{5,3},{1,3},{1,2},{2,3},{4,1}}, {{4,2},{2,1},{1,1},{1,0},{3,3},{5,1}}, {{2,0},{4,3},{1,0},{1,1},{5,0},…

Web3.js 4.x版本事件监听详解:从HTTP到WebSocket的迁移

项目场景 在一个使用以太坊区块链技术的项目中&#xff0c;需要监听智能合约的事件&#xff0c;以便在事件触发时能够及时响应。项目中使用了web3.js库的4.x版本&#xff0c;节点使用Geth启动&#xff0c;并通过HTTP与节点进行通信。 问题描述 合约DataStorage.sol文件已经定…