Redis--事务理解

Redis提供简单的事务支持,包括multi、exec、discard命令。事务具有非原子性,即在事务中如果出现错误,后续指令仍可能执行。可以通过watch实现乐观锁解决并发问题,但watch不能在multi和exec之间使用。在实际应用中,通常结合pipeline优化事务性能。
摘要由CSDN通过智能技术生成
事务
  • 一个成熟的数据库系统一般都会有事务的支持,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>exec
 1)  "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>exec
 1)  "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");  //此处已经修改,最终返回null
        Pipeline 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存储优化–小对象压缩

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值