讲了这么多Redis的使用,今天我们来讲下Redis的事物
1.首先,我们来看一下Redis中事物相关的指令,
命令原型       命令描述
MULTI   用于标记事务的开始,其后执行的命令都将被存入命令队列,直到执行EXEC时,这些命令才会被原子执行.

EXEC   执行在一个事务内命令执行了WATCH命令,那么只有当WATCH所监控的keys没有被修改的前提下,EXEC命令才能执行事务队列中的所有命令,那么只有
当WATCH所监控的keys没有被修改的前提下,EXEC命令才能执行事务队列中的所有命令,否则EXEC将放弃当前事务中的所有命令。

DISCARD   回滚事务队列中的所有命令,同时再将当前连接的状态恢复为正常状态,即非事务状态。如 果WATCH命令被使用,该命令将UNWATCH所有的keys.

WATCH  key[key...]   在MULTI命令执行之前,可以指定待监控的keys,然而在执行EXEC之前,如果被监控的keys发生修改,EXEC将放弃执行该事务队列中的所有指令。

UNWATCH    取消当前事务中指定监控的keys,如果执行了EXEC或DISCARD命令,则无需再手工执行 该命令了,因为在此之后,事务中所有的keys都将自动取消,
和关系型数据库中的事物相比,在redis事物中如果有某一条命令执行失败,其后的命令仍然会被继续执行。

我们可以通过MULTI命令开启一个事务,有关系型数据库开发经验的人可以理解为BEGIN TRANSACTION语句,在该语句之后执行的命令都将被视为事务之内的操作,最后我
们可以通过执行EXEC/DISCARD命令来提交/回滚该事务内的所有操作。这两个Redis命令可被视为等同于关系型数据库中的COMMIT/ROLLERBACK语句。

在事物开启之前,如果客户端与服务端之间出现通讯故障并导致网络断开,其后所有带执行的语句都将不会被服务器执行,然而如果网络中短事件是在客户端执行EXEC命令之后,那么该事务中所有命令都会被服务器执行。

当使用Append-Only模式时,Redis会通过调用系统函数write将该事物内的所有写操作在本次调用全部写入磁盘。然而如果在写入的过程中会出现系统崩溃,如电源故障导致的宕机,那么此时也许只有部分数据被写入到磁盘,而另外一部分数据却已经丢失,Redis服务会在重新启动时执行一系列必要的一致性检测,一旦发现类似问题,就会立即退出并给出相应的错误提示。此刻,我们就要充分利用Redis工具包中提供的Redis-check-aof工具,该工具可以帮助我们定位到数据不一致的错误,并将已经写入的部分数据进行回滚。修复之后我们就可以再次重新启动Redis服务器。

WATCH 命令
WATCH为MULTI执行之前的某个key提供监控(乐观锁)的功能,如果key的值发生了变化,就会放弃事务的执行。

当事务EXEC执行成功之后,就会自动UNWATCH。
下面我们根据一个命令行指令来看一下:
(1)第一步:
127.0.0.1:6379> redis-cli -h 127.0.0.1 -p 6379    //命令拼接redis服务器
ok
127.0.0.1:6379> get test                                      //获取test的键值
"hello world"
127.0.0.1:6379> multi           //生成事务
ok
127.0.0.1:6379> set test "hello mygod"               //修改指令
QUEUED
127.0.0.1:6379>exec                                           //提交事务
1) OK
127.0.0.1:6379>

从上面的命令执行,我们可以看出,当我们生成事务后,执行set指令时,反馈的信息是QUEUED,最后我们再执行
exec,这些命令才会真正的执行。在这里可能还会有人说事务中还有一个rollback操作,但是redis里面好像并没有发现,的确,redis里面是没有rollback操作的,下面我们再进行一个例子的演示:
127.0.0.1:6379>multi
ok
127.0.0.1:6379>set test "my name is god"
QUEUED
127.0.0.1:6379>lpush testName 99
QUEEUED
127.0.0.1:6379>exec
1)OK
2)(error) WRONGTYPE Opertion against a key holding the wrong kind of value
127.0.0.1:6379>

