1、使用Jedis實現事務
@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起作用導致事務失敗。
2、使用redisTemplate時一定要將enableTransactionSupport設置爲true,否則事務將不會起作用。
@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成功了。
3、使用自旋讓修改操作成功
高併發的場景下操作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"));
}
}
在嘗試多次後事務終於成功了。
4、事務中不適合做邏輯判斷
例如,我想要如下的效果,當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"));
即使可以保證每次事務都成功,依然無法保證最後的結果是1000。
這種怎麼辦呢?目前所知估計只有lua腳本了。