@Test
public void testJedis(){
Jedis jedis = jedisPool.getResource();
jedis.watch("key1");
//开启事务
Transaction transaction = jedis.multi();
//命令入队
transaction.set("key1", "hello");
transaction.expire("key1", 20);
//获取一个新的Jedis实例修改key1
Jedis jedis2 = jedisPool.getResource();
jedis2.set("key1", "world");
//提交事务
List<Object> result = transaction.exec();
System.out.println("transaction exec result : "+result);
System.out.println("jedis get key1: "+jedis.get("key1"));
}
在事务exec之前,使用jedis2去修改key1,使得watch起作用导致事务失败。
@Component
public class RedisTest implements CommandLineRunner {
@Autowired
private StringRedisTemplate redisTemplate;
@Override
public void run(String... args) throws Exception {
redisTemplate.setEnableTransactionSupport(true);
for (int i = 0; i < 10; i++) {
new Thread(new Runnable() {
@Override
public void run() {
int i = 0;
while (true) {
redisTemplate.opsForValue().set("key1", "hw" + i, 5000, TimeUnit.SECONDS);
i++;
}
}
}).start();
}
redisTemplate.opsForValue().set("key1", "0");
new Thread(new Runnable() {
@Override
public void run() {
redisTemplate.watch("key1");
Object result = redisTemplate.execute(new SessionCallback<Object>() {
@Override
public <K, V> Object execute(RedisOperations<K, V> redisOperations) throws DataAccessException {
redisOperations.multi();
redisOperations.opsForValue().set((K) "key1", (V) ("test1"));
redisOperations.expire((K) "key1", 50000, TimeUnit.SECONDS);
redisOperations.opsForValue().get("key1");
return redisOperations.exec();
}
});
System.out.println("redis exec result:" + result);
}
}).start();
Thread.sleep(5 * 1000);
System.out.println("get key:" + redisTemplate.opsForValue().get("key1"));
}
}
一个线程开始事务watch key1,并开启事务修改key1为test1,另一个线程一直在修改key1。
执行后事务的结果为空数组,说明事务执行失败了,我们的watch成功了。
高并发的场景下操作redis并不是watch到有其他线程修改就失败,而需要的效果一般是线程安全的修改成功。借鉴juc中自旋锁的方式,修改第二个例子:
@Component
public class RedisTest implements CommandLineRunner {
@Autowired
private StringRedisTemplate redisTemplate;
private volatile boolean isExist = false;
@Override
public void run(String... args) throws Exception {
redisTemplate.setEnableTransactionSupport(true);
for (int i = 0; i < 10; i++) {
new Thread(new Runnable() {
@Override
public void run() {
int i = 0;
while (!isExist) {
redisTemplate.opsForValue().set("key1", "hw" + i, 5000, TimeUnit.SECONDS);
i++;
}
}
}).start();
}
redisTemplate.opsForValue().set("key1", "0");
new Thread(new Runnable() {
@Override
public void run() {
//事务是否成功
boolean isSuccess = false;
while(!isSuccess) {
redisTemplate.watch("key1");
Object result = redisTemplate.execute(new SessionCallback<Object>() {
@Override
public <K, V> Object execute(RedisOperations<K, V> redisOperations) throws DataAccessException {
redisOperations.multi();
redisOperations.opsForValue().set((K) "key1", (V) ("test1"));
redisOperations.expire((K) "key1", 50000, TimeUnit.SECONDS);
redisOperations.opsForValue().get("key1");
return redisOperations.exec();
}
});
if (ObjectUtils.isEmpty(result)) {
// isExist = true;
isSuccess = false;
}else{
isSuccess = true;
}
System.out.println("redis exec result:" + result);
}
}
}).start();
Thread.sleep(10 * 1000);
System.out.println("get key:" + redisTemplate.opsForValue().get("key1"));
}
}
例如,我想要如下的效果,当key2为5的时候,我将其修改为6:
redisTemplate.setEnableTransactionSupport(true);
redisTemplate.opsForValue().set("key2", "5", 5000, TimeUnit.SECONDS);
Object result = redisTemplate.execute(new SessionCallback<Object>() {
@Override
public <K, V> Object execute(RedisOperations<K, V> operations) throws DataAccessException {
operations.multi();
V key2 = operations.opsForValue().get("key2");
System.out.println("tranctioning get key2: "+ key2);
if("5".equals(key2)){
operations.opsForValue().set((K)"key2", (V)"6");
}
return operations.exec();
}
});
System.out.println("exec result:" + result);
System.out.println("get key2: "+ redisTemplate.opsForValue().get("key2"));
可以发现事务执行过程中,我们根本获取不到任何值。这是因为exec前的所有操作仅仅是将命令放入了队列中,根本没有实际执行,这时候获取结果都是null。
有人说既然事务中不能获取,是否可以在事务之前先获取值,事务中判断呢?更不行哒。看下面这个例子,模拟INCR,10个线程同时操作key3,每个线程进行100次的自增操作,由于事务中无法获取当前的key3的值,只能在事务之前获取。
redisTemplate.setEnableTransactionSupport(true);
redisTemplate.opsForValue().set("key3", "0");
for (int i = 0; i < 10; i++) {
new Thread(()->{
for (int j = 0; j <100 ; j++) {
redisTemplate.watch("key3");
boolean isSuccess = false;
while(!isSuccess) {
final Integer[] num = {Integer.parseInt(redisTemplate.opsForValue().get("key3"))};
Object result = redisTemplate.execute(new SessionCallback<Object>() {
@Override
public <K, V> Object execute(RedisOperations<K, V> operations) throws DataAccessException {
operations.multi();
operations.opsForValue().set((K) "key3", (V) (++num[0] + ""), 5000, TimeUnit.SECONDS);
return operations.exec();
}
});
if(!ObjectUtils.isEmpty(result)){
isSuccess = true;
}
System.out.println(Thread.currentThread().getName()+"-"+j+"-exec result:" + result);
}
}
}, "Thread-"+i).start();
}
Thread.sleep(20 * 1000);
System.out.println("get key3:" + redisTemplate.opsForValue().get("key3"));