【技术应用】redis事务的应用

一、前言

平时工作中关系型数据库的事务ACID接触比较多,非关系型数据库的事务接触比较少,接下来几篇文章会对针对redis的事务做一个学习总结;

今天这篇文章主要介绍一下redis事务的使用示例

二、概念

事务ACID
原子性(atomicity,或称不可分割性)
一致性(consistency)
隔离性(isolation,又称独立性)
持久性(durability)

保证执行指令的时候,要么都执行成功,要么都不执行,要么都成功,要么都没有成功

redis事务:提交,没有回归,提交了就提交,成功或者不成功,只会返回一个结果。

redis里面使用普通事务,没有锁

注:一般要使用redis事务,则需要和Watch方法一起使用,最好监听的这些字段都包含在所有的key里面。

三、关键命令

watch命令可以监听指定键,当后续事务执行前发现这些键已修复时,则拒绝执行事务;
multi命令可以开启一个事务,后续的命令都会被放入事务命令队列;
exec命令可以执行事务命令队列中的所有命令
discard命令可以抛弃事务命令队列中的命令,和exec命令都会结束当前事务;

四、示例代码

1、引入pom依赖

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

2、遇到的问题

按照redis事务原理,代码实现如下:

package com.sk.service;

import org.springframework.dao.DataAccessException;
import org.springframework.data.redis.core.RedisOperations;
import org.springframework.data.redis.core.SessionCallback;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.PostConstruct;
import javax.annotation.Resource;
import java.util.ArrayList;
import java.util.List;

@Service
public class RedisService {

    @Resource
    private StringRedisTemplate stringRedisTemplate;

    @PostConstruct
    public void start(){

        stringRedisTemplate.setEnableTransactionSupport(true);
        String name = "name",age="age",id="id";
        List<String> keys = new ArrayList<>();
        keys.add(name);
        keys.add(age);
        keys.add(id);
        stringRedisTemplate.watch(keys);
        stringRedisTemplate.multi();
        stringRedisTemplate.opsForValue().set(name,"张三");
        stringRedisTemplate.opsForValue().set(age,"20");
        stringRedisTemplate.opsForValue().increment(id);
        //redisTemplate.discard();
        print(stringRedisTemplate.exec());

    }
}

执行异常:

Caused by: io.lettuce.core.RedisCommandExecutionException: ERR EXEC without MULTI
	at io.lettuce.core.internal.ExceptionFactory.createExecutionException(ExceptionFactory.java:147) ~[lettuce-core-6.1.10.RELEASE.jar:6.1.10.RELEASE]
	at io.lettuce.core.internal.ExceptionFactory.createExecutionException(ExceptionFactory.java:116) ~[lettuce-core-6.1.10.RELEASE.jar:6.1.10.RELEASE]
	at io.lettuce.core.protocol.AsyncCommand.completeResult(AsyncCommand.java:120) ~[lettuce-core-6.1.10.RELEASE.jar:6.1.10.RELEASE]
	at io.lettuce.core.protocol.AsyncCommand.complete(AsyncCommand.java:111) ~[lettuce-core-6.1.10.RELEASE.jar:6.1.10.RELEASE]
	at io.lettuce.core.protocol.CommandHandler.complete(CommandHandler.java:747) ~[lettuce-core-6.1.10.RELEASE.jar:6.1.10.RELEASE]
	at io.lettuce.core.protocol.CommandHandler.decode(CommandHandler.java:682) ~[lettuce-core-6.1.10.RELEASE.jar:6.1.10.RELEASE]
	at io.lettuce.core.protocol.CommandHandler.channelRead(CommandHandler.java:599) ~[lettuce-core-6.1.10.RELEASE.jar:6.1.10.RELEASE]

问题原因:最开始以为没有开启事务,所以新增了命令stringRedisTemplate.setEnableTransactionSupport(true),但是还是报一样的错误;
查询了其它资料,官方文档说不支持上述的写法,由于multi()命令导致了redis connect()断开重连导致的;

2、正确写法

package com.sk.service;

import org.springframework.dao.DataAccessException;
import org.springframework.data.redis.core.RedisOperations;
import org.springframework.data.redis.core.SessionCallback;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.PostConstruct;
import javax.annotation.Resource;
import java.util.ArrayList;
import java.util.List;

@Service
public class RedisService {

    @Resource
    private StringRedisTemplate stringRedisTemplate;

    @PostConstruct
    public void start02(){
        //stringRedisTemplate.setEnableTransactionSupport(true);
        String name = "name",age="age",id="id";
        List<String> keys = new ArrayList<>();
        keys.add(name);
        keys.add(age);
        keys.add(id);

        List<Object> txResults = stringRedisTemplate.execute(new SessionCallback<List<Object>>() {
            public List<Object> execute(RedisOperations operations) throws DataAccessException {
                operations.watch(keys);
                operations.multi();
                operations.opsForValue().set(name,"zhangsan");
                operations.opsForValue().set(age,"20");
                operations.opsForValue().increment(id);
                return operations.exec();
            }
        });
        print(txResults);
    }
   public void print(List<Object> resultList){
        System.out.println("----------------开始输出结果-----------------");
        resultList.stream().forEach(x->{
            System.out.println(x);
        });
        System.out.println("----------------结果输出完成-----------------");
    }
}

执行结果:

----------------开始输出结果-----------------
true
true
13
----------------结果输出完成-----------------

3、当事务执行过程中相关key的值被其它线程修改时

1)执行代码打断点

在这里插入图片描述
2)修改key值

127.0.0.1:6379> 
127.0.0.1:6379> set name zhangsan
OK
127.0.0.1:6379> 
127.0.0.1:6379> 
127.0.0.1:6379> get name
"zhangsan"
127.0.0.1:6379> 
127.0.0.1:6379> 
127.0.0.1:6379> 
127.0.0.1:6379>

3)断点执行

在这里插入图片描述
当执行中的事务中的数据,被其它任务修改时,事务执行取消,这正式我们使用事务的原因。

五、总结

redis事务更多的是供大家理解redis的特点,在实际应用中,更多的是使用lua脚本,我们知道redis 支持 lua 脚本,能保证 lua 脚本执行的原子性,可以取代 multi + exec

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

Dylan~~~

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

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

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

打赏作者

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

抵扣说明:

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

余额充值