在上面的例子中故意用lpush命令执行string ,可想而知自然不会执行成功,但从结果来看,你看到了,一个OK一个Error,这就违反了事务的原子性,
redis仅仅是个数据结构服务器,多简单的一件事情,退一万步来说,很明显的错误指令它会直接返回的,比如我故意把lpush 写成了lpush1:
127.0.0.1:6379>multi
OK
127.0.0.1:6379> set test "woqunimeimeide"
QUEUED
127.0.0.1:6379> lpush1 testName 44
(error) ERR unknow command 'lpush1'
127.0.0.1:6379>

上面可以看到,命令终止了你的任何输入。

下面我们探索一下Redis事务的原理:
关于事务操作的源代码,大多数都在redis源码中的multi.c文件中,接下来我会一个一个简单剖析一下:
1.multi
在redis的源码中,它大概是这么写的:
void multiCommand(redisClient *c)
{
if(c->flags & REDIS_MULTI){
addReplyError(c,"MULTI calls can not be nested");
return; 
}
c->flags |= REDIS_MULTI;
addReply(c,shared.ok);
}

2.生成命令
在redisClient 中,里面有一个multiState命令:
typedef struct redisClient{
。。。
multiState mstate;           /* MULTI/EXEC state */
}redisClient;

从注释中我们看到了命令和multi/exec肯定有关系,接下来我很好奇的看看multiState的定义:
typedef struct multiState{
multiState *command;     /** Array of MULTI commands */
int count ;    /*Total number of MULTI comands*/
int minreplicas;                /*MINREPLICAS for synchronous as unixtime */
time_t minreplicas_timeout      /*MINREPLICAS timeout as unixtime*/
}multiState;

从multiState这个举例中,你可以看到下面有一个*commad命令,从注释中我们可以看到它其实指向一个数组,这个数组就是若干条在指令,下面还有一个count,可以看到是实际的command的总数。

3.watch 
为了方便说一下后面的exec,这里想说一下watch 大概是怎样实现的,在multi.c源码中是这样写的:
typedef struct watchedKey {
    robj *key;
    redisDb *db;
} watchedKey;

void watchCommand(redisClient *c) {
    int j;

if (c->flags & REDIS_MULTI) {
        addReplyError(c,"WATCH inside MULTI is not allowed");
        return;
    }
    for (j = 1; j < c->argc; j++)
        watchForKey(c,c->argv[j]);
    addReply(c,shared.ok);
}

/* Watch for the specified key */
void watchForKey(redisClient *c, robj *key) {
    list *clients = NULL;
    listIter li;
    listNode *ln;
    watchedKey *wk;

/* Check if we are already watching for this key */
    listRewind(c->watched_keys,&li);
    while((ln = listNext(&li))) {
        wk = listNodeValue(ln);
        if (wk->db == c->db && equalStringObjects(key,wk->key))
            return; /* Key already watched */
    }
    /* This key is not already watched in this DB. Let's add it */
    clients = dictFetchValue(c->db->watched_keys,key);
    if (!clients) {
        clients = listCreate();
        dictAdd(c->db->watched_keys,key,clients);
        incrRefCount(key);
    }
    listAddNodeTail(clients,c);
    /* Add the new key to the list of keys watched by this client */
    wk = zmalloc(sizeof(*wk));
    wk->key = key;
    wk->db = c->db;
    incrRefCount(key);
    listAddNodeTail(c->watched_keys,wk);
}
这段代码中最大的核心点就是:

/* This key is not already watched in this DB. Let's add it */
    clients = dictFetchValue(c->db->watched_keys,key);

关于这个key的所有client ,最后还会塞入到redisclientde watched_keys字典中,如下代码:

/* Add the new key to the list of keys watched by this client */
    wk = zmalloc(sizeof(*wk));
    wk->key = key;
    wk->db = c->db;
    incrRefCount(key);
    listAddNodeTail(c->watched_keys,wk);

如果画图大概就是下面这样:

其中watched_key是个字典结构,字典的键为上面的key1,key2....,,value为client的链表,这样的话,就可以非常的清楚知道某个key中是被哪些client监视着的。

4.exec
这个命令大概做了两件事:
<1>:判断c-flags = REDIS_DIRTY_EXEC 打开与否,如果是的话,取消事务discardTransation(c),也就是说这个key已经被别的client修改了。
<2>:如果没有修改,那么就for循环执行command[]中命令,如图所示的两处信息:

接下来是一段在php中使用Redis事务的案例:

//关于redis事务的案例
header("content-type:text/html;charset=utf-8");
$redis = new redis();
$redis->connect('localhost', 6379);
//$result = $redis->connect('localhost', 6379);

//$redis = Yii::app()->redis;
$redis->set("testName","33");
//$mywatchkey = $redis->get("mywatchkey");

$mywatchkey = $redis->get('testName');
//($test);exit;

$rob_total = 100;   //抢购数量
if($mywatchkey<$rob_total){
    $redis->watch("testName");
    $redis->multi();

//设置延迟,方便测试效果。
    sleep(5);
    //插入抢购数据
    $redis->hSet("testName","user_id_".mt_rand(1, 9999),time());
    $redis->set("testName",$mywatchkey+1);
    $rob_result = $redis->exec();
    if($rob_result){
        $mywatchlist = $redis->hGetAll("testName");
        echo "抢购成功!<br/>";
        echo "剩余数量:".($rob_total-$mywatchkey-1)."<br/>";
        echo "用户列表:<pre>";
        var_dump($mywatchlist);
    }else{
        echo "手气不好,再抢购!";exit;
    }
}

在上例是一个秒杀的场景,该部分抢购的功能会被并行执行
通过已销售数量(mywhtchkey)的监控,达到了控制库存,避免超卖的作用。
WHTCH是一个乐观锁,有利于减少并发中的冲突,提高吞吐量。

乐观锁和共享锁
乐观锁(Optimistic Lock)又叫做共享锁,每次别人拿数据的时候都认为别人不会修改数据,所以不会上锁,但是在更新的时候会判断一下在此期间别人有没有去更新这个数据,可以使用版本号等机制。乐观锁适用于多读得应用类型,这样会提高吞吐量。

悲观锁(Pessimistic Lock)又叫做排它锁(x锁),每次拿刀数据的时候都认为别人会修改数据,所以每次在拿到数据的时候都会上锁,这样别人想拿到这个数据就会block直到
它拿到锁。传统的关系型数据库里边就用到了很多这种锁机制,比如行锁,表锁,都是在操作之前先上锁。

最后我们介绍一下Redis事务错误处理:
如果一个事务中的某个命令执行出错,Redis将会怎么处理呢?要回答这个问题,我们首先需要知道是什么原因导致命令执行出错:

1.语法错误:
语法错误表示命令不存在或者参数错误
这种情况需要区别Redis版本,Redis2.65之前的版本会忽略错误的命令,执行其他正确的命令,2.65之后的版本会忽略这个事务中的所有命令,都不执行,就比如上面的例子(使用的Redis版本是2.8的);
2.运行错误:
运行错误表示命令执行过程中出现错误,就比如用GET命令去获取一个散列表类型的键值。
这种错误在命令执行之前Redis是无法发现的,所以在事务里这样的命令都会被Redis接受并执行.如果事务里有一条命令执行错误,其他命令依旧会执行(包括出错后的命令)。

--------------------- 
作者:想念-忘记了 
来源:CSDN 
原文:https://blog.csdn.net/dannyiscoder/article/details/78285549?utm_source=copy 
版权声明:本文为博主原创文章,转载请附上博文链接!

来源:https://blog.csdn.net/dannyiscoder/article/details/78285549

