redis——事务
一、Redis事务的基本用法
1 127.0.0.1:6379> set int 0 2 OK 3 127.0.0.1:6379> multi //begin 事务开始 4 OK 5 127.0.0.1:6379> incr int 6 QUEUED //QUEUE 指令已经被服务器缓存到队列里了 7 127.0.0.1:6379> incr int 8 QUEUED 9 127.0.0.1:6379> exec //commit 事务提交 10 1) (integer) 1 11 2) (integer) 2 12 127.0.0.1:6379> multi //begin 事务开始 13 OK 14 127.0.0.1:6379> incr int 15 QUEUED 16 127.0.0.1:6379> incr int 17 QUEUED 18 127.0.0.1:6379> discard //discard 事务丢弃 19 OK 20 127.0.0.1:6379> get int 21 "2"
二、redis没有保证事务的的原子性
事务四大特性:原子性、隔离性、持久性、一致性
由于redis是单线程的,保证事务的隔离性,一致性,每个指令的原子性,redis持久性也能保持事务的持久性。但是redis没有保证多个指令的原子性,无法保证事务的原子性。
1 127.0.0.1:6379> multi 2 OK 3 127.0.0.1:6379> set books java 4 QUEUED 5 127.0.0.1:6379> incr books 6 QUEUED 7 127.0.0.1:6379> set bookss golang 8 QUEUED 9 127.0.0.1:6379> exec 10 1) OK //执行成功 11 2) (error) ERR value is not an integer or out of range //执行失败 12 3) OK //执行成功 13 127.0.0.1:6379> get books 14 "java" //没有回退rollback 15 127.0.0.1:6379> get bookss 16 "golang" //没有回退rollback
三、优化
redis事务的每个指令到事务缓存队列时都要经过一次网络读写,所以通常Redis的客户端在执行事务时都会结合pipeline一起使用,可以将多次IO操作压缩为单次IO操作。
1 public class TransactionTest { 2 3 public static void main(String[] args){ 4 Pipeline pipeline = RedisUtils.pipelined(); 5 pipeline.multi(); 6 pipeline.incr("int"); 7 pipeline.incr("int"); 8 Response<List<Object>> response = pipeline.exec(); 9 pipeline.close();//get前需要关闭管道 10 System.out.println(response.get()); 11 RedisUtils.close(); 12 } 13 }
四、watch
watch在事务开始之前盯住一个或多个变量,当事务执行时,也就是服务器收到exec指令要顺序执行缓存的事务队列,Redis会检查关键变量自watch之后是否被修改。如果关键变量被修改过,exec指令就会返回NULL回复告知客户端事务执行失败。
1 127.0.0.1:6379> watch int //watch int 2 OK 3 127.0.0.1:6379> get int 4 "10" 5 127.0.0.1:6379> incr int //watch期间int被修改了 6 (integer) 11 7 127.0.0.1:6379> multi //开启事务 8 OK 9 127.0.0.1:6379> incr int 10 QUEUED 11 127.0.0.1:6379> exec //执行事务 12 (nil) //执行失败 返回null
注意:
①禁止在multi与exec之间执行watch指令,必须在multi之前执行,否则会报错
②multi后exec前,不能关闭redis连接,否则会抛错ERR EXE without MULTI
③watch与multi与exec必须是同一个redis连接实例,否则不会生效。
public class WatchTest { public static void main(String[] args){ RedisUtils.CallWithRedis caller = new RedisUtils.CallWithRedis() { @Override public Object call(Jedis jedis) { for (;;){ // RedisUtils.watch("int");//watch multi exec必须是一个jedis实例 jedis.watch("int"); int value = Integer.parseInt(jedis.get("int")); System.out.println("int : " + value); // RedisUtils.incr("int");//这里修改操作可以是不同的连接 jedis.incr("int"); // Transaction transaction = RedisUtils.multi();//里面包含redis自动关闭连接,会导致exec报错 Transaction transaction = jedis.multi(); transaction.set("int","0"); List<Object> res = transaction.exec(); if(res != null){ System.out.println("transaction success"); break; } System.out.println("watchError!"); } return 0; } }; RedisUtils.watch(caller); RedisUtils.close(); } }
RedisUtils.java
public static Object watch(CallWithRedis caller){
return RedisUtils.execute(caller);
}