【Redis缓存机制】13.Java连接Redis_Jedis_事务

本文介绍如何使用Redis的事务管理功能,包括基本的事务操作如开启、执行、回滚等,并通过模拟信用卡交易过程演示如何利用watch命令确保事务的一致性和安全性。
Jedis事务
我们使用JDBC连接Mysql的时候,每次执行sql语句之前,都需要开启事务;在MyBatis中,
也需要使用openSession()来获取session事务对象,来进行sql执行、查询等操作。当我们
对数据库的操作结束的时候,是事务对象负责关闭数据库连接。

事务对象用于管理、执行各种数据库操作的动作。它能够开启和关闭数据库连接,执行sql
语句,回滚错误的操作。

我们的Redis也有事务管理对象,其位于redis.clients.jedis.Transaction下。

Jedis事务的相关代码:
package cn.com.redis;

import redis.clients.jedis.Jedis;
import redis.clients.jedis.Transaction;

public class Test7 {
    public static void main(String[] args) {
        Jedis jedis = new Jedis("192.168.248.129",6379);
        
        Transaction transaction=jedis.multi();//返回一个事务控制对象
        
        //预先在事务对象中装入要执行的操作
        transaction.set("k4", "v4");
        transaction.set("k5", "v5");
        
        transaction.exec();//执行
    }
}
我们查看一下redis:

发现数据已经加入进去

我们把k4的value和k5的value改为“v44”和“v55”,
然后在transaction.exec()语句后加入transaction.discard()语句:
package cn.com.redis;

import redis.clients.jedis.Jedis;
import redis.clients.jedis.Transaction;

public class Test7 {
    public static void main(String[] args) {
        Jedis jedis = new Jedis("192.168.248.129",6379);
        
        Transaction transaction=jedis.multi();//返回一个事务控制对象
        
        //预先在事务对象中装入要执行的操作
        transaction.set("k4", "v44");
        transaction.set("k5", "v55");

        transaction.discard();//回滚
    }
}
会发现数据插入操作被回滚,redis中那两个值未被改变:


我们模拟一个刷一次信用卡的交易,使用redis的事务来处理一些逻辑:
package cn.com.redis;

import redis.clients.jedis.Jedis;
import redis.clients.jedis.Transaction;

public class TestTransaction {
    //模拟信用卡消费和还款
    public static void main(String[] args) {
        TestTransaction t = new TestTransaction();
        boolean retValue = t.transMethod(100);
        if(retValue){
            System.out.println("使用信用卡消费成功!");
        }else{
            System.out.println("使用信用卡消费失败!");
        }
        
    }

    /**
     * 通俗点讲,watch命令就是标记一个键,如果标记了一个键,
     * 在提交事务前如果该键被别人修改过,那事务就会失败,这种情况通常可以在程序中
     * 重新再尝试一次。
     *
     * 首先标记了balance,然后检查余额是否足够,不足就取消标记,并不做扣减;
     * 足够的话,就启动事务进行更新操作。
     * 如果在此期间键balance被其他人修改,拿在提交事务(执行exec)时就会报错,
     * 程序中通常可以捕获这类错误再重新执行一次,直到成功。
     * */
    private boolean transMethod(int amount) {
        
        System.out.println("您使用信用卡预付款"+amount+"元");
        
        Jedis jedis = new Jedis("192.168.248.129",6379);
        
        int balance = 1000;//可用余额
        int debt;//欠额
        int amtToSubtract = amount;//实刷额度
        
        jedis.set("balance", String.valueOf(balance));
        jedis.watch("balance");
        //jedis.set("balance", "1100");//此句不该出现,为了模拟其他程序已经修改了该条目
        balance = Integer.parseInt(jedis.get("balance"));
        if(balance < amtToSubtract){//可用余额小于实刷金额,拒绝交易
            jedis.unwatch();
            System.out.println("可用余额不足!");
            return false;
        }else{//可用余额够用的时候再去执行扣费操作
            System.out.println("扣费transaction事务开始执行...");
            Transaction transaction = jedis.multi();
            transaction.decrBy("balance",amtToSubtract);//余额减去amtToSubtract的钱数
            transaction.incrBy("debt", amtToSubtract);//信用卡欠款增加amtToSubtract的钱数
            transaction.exec();//执行事务
            balance = Integer.parseInt(jedis.get("balance"));
            debt = Integer.parseInt(jedis.get("debt"));
            System.out.println("扣费transaction事务执行结束...");
            
            System.out.println("您的可用余额:"+balance);
            System.out.println("您目前欠款:"+debt);
            return true;
        }
    }
    
}

