事务
- 一个成熟的数据库系统一般都会有事务的支持,Redis作为一个缓存数据库也不例外,Redis的事务比之关系型数据库mysql,oracle等算比较简单的,Redis中无需理解那么多事务模型,可以直接使用。不过也正是因为简单,redis的事务模型是不严谨的,不能像关系型数据库那么用Redis的事务。
Redis事务的基本用法
- 每个事务操作的基本过程都有如下begin, commit,和rollback,如下
- begin指令标识事务的开始
- commit指令标识事务的提交
- rollback指令标识事务的回滚
begin();
try{command1();command2();....commit();
}cache(Exception e){rollback();
}
- Redis事务形式上基本相同指令分别是multi, exec,discard,
- multi:事务开始
- exec:事务执行
- discard:事务丢弃
新docker-redis:0>multi
"OK"新docker-redis:0>incr books
"QUEUED"新docker-redis:0>incr books
"QUEUED"新docker-redis:0>exec1) "2"2) "3"
- 以上过程如下图所示,所有指令在exec之前都不执行,二手缓存在服务器的一个服务队列中,服务器一旦收到exec指令,才开始执行整个事务队列,执行完后一次性返回所有指令结果,因为Redis 是单线程,不用担心自己在执行队列的时候被其他指令打搅,保证原子性。
原子性
- 事务的原子性指要么全部成功,要么都失败,Redis的事务是非原子性的。如下案例。
新docker-redis:0>multi
"OK"新docker-redis:0>set books iamastring
"QUEUED"新docker-redis:0>incr books
"QUEUED"新docker-redis:0>set poorman iamdesperate
"QUEUED"新docker-redis:0>exec1) "OK"2) "ERR value is not an integer or out of range"3) "OK"
- 如上案例是事务执行到中间有一条指令是执行失败的,提示不能对字符串进行累加。但是后面的指令还是可以成功执行,所有poorman 的值是正常设置
- 此处说明Redis事务不具备原子性,而仅仅是满足了事务的“隔离性”中的串行化-----当前执行的事务有不被其他事务打断的权利。
discard(丢弃)
- Redis为事务提供discard指令,用于丢弃事务缓存队列中所有指令,在exec执行之前。
新docker-redis:0>get books
null新docker-redis:0>multi
"OK"新docker-redis:0>incr books
"QUEUED"新docker-redis:0>incr books
"QUEUED"新docker-redis:0> discard
"OK"新docker-redis:0>exec
"ERR EXEC without MULTI"新docker-redis:0>get books
null
- 执行discard后,队列中所有指令都没有执行,我们执行exec后提示无法执行空的队列
Redis事务的优化
- Redis事务在发送每个指令到事务队列缓存时候需要经一次网络读写,当一个事务内部指令较多,需要的IOShi就也会线性增加,所以同城Redis客户端在执行事务时候回结合pipeline一起使用,这样可以将多次IO操作压缩为单次。例如java中Redis的使用
public class MultiTest {public static void main(String[] args) throws IOException {Jedis jedis = JedisPoolTools.getJedis();Pipeline pipe = jedis.pipelined();pipe.multi();pipe.incr("books");pipe.incr("books");Response<List<Object>> value = pipe.exec();pipe.close();System.out.println(value.get());}
}
watch
- 当有一个业务场景,Redis存储账户余额,是一个整数,多个客户端并发修改余额,这个修改不是简单的incrby,而是需要通过一个逻辑计算,但是Redis单指令无法完成,我们需要先取出,计算,在存储。这样就是去原子性,有并发问题:
- 方案一:我们用Redis分布式锁来避免冲突,分布式锁是悲观锁,只允许一方持有锁,其他等待
- 方案二:Redis提供watch机制,是乐观锁,有watch我们又多了一种可以解决并发修改的方法。watch使用方法如下。
public static void main(String[] args) {Jedis jedis = JedisPoolTools.getJedis();jedis.watch("books");jedis.set("books" , "100"); //此处已经修改,最终返回nullPipeline pipe = jedis.pipelined();pipe.multi();pipe.incr("books");pipe.incr("books");try {Response<List<Object>> value = pipe.exec();pipe.close();System.out.println(value.get());}catch (Exception e){System.out.println(e.getMessage());}}
- watch会在事务开始之前叮嘱一个或者多个关键变量,流程如下:
- 当事务执行之后,执行exec命令后要顺序执行缓存的指令队列
- Redis检查关键变量watch之后是否被修改
- 如果被变量被修改过,exec指令将返回null,通知客户端执行失败,此时我们可以重试或者其他逻辑
注意
- Redis禁止在multi 和exec之间执行watch,必须在multi之前执行watch,否则抛出异常。
上一篇:Redis持久化-深入理解AOF,RDB
下一篇:Redis存储优化–小对象压缩