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

猜你喜欢

转载自blog.csdn.net/jia_costa/article/details/79035743