此代码就是模拟用户使用信用卡刷了100元的东西,此时应该减去信用卡的可用余额100元,
增加100元的欠款。

运行结果:


redis的结果:

证明我们的操作是成功的。

加watch命令是为了在事务执行的过程中,防止其它的操作打断事务,或者是影响事务的计算结果,
导致“幻读”、“脏数据”等异常情况的发生。watch命令建立了一个键,一旦发现执行过程中该
键被别人修改过,那事务就会失败,程序中通常可以捕获这类错误再重新执行一次,直到成功。
所以watch命令可以保证数据的同步安全。

为了证明watch命令的用途,我们把上面代码里面的jedis.set("balance", "1100");注释释放,
然后transMethod方法抛出打断异常:throws InterruptedException,main方法捕获打断异常,
然后弹出相应警告框。
package cn.com.redis;

import java.util.List;

import redis.clients.jedis.Jedis;
import redis.clients.jedis.Transaction;

public class TestTransaction {
    //模拟信用卡消费和还款
    public static void main(String[] args) {
        TestTransaction t = new TestTransaction();
        boolean retValue=false;
        boolean Interrupted = false;
        
        try {
            retValue = t.transMethod(100);
        } catch (InterruptedException e) {
            Interrupted = true;
            System.out.println("事务被打断,请重新执行!");
        }finally{
            if(retValue){
                System.out.println("使用信用卡消费成功!");
            }else{
                if(!Interrupted){
                    System.out.println("使用信用卡消费失败!余额不足!");
                }
            }
        }
    }

    /**
     * 通俗点讲,watch命令就是标记一个键,如果标记了一个键,
     * 在提交事务前如果该键被别人修改过,那事务就会失败,这种情况通常可以在程序中
     * 重新再尝试一次。
     *
     * 首先标记了balance,然后检查余额是否足够,不足就取消标记,并不做扣减;
     * 足够的话,就启动事务进行更新操作。
     * 如果在此期间键balance被其他人修改,拿在提交事务(执行exec)时就会报错,
     * 程序中通常可以捕获这类错误再重新执行一次,直到成功。
     * */
    private boolean transMethod(int amount) throws InterruptedException{
        
        System.out.println("您使用信用卡预付款"+amount+"元");
        
        Jedis jedis = new Jedis("192.168.248.129",6379);
        
        int balance = 1000;//可用余额
        int debt;//欠额
        int amtToSubtract = amount;//实刷额度
        
        jedis.set("balance", String.valueOf(balance));
        jedis.watch("balance");
        jedis.set("balance", "1100");//此句不该出现,为了模拟其他程序已经修改了该条目
        balance = Integer.parseInt(jedis.get("balance"));
        if(balance < amtToSubtract){//可用余额小于实刷金额,拒绝交易
            jedis.unwatch();
            System.out.println("可用余额不足!");
            return false;
        }else{//可用余额够用的时候再去执行扣费操作
            System.out.println("扣费transaction事务开始执行...");
            Transaction transaction = jedis.multi();
            transaction.decrBy("balance",amtToSubtract);//余额减去amtToSubtract的钱数
            transaction.incrBy("debt", amtToSubtract);//信用卡欠款增加amtToSubtract的钱数
            List<Object> result = transaction.exec();//执行事务
            
            if(result==null){//事务提交失败,说明在执行期间数据被修改过
                
                System.out.println("扣费transaction事务执行中断...");
                throw new InterruptedException();
                
            }else{//事务提交成功
                balance = Integer.parseInt(jedis.get("balance"));
                debt = Integer.parseInt(jedis.get("debt"));
                System.out.println("扣费transaction事务执行结束...");
                
                System.out.println("您的可用余额:"+balance);
                System.out.println("您目前欠款:"+debt);
                
                return true;
            }
        }
    }
    
}
再运行一下,看一下效果:


这就说明了,如果在watch命令执行后和事务提交之前,如果数据发生了修改操作,事务执行就不会成功,

此举保证了数据的安全性。

转载请注明出处:http://blog.csdn.net/acmman/article/details/53579378

