一、前言
平时工作中关系型数据库的事务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
。