void delkeysCommand(redisClient *c) { dictIterator *di; dictEntry *de; sds pattern = c->argv[1]->ptr; int plen = sdslen(pattern), allkeys; unsigned long deleted = 0; di = dictGetIterator(c->db->dict); allkeys = (pattern[0] == '*' && pattern[1] == '\0'); while((de = dictNext(di)) != NULL) { sds key = dictGetEntryKey(de); robj *keyobj; if (allkeys || stringmatchlen(pattern,plen,key,sdslen(key),0)) { keyobj = createStringObject(key,sdslen(key)); if (dbDelete(c->db,keyobj)) { touchWatchedKey(c->db,keyobj); server.dirty++; deleted++; } decrRefCount(keyobj); } } dictReleaseIterator(di); addReplyLongLong(c,deleted); }
void RLRequest::rl_keys(){ if(args.size()!=1){ connection->write_error("ERR wrong number of arguments for 'keys' command"); return; } std::vector<std::string> keys; size_t arg_len=args[0].size(); bool allkeys = (arg_len==1 && args[0][0]=='*'); if(arg_len>0){ leveldb::Iterator *kit = connection->server->db[connection->db_index]->NewIterator( connection->server->read_options); leveldb::Slice key; kit->SeekToFirst(); while(kit->Valid()) { key = kit->key(); if(allkeys || stringmatchlen(args[0].c_str(), arg_len, key.data(), key.size(), 0)){ keys.push_back(key.ToString()); } kit->Next(); } delete kit; }else{ keys.push_back(""); } connection->write_mbulk_header(keys.size()); //std::for_each(keys.begin(), keys.end(), std::bind1st(std::mem_fun(&RLConnection::write_bulk),connection)); std::vector<std::string>::iterator it=keys.begin(); while(it!=keys.end())connection->write_bulk(*it++); }
int Ardb::PublishMessage(Context& ctx, const std::string& channel, const std::string& message) { ReadLockGuard<SpinRWLock> guard(m_pubsub_ctx_lock); PubsubContextTable::iterator fit = m_pubsub_channels.find(channel); int receiver = 0; if (fit != m_pubsub_channels.end()) { ContextSet::iterator cit = fit->second.begin(); while (cit != fit->second.end()) { Context* cc = *cit; if (NULL != cc && cc->client != NULL) { RedisReply* r = NULL; NEW(r, RedisReply); RedisReply& r1 = r->AddMember(); RedisReply& r2 = r->AddMember(); RedisReply& r3 = r->AddMember(); fill_str_reply(r1, "message"); fill_str_reply(r2, channel); fill_str_reply(r3, message); cc->client->GetService().AsyncIO(cc->client->GetID(), async_write_message, r); receiver++; } cit++; } } PubsubContextTable::iterator pit = m_pubsub_patterns.begin(); while (pit != m_pubsub_patterns.end()) { const std::string& pattern = pit->first; if (stringmatchlen(pattern.c_str(), pattern.size(), channel.c_str(), channel.size(), 0)) { ContextSet::iterator cit = pit->second.begin(); while (cit != pit->second.end()) { Context* cc = *cit; if (NULL != cc && cc->client != NULL) { RedisReply* r = NULL; NEW(r, RedisReply); RedisReply& r1 = r->AddMember(); RedisReply& r2 = r->AddMember(); RedisReply& r3 = r->AddMember(); RedisReply& r4 = r->AddMember(); fill_str_reply(r1, "pmessage"); fill_str_reply(r2, pattern); fill_str_reply(r3, channel); fill_str_reply(r4, message); cc->client->GetService().AsyncIO(cc->client->GetID(), async_write_message, r); receiver++; } cit++; } } pit++; } return receiver; }
void keysCommand(redisClient *c) { dictIterator *di; dictEntry *de; sds pattern = c->argv[1]->ptr; int plen = sdslen(pattern), allkeys; unsigned long numkeys = 0; void *replylen = addDeferredMultiBulkLength(c); di = dictGetSafeIterator(c->db->dict); allkeys = (pattern[0] == '*' && pattern[1] == '\0'); while((de = dictNext(di)) != NULL) { sds key = dictGetKey(de); robj *keyobj; if (allkeys || stringmatchlen(pattern,plen,key,sdslen(key),0)) { keyobj = createStringObject(key,sdslen(key)); if (expireIfNeeded(c->db,keyobj) == 0) { addReplyBulk(c,keyobj); numkeys++; } decrRefCount(keyobj); } } dictReleaseIterator(di); setDeferredMultiBulkLength(c,replylen,numkeys); }
void keysCommand(redisClient *c) { dictIterator *di; dictEntry *de; sds pattern = c->argv[1]->ptr; int plen = sdslen(pattern); unsigned long numkeys = 0; robj *lenobj = createObject(REDIS_STRING,NULL); di = dictGetIterator(c->db->dict); addReply(c,lenobj); decrRefCount(lenobj); while((de = dictNext(di)) != NULL) { sds key = dictGetEntryKey(de); robj *keyobj; if ((pattern[0] == '*' && pattern[1] == '\0') || stringmatchlen(pattern,plen,key,sdslen(key),0)) { keyobj = createStringObject(key,sdslen(key)); if (expireIfNeeded(c->db,keyobj) == 0) { addReplyBulk(c,keyobj); numkeys++; } decrRefCount(keyobj); } } dictReleaseIterator(di); lenobj->ptr = sdscatprintf(sdsempty(),"*%lu\r\n",numkeys); }
/* 发布一则消息,即将消息发送给所有订阅了指定频道channel的所有客户端以及所有订阅了和指定频道匹配的模式的客户端。*/ int pubsubPublishMessage(robj *channel, robj *message) { int receivers = 0; dictEntry *de; listNode *ln; listIter li; /* Send to clients listening for that channel */ // 取出订阅指定频道的客户端链表 de = dictFind(server.pubsub_channels,channel); if (de) { list *list = dictGetVal(de); listNode *ln; listIter li; listRewind(list,&li); // 遍历该客户端链表,并将消息逐一发送给这些客户端 while ((ln = listNext(&li)) != NULL) { redisClient *c = ln->value; // 发送消息 addReply(c,shared.mbulkhdr[3]); // 回复“message”字符串 addReply(c,shared.messagebulk); // 回复消息的来源频道 addReplyBulk(c,channel); // 回复消息内容 addReplyBulk(c,message); // 统计接收客户端的数量 receivers++; } } /* Send to clients listening to matching channels */ // 将消息发送个订阅了和指定频道匹配的模式的客户端 if (listLength(server.pubsub_patterns)) { listRewind(server.pubsub_patterns,&li); channel = getDecodedObject(channel); // 遍历server.pubsub_patterns while ((ln = listNext(&li)) != NULL) { // 取出当前模式 pubsubPattern *pat = ln->value; // 判断当前模式是否和给定频道相匹配 if (stringmatchlen((char*)pat->pattern->ptr, sdslen(pat->pattern->ptr), (char*)channel->ptr, sdslen(channel->ptr),0)) { // 发送消息 addReply(pat->client,shared.mbulkhdr[4]); addReply(pat->client,shared.pmessagebulk); addReplyBulk(pat->client,pat->pattern); addReplyBulk(pat->client,channel); addReplyBulk(pat->client,message); receivers++; } } decrRefCount(channel); } return receivers; }
/* PUBSUB command for Pub/Sub introspection. */ void pubsubCommand(redisClient *c) { // 处理PUBSUB CHANNELS [pattern]命令 if (!strcasecmp(c->argv[1]->ptr,"channels") && (c->argc == 2 || c->argc ==3)) { /* PUBSUB CHANNELS [<pattern>] */ // 获取pattern参数,如果没有则为NULL sds pat = (c->argc == 2) ? NULL : c->argv[2]->ptr; dictIterator *di = dictGetIterator(server.pubsub_channels); dictEntry *de; long mblen = 0; void *replylen; replylen = addDeferredMultiBulkLength(c); // 遍历server.pubsub_channels字典 while((de = dictNext(di)) != NULL) { // 取出当前频道channel robj *cobj = dictGetKey(de); sds channel = cobj->ptr; // 如果没有给定pattern参数,则打印出所有频道 // 如果给定pattern参数,则打印出与pattern参数相匹配的频道 if (!pat || stringmatchlen(pat, sdslen(pat), channel, sdslen(channel),0)) { addReplyBulk(c,cobj); mblen++; } } dictReleaseIterator(di); setDeferredMultiBulkLength(c,replylen,mblen); } // 处理PUBSUB NUMSUB [Channel_1 ... Channel_N]命令 else if (!strcasecmp(c->argv[1]->ptr,"numsub") && c->argc >= 2) { /* PUBSUB NUMSUB [Channel_1 ... Channel_N] */ int j; addReplyMultiBulkLen(c,(c->argc-2)*2); for (j = 2; j < c->argc; j++) { list *l = dictFetchValue(server.pubsub_channels,c->argv[j]); addReplyBulk(c,c->argv[j]); addReplyLongLong(c,l ? listLength(l) : 0); } } // 处理PUBSUB NUMPA命令 else if (!strcasecmp(c->argv[1]->ptr,"numpat") && c->argc == 2) { /* PUBSUB NUMPAT */ addReplyLongLong(c,listLength(server.pubsub_patterns)); } else { addReplyErrorFormat(c, "Unknown PUBSUB subcommand or wrong number of arguments for '%s'", (char*)c->argv[1]->ptr); } }
void movekeysCommand(redisClient *c) { redisDb *src, *dst; int srcid; dictIterator *di; dictEntry *de; sds pattern = c->argv[1]->ptr; int plen = sdslen(pattern), allkeys; unsigned long numkeys = 0; /* Obtain source and target DB pointers */ src = c->db; srcid = c->db->id; if (selectDb(c,atoi(c->argv[2]->ptr)) == REDIS_ERR) { addReply(c,shared.outofrangeerr); return; } dst = c->db; selectDb(c,srcid); /* Back to the source DB */ /* If the user is moving using as target the same * DB as the source DB it is probably an error. */ if (src == dst) { addReply(c,shared.sameobjecterr); return; } di = dictGetIterator(c->db->dict); allkeys = (pattern[0] == '*' && pattern[1] == '\0'); while((de = dictNext(di)) != NULL) { sds key = dictGetEntryKey(de); robj *keyobj; if (allkeys || stringmatchlen(pattern,plen,key,sdslen(key),0)) { keyobj = createStringObject(key,sdslen(key)); if (expireIfNeeded(c->db,keyobj) == 0) { robj *val = dictGetEntryVal(de); /* Try to add the element to the target DB */ if (dbAdd(dst,keyobj,val) != REDIS_ERR) { incrRefCount(val); /* OK! key moved, free the entry in the source DB */ dbDelete(src,keyobj); server.dirty++; numkeys++; } } decrRefCount(keyobj); } } dictReleaseIterator(di); addReplyLongLong(c,numkeys); }
int Ardb::MatchValueByPattern(Context& ctx, const Slice& key_pattern, const Slice& value_pattern, Data& subst, Data& value) { if (0 != GetValueByPattern(ctx, key_pattern, subst, value)) { return -1; } std::string str; subst.GetDecodeString(str); std::string vpattern(value_pattern.data(), value_pattern.size()); return stringmatchlen(vpattern.c_str(), vpattern.size(), str.c_str(),str.size(), 0) == 1 ? 0 : -1; }
/* * 发布一个消息至指定频道 */ int pubsubPublishMessage(robj *channel, robj *message) { int receivers = 0; dictEntry *de; listNode *ln; listIter li; /* Send to clients listening for that channel */ /* 发布给监听指定频道的客户端 */ de = dictFind(server.pubsub_channels,channel); if (de) { list *list = dictGetVal(de); listNode *ln; listIter li; listRewind(list,&li); while ((ln = listNext(&li)) != NULL) { redisClient *c = ln->value; addReply(c,shared.mbulkhdr[3]); addReply(c,shared.messagebulk); addReplyBulk(c,channel); addReplyBulk(c,message); receivers++; } } /* Send to clients listening to matching channels */ /* 发布给监听模式匹配的客户端 */ if (listLength(server.pubsub_patterns)) { listRewind(server.pubsub_patterns,&li); channel = getDecodedObject(channel); while ((ln = listNext(&li)) != NULL) { pubsubPattern *pat = ln->value; if (stringmatchlen((char*)pat->pattern->ptr, sdslen(pat->pattern->ptr), (char*)channel->ptr, sdslen(channel->ptr),0)) { addReply(pat->client,shared.mbulkhdr[4]); addReply(pat->client,shared.pmessagebulk); addReplyBulk(pat->client,pat->pattern); addReplyBulk(pat->client,channel); addReplyBulk(pat->client,message); receivers++; } } decrRefCount(channel); } return receivers; }
void expirekeysGenericCommand(redisClient *c, robj *keypattern, robj *param, long offset) { dictIterator *di; dictEntry *de; sds pattern = keypattern->ptr; int plen = sdslen(pattern); unsigned long numkeys = 0, allkeys; time_t seconds; time_t when; if (getLongFromObjectOrReply(c, param, &seconds, NULL) != REDIS_OK) return; seconds -= offset; when = time(NULL)+seconds; di = dictGetIterator(c->db->dict); allkeys = (pattern[0] == '*' && pattern[1] == '\0'); while((de = dictNext(di)) != NULL) { sds key = dictGetEntryKey(de); robj *keyobj; if (allkeys || stringmatchlen(pattern,plen,key,sdslen(key),0)) { keyobj = createStringObject(key,sdslen(key)); if (seconds <= 0 && !server.loading && !server.masterhost) { robj *aux; redisAssert(dbDelete(c->db,keyobj)); server.dirty++; numkeys++; /* Replicate/AOF this as an explicit DEL. */ aux = createStringObject("DEL",3); rewriteClientCommandVector(c,2,aux,keyobj); decrRefCount(aux); touchWatchedKey(c->db,keyobj); } else { time_t when = time(NULL)+seconds; setExpire(c->db,keyobj,when); touchWatchedKey(c->db,keyobj); server.dirty++; numkeys++; } decrRefCount(keyobj); } } dictReleaseIterator(di); addReplyLongLong(c,numkeys); }
/* * PUBSUB 命令, 内省命令 */ void pubsubCommand(redisClient *c) { if (!strcasecmp(c->argv[1]->ptr,"channels") && (c->argc == 2 || c->argc ==3)) //列出当前活跃的频道,每个频道至少有一个订阅者,订阅模式的客户端不计算在内 { /* PUBSUB CHANNELS [<pattern>] */ sds pat = (c->argc == 2) ? NULL : c->argv[2]->ptr; dictIterator *di = dictGetIterator(server.pubsub_channels); dictEntry *de; long mblen = 0; void *replylen; replylen = addDeferredMultiBulkLength(c); while((de = dictNext(di)) != NULL) { robj *cobj = dictGetKey(de); sds channel = cobj->ptr; if (!pat || stringmatchlen(pat, sdslen(pat), channel, sdslen(channel),0)) { addReplyBulk(c,cobj); mblen++; } } dictReleaseIterator(di); setDeferredMultiBulkLength(c,replylen,mblen); } else if (!strcasecmp(c->argv[1]->ptr,"numsub") && c->argc >= 2) { //返回给定频道的订阅者数量,订阅模式的客户端不计算在内 /* PUBSUB NUMSUB [Channel_1 ... Channel_N] */ int j; addReplyMultiBulkLen(c,(c->argc-2)*2); for (j = 2; j < c->argc; j++) { list *l = dictFetchValue(server.pubsub_channels,c->argv[j]); addReplyBulk(c,c->argv[j]); addReplyLongLong(c,l ? listLength(l) : 0); } } else if (!strcasecmp(c->argv[1]->ptr,"numpat") && c->argc == 2) { //返回订阅模式的数量 /* PUBSUB NUMPAT */ addReplyLongLong(c,listLength(server.pubsub_patterns)); } else { addReplyErrorFormat(c, "Unknown PUBSUB subcommand or wrong number of arguments for '%s'", (char*)c->argv[1]->ptr); } }
int64_t MMKVImpl::SScan(DBID db, const Data& key, int64_t cursor, const std::string& pattern, int32_t limit_count, const StringArrayResult& results) { int err = 0; RWLockGuard<MemorySegmentManager, READ_LOCK> keylock_guard(m_segment); StringSet* set = GetObject<StringSet>(db, key, V_TYPE_SET, false, err)(); if (NULL == set || 0 != err) { return err; } StringSet::iterator it = set->begin(); if (cursor >= set->size()) { return 0; } it.increment_by(cursor); int match_count = 0; while (it != set->end()) { std::string key_str; it->ToString(key_str); if (pattern == "" || stringmatchlen(pattern.c_str(), pattern.size(), key_str.c_str(), key_str.size(), 0) == 1) { std::string& ss = results.Get(); ss = key_str; match_count++; if (limit_count > 0 && match_count >= limit_count) { break; } } cursor++; it++; } return it != set->end() ? cursor : 0; }
void countCommand(redisClient *c) { dictIterator *di; dictEntry *de; sds pattern = c->argv[1]->ptr; int plen = sdslen(pattern), allkeys; unsigned long numkeys = 0; di = dictGetIterator(c->db->dict); allkeys = (pattern[0] == '*' && pattern[1] == '\0'); while((de = dictNext(di)) != NULL) { sds key = dictGetEntryKey(de); robj *keyobj; if (allkeys || stringmatchlen(pattern,plen,key,sdslen(key),0)) { keyobj = createStringObject(key,sdslen(key)); if (expireIfNeeded(c->db,keyobj) == 0) { numkeys++; } decrRefCount(keyobj); } } dictReleaseIterator(di); addReplyLongLong(c,numkeys); }
int Ardb::Scan(Context& ctx, RedisCommandFrame& cmd) { StringArray keys; std::string pattern; uint32 limit = 10000; //return max 10000 keys one time if (cmd.GetArguments().size() > 1) { for (uint32 i = 1; i < cmd.GetArguments().size(); i++) { if (!strcasecmp(cmd.GetArguments()[i].c_str(), "count")) { if (i + 1 >= cmd.GetArguments().size() || !string_touint32(cmd.GetArguments()[i + 1], limit)) { fill_error_reply(ctx.reply, "value is not an integer or out of range"); return 0; } i++; } else if (!strcasecmp(cmd.GetArguments()[i].c_str(), "match")) { if (i + 1 >= cmd.GetArguments().size()) { fill_error_reply(ctx.reply, "'MATCH' need one args followed"); return 0; } pattern = cmd.GetArguments()[i + 1]; i++; } else { fill_error_reply(ctx.reply, "Syntax error, try scan 0 "); return 0; } } } RedisReply& r1 = ctx.reply.AddMember(); RedisReply& r2 = ctx.reply.AddMember(); r2.type = REDIS_REPLY_ARRAY; KeyObject from; from.db = ctx.currentDB; from.type = KEY_META; const std::string& cursor = cmd.GetArguments()[0]; std::string scan_start_cursor; if (cmd.GetArguments()[0] != "0") { if (m_cfg.scan_redis_compatible) { FindElementByRedisCursor(cursor, scan_start_cursor); } from.key = scan_start_cursor; } Iterator* iter = IteratorKeyValue(from, false); bool reachend = false; std::string tmpkey; while (NULL != iter && iter->Valid()) { KeyObject kk; if (!decode_key(iter->Key(), kk) || kk.db != ctx.currentDB || kk.type != KEY_META) { reachend = true; break; } tmpkey.clear(); tmpkey.assign(kk.key.data(), kk.key.size()); if (r2.MemberSize() >= limit) { break; } if ((pattern.empty() || stringmatchlen(pattern.c_str(), pattern.size(), tmpkey.c_str(), tmpkey.size(), 0) == 1)) { RedisReply& rr1 = r2.AddMember(); fill_str_reply(rr1, tmpkey); } iter->Next(); } if (reachend || !iter->Valid()) { fill_str_reply(r1, "0"); } else { if (m_cfg.scan_redis_compatible) { uint64 newcursor = GetNewRedisCursor(tmpkey); fill_str_reply(r1, stringfromll(newcursor)); } else { fill_str_reply(r1, tmpkey); } } DELETE(iter); return 0; }
int Ardb::KeysOperation(Context& ctx, const KeysOptions& options) { KeyObject from; from.db = ctx.currentDB; from.type = KEY_META; std::string::size_type cursor = options.pattern.find("*"); if (cursor == std::string::npos) { from.key = options.pattern; } else { if (options.pattern != "*" && cursor > 0) { from.key = options.pattern.substr(0, cursor); } } Iterator* iter = IteratorKeyValue(from, false); std::string tmpkey; uint32 count = 0; while (NULL != iter && iter->Valid()) { KeyObject kk; if (!decode_key(iter->Key(), kk) || kk.db != ctx.currentDB || kk.type != KEY_META) { break; } tmpkey.clear(); tmpkey.assign(kk.key.data(), kk.key.size()); if ((options.pattern == "*" || stringmatchlen(options.pattern.c_str(), options.pattern.size(), tmpkey.data(), tmpkey.size(), 0) == 1)) { if (options.op == OP_GET) { RedisReply& r = ctx.reply.AddMember(); fill_str_reply(r, tmpkey); } else { count++; } } else { /* * If the '*' is the last char, we can break iteration now */ if (cursor == options.pattern.size() - 1) { break; } } iter->Next(); } DELETE(iter); if (options.op == OP_GET) { ctx.reply.type = REDIS_REPLY_ARRAY; } else { fill_int_reply(ctx.reply, count); } return 0; }
/*支持glob-style的通配符格式,如*表示任意一个或多个字符,?表示任意字符,[abc]表示方括号中任意一个字母。*/ int stringmatchlen(const char *pattern, int patternLen, const char *string, int stringLen, int nocase) { while(patternLen) { switch(pattern[0]) { case '*': while (pattern[1] == '*') { //如果出现的是**,说明一定匹配 pattern++; patternLen--; } if (patternLen == 1) return 1; /* match */ while(stringLen) { if (stringmatchlen(pattern+1, patternLen-1, string, stringLen, nocase)) return 1; /* match */ string++; stringLen--; } return 0; /* no match */ break; case '?': if (stringLen == 0) return 0; /* no match */ /* 因为?能代表任何字符,所以,匹配的字符再往后挪一个字符 */ string++; stringLen--; break; case '[': { int not, match; pattern++; patternLen--; not = pattern[0] == '^'; if (not) { pattern++; patternLen--; } match = 0; while(1) { if (pattern[0] == '\\') { //如果遇到转义符,则模式字符往后移一个位置 pattern++; patternLen--; if (pattern[0] == string[0]) match = 1; } else if (pattern[0] == ']') { //直到遇到另外一个我中括号,则停止 break; } else if (patternLen == 0) { pattern--; patternLen++; break; } else if (pattern[1] == '-' && patternLen >= 3) { int start = pattern[0]; int end = pattern[2]; int c = string[0]; if (start > end) { int t = start; start = end; end = t; } if (nocase) { start = tolower(start); end = tolower(end); c = tolower(c); } pattern += 2; patternLen -= 2; if (c >= start && c <= end) match = 1; } else { if (!nocase) { if (pattern[0] == string[0]) match = 1; } else { if (tolower((int)pattern[0]) == tolower((int)string[0])) match = 1; } } pattern++; patternLen--; } if (not) match = !match; if (!match) return 0; /* no match */ string++; stringLen--; break; } case '\\': if (patternLen >= 2) { pattern++; patternLen--; } /* fall through */ default: /* 如果没有正则表达式的关键字符,则直接比较 */ if (!nocase) { if (pattern[0] != string[0]) //不相等,直接不匹配 return 0; /* no match */ } else { if (tolower((int)pattern[0]) != tolower((int)string[0])) return 0; /* no match */ } string++; stringLen--; break; } pattern++; patternLen--; if (stringLen == 0) { while(*pattern == '*') { pattern++; patternLen--; } break; } } if (patternLen == 0 && stringLen == 0) //如果匹配字符和模式字符匹配的长度都减少到0了,说明匹配成功了 return 1; return 0; }
/* This command implements SCAN, HSCAN and SSCAN commands. * If object 'o' is passed, then it must be a Hash or Set object, otherwise * if 'o' is NULL the command will operate on the dictionary associated with * the current database. * * When 'o' is not NULL the function assumes that the first argument in * the client arguments vector is a key so it skips it before iterating * in order to parse options. * * In the case of a Hash object the function returns both the field and value * of every element on the Hash. */ void scanGenericCommand(client *c, robj *o, unsigned long cursor) { int i, j; list *keys = listCreate(); listNode *node, *nextnode; long count = 10; sds pat = NULL; int patlen = 0, use_pattern = 0; dict *ht; /* Object must be NULL (to iterate keys names), or the type of the object * must be Set, Sorted Set, or Hash. */ serverAssert(o == NULL || o->type == OBJ_SET || o->type == OBJ_HASH || o->type == OBJ_ZSET); /* Set i to the first option argument. The previous one is the cursor. */ i = (o == NULL) ? 2 : 3; /* Skip the key argument if needed. */ /* Step 1: Parse options. */ while (i < c->argc) { j = c->argc - i; if (!strcasecmp(c->argv[i]->ptr, "count") && j >= 2) { if (getLongFromObjectOrReply(c, c->argv[i+1], &count, NULL) != C_OK) { goto cleanup; } if (count < 1) { addReply(c,shared.syntaxerr); goto cleanup; } i += 2; } else if (!strcasecmp(c->argv[i]->ptr, "match") && j >= 2) { pat = c->argv[i+1]->ptr; patlen = sdslen(pat); /* The pattern always matches if it is exactly "*", so it is * equivalent to disabling it. */ use_pattern = !(pat[0] == '*' && patlen == 1); i += 2; } else { addReply(c,shared.syntaxerr); goto cleanup; } } /* Step 2: Iterate the collection. * * Note that if the object is encoded with a ziplist, intset, or any other * representation that is not a hash table, we are sure that it is also * composed of a small number of elements. So to avoid taking state we * just return everything inside the object in a single call, setting the * cursor to zero to signal the end of the iteration. */ /* Handle the case of a hash table. */ ht = NULL; if (o == NULL) { ht = c->db->dict; } else if (o->type == OBJ_SET && o->encoding == OBJ_ENCODING_HT) { ht = o->ptr; } else if (o->type == OBJ_HASH && o->encoding == OBJ_ENCODING_HT) { ht = o->ptr; count *= 2; /* We return key / value for this type. */ } else if (o->type == OBJ_ZSET && o->encoding == OBJ_ENCODING_SKIPLIST) { zset *zs = o->ptr; ht = zs->dict; count *= 2; /* We return key / value for this type. */ } if (ht) { void *privdata[2]; /* We set the max number of iterations to ten times the specified * COUNT, so if the hash table is in a pathological state (very * sparsely populated) we avoid to block too much time at the cost * of returning no or very few elements. */ long maxiterations = count*10; /* We pass two pointers to the callback: the list to which it will * add new elements, and the object containing the dictionary so that * it is possible to fetch more data in a type-dependent way. */ privdata[0] = keys; privdata[1] = o; do { cursor = dictScan(ht, cursor, scanCallback, privdata); } while (cursor && maxiterations-- && listLength(keys) < (unsigned long)count); } else if (o->type == OBJ_SET) { int pos = 0; int64_t ll; while(intsetGet(o->ptr,pos++,&ll)) listAddNodeTail(keys,createStringObjectFromLongLong(ll)); cursor = 0; } else if (o->type == OBJ_HASH || o->type == OBJ_ZSET) { unsigned char *p = ziplistIndex(o->ptr,0); unsigned char *vstr; unsigned int vlen; long long vll; while(p) { ziplistGet(p,&vstr,&vlen,&vll); listAddNodeTail(keys, (vstr != NULL) ? createStringObject((char*)vstr,vlen) : createStringObjectFromLongLong(vll)); p = ziplistNext(o->ptr,p); } cursor = 0; } else { serverPanic("Not handled encoding in SCAN."); } /* Step 3: Filter elements. */ node = listFirst(keys); while (node) { robj *kobj = listNodeValue(node); nextnode = listNextNode(node); int filter = 0; /* Filter element if it does not match the pattern. */ if (!filter && use_pattern) { if (sdsEncodedObject(kobj)) { if (!stringmatchlen(pat, patlen, kobj->ptr, sdslen(kobj->ptr), 0)) filter = 1; } else { char buf[LONG_STR_SIZE]; int len; serverAssert(kobj->encoding == OBJ_ENCODING_INT); len = ll2string(buf,sizeof(buf),(long)kobj->ptr); if (!stringmatchlen(pat, patlen, buf, len, 0)) filter = 1; } } /* Filter element if it is an expired key. */ if (!filter && o == NULL && expireIfNeeded(c->db, kobj)) filter = 1; /* Remove the element and its associted value if needed. */ if (filter) { decrRefCount(kobj); listDelNode(keys, node); } /* If this is a hash or a sorted set, we have a flat list of * key-value elements, so if this element was filtered, remove the * value, or skip it if it was not filtered: we only match keys. */ if (o && (o->type == OBJ_ZSET || o->type == OBJ_HASH)) { node = nextnode; nextnode = listNextNode(node); if (filter) { kobj = listNodeValue(node); decrRefCount(kobj); listDelNode(keys, node); } } node = nextnode; } /* Step 4: Reply to the client. */ addReplyMultiBulkLen(c, 2); addReplyBulkLongLong(c,cursor); addReplyMultiBulkLen(c, listLength(keys)); while ((node = listFirst(keys)) != NULL) { robj *kobj = listNodeValue(node); addReplyBulk(c, kobj); decrRefCount(kobj); listDelNode(keys, node); } cleanup: listSetFreeMethod(keys,decrRefCountVoid); listRelease(keys); }
int stringmatch(const char *pattern, const char *string, int nocase) { return stringmatchlen(pattern,(int)strlen(pattern),string,(int)strlen(string),nocase); WIN_PORT_FIX /* cast (int) */ }
/* PUBSUB command for Pub/Sub introspection. */ void pubsubCommand(redisClient *c) { // PUBSUB CHANNELS [pattern] 子命令 if (!strcasecmp(c->argv[1]->ptr,"channels") && (c->argc == 2 || c->argc ==3)) { /* PUBSUB CHANNELS [<pattern>] */ // 检查命令请求是否给定了 pattern 参数 // 如果没有给定的话,就设为 NULL sds pat = (c->argc == 2) ? NULL : c->argv[2]->ptr; // 创建 pubsub_channels 的字典迭代器 // 该字典的键为频道,值为链表 // 链表中保存了所有订阅键所对应的频道的客户端 dictIterator *di = dictGetIterator(server.pubsub_channels); dictEntry *de; long mblen = 0; void *replylen; replylen = addDeferredMultiBulkLength(c); // 从迭代器中获取一个客户端 while((de = dictNext(di)) != NULL) { // 从字典中取出客户端所订阅的频道 robj *cobj = dictGetKey(de); sds channel = cobj->ptr; // 顺带一提 // 因为 Redis 的字典实现只能遍历字典的值(客户端) // 所以这里才会有遍历字典值然后通过字典值取出字典键(频道)的蹩脚用法 // 如果没有给定 pattern 参数,那么打印所有找到的频道 // 如果给定了 pattern 参数,那么只打印和 pattern 相匹配的频道 if (!pat || stringmatchlen(pat, sdslen(pat), channel, sdslen(channel),0)) { // 向客户端输出频道 addReplyBulk(c,cobj); mblen++; } } // 释放字典迭代器 dictReleaseIterator(di); setDeferredMultiBulkLength(c,replylen,mblen); // PUBSUB NUMSUB [channel-1 channel-2 ... channel-N] 子命令 } else if (!strcasecmp(c->argv[1]->ptr,"numsub") && c->argc >= 2) { /* PUBSUB NUMSUB [Channel_1 ... Channel_N] */ int j; addReplyMultiBulkLen(c,(c->argc-2)*2); for (j = 2; j < c->argc; j++) { // c->argv[j] 也即是客户端输入的第 N 个频道名字 // pubsub_channels 的字典为频道名字 // 而值则是保存了 c->argv[j] 频道所有订阅者的链表 // 而调用 dictFetchValue 也就是取出所有订阅给定频道的客户端 list *l = dictFetchValue(server.pubsub_channels,c->argv[j]); addReplyBulk(c,c->argv[j]); // 向客户端返回链表的长度属性 // 这个属性就是某个频道的订阅者数量 // 例如:如果一个频道有三个订阅者,那么链表的长度就是 3 // 而返回给客户端的数字也是三 addReplyBulkLongLong(c,l ? listLength(l) : 0); } // PUBSUB NUMPAT 子命令 } else if (!strcasecmp(c->argv[1]->ptr,"numpat") && c->argc == 2) { /* PUBSUB NUMPAT */ // pubsub_patterns 链表保存了服务器中所有被订阅的模式 // pubsub_patterns 的长度就是服务器中被订阅模式的数量 addReplyLongLong(c,listLength(server.pubsub_patterns)); // 错误处理 } else { addReplyErrorFormat(c, "Unknown PUBSUB subcommand or wrong number of arguments for '%s'", (char*)c->argv[1]->ptr); } }
/* Publish a message * * 将 message 发送到所有订阅频道 channel 的客户端, * 以及所有订阅了和 channel 频道匹配的模式的客户端。 pubsubPublishMessage函数是PUBLISH命令的实现函数,执行这个函数 等同于执行PUBLISH命令,订阅数据库通知的客户端收到的信息就是由这个函数发出的 */ int pubsubPublishMessage(robj *channel, robj *message) { int receivers = 0; dictEntry *de; listNode *ln; listIter li; /* Send to clients listening for that channel */ // 取出包含所有订阅频道 channel 的客户端的链表 // 并将消息发送给它们 de = dictFind(server.pubsub_channels,channel); if (de) { list *list = dictGetVal(de); listNode *ln; listIter li; // 遍历客户端链表,将 message 发送给它们 listRewind(list,&li); while ((ln = listNext(&li)) != NULL) { redisClient *c = ln->value; // 回复客户端。 // 示例: // 1) "message" // 2) "xxx" // 3) "hello" addReply(c,shared.mbulkhdr[3]); // "message" 字符串 addReply(c,shared.messagebulk); // 消息的来源频道 addReplyBulk(c,channel); // 消息内容 addReplyBulk(c,message); // 接收客户端计数 receivers++; } } /* Send to clients listening to matching channels */ // 将消息也发送给那些和频道匹配的模式 if (listLength(server.pubsub_patterns)) { // 遍历模式链表 listRewind(server.pubsub_patterns,&li); channel = getDecodedObject(channel); while ((ln = listNext(&li)) != NULL) { // 取出 pubsubPattern pubsubPattern *pat = ln->value; // 如果 channel 和 pattern 匹配 // 就给所有订阅该 pattern 的客户端发送消息 if (stringmatchlen((char*)pat->pattern->ptr, sdslen(pat->pattern->ptr), (char*)channel->ptr, sdslen(channel->ptr),0)) { // 回复客户端 // 示例: // 1) "pmessage" // 2) "*" // 3) "xxx" // 4) "hello" addReply(pat->client,shared.mbulkhdr[4]); addReply(pat->client,shared.pmessagebulk); addReplyBulk(pat->client,pat->pattern); addReplyBulk(pat->client,channel); addReplyBulk(pat->client,message); // 对接收消息的客户端进行计数 receivers++; } } decrRefCount(channel); } // 返回计数 return receivers; }
//检测string是不是符合pattern的模式(glob-style) int stringmatchlen(const char *pattern, int patternLen, const char *string, int stringLen, int nocase) { while(patternLen) { switch(pattern[0]) { case '*': while (pattern[1] == '*') { //多个*等同与一个* pattern++; patternLen--; } if (patternLen == 1) return 1; /* match */ while(stringLen) { //先忽略pattern的*,看string是否匹配后面的pattern,即pattern+1 if (stringmatchlen(pattern+1, patternLen-1, string, stringLen, nocase)) return 1; /* match */ //*匹配string的一个字符 string++; stringLen--; } //到了这里说明没有匹配,否则应该在上面的递归调用返回 return 0; /* no match */ break; case '?': if (stringLen == 0) return 0; /* no match */ //匹配任何字符,因此跳过当前字符 string++; stringLen--; break; case '[': //[xxx]这种情况 { int not, match; pattern++; patternLen--; not = pattern[0] == '^'; if (not) { //[^xxx]这种情况 pattern++; patternLen--; } match = 0; while(1) { if (pattern[0] == '\\') { //跳过当前"\"字符 pattern++; patternLen--; //下一个字符匹配,则将match设为1。即模式中的'\x' 匹配string中的'x',x本身在模式中有特别含义故需转义 if (pattern[0] == string[0]) match = 1; } else if (pattern[0] == ']') { //到了右括号,跳出while break; } else if (patternLen == 0) { pattern--; patternLen++; break; } else if (pattern[1] == '-' && patternLen >= 3) { //[a-z]这种情况 int start = pattern[0]; int end = pattern[2]; int c = string[0]; if (start > end) { int t = start; start = end; end = t; } if (nocase) { start = tolower(start); end = tolower(end); c = tolower(c); } pattern += 2; patternLen -= 2; //c在范围内,将match设为1 if (c >= start && c <= end) match = 1; } else { if (!nocase) { if (pattern[0] == string[0]) match = 1; } else { if (tolower((int)pattern[0]) == tolower((int)string[0])) match = 1; } } pattern++; patternLen--; } if (not) match = !match; if (!match) return 0; /* no match */ string++; stringLen--; break; } case '\\': // "\"后面还有字符,那么跳过当前的字符 if (patternLen >= 2) { pattern++; patternLen--; } /* fall through */ default: if (!nocase) { //比较当前字符 if (pattern[0] != string[0]) return 0; /* no match */ } else { //不考虑大小写,比较当前字符 if (tolower((int)pattern[0]) != tolower((int)string[0])) return 0; /* no match */ } string++; stringLen--; break; } pattern++; patternLen--; if (stringLen == 0) { //当string长度为0了,但是pattern还有*,跳过所有的* while(*pattern == '*') { pattern++; patternLen--; } break; } } //遍历结束后pattern和string长度都为0,说明匹配 if (patternLen == 0 && stringLen == 0) return 1; return 0; }
/* Glob-style pattern matching. */ int stringmatchlen(const char *pattern, int patternLen, const char *string, int stringLen, int nocase) { while(patternLen) { switch(pattern[0]) { case '*': // 跳过所有的 * while (pattern[1] == '*') { pattern++; patternLen--; } if (patternLen == 1) return 1; /* match */ while(stringLen) { // 递归 if (stringmatchlen(pattern+1, patternLen-1, string, stringLen, nocase)) return 1; /* match */ string++; stringLen--; } return 0; /* no match */ break; case '?': if (stringLen == 0) return 0; /* no match */ // 只跳过一个字符 string++; stringLen--; break; case '[': { int not, match; pattern++; patternLen--; not = pattern[0] == '^'; if (not) { // skip ^ pattern++; patternLen--; } match = 0; while(1) { if (pattern[0] == '\\') { pattern++; // skip \\ patternLen--; if (pattern[0] == string[0]) match = 1; } else if (pattern[0] == ']') { // end break; } else if (patternLen == 0) { pattern--; // protect patternLen++; break; } else if (pattern[1] == '-' && patternLen >= 3) { int start = pattern[0]; int end = pattern[2]; int c = string[0]; if (start > end) { // swap int t = start; start = end; end = t; } if (nocase) { // tolower start = tolower(start); end = tolower(end); c = tolower(c); } pattern += 2; patternLen -= 2; if (c >= start && c <= end) match = 1; } else { if (!nocase) { // case if (pattern[0] == string[0]) match = 1; } else { if (tolower((int)pattern[0]) == tolower((int)string[0])) match = 1; } } pattern++; patternLen--; } // [^xxxx] 不能匹配 xxxx,match!=0 表示 string 中出现 xxxx, // 匹配失败 if (not) match = !match; if (!match) return 0; /* no match */ string++; stringLen--; break; } case '\\': if (patternLen >= 2) { // skip \\ pattern++; patternLen--; } /* fall through */ default: if (!nocase) { if (pattern[0] != string[0]) return 0; /* no match */ } else { if (tolower((int)pattern[0]) != tolower((int)string[0])) return 0; /* no match */ } string++; stringLen--; break; } pattern++; patternLen--; if (stringLen == 0) { while(*pattern == '*') { // skip all * pattern++; patternLen--; } break; } } if (patternLen == 0 && stringLen == 0) return 1; return 0; }
int stringmatch(const char *pattern, const char *string, int nocase) { return stringmatchlen(pattern,strlen(pattern),string,strlen(string),nocase); }
/* Glob-style pattern matching. */ int stringmatchlen(const char *pattern, int patternLen, const char *string, int stringLen, int nocase) { while(patternLen) { switch(pattern[0]) { case '*': while (pattern[1] == '*') { pattern++; patternLen--; } if (patternLen == 1) return 1; /* match */ while(stringLen) { if (stringmatchlen(pattern+1, patternLen-1, string, stringLen, nocase)) return 1; /* match */ string++; stringLen--; } return 0; /* no match */ break; case '?': if (stringLen == 0) return 0; /* no match */ string++; stringLen--; break; case '[': { int not, match; pattern++; patternLen--; not = pattern[0] == '^'; if (not) { pattern++; patternLen--; } match = 0; while(1) { if (pattern[0] == '\\') { pattern++; patternLen--; if (pattern[0] == string[0]) match = 1; } else if (pattern[0] == ']') { break; } else if (patternLen == 0) { pattern--; patternLen++; break; } else if (pattern[1] == '-' && patternLen >= 3) { int start = pattern[0]; int end = pattern[2]; int c = string[0]; if (start > end) { int t = start; start = end; end = t; } if (nocase) { start = tolower(start); end = tolower(end); c = tolower(c); } pattern += 2; patternLen -= 2; if (c >= start && c <= end) match = 1; } else { if (!nocase) { if (pattern[0] == string[0]) match = 1; } else { if (tolower((int)pattern[0]) == tolower((int)string[0])) match = 1; } } pattern++; patternLen--; } if (not) match = !match; if (!match) return 0; /* no match */ string++; stringLen--; break; } case '\\': if (patternLen >= 2) { pattern++; patternLen--; } /* fall through */ default: if (!nocase) { if (pattern[0] != string[0]) return 0; /* no match */ } else { if (tolower((int)pattern[0]) != tolower((int)string[0])) return 0; /* no match */ } string++; stringLen--; break; } pattern++; patternLen--; if (stringLen == 0) { while(*pattern == '*') { pattern++; patternLen--; } break; } } if (patternLen == 0 && stringLen == 0) return 1; return 0; }
static int check_response(redis_node *rnode, struct msg *r) { int ret; rmtContext *ctx = rnode->ctx; struct msg *resp, *msg = NULL; check_data *chdata; check_unit *cunit; char extra_err[50]; struct array args; sds *arg; struct array *bulks1 = NULL, *bulks2 = NULL; sds *bulk1, *bulk2; if (r == NULL) { return RMT_ERROR; } extra_err[0] = '\0'; resp = r->peer; r->peer = NULL; array_init(&args, 3, sizeof(sds)); ASSERT(r->request && r->sent); ASSERT(resp != NULL && resp->request == 0); cunit = (check_unit *)r->ptr; chdata = cunit->cdata->data; if(resp->type == MSG_RSP_REDIS_ERROR){ log_warn("Response from node[%s] for %s is error", rnode->addr, msg_type_string(r->type)); goto error; } if (cunit->state == CHECK_UNIT_STATE_GET_KEY) { ASSERT(cunit->key == NULL); ASSERT(cunit->key_type == -1); ASSERT(cunit->result1 == NULL && cunit->result2 == NULL); ASSERT(cunit->srnode == rnode); if (resp->type != MSG_RSP_REDIS_BULK) { log_error("ERROR: the response type for command 'randomkey' from node[%s] is error: %s", rnode->addr, msg_type_string(resp->type)); goto error; } if (msg_cmp_str(resp, (const uint8_t*)REDIS_REPLY_BULK_NULL, rmt_strlen(REDIS_REPLY_BULK_NULL)) == 0) { /* source group may have no keys, stop it */ cunit->cdata->keys_count --; goto done; } cunit->key = redis_msg_response_get_bulk_string(resp); if (cunit->key == NULL) { log_error("ERROR: get bulk string from response of node[%s] failed, " "bulk_len: %"PRIu32", bulk_start: %p", rnode->addr, resp->bulk_len, resp->bulk_start); goto error; } if (ctx->filter != NULL && !stringmatchlen(ctx->filter, sdslen(ctx->filter), cunit->key, sdslen(cunit->key), 0)) { goto done; } ASSERT(sdslen(cunit->key) == resp->bulk_len); msg = msg_get(r->mb, 1, REDIS_DATA_TYPE_CMD); if (msg == NULL) { log_error("ERROR: out of memory."); goto error; } arg = array_push(&args); *arg = sdsnew("type"); arg = array_push(&args); *arg = sdsdup(cunit->key); ret = redis_msg_append_command_full_safe(msg, &args); if (ret != RMT_OK) { log_error("ERROR: msg append multi bulk len failed."); goto error; } while (array_n(&args) > 0) { arg = array_pop(&args); sdsfree(*arg); } msg->ptr = cunit; msg->resp_check = check_response; ret = prepare_send_msg(rnode, msg, rnode); if (ret != RMT_OK) { log_error("ERROR: prepare send msg node[%s] failed.", rnode->addr); goto error; } cunit->state = CHECK_UNIT_STATE_GET_TYPE; goto next_step; } if (cunit->state == CHECK_UNIT_STATE_GET_TYPE) { ASSERT(cunit->key != NULL); ASSERT(cunit->key_type == -1); ASSERT(cunit->result1 == NULL && cunit->result2 == NULL); ASSERT(cunit->srnode == rnode); if (resp->type != MSG_RSP_REDIS_STATUS) { log_error("ERROR: the response type for command 'type' from node[%s] is error: %s", rnode->addr, msg_type_string(resp->type)); goto error; } if (msg_cmp_str(resp, (const uint8_t*)REDIS_REPLY_STATUS_NONE, rmt_strlen(REDIS_REPLY_STATUS_NONE)) == 0) { /* This key doesn't exit, may be expired or evicted */ goto done; } msg = msg_get(r->mb, 1, REDIS_DATA_TYPE_CMD); if (msg == NULL) { log_error("ERROR: out of memory."); goto error; } if (msg_cmp_str(resp, (const uint8_t*)REDIS_REPLY_STATUS_STRING, rmt_strlen(REDIS_REPLY_STATUS_STRING)) == 0) { cunit->key_type = REDIS_STRING; arg = array_push(&args); *arg = sdsnew("get"); arg = array_push(&args); *arg = sdsdup(cunit->key); ret = redis_msg_append_command_full_safe(msg, &args); if (ret != RMT_OK) { log_error("ERROR: msg append multi bulk len failed."); goto error; } while (array_n(&args) > 0) { arg = array_pop(&args); sdsfree(*arg); } } else if (msg_cmp_str(resp, (const uint8_t*)REDIS_REPLY_STATUS_LIST, rmt_strlen(REDIS_REPLY_STATUS_LIST)) == 0) { cunit->key_type = REDIS_LIST; arg = array_push(&args); *arg = sdsnew("lrange"); arg = array_push(&args); *arg = sdsdup(cunit->key); arg = array_push(&args); *arg = sdsnew("0"); arg = array_push(&args); *arg = sdsnew("-1"); ret = redis_msg_append_command_full_safe(msg, &args); if (ret != RMT_OK) { log_error("ERROR: msg append multi bulk len failed."); goto error; } while (array_n(&args) > 0) { arg = array_pop(&args); sdsfree(*arg); } } else if (msg_cmp_str(resp, (const uint8_t*)REDIS_REPLY_STATUS_SET, rmt_strlen(REDIS_REPLY_STATUS_SET)) == 0) { cunit->key_type = REDIS_SET; arg = array_push(&args); *arg = sdsnew("smembers"); arg = array_push(&args); *arg = sdsdup(cunit->key); ret = redis_msg_append_command_full_safe(msg, &args); if (ret != RMT_OK) { log_error("ERROR: msg append multi bulk len failed."); goto error; } while (array_n(&args) > 0) { arg = array_pop(&args); sdsfree(*arg); } } else if (msg_cmp_str(resp, (const uint8_t*)REDIS_REPLY_STATUS_ZSET, rmt_strlen(REDIS_REPLY_STATUS_ZSET)) == 0) { cunit->key_type = REDIS_ZSET; arg = array_push(&args); *arg = sdsnew("zrange"); arg = array_push(&args); *arg = sdsdup(cunit->key); arg = array_push(&args); *arg = sdsnew("0"); arg = array_push(&args); *arg = sdsnew("-1"); ret = redis_msg_append_command_full_safe(msg, &args); if (ret != RMT_OK) { log_error("ERROR: msg append multi bulk len failed."); goto error; } while (array_n(&args) > 0) { arg = array_pop(&args); sdsfree(*arg); } } else if (msg_cmp_str(resp, (const uint8_t*)REDIS_REPLY_STATUS_HASH, rmt_strlen(REDIS_REPLY_STATUS_HASH)) == 0) { cunit->key_type = REDIS_HASH; arg = array_push(&args); *arg = sdsnew("hgetall"); arg = array_push(&args); *arg = sdsdup(cunit->key); ret = redis_msg_append_command_full_safe(msg, &args); if (ret != RMT_OK) { log_error("ERROR: msg append multi bulk len failed."); goto error; } while (array_n(&args) > 0) { arg = array_pop(&args); sdsfree(*arg); } } else { log_error("ERROR: response key type from node[%s] is error: ", rnode->addr); goto error; } msg->ptr = cunit; msg->resp_check = check_response; ret = send_msg_to_all(cunit, msg); if (ret != RMT_OK) { log_error("ERROR: send msg to source and target group failed."); goto error; } cunit->state = CHECK_UNIT_STATE_GET_VALUE; goto next_step; } if (cunit->state == CHECK_UNIT_STATE_GET_VALUE) { ASSERT(cunit->key != NULL); ASSERT(cunit->key_type >= 0); ASSERT(cunit->result1 == NULL || cunit->result2 == NULL); if (cunit->key_type == REDIS_STRING) { if (resp->type != MSG_RSP_REDIS_BULK) { log_error("ERROR: the response type for %s from node[%s] is error: %s", rnode->addr, msg_type_string(r->type), msg_type_string(resp->type)); goto error; } } else if (cunit->key_type == REDIS_LIST) { } else if (cunit->key_type == REDIS_SET) { } else if (cunit->key_type == REDIS_ZSET) { } else if (cunit->key_type == REDIS_HASH) { } else { NOT_REACHED(); } if (cunit->result1 == NULL) { cunit->result1 = resp; resp = NULL; } else if (cunit->result2 == NULL) { cunit->result2 = resp; resp = NULL; } else { NOT_REACHED(); } if (cunit->result1 != NULL && cunit->result2 != NULL) { if (cunit->key_type == REDIS_SET) { uint32_t j; bulks1 = get_multi_bulk_array_from_mbuf_list(cunit->result1->data); bulks2 = get_multi_bulk_array_from_mbuf_list(cunit->result2->data); if (bulks1 == NULL || bulks2 == NULL) { log_error("ERROR: get multi bulk array from mbufs failed"); goto error; } if (array_n(bulks1) != array_n(bulks2)) { chdata->err_inconsistent_value_keys_count ++; rmt_safe_snprintf(extra_err, 50, ", value is inconsistent\0"); goto error; } array_sort(bulks1, string_binary_cmp); array_sort(bulks2, string_binary_cmp); for (j = 0; j < array_n(bulks1); j ++) { bulk1 = array_get(bulks1, j); bulk2 = array_get(bulks2, j); if (string_binary_cmp(bulk1, bulk2) != 0) { chdata->err_inconsistent_value_keys_count ++; rmt_safe_snprintf(extra_err, 50, ", value is inconsistent\0"); goto error; } } } else if (cunit->key_type == REDIS_HASH) { struct array *hash_datas1, *hash_datas2; uint32_t hash_len; uint32_t j; struct hash_data *hd1, *hd2; hash_datas1 = hash_datas2 = NULL; bulks1 = get_multi_bulk_array_from_mbuf_list(cunit->result1->data); bulks2 = get_multi_bulk_array_from_mbuf_list(cunit->result2->data); if (bulks1 == NULL || bulks2 == NULL) { log_error("ERROR: get multi bulk array from mbufs failed"); goto error; } if (array_n(bulks1)%2 != 0 || array_n(bulks2)%2 != 0) { log_error("ERROR: bad hash value"); goto error; } if (array_n(bulks1) != array_n(bulks2)) { chdata->err_inconsistent_value_keys_count ++; rmt_safe_snprintf(extra_err, 50, ", value is inconsistent\0"); goto error; } hash_len = array_n(bulks1)/2; hash_datas1 = array_create(hash_len, sizeof(struct hash_data)); hash_datas2 = array_create(hash_len, sizeof(struct hash_data)); for (j = 0; j < hash_len; j ++) { hd1 = array_push(hash_datas1); hd2 = array_push(hash_datas2); bulk1 = array_pop(bulks1); bulk2 = array_pop(bulks1); hd1->field = *bulk1; hd1->value = *bulk2; bulk1 = array_pop(bulks2); bulk2 = array_pop(bulks2); hd2->field = *bulk1; hd2->value = *bulk2; } array_sort(hash_datas1, hash_data_field_cmp); array_sort(hash_datas2, hash_data_field_cmp); for (j = 0; j < array_n(bulks1); j ++) { hd1 = array_get(hash_datas1, j); hd2 = array_get(hash_datas2, j); if (string_binary_cmp(hd1->field, hd2->field) != 0) { chdata->err_inconsistent_value_keys_count ++; rmt_safe_snprintf(extra_err, 50, ", value is inconsistent\0"); if (hash_datas1 != NULL) { while (array_n(hash_datas1) > 0) { hd1 = array_pop(hash_datas1); sdsfree(hd1->field); sdsfree(hd1->value); } array_destroy(hash_datas1); hash_datas1 = NULL; } if (hash_datas2 != NULL) { while (array_n(hash_datas2) > 0) { hd2 = array_pop(hash_datas2); sdsfree(hd2->field); sdsfree(hd2->value); } array_destroy(hash_datas2); hash_datas2 = NULL; } goto error; } if (string_binary_cmp(hd1->value, hd2->value) != 0) { chdata->err_inconsistent_value_keys_count ++; rmt_safe_snprintf(extra_err, 50, ", value is inconsistent\0"); if (hash_datas1 != NULL) { while (array_n(hash_datas1) > 0) { hd1 = array_pop(hash_datas1); sdsfree(hd1->field); sdsfree(hd1->value); } array_destroy(hash_datas1); hash_datas1 = NULL; } if (hash_datas2 != NULL) { while (array_n(hash_datas2) > 0) { hd2 = array_pop(hash_datas2); sdsfree(hd2->field); sdsfree(hd2->value); } array_destroy(hash_datas2); hash_datas2 = NULL; } goto error; } } if (hash_datas1 != NULL) { while (array_n(hash_datas1) > 0) { hd1 = array_pop(hash_datas1); sdsfree(hd1->field); sdsfree(hd1->value); } array_destroy(hash_datas1); hash_datas1 = NULL; } if (hash_datas2 != NULL) { while (array_n(hash_datas2) > 0) { hd2 = array_pop(hash_datas2); sdsfree(hd2->field); sdsfree(hd2->value); } array_destroy(hash_datas2); hash_datas2 = NULL; } } else if (msg_data_compare(cunit->result1, cunit->result2) != 0) { chdata->err_inconsistent_value_keys_count ++; rmt_safe_snprintf(extra_err, 50, ", value is inconsistent\0"); goto error; } msg_put(cunit->result1); msg_free(cunit->result1); cunit->result1 = NULL; msg_put(cunit->result2); msg_free(cunit->result2); cunit->result2 = NULL; if (bulks1 != NULL) { while (array_n(bulks1) > 0) { bulk1 = array_pop(bulks1); sdsfree(*bulk1); } array_destroy(bulks1); bulks1 = NULL; } if (bulks2 != NULL) { while (array_n(bulks2) > 0) { bulk2 = array_pop(bulks2); sdsfree(*bulk2); } array_destroy(bulks2); bulks2 = NULL; } msg = msg_get(r->mb, 1, REDIS_DATA_TYPE_CMD); if (msg == NULL) { log_error("ERROR: out of memory."); goto error; } arg = array_push(&args); *arg = sdsnew("ttl"); arg = array_push(&args); *arg = sdsdup(cunit->key); ret = redis_msg_append_command_full_safe(msg, &args); if (ret != RMT_OK) { log_error("ERROR: msg append multi bulk len failed."); goto error; } while (array_n(&args) > 0) { arg = array_pop(&args); sdsfree(*arg); } msg->ptr = cunit; msg->resp_check = check_response; ret = send_msg_to_all(cunit, msg); if (ret != RMT_OK) { log_error("ERROR: send msg to source and target group failed."); goto error; } cunit->state = CHECK_UNIT_STATE_GET_EXPIRE; } goto next_step; } if (cunit->state == CHECK_UNIT_STATE_GET_EXPIRE) { ASSERT(cunit->key != NULL); ASSERT(cunit->key_type >= 0); ASSERT(cunit->result1 == NULL || cunit->result2 == NULL); if (resp->type != MSG_RSP_REDIS_INTEGER) { log_error("ERROR: the response type for command 'ttl' from node[%s] is error: %s", rnode->addr, msg_type_string(resp->type)); goto error; } if (cunit->result1 == NULL) { cunit->result1 = resp; resp = NULL; } else if (cunit->result2 == NULL) { cunit->result2 = resp; resp = NULL; } else { NOT_REACHED(); } if (cunit->result1 != NULL && cunit->result2 != NULL) { if (msg_data_compare(cunit->result1, cunit->result2) != 0) { int mistake = (int)cunit->result1->integer - (int)cunit->result2->integer; ASSERT(mistake != 0); if (abs(mistake) > TTL_MISTAKE_CAN_BE_ACCEPT) { chdata->err_inconsistent_expire_keys_count ++; rmt_safe_snprintf(extra_err, 50, ", remaining time are %"PRIu32" and %"PRIu32"\0", cunit->result1->integer, cunit->result2->integer); goto error; } } /* OK, this key is consistent between source group and target group */ goto done; } goto next_step; } done: check_unit_destroy(cunit); next_step: msg_put(r); msg_free(r); if (resp != NULL) { msg_put(resp); msg_free(resp); } array_deinit(&args); return RMT_OK; error: chdata->err_check_keys_count ++; if (cunit->key != NULL) { log_error("ERROR: key checked failed: %s%s. key(len:%zu, type:%s): %.*s", get_check_error(cunit), extra_err, sdslen(cunit->key), get_redis_type_string(cunit->key_type), sdslen(cunit->key), cunit->key); } else { log_error("ERROR: key checked failed: %s%s.", get_check_error(cunit), extra_err); } MSG_DUMP(r, LOG_ERR, 1); msg_put(r); msg_free(r); if (resp != NULL) { MSG_DUMP(resp, LOG_ERR, 1); msg_put(resp); msg_free(resp); } if (msg != NULL) { msg_put(msg); msg_free(msg); } check_unit_destroy(cunit); while (array_n(&args) > 0) { arg = array_pop(&args); sdsfree(*arg); } array_deinit(&args); if (bulks1 != NULL) { while (array_n(bulks1) > 0) { bulk1 = array_pop(bulks1); sdsfree(*bulk1); } array_destroy(bulks1); bulks1 = NULL; } if (bulks2 != NULL) { while (array_n(bulks2) > 0) { bulk2 = array_pop(bulks2); sdsfree(*bulk2); } array_destroy(bulks2); bulks2 = NULL; } return RMT_OK; }