1 #include "redis.h" 2 3 #include <signal.h> 4 #include <ctype.h> 5 6 void SlotToKeyAdd(robj *key); 7 void SlotToKeyDel(robj *key); 8 9 /*----------------------------------------------------------------------------- 10 * C-level DB API 11 *----------------------------------------------------------------------------*/ 12 13 /* Important notes on lookup and disk store. 14 * 15 * When disk store is enabled on lookup we can have different cases. 16 * 17 * a) The key is in memory: 18 * - If the key is not in IO_SAVEINPROG state we can access it. 19 * As if it's just IO_SAVE this means we have the key in the IO queue 20 * but can't be accessed by the IO thread (it requires to be 21 * translated into an IO Job by the cache cron function.) 22 * - If the key is in IO_SAVEINPROG we can't touch the key and have 23 * to blocking wait completion of operations. 24 * b) The key is not in memory: 25 * - If it's marked as non existing on disk as well (negative cache) 26 * we don't need to perform the disk access. 27 * - if the key MAY EXIST, but is not in memory, and it is marked as IO_SAVE 28 * then the key can only be a deleted one. As IO_SAVE keys are never 29 * evicted (dirty state), so the only possibility is that key was deleted. 30 * - if the key MAY EXIST we need to blocking load it. 31 * We check that the key is not in IO_SAVEINPROG state before accessing 32 * the disk object. If it is in this state, we wait. 33 */ 34 35 robj *lookupKey(redisDb *db, robj *key) { 36 dictEntry *de = dictFind(db->dict,key->ptr); 37 if (de) { 38 robj *val = dictGetVal(de); 39 40 /* Update the access time for the aging algorithm. 41 * Don't do it if we have a saving child, as this will trigger 42 * a copy on write madness. */ 43 if (server.rdb_child_pid == -1 && server.aof_child_pid == -1) 44 val->lru = server.lruclock; 45 return val; 46 } else { 47 return NULL; 48 } 49 } 50 51 robj *lookupKeyRead(redisDb *db, robj *key) { 52 robj *val; 53 54 expireIfNeeded(db,key); 55 val = lookupKey(db,key); 56 if (val == NULL) 57 server.stat_keyspace_misses++; 58 else 59 server.stat_keyspace_hits++; 60 return val; 61 } 62 63 robj *lookupKeyWrite(redisDb *db, robj *key) { 64 expireIfNeeded(db,key); 65 return lookupKey(db,key); 66 } 67 68 robj *lookupKeyReadOrReply(redisClient *c, robj *key, robj *reply) { 69 robj *o = lookupKeyRead(c->db, key); 70 if (!o) addReply(c,reply); 71 return o; 72 } 73 74 robj *lookupKeyWriteOrReply(redisClient *c, robj *key, robj *reply) { 75 robj *o = lookupKeyWrite(c->db, key); 76 if (!o) addReply(c,reply); 77 return o; 78 } 79 80 /* Add the key to the DB. It's up to the caller to increment the reference 81 * counte of the value if needed. 82 * 83 * The program is aborted if the key already exists. */ 84 void dbAdd(redisDb *db, robj *key, robj *val) { 85 sds copy = sdsdup(key->ptr); 86 int retval = dictAdd(db->dict, copy, val); 87 88 redisAssertWithInfo(NULL,key,retval == REDIS_OK); 89 if (server.cluster_enabled) SlotToKeyAdd(key); 90 } 91 92 /* Overwrite an existing key with a new value. Incrementing the reference 93 * count of the new value is up to the caller. 94 * This function does not modify the expire time of the existing key. 95 * 96 * The program is aborted if the key was not already present. */ 97 void dbOverwrite(redisDb *db, robj *key, robj *val) { 98 struct dictEntry *de = dictFind(db->dict,key->ptr); 99 100 redisAssertWithInfo(NULL,key,de != NULL); 101 dictReplace(db->dict, key->ptr, val); 102 } 103 104 /* High level Set operation. This function can be used in order to set 105 * a key, whatever it was existing or not, to a new object. 106 * 107 * 1) The ref count of the value object is incremented. 108 * 2) clients WATCHing for the destination key notified. 109 * 3) The expire time of the key is reset (the key is made persistent). */ 110 void setKey(redisDb *db, robj *key, robj *val) { 111 if (lookupKeyWrite(db,key) == NULL) { 112 dbAdd(db,key,val); 113 } else { 114 dbOverwrite(db,key,val); 115 } 116 incrRefCount(val); 117 removeExpire(db,key); 118 signalModifiedKey(db,key); 119 } 120 121 int dbExists(redisDb *db, robj *key) { 122 return dictFind(db->dict,key->ptr) != NULL; 123 } 124 125 /* Return a random key, in form of a Redis object. 126 * If there are no keys, NULL is returned. 127 * 128 * The function makes sure to return keys not already expired. */ 129 robj *dbRandomKey(redisDb *db) { 130 struct dictEntry *de; 131 132 while(1) { 133 sds key; 134 robj *keyobj; 135 136 de = dictGetRandomKey(db->dict); 137 if (de == NULL) return NULL; 138 139 key = dictGetKey(de); 140 keyobj = createStringObject(key,sdslen(key)); 141 if (dictFind(db->expires,key)) { 142 if (expireIfNeeded(db,keyobj)) { 143 decrRefCount(keyobj); 144 continue; /* search for another key. This expired. */ 145 } 146 } 147 return keyobj; 148 } 149 } 150 151 /* Delete a key, value, and associated expiration entry if any, from the DB */ 152 int dbDelete(redisDb *db, robj *key) { 153 /* Deleting an entry from the expires dict will not free the sds of 154 * the key, because it is shared with the main dictionary. */ 155 if (dictSize(db->expires) > 0) dictDelete(db->expires,key->ptr); 156 if (dictDelete(db->dict,key->ptr) == DICT_OK) { 157 if (server.cluster_enabled) SlotToKeyDel(key); 158 return 1; 159 } else { 160 return 0; 161 } 162 } 163 164 /* Empty the whole database. 165 * If diskstore is enabled this function will just flush the in-memory cache. */ 166 long long emptyDb() { 167 int j; 168 long long removed = 0; 169 170 for (j = 0; j < server.dbnum; j++) { 171 removed += dictSize(server.db[j].dict); 172 dictEmpty(server.db[j].dict); 173 dictEmpty(server.db[j].expires); 174 } 175 return removed; 176 } 177 178 int selectDb(redisClient *c, int id) { 179 if (id < 0 || id >= server.dbnum) 180 return REDIS_ERR; 181 c->db = &server.db[id]; 182 return REDIS_OK; 183 } 184 185 /*----------------------------------------------------------------------------- 186 * Hooks for key space changes. 187 * 188 * Every time a key in the database is modified the function 189 * signalModifiedKey() is called. 190 * 191 * Every time a DB is flushed the function signalFlushDb() is called. 192 *----------------------------------------------------------------------------*/ 193 194 void signalModifiedKey(redisDb *db, robj *key) { 195 touchWatchedKey(db,key); 196 } 197 198 void signalFlushedDb(int dbid) { 199 touchWatchedKeysOnFlush(dbid); 200 } 201 202 /*----------------------------------------------------------------------------- 203 * Type agnostic commands operating on the key space 204 *----------------------------------------------------------------------------*/ 205 206 void flushdbCommand(redisClient *c) { 207 server.dirty += dictSize(c->db->dict); 208 signalFlushedDb(c->db->id); 209 dictEmpty(c->db->dict); 210 dictEmpty(c->db->expires); 211 addReply(c,shared.ok); 212 } 213 214 void flushallCommand(redisClient *c) { 215 signalFlushedDb(-1); 216 server.dirty += emptyDb(); 217 addReply(c,shared.ok); 218 if (server.rdb_child_pid != -1) { 219 kill(server.rdb_child_pid,SIGKILL); 220 rdbRemoveTempFile(server.rdb_child_pid); 221 } 222 if (server.saveparamslen > 0) { 223 /* Normally rdbSave() will reset dirty, but we don't want this here 224 * as otherwise FLUSHALL will not be replicated nor put into the AOF. */ 225 int saved_dirty = server.dirty; 226 rdbSave(server.rdb_filename); 227 server.dirty = saved_dirty; 228 } 229 server.dirty++; 230 } 231 232 void delCommand(redisClient *c) { 233 int deleted = 0, j; 234 235 for (j = 1; j < c->argc; j++) { 236 if (dbDelete(c->db,c->argv[j])) { 237 signalModifiedKey(c->db,c->argv[j]); 238 server.dirty++; 239 deleted++; 240 } 241 } 242 addReplyLongLong(c,deleted); 243 } 244 245 void existsCommand(redisClient *c) { 246 expireIfNeeded(c->db,c->argv[1]); 247 if (dbExists(c->db,c->argv[1])) { 248 addReply(c, shared.cone); 249 } else { 250 addReply(c, shared.czero); 251 } 252 } 253 254 void selectCommand(redisClient *c) { 255 int id = atoi(c->argv[1]->ptr); 256 257 if (server.cluster_enabled && id != 0) { 258 addReplyError(c,"SELECT is not allowed in cluster mode"); 259 return; 260 } 261 if (selectDb(c,id) == REDIS_ERR) { 262 addReplyError(c,"invalid DB index"); 263 } else { 264 addReply(c,shared.ok); 265 } 266 } 267 268 void randomkeyCommand(redisClient *c) { 269 robj *key; 270 271 if ((key = dbRandomKey(c->db)) == NULL) { 272 addReply(c,shared.nullbulk); 273 return; 274 } 275 276 addReplyBulk(c,key); 277 decrRefCount(key); 278 } 279 280 void keysCommand(redisClient *c) { 281 dictIterator *di; 282 dictEntry *de; 283 sds pattern = c->argv[1]->ptr; 284 int plen = sdslen(pattern), allkeys; 285 unsigned long numkeys = 0; 286 void *replylen = addDeferredMultiBulkLength(c); 287 288 di = dictGetIterator(c->db->dict); 289 allkeys = (pattern[0] == '*' && pattern[1] == '\0'); 290 while((de = dictNext(di)) != NULL) { 291 sds key = dictGetKey(de); 292 robj *keyobj; 293 294 if (allkeys || stringmatchlen(pattern,plen,key,sdslen(key),0)) { 295 keyobj = createStringObject(key,sdslen(key)); 296 if (expireIfNeeded(c->db,keyobj) == 0) { 297 addReplyBulk(c,keyobj); 298 numkeys++; 299 } 300 decrRefCount(keyobj); 301 } 302 } 303 dictReleaseIterator(di); 304 setDeferredMultiBulkLength(c,replylen,numkeys); 305 } 306 307 void dbsizeCommand(redisClient *c) { 308 addReplyLongLong(c,dictSize(c->db->dict)); 309 } 310 311 void lastsaveCommand(redisClient *c) { 312 addReplyLongLong(c,server.lastsave); 313 } 314 315 void typeCommand(redisClient *c) { 316 robj *o; 317 char *type; 318 319 o = lookupKeyRead(c->db,c->argv[1]); 320 if (o == NULL) { 321 type = "none"; 322 } else { 323 switch(o->type) { 324 case REDIS_STRING: type = "string"; break; 325 case REDIS_LIST: type = "list"; break; 326 case REDIS_SET: type = "set"; break; 327 case REDIS_ZSET: type = "zset"; break; 328 case REDIS_HASH: type = "hash"; break; 329 default: type = "unknown"; break; 330 } 331 } 332 addReplyStatus(c,type); 333 } 334 335 void shutdownCommand(redisClient *c) { 336 int flags = 0; 337 338 if (c->argc > 2) { 339 addReply(c,shared.syntaxerr); 340 return; 341 } else if (c->argc == 2) { 342 if (!strcasecmp(c->argv[1]->ptr,"nosave")) { 343 flags |= REDIS_SHUTDOWN_NOSAVE; 344 } else if (!strcasecmp(c->argv[1]->ptr,"save")) { 345 flags |= REDIS_SHUTDOWN_SAVE; 346 } else { 347 addReply(c,shared.syntaxerr); 348 return; 349 } 350 } 351 if (prepareForShutdown(flags) == REDIS_OK) exit(0); 352 addReplyError(c,"Errors trying to SHUTDOWN. Check logs."); 353 } 354 355 void renameGenericCommand(redisClient *c, int nx) { 356 robj *o; 357 long long expire; 358 359 /* To use the same key as src and dst is probably an error */ 360 if (sdscmp(c->argv[1]->ptr,c->argv[2]->ptr) == 0) { 361 addReply(c,shared.sameobjecterr); 362 return; 363 } 364 365 if ((o = lookupKeyWriteOrReply(c,c->argv[1],shared.nokeyerr)) == NULL) 366 return; 367 368 incrRefCount(o); 369 expire = getExpire(c->db,c->argv[1]); 370 if (lookupKeyWrite(c->db,c->argv[2]) != NULL) { 371 if (nx) { 372 decrRefCount(o); 373 addReply(c,shared.czero); 374 return; 375 } 376 /* Overwrite: delete the old key before creating the new one with the same name. */ 377 dbDelete(c->db,c->argv[2]); 378 } 379 dbAdd(c->db,c->argv[2],o); 380 if (expire != -1) setExpire(c->db,c->argv[2],expire); 381 dbDelete(c->db,c->argv[1]); 382 signalModifiedKey(c->db,c->argv[1]); 383 signalModifiedKey(c->db,c->argv[2]); 384 server.dirty++; 385 addReply(c,nx ? shared.cone : shared.ok); 386 } 387 388 void renameCommand(redisClient *c) { 389 renameGenericCommand(c,0); 390 } 391 392 void renamenxCommand(redisClient *c) { 393 renameGenericCommand(c,1); 394 } 395 396 void moveCommand(redisClient *c) { 397 robj *o; 398 redisDb *src, *dst; 399 int srcid; 400 401 if (server.cluster_enabled) { 402 addReplyError(c,"MOVE is not allowed in cluster mode"); 403 return; 404 } 405 406 /* Obtain source and target DB pointers */ 407 src = c->db; 408 srcid = c->db->id; 409 if (selectDb(c,atoi(c->argv[2]->ptr)) == REDIS_ERR) { 410 addReply(c,shared.outofrangeerr); 411 return; 412 } 413 dst = c->db; 414 selectDb(c,srcid); /* Back to the source DB */ 415 416 /* If the user is moving using as target the same 417 * DB as the source DB it is probably an error. */ 418 if (src == dst) { 419 addReply(c,shared.sameobjecterr); 420 return; 421 } 422 423 /* Check if the element exists and get a reference */ 424 o = lookupKeyWrite(c->db,c->argv[1]); 425 if (!o) { 426 addReply(c,shared.czero); 427 return; 428 } 429 430 /* Return zero if the key already exists in the target DB */ 431 if (lookupKeyWrite(dst,c->argv[1]) != NULL) { 432 addReply(c,shared.czero); 433 return; 434 } 435 dbAdd(dst,c->argv[1],o); 436 incrRefCount(o); 437 438 /* OK! key moved, free the entry in the source DB */ 439 dbDelete(src,c->argv[1]); 440 server.dirty++; 441 addReply(c,shared.cone); 442 } 443 444 /*----------------------------------------------------------------------------- 445 * Expires API 446 *----------------------------------------------------------------------------*/ 447 448 int removeExpire(redisDb *db, robj *key) { 449 /* An expire may only be removed if there is a corresponding entry in the 450 * main dict. Otherwise, the key will never be freed. */ 451 redisAssertWithInfo(NULL,key,dictFind(db->dict,key->ptr) != NULL); 452 return dictDelete(db->expires,key->ptr) == DICT_OK; 453 } 454 455 void setExpire(redisDb *db, robj *key, long long when) { 456 dictEntry *kde, *de; 457 458 /* Reuse the sds from the main dict in the expire dict */ 459 kde = dictFind(db->dict,key->ptr); 460 redisAssertWithInfo(NULL,key,kde != NULL); 461 de = dictReplaceRaw(db->expires,dictGetKey(kde)); 462 dictSetSignedIntegerVal(de,when); 463 } 464 465 /* Return the expire time of the specified key, or -1 if no expire 466 * is associated with this key (i.e. the key is non volatile) */ 467 long long getExpire(redisDb *db, robj *key) { 468 dictEntry *de; 469 470 /* No expire? return ASAP */ 471 if (dictSize(db->expires) == 0 || 472 (de = dictFind(db->expires,key->ptr)) == NULL) return -1; 473 474 /* The entry was found in the expire dict, this means it should also 475 * be present in the main dict (safety check). */ 476 redisAssertWithInfo(NULL,key,dictFind(db->dict,key->ptr) != NULL); 477 return dictGetSignedIntegerVal(de); 478 } 479 480 /* Propagate expires into slaves and the AOF file. 481 * When a key expires in the master, a DEL operation for this key is sent 482 * to all the slaves and the AOF file if enabled. 483 * 484 * This way the key expiry is centralized in one place, and since both 485 * AOF and the master->slave link guarantee operation ordering, everything 486 * will be consistent even if we allow write operations against expiring 487 * keys. */ 488 void propagateExpire(redisDb *db, robj *key) { 489 robj *argv[2]; 490 491 argv[0] = shared.del; 492 argv[1] = key; 493 incrRefCount(argv[0]); 494 incrRefCount(argv[1]); 495 496 if (server.aof_state != REDIS_AOF_OFF) 497 feedAppendOnlyFile(server.delCommand,db->id,argv,2); 498 if (listLength(server.slaves)) 499 replicationFeedSlaves(server.slaves,db->id,argv,2); 500 501 decrRefCount(argv[0]); 502 decrRefCount(argv[1]); 503 } 504 505 int expireIfNeeded(redisDb *db, robj *key) { 506 long long when = getExpire(db,key); 507 508 if (when < 0) return 0; /* No expire for this key */ 509 510 /* Don't expire anything while loading. It will be done later. */ 511 if (server.loading) return 0; 512 513 /* If we are running in the context of a slave, return ASAP: 514 * the slave key expiration is controlled by the master that will 515 * send us synthesized DEL operations for expired keys. 516 * 517 * Still we try to return the right information to the caller, 518 * that is, 0 if we think the key should be still valid, 1 if 519 * we think the key is expired at this time. */ 520 if (server.masterhost != NULL) { 521 return time(NULL) > when; 522 } 523 524 /* Return when this key has not expired */ 525 if (mstime() <= when) return 0; 526 527 /* Delete the key */ 528 server.stat_expiredkeys++; 529 propagateExpire(db,key); 530 return dbDelete(db,key); 531 } 532 533 /*----------------------------------------------------------------------------- 534 * Expires Commands 535 *----------------------------------------------------------------------------*/ 536 537 /* Given an string object return true if it contains exactly the "ms" 538 * or "MS" string. This is used in order to check if the last argument 539 * of EXPIRE, EXPIREAT or TTL is "ms" to switch into millisecond input/output */ 540 int stringObjectEqualsMs(robj *a) { 541 char *arg = a->ptr; 542 return tolower(arg[0]) == 'm' && tolower(arg[1]) == 's' && arg[2] == '\0'; 543 } 544 545 void expireGenericCommand(redisClient *c, long long offset, int unit) { 546 dictEntry *de; 547 robj *key = c->argv[1], *param = c->argv[2]; 548 long long milliseconds; 549 550 if (getLongLongFromObjectOrReply(c, param, &milliseconds, NULL) != REDIS_OK) 551 return; 552 553 if (unit == UNIT_SECONDS) milliseconds *= 1000; 554 milliseconds -= offset; 555 556 de = dictFind(c->db->dict,key->ptr); 557 if (de == NULL) { 558 addReply(c,shared.czero); 559 return; 560 } 561 /* EXPIRE with negative TTL, or EXPIREAT with a timestamp into the past 562 * should never be executed as a DEL when load the AOF or in the context 563 * of a slave instance. 564 * 565 * Instead we take the other branch of the IF statement setting an expire 566 * (possibly in the past) and wait for an explicit DEL from the master. */ 567 if (milliseconds <= 0 && !server.loading && !server.masterhost) { 568 robj *aux; 569 570 redisAssertWithInfo(c,key,dbDelete(c->db,key)); 571 server.dirty++; 572 573 /* Replicate/AOF this as an explicit DEL. */ 574 aux = createStringObject("DEL",3); 575 rewriteClientCommandVector(c,2,aux,key); 576 decrRefCount(aux); 577 signalModifiedKey(c->db,key); 578 addReply(c, shared.cone); 579 return; 580 } else { 581 long long when = mstime()+milliseconds; 582 setExpire(c->db,key,when); 583 addReply(c,shared.cone); 584 signalModifiedKey(c->db,key); 585 server.dirty++; 586 return; 587 } 588 } 589 590 void expireCommand(redisClient *c) { 591 expireGenericCommand(c,0,UNIT_SECONDS); 592 } 593 594 void expireatCommand(redisClient *c) { 595 expireGenericCommand(c,mstime(),UNIT_SECONDS); 596 } 597 598 void pexpireCommand(redisClient *c) { 599 expireGenericCommand(c,0,UNIT_MILLISECONDS); 600 } 601 602 void pexpireatCommand(redisClient *c) { 603 expireGenericCommand(c,mstime(),UNIT_MILLISECONDS); 604 } 605 606 void ttlGenericCommand(redisClient *c, int output_ms) { 607 long long expire, ttl = -1; 608 609 expire = getExpire(c->db,c->argv[1]); 610 if (expire != -1) { 611 ttl = expire-mstime(); 612 if (ttl < 0) ttl = -1; 613 } 614 if (ttl == -1) { 615 addReplyLongLong(c,-1); 616 } else { 617 addReplyLongLong(c,output_ms ? ttl : ((ttl+500)/1000)); 618 } 619 } 620 621 void ttlCommand(redisClient *c) { 622 ttlGenericCommand(c, 0); 623 } 624 625 void pttlCommand(redisClient *c) { 626 ttlGenericCommand(c, 1); 627 } 628 629 void persistCommand(redisClient *c) { 630 dictEntry *de; 631 632 de = dictFind(c->db->dict,c->argv[1]->ptr); 633 if (de == NULL) { 634 addReply(c,shared.czero); 635 } else { 636 if (removeExpire(c->db,c->argv[1])) { 637 addReply(c,shared.cone); 638 server.dirty++; 639 } else { 640 addReply(c,shared.czero); 641 } 642 } 643 } 644 645 /* ----------------------------------------------------------------------------- 646 * API to get key arguments from commands 647 * ---------------------------------------------------------------------------*/ 648 649 int *getKeysUsingCommandTable(struct redisCommand *cmd,robj **argv, int argc, int *numkeys) { 650 int j, i = 0, last, *keys; 651 REDIS_NOTUSED(argv); 652 653 if (cmd->firstkey == 0) { 654 *numkeys = 0; 655 return NULL; 656 } 657 last = cmd->lastkey; 658 if (last < 0) last = argc+last; 659 keys = zmalloc(sizeof(int)*((last - cmd->firstkey)+1)); 660 for (j = cmd->firstkey; j <= last; j += cmd->keystep) { 661 redisAssert(j < argc); 662 keys[i++] = j; 663 } 664 *numkeys = i; 665 return keys; 666 } 667 668 int *getKeysFromCommand(struct redisCommand *cmd,robj **argv, int argc, int *numkeys, int flags) { 669 if (cmd->getkeys_proc) { 670 return cmd->getkeys_proc(cmd,argv,argc,numkeys,flags); 671 } else { 672 return getKeysUsingCommandTable(cmd,argv,argc,numkeys); 673 } 674 } 675 676 void getKeysFreeResult(int *result) { 677 zfree(result); 678 } 679 680 int *noPreloadGetKeys(struct redisCommand *cmd,robj **argv, int argc, int *numkeys, int flags) { 681 if (flags & REDIS_GETKEYS_PRELOAD) { 682 *numkeys = 0; 683 return NULL; 684 } else { 685 return getKeysUsingCommandTable(cmd,argv,argc,numkeys); 686 } 687 } 688 689 int *renameGetKeys(struct redisCommand *cmd,robj **argv, int argc, int *numkeys, int flags) { 690 if (flags & REDIS_GETKEYS_PRELOAD) { 691 int *keys = zmalloc(sizeof(int)); 692 *numkeys = 1; 693 keys[0] = 1; 694 return keys; 695 } else { 696 return getKeysUsingCommandTable(cmd,argv,argc,numkeys); 697 } 698 } 699 700 int *zunionInterGetKeys(struct redisCommand *cmd,robj **argv, int argc, int *numkeys, int flags) { 701 int i, num, *keys; 702 REDIS_NOTUSED(cmd); 703 REDIS_NOTUSED(flags); 704 705 num = atoi(argv[2]->ptr); 706 /* Sanity check. Don't return any key if the command is going to 707 * reply with syntax error. */ 708 if (num > (argc-3)) { 709 *numkeys = 0; 710 return NULL; 711 } 712 keys = zmalloc(sizeof(int)*num); 713 for (i = 0; i < num; i++) keys[i] = 3+i; 714 *numkeys = num; 715 return keys; 716 } 717 718 /* Slot to Key API. This is used by Redis Cluster in order to obtain in 719 * a fast way a key that belongs to a specified hash slot. This is useful 720 * while rehashing the cluster. */ 721 void SlotToKeyAdd(robj *key) { 722 unsigned int hashslot = keyHashSlot(key->ptr,sdslen(key->ptr)); 723 724 zslInsert(server.cluster.slots_to_keys,hashslot,key); 725 incrRefCount(key); 726 } 727 728 void SlotToKeyDel(robj *key) { 729 unsigned int hashslot = keyHashSlot(key->ptr,sdslen(key->ptr)); 730 731 zslDelete(server.cluster.slots_to_keys,hashslot,key); 732 } 733 734 unsigned int GetKeysInSlot(unsigned int hashslot, robj **keys, unsigned int count) { 735 zskiplistNode *n; 736 zrangespec range; 737 int j = 0; 738 739 range.min = range.max = hashslot; 740 range.minex = range.maxex = 0; 741 742 n = zslFirstInRange(server.cluster.slots_to_keys, range); 743 while(n && n->score == hashslot && count--) { 744 keys[j++] = n->obj; 745 n = n->level[0].forward; 746 } 747 return j; 748 } 749