Grape命令语法命令含义:将当前数据库的key移动到给定的数据库db。命令注释:如果当前数据库(源数据库)和给定数据库(目标数据库)的给定键具有相同的名称,或者当前数据库中不存在该键,则MOVE无效。因此,您也可以利用此功能并将MOVE用作锁定原语。命令格式:MOVEkeydb命令实战:#key存在于当前数据库redis>SELECT0#redis默认使用0号数据库,为了清楚,这里再次指定。OKredis>SETsong"secretbase-Zone"OKredis>MOVEsong1#movesongtodatabase1(integer)1redis>EXISTSsong#song#songhasbeenremoved(integer)0redis>SELECT1#使用数据库1OKredis:1>EXISTSsong#确认歌曲已经移动到数据库1(注意命令提示符变成了“redis:1”,表示正在使用数据库1)(整数)1#当key不存在时redis:1>EXISTSfake_key(integer)0redis:1>MOVEfake_key0#尝试将一个不存在的键从数据库1移动到数据库0,失败(integer)0redis:1>select0#使用数据库0OKredis>EXISTSfake_key#confirmfake_keydoesnotexist(integer)0#当源数据库和目标数据库有相同的keyredis>SELECT0#使用数据库0OKredis>SETfavorite_fruit"banana"OKredis>SELECT1#使用数据库1OKredis:1>SETfavorite_fruit"apple"OKredis:1>SELECT0#usingdatabase0andtryingtomovefavorite_fruittodatabase1OKredis>MOVEfavorite_fruit1#MOVEfailedbecausebothdatabaseshavethesamekey(integer)0redis>GETfavorite_fruit#database的favorite_fruit0没有变“banana”redis>SELECT1OKredis:1>GETfavorite_fruit#数据库1的favorite_fruit也是“apple”返回值移动成功则返回1,失败则返回0源码分析moveCommand函数,这是移动命令的入口函数:voidmoveCommand(client*c){robj*o;redisDb*src,*dst;内部服务;longlongdbid,过期;//判断集群模式是否开启if(server.cluster_enabled){addReplyError(c,"集群模式下不允许MOVE");返回;}//从客户端信息中获取当前db信息src=c->db;srcid=c->db->id;//c->argv是一个参数数组,argv[1]存放的是移动的key,argv[2]存放的是目标数据库//getLongLongFromObject获取目标数据库id并转为int类型//判断条件因此是强制将string转为int,判断是否在dbid范围内,将数据库切换到目标数据库if(getLongLongFromObject(c->argv[2],&dbid)==C_ERR||dbidINT_MAX||selectDb(c,dbid)==C_ERR){addReply(c,shared.outofrangeerr);返回;}//获取目标数据库信息dst=c->db;//切换到原来的数据库selectDb(c,srcid);/*回源DB*///判断目标数据库与原数据库是否一致if(src==dst){addReply(c,shared.sameobjecterr);返回;}/*查询原始数据库中是否存在key及其信息*/o=lookupKeyWrite(c->db,c->argv[1]);如果(!o){addReply(c,shared.czero);返回;}//获取这个key的过期时间,如果没有则返回-1expire=getExpire(c->db,c->argv[1]);//查询目标数据库中是否存在这个key,如果不存在则返回错误信息if(lookupKeyWrite(dst,c->argv[1])!=NULL){addReply(c,shared.czero);返回;}//将这个键和这个对象添加到目标数据库dbAdd(dst,c->argv[1],o);如果(过期!=-1)setExpire(c,dst,c->argv[1],过期);incrRefCount(o);/*移动完成,删除原数据库*/dbDelete(src,c->argv[1]);服务器脏++;addReply(c,shared.cone);}dbAdd函数:在move命令中,我们需要给目标数据库添加一个key,这个命令就是keyvoiddbAdd(redisDb*db,robj*key,robj*val){//复制keysdscopy=sdsdup(key->ptr);//将这个key插入dict,copy为key,val为key对应intretval=dictAdd(db->dict,copy,val);serverAssertWithInfo(NULL,key,retval==DICT_OK);if(val->type==OBJ_LIST||val->type==OBJ_ZSET)signalKeyAsReady(db,key);if(server.cluster_enabled)slotToKeyAdd(key);}dictAdd函数:在dbAdd中调用此函数以将条目添加到dict。intdictAdd(dict*d,void*key,void*val){//向dict中插入一个key并返回entrydictEntry*entry=dictAddRaw(d,key,NULL);如果(!entry)返回DICT_ERR;//设置这个条目的值dictSetVal(d,entry,val);returnDICT_OK;}GDB进程先设置key为kkkk,值为2,然后执行move命令127.0.0.1:6380>setkkkk2OK127.0.0.1:6380>select0OK127.0.0.1:6380>movekkkk11、我们先打印客户端传入的参数。可以看到argv的三个元素是move,kkkk,1:(gdb)p(char*)c->argv[0].ptr$10=0x7f175b820ae3"move"(gdb)p(char*)c->argv[1].ptr$11=0x7f175b820afb"kkkk"(gdb)p(char*)c->argv[2].ptr$12=0x7f175b820acb"1"2.然后我们来到getLongLongFromObject函数。上面我们说了,这个函数的作用就是把数据强制转换成int类型。在上一篇文章中已有介绍,这里不再赘述。然后进入第二个判断条件判断dbid的范围,最后切换到目标数据库,符合上面的推理:(gdb)n934if(getLongLongFromObject(c->argv[2],&dbid)==C_ERR||(gdb)n935dbidINT_MAX||(gdb)936selectDb(c,dbid)==C_ERR)(gdb)3.打印原始数据库和目标数据库信息,我们可以看到原来databaseidis0,thetarget数据库id为1(gdb)p*src$14={dict=0x7f175b80b360,expires=0x7f175b80b3c0,blocking_keys=0x7f175b80b420,ready_keys=0x7f175b80b480,watched_keys=0x7f175b80b4e0,id=0,avg_ttl=0,defrag_later=0x7f175b80f330}(gdb)p*dst$15={dict=0x7f175b80b540,expires=0x7f175b80b5a0,blocking_keys=0x7f175b80b600,ready_keys=0x7f175b80b660,watched_keys=0x7f175b80b6c0,id=1,avg_ttl=0,defrag_later=0x7f175b80f360}4.在将当前数据库实例赋值给dst后切换回原数据库,判断目标数据库是否与原da一致tabase942selectDb(c,srcid);/*回到源数据库*/(gdb)946if(src==dst){5.检查这个key是否存在,如果存在,返回这个对象,我们看返回值,发现key值的类型是0,value是1,然后得到他的expire(gdb)n952o=lookupKeyWrite(c->db,c->argv[1]);(gdb)953if(!o){(gdb)po$2=(robj*)0x7f175b80ac80(gdb)p*o$3={type=0,encoding=1,lru=9180225,refcount=2147483647,ptr=0x1}(gdb)p$3.ptr$4=(void*)0x1(gdb)p(char*)$3.ptr$5=0x1<地址0x1越界>(gdb)p(char)$3.ptr$6=1'\001'(gdb)n957expire=getExpire(c->db,c->argv[1]);6.接下来判断目标数据库中是否存在key,因为目标数据库不存在,跳过传递if语句(gdb)960if(lookupKeyWrite(dst,c->argv[1])!=空){7。下一步是将此密钥添加到目标数据库。这里的过程在源码分析中已经解释过了,所以这里只贴执行过程第173话){(gdb)174sdscopy=sdsdup(key->ptr);(gdb)175intretval=dictAdd(db->dict,copy,val);(gdb)177serverAssertWithInfo(NULL,key,retval==DICT_OK);(gdb)178if(val->type==OBJ_LIST||(gdb)181if(server.cluster_enabled)slotToKeyAdd(key);(gdb)182}8然后判断是否有expire。如果是存在,设置并增加引用计数,目标数据库的key已经建立,同时我们需要删除原数据库的key965if(expire!=-1)setExpire(c,dst,c->argv[1],expire);(gdb)966incrRefCount(o);(gdb)n969dbDelete(src,c->argv[1]);9.我们打印目标数据库的dict,发现kkkk,一开始设置的,已经存在了,原来的key已经不存在了。(gdb)p*dst$19={dict=0x7f175b80b540,expires=0x7f175b80b5a0,blocking_keys=0x7f175b80b600,ready_keys=0x7f175b80b660,watched_keys=0x7f175b80b6c0,id=1,avg_ttl=0,defrag_later=0x7f175b80f360}(gdb)p(char*)$19.dict.ht.table.key$20=0x7f175b809931"kkkk"(gdb)p(char*)($21.dict.ht.table+0).key$31=0x7f175b809921"dddd"(gdb)p(char*)($21.dict.ht.table+2).key$32=0x7f175b8098f9"key1"10.最后response返回客户端信息。展开Redis多数据库:根据我们讲解的move命令,可以看出redis是多命令的。执行move时,我们会执行select0来设置数据库。Redis默认是0号数据库,我们可以通过select命令来选择数据库,一个redis实例最多可以提供16个数据库,下标从0-15。命令如下:select1#选择redis事务连接1号数据库,在redis中可以使用multiexecdiscard这三个命令来实现事务。在一个事务中,所有的命令都会按顺序执行,redis在事务执行过程中不会为其他客户端提供任何服务,从而保证事务中的命令是原子执行的。命令队列中存放的exec相当于关系数据库事务中的commit,丢弃事务相当于关系数据库事务中的rollback。例如回滚操作:127.0.0.1:6380>setusergrape//SetaValueOK127.0.0.1:6380>getuser"grape"127.0.0.1:6380>multi//opentransactionOK127.0.0.1:6380>setuserxiaomingQUEUED127.0.0.1:6380>discard//回滚OK127.0.0.1:6380>getuser"grape"//值不变127.0.0.1:6380>127.0.0.1:6380>setgrape123//设置一个值OK127.0.0.1:6380>multi//开启交易OK127.0.0.1:6380>incrgrapeQUEUED127.0.0.1:6380>exec//执行事务1)(integer)124127.0.0.1:6380>getgrape"124"//valuechange127.0.0.1:6380>redislock悲观锁:数据被外界保守地(悲观地)修改。因此,在整个数据处理过程中,数据处理是被锁定的。实现方法:在修改任何记录之前,尽量给记录加排他锁。如果加锁失败,说明记录正在被修改。当前查询可能必须等待或抛出异常。如果加锁成功,就可以修改记录了。乐观锁:乐观锁假设一般情况下数据不会引起冲突,所以在提交更新数据时,会正式检测到数据冲突。如果发现冲突,将返回一条错误消息。这里我们使用move命令来分析,假设redis数据库中存在一个值为10的keya,同一时刻,两个redis客户端(客户端1,客户端2)对a进行move操作,所以会发生什么?我们发现后面执行失败了但是他没有报错。为什么?当两个客户端对同一个键进行操作时,有一个序列。第一个移动后会失败,第二个如果没有钥匙也会失败。这意味着我们可以利用此功能并将MOVE用作锁定原语。我们可以在代码中实现锁。move命令本身是无锁实现的,我们在源码中也没有看到。127.0.0.1:6380>keys*1)"dddd"2)"grape"3)"key1"4)"user"127.0.0.1:6380>movegrape1(integer)1(55.51s)127.0.0.1:6380>127.0.0.1:6380>keys*1)"dddd"2)"grape"3)"key1"4)"user"127.0.0.1:6380>movegrape1(integer)0(66.41s)forredilock实现,推荐阅读:解锁Redis锁的正确姿势