redis事物可以使得一組命令在執行期間不會被打斷,因此事物中的這組命令也是一個原子操作。因爲redis本身就是單線程的,所以redis的事物就簡單很多,不像關係型數據庫那樣還有隔離級別的概念,我們甚至可以這樣理解,redis的每條命令都是包含在一個事物中。
redis的命令行操作,使用multi開啓事物,使用exec提交事物。例如:
127.0.0.1:6379> multi
OK
127.0.0.1:6379> set a a
QUEUED
127.0.0.1:6379> set b b
QUEUED
127.0.0.1:6379> exec
1) OK
2) OK
下面重點介紹jedis客戶端的操作。
同樣新建一個maven工程,pom文件如下
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.tansun</groupId>
<artifactId>RedisTest</artifactId>
<version>0.0.1-SNAPSHOT</version>
<dependencies>
<!-- https://mvnrepository.com/artifact/redis.clients/jedis -->
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>2.9.0</version>
</dependency>
</dependencies>
</project>
方便演示,我們使用單節點的redis,也不使用池化技術。
package com.tansun;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.Transaction;
public class RedisTest {
@SuppressWarnings("resource")
public static void main(String[] args) {
Jedis jedis = new Jedis("192.168.229.128", 6379);
// 開啓事物
Transaction multi = jedis.multi();
for(int i = 0; i < 10; i++){
multi.set(i + "", i + "");
}
// 提交事物
multi.exec();
}
}
實例化redis.clients.jedis.Jedis,調用multi()方法開啓事物,返回一個redis.clients.jedis.Transaction對象,用這個對象調用redis的命令,最後調用exec()方法提交事物。
需要注意的是,redis的事物並不支持回滾。在redis服務器上敲命令行,如果語句有錯誤,事物就無法執行,這是語句編譯的錯誤,redis當然不會執行事物。但是如果是事物運行時產生錯誤,例如向一個string類型的鍵值對中使用lpush方法壓入數據,那麼不會影響其他正確語句的執行的。最後的結果是,有錯誤的語句無法執行,但是其他的語句(包括錯誤語句之後的語句)都會正常執行。至於redis的事物爲什麼不支持回滾,官網的意思是,這種隱藏在事物中的錯誤語句(redis服務器在執行之前無法發現),是不應該出現在生產系統上的。另外,redis提供了一個discard的指令,千萬不要以爲這是回滾事物,這個指令的意思是放棄事物,不再執行了。
下面看另一個問題,如何實現redis的樂觀鎖。
redis提供了兩個指令,watch和unwatch,這兩個命令是配合事物使用的(事實是,只能配合事物使用),這樣可以實現樂觀鎖。
watch命令可以監視某些鍵,一旦這些鍵的值發生變化,那麼跟在watch命令之後的事物就不會執行(當然你也可以認爲這個事物回滾了),執行exec命令後監視取消(即使鍵值有變化,事物提交後監視也會取消),也可以通過unwatch命令取消所有鍵的監視。
下面用一個例子來演示如何實現redis的樂觀鎖,使用Jedis客戶端模擬incr操作
package com.tansun;
import java.util.List;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.Transaction;
public class RedisTest {
/**
* 模擬incr命令
*/
@SuppressWarnings("resource")
public static void main(String[] args) {
Jedis jedis = new Jedis("192.168.229.128", 6379);
while(true){
// 監視age這個鍵,一旦這個鍵的值發生變化,那麼後面的事物將不會執行
jedis.watch("age");
int age = Integer.valueOf(jedis.get("age"));
// 開啓事物
Transaction multi = jedis.multi();
multi.set("age", age + 1 + "");
// 提交事物
List<Object> result = multi.exec();
// 如果執行成功,會返回一個“OK”,否則返回空
// 如果執行失敗,說明有其他線程修改了age這個鍵,需要再執行一次
if(result.size() > 0){
break;
}
}
}
}
剛纔已經說過,執行exec()方法之後會取消對鍵的監視,那麼你可能會擔心,我們在執行了
jedis.watch("age");
這行代碼後,會不會有其他事物操作這個鍵。當然會有了,但是watch()方法對其他線程並不會起作用。watch()方法的作用範圍僅限當前線程,不會對其他線程產生影響。
其實,用redis的腳本完全可以實現事物的所有功能,但是事物的學習成本比腳本低很多。redis官網上是這麼說的:
However it is not impossible
that in a non immediate future
we'll see that the whole user
base is just using scripts.
If this happens we may deprecate
and finally remove transactions.
大意是,如果大家都習慣用腳本來代替事物的話,他們就會取消redis中的事物功能。