zoukankan      html  css  js  c++  java
  • 15天玩转redis —— 第八篇 你不得不会的事务玩法

       我们都知道redis追求的是简单,快速,高效,在这种情况下也就拒绝了支持window平台,学sqlserver的时候,我们知道事务还算是个比较复杂的东西,

    所以这吊毛要是照搬到redis中去,理所当然redis就不是那么简单纯碎的东西了,但是呢,事务是我们写程序无法逃避的场景,所以redis作者折衷的写了个简

    化版的事务机制,下面我来扯一下它的蛋蛋。

    一: 事务实战

      具体到事务是什么,要保证什么。。。这个我想没必要说了,先不管三七二十一,看一下redis手册,领略下它的魔力。

    1. multi,exec

       还记得sqlserver是怎么玩的吗?一般都是这样的三个步骤,生成事务,产生命令,执行事务,对吧,而对应redis呢??multi就是生成事务,然后

    输入redis命令,最后用exec执行命令,就像下面这样:

    可以看到,我set完命令之后,反馈信息是QUEUED,最后我再执行exec,这些命令才会真正的执行,就是这么的简单,一切执行的就是那么的顺利,

    一点都不拖泥带水,牛逼的不要不要的,可能有些人说,其实事务中还有一个rollback操作,但好像在redis中没有看到,哈哈,牛逼哈,很遗憾是

    redis中没有rollback操作,比如下面这样。

    在图中我故意用lpush命令去执行string,可想而知自然不会执行成功,但从结果中,你看到什么了呢?两个OK,一个Error,这就是违反了事务

    的原子性,对吧,但是我该怎么反驳呢??? 我会说,错你妹啊。。。连个基本的命令都写错了,你搞个毛啊。。。还写个吊毛代码,reids仅仅

    是个数据结构服务器,多简单的一件事情,退一万步说,很明显的错误命令它会直接返回的,比如我故意把lpush写成lpush1:

    2. watch

      不知道你看完multi后面的三条set命令之后,有没有一种心虚的感觉,怎么说呢,就是只要命令是正确的,redis保证会一并执行,誓死完成

    任务,虽然说命令是一起执行的,但是谁可以保证我在执行命令的过程中,其他client不会修改这些值呢???如果修改了这些值,那我的exec

    还有什么意义呢???没关系,这种烂大街的需求,redis怎可能袖手旁观???这里的watch就可以助你一臂之力。

    WATCH
    WATCH key [key ...]
    
    监视一个(或多个) key ,如果在事务执行之前这个(或这些) key 被其他命令所改动,那么事务将被打断。

     上面就是redis手册中关于watch的解释,使用起来貌似很简单,就是我在multi之前,用watch去监视我要修改的key,如果说我在exec之前,

    multi之后的这段时间,key被其他client修改,那么exec就会执行失败,返回(nil),就这么简单,我还是来举个例子:

     

    二:原理探索

      关于事务操作的源代码,大多都在redis源码中的multi.c 文件中,接下来我会一个一个的简单剖析一下:

    1. multi

      在redis的源代码中,它大概是这么写的:

    1 void multiCommand(redisClient *c) {
    2     if (c->flags & REDIS_MULTI) {
    3         addReplyError(c,"MULTI calls can not be nested");
    4         return;
    5     }
    6     c->flags |= REDIS_MULTI;
    7     addReply(c,shared.ok);
    8 }

    从这段代码中,你可以看到multi只是简单的把redisClient的REDIS_MULTI状态打开,告诉这个redis客户端已经进入事务模式了,对吧。

    2. 生成命令

    在redisClient中,里面有一个multiState命令:

    typedef struct redisClient {
        。。。
        multiState mstate;      /* MULTI/EXEC state */
        。。。
    } redisClient;

    从注释中你大概也看到了这个命令和multi/exec肯定有关系,接下来我很好奇的看看multiState的定义:

    typedef struct multiState {
        multiCmd *commands;     /* Array of MULTI commands */
        int count;              /* Total number of MULTI commands */
        int minreplicas;        /* MINREPLICAS for synchronous replication */
        time_t minreplicas_timeout; /* MINREPLICAS timeout as unixtime. */
    } multiState;

    从multiState这个枚举中,你可以看到下面有一个*command命令,从注释中可以看到它其实指向的是一个数组,这个数组我想你闭着眼睛都

    能想得到吧。。。它就是你的若干条命令啦。。。下面还有一个count,可以看到是实际的commands的总数。

    3. watch

        为了方便说到后面的exec,这里想说一下watch大概是怎么实现的,在multi.c源代码中是这样写的。

     1 typedef struct watchedKey {
     2     robj *key;
     3     redisDb *db;
     4 } watchedKey;
     5 
     6 void watchCommand(redisClient *c) {
     7     int j;
     8 
     9     if (c->flags & REDIS_MULTI) {
    10         addReplyError(c,"WATCH inside MULTI is not allowed");
    11         return;
    12     }
    13     for (j = 1; j < c->argc; j++)
    14         watchForKey(c,c->argv[j]);
    15     addReply(c,shared.ok);
    16 }
    17 
    18 /* Watch for the specified key */
    19 void watchForKey(redisClient *c, robj *key) {
    20     list *clients = NULL;
    21     listIter li;
    22     listNode *ln;
    23     watchedKey *wk;
    24 
    25     /* Check if we are already watching for this key */
    26     listRewind(c->watched_keys,&li);
    27     while((ln = listNext(&li))) {
    28         wk = listNodeValue(ln);
    29         if (wk->db == c->db && equalStringObjects(key,wk->key))
    30             return; /* Key already watched */
    31     }
    32     /* This key is not already watched in this DB. Let's add it */
    33     clients = dictFetchValue(c->db->watched_keys,key);
    34     if (!clients) {
    35         clients = listCreate();
    36         dictAdd(c->db->watched_keys,key,clients);
    37         incrRefCount(key);
    38     }
    39     listAddNodeTail(clients,c);
    40     /* Add the new key to the list of keys watched by this client */
    41     wk = zmalloc(sizeof(*wk));
    42     wk->key = key;
    43     wk->db = c->db;
    44     incrRefCount(key);
    45     listAddNodeTail(c->watched_keys,wk);
    46 }

    这段代码中大概最核心的一点就是:

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

    就是通过dicFetchValue这个字典方法,从watched_keys中找到指定key的value,而这个value是一个clients的链表,说明人家其实是想找到

    关于这个key的所有client,对吧,最后还会将本次key塞入到redisclient的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 打开与否,如果是的话,取消事务discardTransaction(c),也就是说这个key已经

              被的client修改了。

    <2>:   如果没有修改,那么就for循环执行comannd[]中的命令,如下图中的两处信息:

      

    好了,大概就这么说了,希望对你有帮助哈~~~

  • 相关阅读:
    redis安装等其他操作
    jquery 图片缩放插件使用
    bootstrap 自定义模态窗口
    Linux下 mkdir 命令详解
    linux,chmod如何更改所有子目录的权限,要包括隐藏文件
    MFC中的句柄
    MFC——CDC
    CREATESTRUCT cs 结构体
    MFC函数—— CFrameWnd::OnCreateClient
    MFC函数——CWnd::OnEraseBkgnd
  • 原文地址:https://www.cnblogs.com/huangxincheng/p/4991096.html
Copyright © 2011-2022 走看看