<think>首先,用户的问题是关于解决Spring Boot应用中连接Redis时因认证失败导致的异常。具体错误是:`InvalidDataAccessApiUsageException` 和 `RedisHealthIndicator NOAUTH Authentication required`。 从系统指令中,我需要: - 使用中文回答。 - 结构清晰,逐步解决问题。 - 确保回答真实可靠。 - 行内数学表达式用$...$,但这个问题似乎不涉及数学,所以可能不需要。 - 独立公式用$$...$$,但同样,可能不相关。 - 生成相关问题。 - 在引用的段落末尾添加引用标识,如[^1]。 用户提供了两个引用: - 引用[1]:关于设置Redis密码的命令。 - 引用[2]:Spring Boot中Redis配置的YAML示例。 这些引用是相关的,因为问题涉及Redis认证。 关键点是: - 异常表明认证失败(NOAUTH)。 - 用户需要修复Spring Boot应用中的Redis连接问题。 步骤来解决: 1. **确认Redis服务器设置了密码**:用户可能已经设置了密码,但应用没有提供。 2. **检查Spring Boot配置**:确保在application.properties或application.yml中正确配置了Redis密码。 3. **验证密码正确性**:密码必须匹配。 4. **处理健康指示器问题**:`RedisHealthIndicator` 是Spring Boot Actuator的一部分,用于健康检查,也需要配置认证。 从引用[2]中,有YAML配置示例: ```yaml spring: redis: host: localhost port: 6379 jedis: pool: max-wait: 30m max-active: 100 max-idle: 20 min-idle: 0 timeout: 3000 password: 0 ``` 这里,`password: 0` 可能是个错误;密码应该是字符串,如 "123456"。 在用户的问题中,错误是`NOAUTH Authentication required`,这表明Redis服务器要求认证,但应用没有提供或提供了错误的凭证。 修复步骤: - **步骤1:检查Redis服务器密码**:使用Redis CLI确认密码是否设置。 - 参考引用[1]:`config get requirepass` 和 `config set requirepass "123456"`。 - **步骤2:更新Spring Boot配置**:在application.properties或application.yml中添加密码。 - 例如:`spring.redis.password=your_password` - **步骤3:确保密码正确**:密码必须与Redis服务器上设置的匹配。 - **步骤4:处理Actuator健康检查**:如果使用Spring Boot Actuator,`RedisHealthIndicator` 会自动使用相同的连接配置。所以,正确配置`spring.redis`应该解决它。 - **步骤5:测试连接**:重启应用后,检查是否连接成功。 常见错误: - 密码拼写错误。 - 配置文件中密码未设置或设置错误。 - Redis服务器没有启用认证。 在回答中,我需要结构清晰: - 先解释问题原因。 - 然后给出逐步解决方案。 - 最后生成相关问题。 生成相关问题: 1. 如何设置Redis密码? 2. 如何在Spring Boot中配置多个Redis实例? 3. 如何自定义Spring Boot Actuator的健康检查? 添加引用: - 在相关部分末尾添加[^1]和[^2]。 回答结构: 1. **问题分析**:简要解释异常原因。 2. **解决方案步骤**:详细步骤。 3. **配置示例**:给出YAML或properties示例。 4. **测试验证**:如何测试修复是否成功。 5. **相关问题**:生成几个相关问题。</think>### 解决Spring Boot应用连接Redis时认证失败导致的异常 当Spring Boot应用连接Redis时出现`InvalidDataAccessApiUsageException`和`RedisHealthIndicator NOAUTH Authentication required`错误,这通常是因为Redis服务器启用了密码认证,但应用配置中未提供或提供了错误的密码。错误信息`NOAUTH`明确表示认证失败。以下我将逐步解释原因并提供完整解决方案。解决方案基于Redis和Spring Boot的标准配置,确保真实可靠。 #### 问题原因分析 - **核心问题**:Redis服务器设置了密码(通过`requirepass`配置),但Spring Boot应用在连接时未正确传递该密码。 - **异常触发场景**: - 当应用尝试访问Redis时,Redis服务器拒绝未认证的连接。 - Spring Boot Actuator的`RedisHealthIndicator`在健康检查时也会尝试连接Redis,如果认证失败,会抛出`NOAUTH`错误。 - **根本原因**:Spring Boot的`spring-boot-starter-data-redis`模块未正确配置密码,导致连接池(如Jedis或Lettuce)无法通过认证。 #### 解决方案步骤 以下是逐步修复方法,确保应用配置与Redis服务器设置一致。所有步骤均基于Spring Boot 2.x/3.x和Redis标准实践。 1. **验证Redis服务器密码设置** 首先,确认Redis服务器是否启用了密码认证。使用Redis CLI检查: - 连接Redis服务器:`redis-cli -h localhost -p 6379` - 检查当前密码:`config get requirepass` 如果输出类似`1) "requirepass" 2) "123456"`,则表示密码已设置为"123456";如果输出为空字符串,则未设置密码[^1]。 - 如果未设置密码但需要认证,运行:`config set requirepass "your_password"`(替换为你的密码),并确保在Redis配置文件中持久化(如修改`redis.conf`中的`requirepass`项)。 2. **更新Spring Boot应用配置** 在应用的`application.properties`或`application.yml`中添加Redis密码。确保密码与Redis服务器设置的完全一致(包括大小写和特殊字符)。 - **YAML配置示例(推荐)**: 参考引用[2]中的配置模板,但修正密码字段(原示例`password: 0`是错误的,密码应为字符串): ```yaml spring: redis: host: localhost # Redis服务器地址 port: 6379 # Redis端口 password: "123456" # 密码必须用双引号包裹,与Redis服务器的requirepass匹配 jedis: pool: max-active: 100 # 连接池最大连接数 max-wait: 30m # 最大等待时间 max-idle: 20 # 最大空闲连接 min-idle: 0 # 最小空闲连接 timeout: 3000 # 连接超时(毫秒) ``` - **Properties配置示例**: 如果使用`application.properties`: ```properties spring.redis.host=localhost spring.redis.port=6379 spring.redis.password=123456 # 直接写密码字符串,无需引号 spring.redis.jedis.pool.max-active=100 spring.redis.jedis.pool.max-wait=1800000 # 30分钟(单位毫秒) ``` 3. **处理Spring Boot Actuator健康检查** `RedisHealthIndicator`是Actuator的一部分,它会自动使用`spring.redis`配置。如果仍报错,确保: - Actuator依赖已添加(在`pom.xml`或`build.gradle`中): ```xml <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-actuator</artifactId> </dependency> ``` - 如果使用自定义健康检查,确保不覆盖Redis连接设置。检查`HealthIndicator`实现类中是否硬编码了错误配置。 4. **测试修复是否成功** - 重启Spring Boot应用。 - 验证连接: - 访问Actuator健康端点:`http://localhost:8080/actuator/health`(默认端口)。如果返回`{"status":"UP"}`,表示Redis健康检查通过。 - 在代码中添加简单测试,例如使用`RedisTemplate`执行一个命令: ```java @Autowired private RedisTemplate<String, String> redisTemplate; public void testConnection() { redisTemplate.opsForValue().set("testKey", "testValue"); // 无异常则成功 } ``` - 如果仍有`NOAUTH`错误,检查: - 密码是否拼写错误(例如多空格或大小写问题)。 - Redis服务器防火墙是否阻止连接(使用`telnet localhost 6379`测试端口)。 - 是否多个配置源冲突(如`application.properties`和`application.yml`同时存在时,后者优先级更高)。 #### 注意事项 - **安全建议**:不要在代码中硬编码密码。使用环境变量或Spring Cloud Config管理敏感信息: ```yaml spring: redis: password: ${REDIS_PASSWORD:default_password} # 从环境变量读取 ``` - **版本兼容性**:Spring Boot 2.x以上默认使用Lettuce连接池,但Jedis配置也兼容。如果使用Lettuce,配置方式类似(`spring.redis.lettuce.pool`)。 - **常见错误**:如果Redis服务器未设置密码,但配置了`spring.redis.password`,会导致连接失败。确保密码设置与服务器一致[^1][^2]。 #### 相关问题 1. 如何在Redis中动态修改密码而不重启服务? 2. Spring Boot中如何配置高可用的Redis集群(如哨兵模式)? 3. 如何优化Redis连接池参数以提升Spring Boot应用性能? 通过以上步骤,您的应用应能成功连接Redis。如果问题依旧,请提供更多配置细节,我会进一步协助分析。
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

光仔December

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

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

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

打赏作者

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

抵扣说明:

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

余额充值