Nodejs 数据库 ·

nodejs操作Redis事务

有时候为了同时处理多个结构,我们需要向redis发送多个命令,或者服务器采用负载均衡的模式,多个负载同时访问redis,造成并发。为了让redis执行期间不受其他命令的影响,redis提供了事务的命令,事务在关系型数据库如mysql中很常见,也是为了应对并发等来产生的。

Redis的基本事务需要用到MULTI命令和exec命令,这种事务可以让一个客户端在不被其他客户端打断的情况下执行多个命令。和关系数据库那种可以在执行的过程中进行回滚的事务不同,在Redis里面,被multi命令和exec命令包围的所有命令会一个接一个的执行,知道所有的命令都执行完毕为止。当一个事务执行完毕之后,Redis才会处理其他客户端的命令。

要在redis里面执行事务,我们优先需要执行multi命令,然后输入我们想要在事务里面执行的命令,最后在执行EXEC命令。当Redis从一个客户端那里接收到multi命令时,redis会将这个客户端之后发送的所有命令都放入一个队列里面,知道这个客户端发送exec命令为止,然后redis就会在不被打断的情况下,一个接一个的执行存储在队列里面的命令。

在redis事务中,最常用的是multi命令和exec命令,下面来看一想redis事务的相关命令

命令 描述
DISCARD 取消事务,放弃执行事务块内的所有命令。
EXEC 执行所有事务块内的命令。
MULTI 标记一个事务块的开始。
UNWATCH 取消 WATCH 命令对所有 key 的监视。
WATCH key [key ...] 监视一个(或多个) key ,如果在事务执行之前这个(或这些) key 被其他命令所改动,那么事务将被打断。

下面来看一下命令行如何使用
nodejs操作Redis事务

很简单的是一个使用方法,下面再来看一下在nodejs中如何使用

/*
redis事务
*/
router.get("/redisThing",function(req,res){
    var data = req.query.data;
    myRedis.client.multi([
        ["set", "ttt_1", "1111111"],
        ["incr", "ttt_1"],
        ["set", "ttt_3","hese"],
        ["get", "ttt_1"]
    ]).exec(function(err,result){
        console.log("第一个redis命令执行完成");
    });
    myRedis.client.multi([
        ["set", "ttt_1", "1111114"],
        ["incr", "ttt_1"],
        ["set", "ttt_3","hahaha"],
        ["get", "ttt_1"]
    ]).exec(function(err,result){

        console.log("第二个redis命令执行完成");
        if(!err){
            res.json(result)
        }else{
            res.json(err)
        }
    });
})

上面的代码是在正常的执行一次事务,这里的前提是正常执行,但是如果命令执行过程中失败了会有什么效果呢,我们看一下下图
nodejs操作Redis事务
从上图中,我们可以看到,redis命令即使中间有失败的,后续命令依然会继续执行,这就感觉有点坑爹了,按照关系型数据库的事务来说,这种情况应该回滚的,但是很遗憾,redis没有回滚。

其主要原因在于单个 Redis 命令的执行是原子性的,但 Redis 没有在事务上增加任何维持原子性的机制,所以 Redis 事务的执行并不是原子性的。事务可以理解为一个打包的批量执行脚本,但批量指令并非原子化的操作,中间某条指令的失败不会导致前面已做指令的回滚,也不会造成后续的指令不做。

除此之外,还有一件事使我们需要知道的,接着来看下图
nodejs操作Redis事务
上图中,我是先开了左侧的客户端事务,在提交exec之前又开了右侧的事务,然后将右侧的事务进行exec提交执行,然后再提交执行左侧的事务,我们发现,最终的执行结果是以左侧为准,也就是说,redis在整体提交以后才会进行阻塞其他客户端的操作,先提交exec的先执行。

说到这里,我想应该会有一个担心就是当我从redis从获取数据,进行判断以后得出相应的事务命令组合进行执行,但是我刚刚判断完成,其他的客户端又将该值进行了修改,那么我再修改就会产生问题,比如说商品数量,会出现负数的情况。对于这种情况,就需要用到watch命令了,在提交事务之前对需要修改的key进行watch,如果在这段时间内key值变化了,那么就会打断事务的执行。

nodejs操作Redis事务

来看一下上图,我在左侧先对name进行watch,然后multi命令,在执行exec之前,我在第二个也就是右侧的客户端对name执行了set命令,之后在左侧执行exec命令,我们发现左侧的exec命令返回了nil,其结果就是事务执行失败,没有进行任何的修改。

在nodejs中我们来进行一次测试

router.get("/redisThing",function(req,res){
    var data = req.query.data;
    myRedis.client.watch(["ttt_1","ttt_3"])
    setTimeout(function(){
        myRedis.client.multi([
            ["set", "ttt_1", "1111111"],
            ["incr", "ttt_1"],
            ["set", "ttt_3","hese"],
            ["get", "ttt_1"]
        ]).exec(function(err,result){
            if(!err){
                res.json(result)
            }else{
                res.json(err)
            }
            console.log("第一个redis命令执行完成");
        });
    },10000);
})

上面代码中给了10秒的延迟,以方便watch后在开一个客户端进行set修改,模拟watch后key被其他客户端修改的情况,执行后我们发现返回的是null,事务没有执行。至此我们解决了因其他客户端修改造成的数据错乱问题。

参与评论