redis的事务类似于队列操作,执行过程分为三步:
- 开启事务
- 入队操作
- 执行事务
使用到的几个命令如下:
命令 | 说明 |
---|---|
multi | 开启一个事务 |
exec | 事务提交 |
discard | 事务回滚 |
watch | 监听key(s):当监听一个key(s)时,如果在本次事务提交之前,有其他命令修改了该key的值,那么本地事务就会失效 |
unwatch | 取消监听key(s) |
下面我们使用一个springboot的代码操作来说明这几个命令的含义:
package com.test.spring;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.dao.DataAccessException;
import org.springframework.data.redis.RedisSystemException;
import org.springframework.data.redis.connection.RedisConnection;
import org.springframework.data.redis.core.RedisCallback;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@SpringBootApplication
@RestController
public class TestSpringApplication {
@Autowired
private StringRedisTemplate stringRedisTemplate;
@RequestMapping("/exec1")
public String test1(){
stringRedisTemplate.execute(new RedisCallback<Boolean>() {
@Override
public Boolean doInRedis(RedisConnection connection) throws DataAccessException {
connection.multi();
connection.commands().set("k1".getBytes(),"1".getBytes());
connection.commands().set("k2".getBytes(),"2".getBytes());
connection.exec();
return true;
}
});
return "k1="+stringRedisTemplate.opsForValue().get("k1")+",k2="+stringRedisTemplate.opsForValue().get("k2");
}
@RequestMapping("/exec2")
public String exec2(){
try{
stringRedisTemplate.execute(new RedisCallback<Boolean>() {
private int i=0;
@Override
public Boolean doInRedis(RedisConnection connection) throws DataAccessException {
connection.multi();
connection.commands().set("k1".getBytes(),"11".getBytes());
connection.commands().set("k2".getBytes(),"22".getBytes());
if(i==0){
throw new RedisSystemException("一个异常",new RuntimeException("1"));
}
connection.exec();
return true;
}
});
}catch (Exception e){
e.printStackTrace();
}
return "k1="+stringRedisTemplate.opsForValue().get("k1")+",k2="+stringRedisTemplate.opsForValue().get("k2");
}
@RequestMapping("/discard")
public String discard(){
try{
stringRedisTemplate.execute(new RedisCallback<Boolean>() {
@Override
public Boolean doInRedis(RedisConnection connection) throws DataAccessException {
connection.multi();
connection.commands().set("k3".getBytes(),"3".getBytes());
connection.commands().set("k4".getBytes(),"4".getBytes());
connection.discard();
connection.exec();
return true;
}
});
}catch (Exception e){
e.printStackTrace();
}
return "k3="+stringRedisTemplate.opsForValue().get("k3")+",k4="+stringRedisTemplate.opsForValue().get("k4");
}
@RequestMapping("/watch1")
public String watch1(){
//开启一个线程
new Thread(()->{
stringRedisTemplate.execute(new RedisCallback<Boolean>() {
@Override
public Boolean doInRedis(RedisConnection connection) throws DataAccessException {
try {
connection.watch("k5".getBytes());
connection.multi();
connection.commands().set("k5".getBytes(), "5".getBytes());
//休眠5秒钟在提交事务
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
}
connection.exec();
}catch (Exception e){
e.printStackTrace();
}
return true;
}
});
}).start();
//开启一个线程
new Thread(()->{
stringRedisTemplate.execute(new RedisCallback<Boolean>() {
@Override
public Boolean doInRedis(RedisConnection connection) throws DataAccessException {
try {
connection.multi();
connection.commands().set("k5".getBytes(), "55".getBytes());
connection.exec();
}catch (Exception e){
e.printStackTrace();
}
return true;
}
});
}).start();
return "success";
}
@RequestMapping("/watch2")
public String watch2(){
return "k5="+stringRedisTemplate.opsForValue().get("k5");
}
public static void main(String[] args) {
SpringApplication.run(TestSpringApplication.class, args);
}
}
- exec1
正常流程,使用curl进行测试会返回:k1=1,k2=2 - exec2
我们模拟在事务队列中发送异常,会发现这段设值不成功,测试返回:k1=1,k2=2 - discard
事务回滚,我们先回滚,再提交,后台会抛出:ERR EXEC without MULTI错误,说明设值失败 - watch1、watch2
这里我们模拟两个线程,第一个线程先监听key,然后等待5秒钟,但是第二个线程直接去修改这个key,当5秒结束时,第一个线程再去提交事务时,会发现已经失效了,然后我们再通过watch2去查询值,测试返回:k5=55,说明线程1事务失效
最后再说明一下unwatch,每次操作exec()后,底层会自动调用unwatch,所以我们可以不用显示去调用unwatch命令。