redis事物介紹和jedis事物操作

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中的事物功能。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章