zoukankan      html  css  js  c++  java
  • Redis的事务

     redis中的事务定义

    Redis中的事务(transaction)是一组命令的集合。

    事务同命令一样都是Redis的最小执行单位,一个事务中的命令要么都执行,要么都不执行。
    事务的原理是先将属于一个事务的命令发送给Redis,然后再让Redis依次执行这些命令。

    Redis保证一个事务中的所有命令要么都执行,要么都不执行。如果在发送EXEC命令前客户端断线了,则Redis会清空事务队列,事务中的所有命令都不会执行。而一旦客户端发送了EXEC命令,所有的命令就都会被执行,即使此后客户端断线也没关系,因为Redis中已经记录了所有要执行的命令。

    除此之外,Redis的事务还能保证一个事务内的命令依次执行而不被其他命令插入。试想客户端A需要执行几条命令,同时客户端B发送了一条命令,如果不使用事务,则客户端B的命令可能会插入到客户端A的几条命令中执行。如果不希望发生这种情况,也可以使用事务。

    事务的应用

    事务的应用非常普遍,如银行转账过程中A给B汇款,首先系统从A的账户中将钱划走,然后向B的账户增加相应的金额。这两个步骤必须属于同一个事务,要么全执行,要么全不执行。否则只执行第一步,钱就凭空消失了,这显然让人无法接受。

    和传统的事务不同

    和传统的mysql事务不同的事,即使我们的加钱操作失败,我们也无法在这一组命令中让整个状态回滚到操作之前

    事务的基本操作
    开启事务

    multi

    设定事务的开启位置,此指令执行后,后续的所有指令均加入到事务中
    执行事务

    exec

    设定事务的结束位置,同时执行事务。与multi成对出现,成对使用

     演示2个客户端同时操作一个key,就会发现有问题

    127.0.0.1:6379> set name test1
    OK
    127.0.0.1:6379> get name
    "test2"
    127.0.0.1:6379>
    127.0.0.1:6379> set name test2
    OK
    127.0.0.1:6379>

     演示开启事务

    127.0.0.1:6379> MULTI
    OK
    127.0.0.1:6379> SET AGE 30
    QUEUED
    127.0.0.1:6379> get age
    QUEUED
    127.0.0.1:6379> set age 31
    QUEUED
    127.0.0.1:6379> get age
    QUEUED
    127.0.0.1:6379> exec
    1) OK
    2) (nil)
    3) OK
    4) "31"

     演示在开启事务的过程中,发现操作事务。

    127.0.0.1:6379> MULTI
    OK
    127.0.0.1:6379> set age 32
    QUEUED
    127.0.0.1:6379> get age
    QUEUED
    127.0.0.1:6379> set age 33
    QUEUED
    127.0.0.1:6379> DISCARD
    OK
    127.0.0.1:6379> get age
    "31"
    127.0.0.1:6379>

     事务的执行流程

     定义事务的过程中,命令格式输入错误怎么办?
    语法错误
    指命令书写格式有误

    127.0.0.1:6379> MULTI
    OK
    127.0.0.1:6379> set name test
    QUEUED
    127.0.0.1:6379> get name
    QUEUED
    127.0.0.1:6379> stde name
    (error) ERR unknown command `stde`, with args beginning with: `name`,
    127.0.0.1:6379> exec
    (error) EXECABORT Transaction discarded because of previous errors.
    127.0.0.1:6379>

    处理结果
    如果定义的事务中所包含的命令存在语法错误,整体事务中所有命令均不会执行。包括那些语法正确的命令。 

    定义事务的过程中,命令执行出现错误怎么办?
    运行错误
    指命令格式正确,但是无法正确的执行。例如对list进行incr操作

    127.0.0.1:6379> MULTI
    OK
    127.0.0.1:6379> set name n1
    QUEUED
    127.0.0.1:6379> get name
    QUEUED
    127.0.0.1:6379> set name n2
    QUEUED
    127.0.0.1:6379> get name
    QUEUED
    127.0.0.1:6379> LPUSH name a b c
    QUEUED
    127.0.0.1:6379> get name
    QUEUED
    127.0.0.1:6379> exec
    1) OK
    2) "n1"
    3) OK
    4) "n2"
    5) (error) WRONGTYPE Operation against a key holding the wrong kind of value
    6) "n2"
    127.0.0.1:6379> get name
    "n2"
    127.0.0.1:6379>

    能够正确运行的命令会执行,运行错误的命令不会被执行 ,那就需要自己回滚一些脏数据了。

     注意:已经执行完毕的命令对应的数据不会自动回滚,需要程序员自己在代码中实现回滚。

    手动进行事务回滚
    记录操作过程中被影响的数据之前的状态
    单数据: string
    多数据: hash、 list、 set、 zset
    设置指令恢复所有的被修改的项
    单数据:直接set(注意周边属性,例如时效)
    多数据:修改对应值或整体克隆复制

    基于特定条件的事务执行——锁

    业务场景

    天猫双11热卖过程中,对已经售罄的货物追加补货, 4个业务员都有权限进行补货。补货的操作可能是一系列的操作,牵扯到多个连续操作,如何保障不会重复操作?
    业务分析
    多个客户端有可能同时操作同一组数据,并且该数据一旦被操作修改后,将不适用于继续操作
    在操作之前锁定要操作的数据,一旦发生变化,终止当前操作

    解决方案
    对 key 添加监视锁,在执行exec前如果key发生了变化,终止事务执行

    watch key1 [key2……] 

    取消对所有 key 的监视

    unwatch

    演示如下,此为第二个客户端,在第一个客户端的开启事务执行

    127.0.0.1:6379> set name 123
    OK
    127.0.0.1:6379>
    27.0.0.1:6379> watch name
    OK
    127.0.0.1:6379> get name
    "n2"
    127.0.0.1:6379> MULTI
    OK
    127.0.0.1:6379> set aa bb
    QUEUED
    127.0.0.1:6379> get aa
    QUEUED
    127.0.0.1:6379> exec
    1) OK
    2) "bb"
    127.0.0.1:6379> watch name age
    OK
    127.0.0.1:6379> MULTI
    OK
    127.0.0.1:6379> set aa cc
    QUEUED
    127.0.0.1:6379> get aa
    QUEUED
    127.0.0.1:6379> exec
    (nil)
    127.0.0.1:6379>

     不能在事务中开启watch

    127.0.0.1:6379> WATCH name
    OK
    127.0.0.1:6379> get name
    "123"
    127.0.0.1:6379> UNWATCH
    OK
    127.0.0.1:6379>

    基于特定条件的事务执行——分布式锁
    解决方案
    使用 setnx 设置一个公共锁 

    127.0.0.1:6379> set num 10
    OK
    127.0.0.1:6379> SETNX lock 1
    (integer) 1
    127.0.0.1:6379> INCRBY num -1
    (integer) 9
    127.0.0.1:6379> del lock
    (integer) 1
    127.0.0.1:6379> get num
    "9"
    127.0.0.1:6379>

    演示先锁在释放20秒

    127.0.0.1:6379> set name 123
    OK
    127.0.0.1:6379> SETNX lock 1
    (integer) 1
    127.0.0.1:6379> EXPIRE lock 20
    (integer) 1
    127.0.0.1:6379> get name
    "123"
    127.0.0.1:6379> del lock
    (integer) 1
    127.0.0.1:6379> setnx lock-name 1
    (integer) 1
    127.0.0.1:6379> EXPIRE lock-name 10
    (integer) 1
    127.0.0.1:6379>
    127.0.0.1:6379> setnx lock-name 1
    (integer) 0
    127.0.0.1:6379> setnx lock-name 1
    (integer) 0
    127.0.0.1:6379> setnx lock-name 1
    (integer) 0
    127.0.0.1:6379> setnx lock-name 1
    (integer) 1
    127.0.0.1:6379>

    利用setnx命令的返回值特征,有值则返回设置失败,无值则返回设置成功
        对于返回设置成功的,拥有控制权,进行下一步的具体业务操作
        对于返回设置失败的,不具有控制权,排队或等待
    操作完毕通过del操作释放锁
    注意:上述解决方案是一种设计概念,依赖规范保障,具有风险性

    使用 expire 为锁key添加时间限定,到时不释放,放弃锁
    由于操作通常都是微秒或毫秒级,因此该锁定时间不宜设置过大。具体时间需要业务测试后确认。

    expire lock-key second
    pexpire lock-key milliseconds 

        例如:持有锁的操作最长执行时间127ms,最短执行时间7ms。
        测试百万次最长执行时间对应命令的最大耗时,测试百万次网络延迟平均耗时
        锁时间设定推荐:最大耗时*120%+平均网络延迟*110%
        如果业务最大耗时<<网络平均延迟,通常为2个数量级,取其中单个耗时较长即可

    假如在没有执行expire的时候,redis的服务器宕机的话,也会造成死锁,从2.8版本以后,加入了set指令的扩展参数,

    setnx和expire指令可以一起执行了,可以解决分布式锁的乱象。

    127.0.0.1:6379> set lock:code true ex 5 nx
    OK
    127.0.0.1:6379> get lock:code
    (nil)
  • 相关阅读:
    PAT B1027 打印沙漏 (20 分)
    PAT B1025 反转链表 (25 分)
    PAT B1022 D进制的A+B (20 分)
    PAT B1018 锤子剪刀布 (20 分)
    PAT B1017 A除以B (20 分)
    PAT B1015 德才论 (25 分)
    PAT B1013 数素数 (20 分)
    PAT B1010 一元多项式求导 (25 分)
    HDU 1405 The Last Practice
    HDU 1165 Eddy's research II
  • 原文地址:https://www.cnblogs.com/dalianpai/p/12581813.html
Copyright © 2011-2022 走看看