PHP 如何在Redis中实现事物(事物提交和事物回滚)相关推荐

  1. 【Redis】如何在redis中防止消息丢失

    如何在redis中防止消息丢失 前言 同步的事务--停止等待 同步的事务--连续ARQ 异步的事务--回调机制 消息的幂等处理 前言 在项目中,由于网络问题,我们很难保证生产者发送的消息能100%到达 ...

  2. (Redis基础教程之八) 如何在Redis中管理Sets

    如何在ubuntu18.04上安装和保护redis 如何连接到Redis数据库 如何管理Redis数据库和Keys 如何在Redis中管理副本和客户端 如何在Redis中管理字符串 如何在Redis中 ...

  3. 如何在Mercurial中编辑错误的提交消息? [重复]

    本文翻译自:How to edit incorrect commit message in Mercurial? [duplicate] This question already has answe ...

  4. tomcat不停机部署_Tomcat中的零停机部署(和回滚); 演练和清单

    tomcat不停机部署 亲爱的大家, 如果您认为Tomcat不能再进步,那您就错了. Tomcat 7引入了所谓的并行部署 . 这是由SpringSource / VMWare贡献的. 简而言之,并行 ...

  5. Tomcat中的零停机部署(和回滚); 演练和清单

    亲爱的大家, 如果您认为Tomcat不能再进步,那您就错了. Tomcat 7引入了所谓的并行部署 . 这是由SpringSource / VMWare贡献的. 简而言之,并行部署是一种能够并行部署一 ...

  6. redis中存集合_如何在Redis中管理集合

    redis中存集合 介绍 (Introduction) Redis is an open-source, in-memory key-value data store. Sets in Redis a ...

  7. 如何在Redis中查找大key

    工作中,经常有些Redis实例使用不恰当,或者对业务预估不准确,或者key没有及时进行处理等等原因,导致某些KEY相当大.  那么大Key会带来哪些问题呢? 如果是集群模式下,无法做到负载均衡,导致请 ...

  8. java事务抛异常_java中抛异常后如何使事务回滚

    spring声明式事务管理默认对非检查型异常和运行时异常进行事务回滚,而对检查型异常则不进行回滚操作 代码中try--catch抛出的Exception异常,属于检查型异常,Spring的框架默认是不 ...

  9. java事务中使用try catch 导致事务不回滚的问题

    @Transactional注解的触发,只回滚RuntimeException和Error异常,默认不回滚非RuntimeException异常 解决方法: 1.方法前添加注解(基础的  @Trans ...

最新文章

  1. Spring的一些资源
  2. JavaScript实现链表reverseTraversal反向遍历算法(附完整源码)
  3. YOLOv3目标检测有了TensorFlow实现,可用自己的数据来训练
  4. 北邮 鲁鹏老师 视频笔记
  5. Fiddler-学习笔记-远程抓包
  6. 浏览器获取CA认证流程
  7. Android setBackgroundResource无反应?
  8. Postman系列番外篇 - postman web版介绍及与PC版的对比
  9. 数字电路基础知识——数字IC中的进制问题(原码,反码,补码以及各进制的转换)
  10. macOS Mojave patcher 无法制作启动盘
  11. word 职称计算机考试大纲,全国职称计算机考试Word2003大纲
  12. 第12课:JSP动作 Jsp include动作(JSP教程 JSP入门实战教程 黄菊华Java网站开发系列教程)
  13. 热加工作业考研题目答案分享——metal casting 1
  14. 架构师修炼系列【业务高可用】
  15. CeSi 安装与配置
  16. 集运转运系统源码,快递物流一件代付系统源码
  17. 软件测试中的用户思维
  18. Android 单线程下载与多线程下载
  19. React Native性能优化总结
  20. php自定义微博尾巴,新浪微博怎么自定义小尾巴 新浪微博自定义小尾巴方法

热门文章

  1. 53.垃圾回收算法的实现原理、启动Java垃圾回收、Java垃圾回收过程、垃圾回收中实例的终结、对象什么时候符合垃圾回收的条件、GC Scope 示例程序、GC OutOfMemoryError的示例
  2. Python爬虫项目,获取所有网站上的新闻,并保存到数据库中,解析html网页等(未完待续)
  3. Strom集群安裝,Python安裝,Strom配置,Strom常用命令
  4. 产品打包工具的制作,ant,编译源码,打jar包,打tag,打war包,备份release版本等
  5. TopLink JPA
  6. 表的插入、更新、删除、合并操作_2_插入含自增列的记录
  7. 微擎jsapi支付必须传openid怎么解决_面经腾讯微信支付面试记
  8. sscanf,sprintf,fscanf,fprintf 系列函数
  9. 装箱---一个工厂制造的产品形状都是长方体,它们的高度都是 h,长和宽都相等,一共有六个型号,他们的长宽分别为 1*1, 2*2, 3*3, 4*4, 5*5, 6*6.
  10. 关于磁盘,磁柱,磁头,扇区的概念