redis4.0.9 SET\GET方法

从哪里开始

server.c里面有每个redis命令对应的执行方法

struct redisCommand redisCommandTable[] = {
{"module",moduleCommand,-2,"as",0,NULL,0,0,0,0,0},
{"get",getCommand,2,"rF",0,NULL,1,1,1,0,0},
{"set",setCommand,-3,"wm",0,NULL,1,1,1,0,0},
{"setnx",setnxCommand,3,"wmF",0,NULL,1,1,1,0,0},
{"setex",setexCommand,4,"wm",0,NULL,1,1,1,0,0},
{"psetex",psetexCommand,4,"wm",0,NULL,1,1,1,0,0},
...
}

set命令对应setCommand方法,get命令对应getCommand方法

Set in t_string.c

先来看一下setCommand方法,了解一下set命令的流程

/* SET key value [NX] [XX] [EX <seconds>] [PX <milliseconds>] */
void setCommand(client *c) {
int j;
robj *expire = NULL;
int unit = UNIT_SECONDS;
int flags = OBJ_SET_NO_FLAGS; for (j = 3; j < c->argc; j++) {
char *a = c->argv[j]->ptr;
robj *next = (j == c->argc-1) ? NULL : c->argv[j+1]; if ((a[0] == 'n' || a[0] == 'N') &&
(a[1] == 'x' || a[1] == 'X') && a[2] == '\0' &&
!(flags & OBJ_SET_XX))
{
flags |= OBJ_SET_NX;
} else if ((a[0] == 'x' || a[0] == 'X') &&
(a[1] == 'x' || a[1] == 'X') && a[2] == '\0' &&
!(flags & OBJ_SET_NX))
{
flags |= OBJ_SET_XX;
} else if ((a[0] == 'e' || a[0] == 'E') &&
(a[1] == 'x' || a[1] == 'X') && a[2] == '\0' &&
!(flags & OBJ_SET_PX) && next)
{
flags |= OBJ_SET_EX;
unit = UNIT_SECONDS;
expire = next;
j++;
} else if ((a[0] == 'p' || a[0] == 'P') &&
(a[1] == 'x' || a[1] == 'X') && a[2] == '\0' &&
!(flags & OBJ_SET_EX) && next)
{
flags |= OBJ_SET_PX;
unit = UNIT_MILLISECONDS;
expire = next;
j++;
} else {
addReply(c,shared.syntaxerr);
return;
}
} c->argv[2] = tryObjectEncoding(c->argv[2]);
setGenericCommand(c,flags,c->argv[1],c->argv[2],expire,unit,NULL,NULL);
}
  • 可以看到是从第4(数组下标为3)个参数开始解析参数的,目的是判断出当前set操作,是NX|XX|EX|PX,如果没有任何命令,会报语法错误
  • encoding第3个参数,这是存储的值所在的参数
  • set操作执行

tryObjectEncoding会根据类型将值进行encoding,这里包含了很多优化操作,如字符串类型,实际上保存的是一个20位内的数字,会用long来存,原因是long占用的空间更少,这个方法只encoding OBJ_STRING类型

/* Try to encode a string object in order to save space */
robj *tryObjectEncoding(robj *o) {
long value;
sds s = o->ptr;
size_t len; /* Make sure this is a string object, the only type we encode
* in this function. Other types use encoded memory efficient
* representations but are handled by the commands implementing
* the type. */
serverAssertWithInfo(NULL,o,o->type == OBJ_STRING); /* We try some specialized encoding only for objects that are
* RAW or EMBSTR encoded, in other words objects that are still
* in represented by an actually array of chars. */
if (!sdsEncodedObject(o)) return o; /* It's not safe to encode shared objects: shared objects can be shared
* everywhere in the "object space" of Redis and may end in places where
* they are not handled. We handle them only as values in the keyspace. */
if (o->refcount > 1) return o; /* Check if we can represent this string as a long integer.
* Note that we are sure that a string larger than 20 chars is not
* representable as a 32 nor 64 bit integer. */
len = sdslen(s);
if (len <= 20 && string2l(s,len,&value)) {
/* This object is encodable as a long. Try to use a shared object.
* Note that we avoid using shared integers when maxmemory is used
* because every object needs to have a private LRU field for the LRU
* algorithm to work well. */
if ((server.maxmemory == 0 ||
!(server.maxmemory_policy & MAXMEMORY_FLAG_NO_SHARED_INTEGERS)) &&
value >= 0 &&
value < OBJ_SHARED_INTEGERS)
{
decrRefCount(o);
incrRefCount(shared.integers[value]);
return shared.integers[value];
} else {
if (o->encoding == OBJ_ENCODING_RAW) sdsfree(o->ptr);
o->encoding = OBJ_ENCODING_INT;
o->ptr = (void*) value;
return o;
}
} /* If the string is small and is still RAW encoded,
* try the EMBSTR encoding which is more efficient.
* In this representation the object and the SDS string are allocated
* in the same chunk of memory to save space and cache misses. */
if (len <= OBJ_ENCODING_EMBSTR_SIZE_LIMIT) {
robj *emb; if (o->encoding == OBJ_ENCODING_EMBSTR) return o;
emb = createEmbeddedStringObject(s,sdslen(s));
decrRefCount(o);
return emb;
} /* We can't encode the object...
*
* Do the last try, and at least optimize the SDS string inside
* the string object to require little space, in case there
* is more than 10% of free space at the end of the SDS string.
*
* We do that only for relatively large strings as this branch
* is only entered if the length of the string is greater than
* OBJ_ENCODING_EMBSTR_SIZE_LIMIT. */
if (o->encoding == OBJ_ENCODING_RAW &&
sdsavail(s) > len/10)
{
o->ptr = sdsRemoveFreeSpace(o->ptr);
} /* Return the original object. */
return o;
}
  • 方法只encoding String类型的值,目的是压缩字符串占用的空间

  • 如果不是指定可压缩类型,直接返回

    define sdsEncodedObject(objptr) (objptr->encoding == OBJ_ENCODING_RAW || objptr->encoding == OBJ_ENCODING_EMBSTR)

  • 共享的对象不压缩

  • redis缓存了一定长度的数字 shared.integers[j] 根据 #define OBJ_SHARED_INTEGERS 10000,可以发现,数字范围0~10000,在redis中是共享的,不会分配新的内存,因此,设置10w个key的值为10000以内,value占用的空间是不会变化的

关于为什么只能encoding OBJ_STRING,原因是Redis协议规范中,约定了客户端发给服务端的命令,是一个bulk string数组组成的????代码里面放置的复杂对象,如何变成string??????

Sending commands to a Redis Server

Now that you are familiar with the RESP serialization format, writing an implementation of a Redis client library will be easy. We can further specify how the interaction between the client and the server works:

A client sends to the Redis server a RESP Array consisting of just Bulk Strings.

A Redis server replies to clients sending any valid RESP data type as reply.

setKey(c->db,key,val);

setKey方法执行的set操作

/* High level Set operation. This function can be used in order to set
* a key, whatever it was existing or not, to a new object.
*
* 1) The ref count of the value object is incremented.
* 2) clients WATCHing for the destination key notified.
* 3) The expire time of the key is reset (the key is made persistent).
*
* All the new keys in the database should be craeted via this interface. */
void setKey(redisDb *db, robj *key, robj *val) {
if (lookupKeyWrite(db,key) == NULL) {
dbAdd(db,key,val);
} else {
dbOverwrite(db,key,val);
}
incrRefCount(val);
removeExpire(db,key);
signalModifiedKey(db,key);
}

在lookupKeyWrite时,会先检查expireIfNeeded

/* Lookup a key for write operations, and as a side effect, if needed, expires
* the key if its TTL is reached.
*
* Returns the linked value object if the key exists or NULL if the key
* does not exist in the specified DB. */
robj *lookupKeyWrite(redisDb *db, robj *key) {
expireIfNeeded(db,key); //获取值时驱动检查过期时间,如果未过期,则什么都不处理,返回0,如果过期了,先把过期事件扩散到slaves或者aof文件,就删除key
//如果server端启动lazyfree_lazy_expire,则会异步删除,否则同步删除key,
return lookupKey(db,key,LOOKUP_NONE);
}
  • 从这里可以看出,redis的key如果设置了过期时间,并不是到了时间,立即就会被移除,而是基于访问时先检查来做的,先判断是否过期,过期就删除(ps:也有通过一些算法来主动删除key)

    /* This function is called when we are going to perform some operation

    • in a given key, but such key may be already logically expired even if
    • it still exists in the database. The main way this function is called
    • is via lookupKey*() family of functions.
    • The behavior of the function depends on the replication role of the
    • instance, because slave instances do not expire keys, they wait
    • for DELs from the master for consistency matters. However even
    • slaves will try to have a coherent return value for the function,
    • so that read commands executed in the slave side will be able to
    • behave like if the key is expired even if still present (because the
    • master has yet to propagate the DEL).
    • In masters as a side effect of finding a key which is expired, such
    • key will be evicted from the database. Also this may trigger the
    • propagation of a DEL/UNLINK command in AOF / replication stream.
    • The return value of the function is 0 if the key is still valid,
    • otherwise the function returns 1 if the key is expired. */

    int expireIfNeeded(redisDb *db, robj *key) {

    mstime_t when = getExpire(db,key);

    mstime_t now;

      if (when < 0) return 0; /* No expire for this key */
    
      /* Don't expire anything while loading. It will be done later. */
    if (server.loading) return 0; /* If we are in the context of a Lua script, we pretend that time is
    * blocked to when the Lua script started. This way a key can expire
    * only the first time it is accessed and not in the middle of the
    * script execution, making propagation to slaves / AOF consistent.
    * See issue #1525 on Github for more information. */
    now = server.lua_caller ? server.lua_time_start : mstime(); /* If we are running in the context of a slave, return ASAP:
    * the slave key expiration is controlled by the master that will
    * send us synthesized DEL operations for expired keys.
    *
    * Still we try to return the right information to the caller,
    * that is, 0 if we think the key should be still valid, 1 if
    * we think the key is expired at this time. */
    if (server.masterhost != NULL) return now > when; /* Return when this key has not expired */
    if (now <= when) return 0; /* Delete the key */
    server.stat_expiredkeys++;
    propagateExpire(db,key,server.lazyfree_lazy_expire); //把过期事件扩散到slaves或者aof文件
    notifyKeyspaceEvent(NOTIFY_EXPIRED,
    "expired",key,db->id); //通知key为空事件,NOTIFY_KEYSPACE,NOTIFY_KEYEVENT
    return server.lazyfree_lazy_expire ? dbAsyncDelete(db,key) :
    dbSyncDelete(db,key);

    }

  • 先notify后delete会不会不安全,如果redis是单线程的,那自然就安全了

  • 疑点:redis如果完全是多路复用 + 单线程,那是不是在multi命令时,不需要watch某个key了??

    /* The API provided to the rest of the Redis core is a simple function:

    *

    • notifyKeyspaceEvent(char *event, robj *key, int dbid);

    • 'event' is a C string representing the event name.

    • 'key' is a Redis object representing the key name.

    • 'dbid' is the database ID where the key lives. */

      void notifyKeyspaceEvent(int type, char *event, robj *key, int dbid) {

      sds chan;

      robj *chanobj, *eventobj;

      int len = -1;

      char buf[24];

      /* If any modules are interested in events, notify the module system now.

      • This bypasses the notifications configuration, but the module engine
      • will only call event subscribers if the event type matches the types
      • they are interested in. */

        moduleNotifyKeyspaceEvent(type, event, key, dbid);

      /* If notifications for this class of events are off, return ASAP. */

      if (!(server.notify_keyspace_events & type)) return;

      eventobj = createStringObject(event,strlen(event));

      /* keyspace@: notifications. */

      if (server.notify_keyspace_events & NOTIFY_KEYSPACE) {

      chan = sdsnewlen("keyspace@",11);

      len = ll2string(buf,sizeof(buf),dbid);

      chan = sdscatlen(chan, buf, len);

      chan = sdscatlen(chan, "
      :", 3);

      chan = sdscatsds(chan, key->ptr);

      chanobj = createObject(OBJ_STRING, chan);

      pubsubPublishMessage(chanobj, eventobj);

      decrRefCount(chanobj);

      }

      /* keyevent@: notifications. */

      if (server.notify_keyspace_events & NOTIFY_KEYEVENT) {

      chan = sdsnewlen("keyevent@",11);

      if (len == -1) len = ll2string(buf,sizeof(buf),dbid);

      chan = sdscatlen(chan, buf, len);

      chan = sdscatlen(chan, "
      :", 3);

      chan = sdscatsds(chan, eventobj->ptr);

      chanobj = createObject(OBJ_STRING, chan);

      pubsubPublishMessage(chanobj, key);

      decrRefCount(chanobj);

      }

      decrRefCount(eventobj);

      }

删除key,dict结构

dbSyncDelete方法最终会执行删除key操作dictDelete

可见dic的数据结构类型java的hashMap,为数组+链表结构(ps:这只是其中一种结构,并非全部)

/* Search and remove an element,由此可见dic的数据结构类型java的hashMap,为数组+链表结构 */
static int dictDelete(dict *ht, const void *key) {
unsigned int h;
dictEntry *de, *prevde; if (ht->size == 0)
return DICT_ERR;
h = dictHashKey(ht, key) & ht->sizemask;
de = ht->table[h]; prevde = NULL;
while(de) {
if (dictCompareHashKeys(ht,key,de->key)) {
/* Unlink the element from the list */
if (prevde)
prevde->next = de->next;
else
ht->table[h] = de->next; dictFreeEntryKey(ht,de);
dictFreeEntryVal(ht,de);
free(de);
ht->used--;
return DICT_OK;
}
prevde = de;
de = de->next;
}
return DICT_ERR; /* not found */
}
  • 通过hashKey 和 sizemask 与操作,获取数组下标
  • 删除entry后,会释放key和值占用的空间

dicthashtable结构

/* This is our hash table structure. Every dictionary has two of this as we

* implement incremental rehashing, for the old to the new table. */

typedef struct dictht {

dictEntry **table;

unsigned long size;

unsigned long sizemask;

unsigned long used;

} dictht;

typedef struct dict {
dictType *type; //不同的type,会有不同的处理函数,通过type来找到多态方法
void *privdata;
dictht ht[2];
long rehashidx; /* rehashing not in progress if rehashidx == -1 */
unsigned long iterators; /* number of iterators currently running */
} dict;

dictExpand

两步走

  • 分配新的hash表

  • 执行rehash,将老的hash表的值,移动到新的hash表中

    /* Expand or create the hash table */

    int dictExpand(dict d, unsigned long size)

    {

    dictht n; /
    the new hash table */

    unsigned long realsize = _dictNextPower(size); //保证realsize为2的倍数

      /* the size is invalid if it is smaller than the number of
    * elements already inside the hash table */
    if (dictIsRehashing(d) || d->ht[0].used > size)
    return DICT_ERR; /* Rehashing to the same table size is not useful. */
    if (realsize == d->ht[0].size) return DICT_ERR; /* Allocate the new hash table and initialize all pointers to NULL */
    n.size = realsize;
    n.sizemask = realsize-1;
    n.table = zcalloc(realsize*sizeof(dictEntry*));
    n.used = 0; /* Is this the first initialization? If so it's not really a rehashing
    * we just set the first hash table so that it can accept keys. */
    if (d->ht[0].table == NULL) {
    d->ht[0] = n;
    return DICT_OK;
    } /* Prepare a second hash table for incremental rehashing */
    d->ht[1] = n; //正在rehash,把新的hash表赋给ht[1]
    d->rehashidx = 0;
    return DICT_OK;

    }

  • 先判断新的size是否大于当前的size,如果不是,则返回错误

  • 修改dict的size,sizemask为size-1,原因是数组下标是从0开始的

  • 重新分配内存

  • 如果是第一次初始化,分配内存后就直接返回

  • 非初始化时,为下次rehashing准备

rehash 操作

/* Performs N steps of incremental rehashing. Returns 1 if there are still
* keys to move from the old to the new hash table, otherwise 0 is returned.
*
* Note that a rehashing step consists in moving a bucket (that may have more
* than one key as we use chaining) from the old to the new hash table, however
* since part of the hash table may be composed of empty spaces, it is not
* guaranteed that this function will rehash even a single bucket, since it
* will visit at max N*10 empty buckets in total, otherwise the amount of
* work it does would be unbound and the function may block for a long time. */
int dictRehash(dict *d, int n) {
int empty_visits = n*10; /* Max number of empty buckets to visit. */
if (!dictIsRehashing(d)) return 0; while(n-- && d->ht[0].used != 0) {
dictEntry *de, *nextde; /* Note that rehashidx can't overflow as we are sure there are more
* elements because ht[0].used != 0 */
assert(d->ht[0].size > (unsigned long)d->rehashidx);
while(d->ht[0].table[d->rehashidx] == NULL) {
d->rehashidx++;
if (--empty_visits == 0) return 1;
}
de = d->ht[0].table[d->rehashidx];
/* Move all the keys in this bucket from the old to the new hash HT */
while(de) {
uint64_t h; nextde = de->next;
/* Get the index in the new hash table */
h = dictHashKey(d, de->key) & d->ht[1].sizemask;
de->next = d->ht[1].table[h];
d->ht[1].table[h] = de;
d->ht[0].used--;
d->ht[1].used++;
de = nextde;
}
d->ht[0].table[d->rehashidx] = NULL;
d->rehashidx++;
} /* Check if we already rehashed the whole table... */
if (d->ht[0].used == 0) {
zfree(d->ht[0].table);
d->ht[0] = d->ht[1];
_dictReset(&d->ht[1]);
d->rehashidx = -1;
return 0;
} /* More to rehash... */
return 1;
}
  • 根据方法参数n,可以发现rehash操作是慢慢进行的,而不是一蹴而就的,实际上也是这样的,上层方法会根据每执行n次的耗时,和预估的耗时进行比较,如果这次while循环的总的执行时间超过预估时间,就break

int dictRehashMilliseconds(dict *d, int ms) {

long long start = timeInMilliseconds();

int rehashes = 0;

while(dictRehash(d,100)) {

rehashes += 100;

if (timeInMilliseconds()-start > ms) break;

}

return rehashes;

}

可以参见博客:https://blog.csdn.net/u012658346/article/details/51316029

  • 总的来说,就是把ht[0]中所有值移动到ht[1]中,再把ht[1]赋给ht[0]

lookupKey

讲述了较多dict的数据结构,现在继续set方法相关的代码

/* Low level key lookup API, not actually called directly from commands
* implementations that should instead rely on lookupKeyRead(),
* lookupKeyWrite() and lookupKeyReadWithFlags(). */
robj *lookupKey(redisDb *db, robj *key, int flags) {
dictEntry *de = dictFind(db->dict,key->ptr);
if (de) {
robj *val = dictGetVal(de); /* Update the access time for the ageing algorithm.
* Don't do it if we have a saving child, as this will trigger
* a copy on write madness. */
//当前没有rdb备份进程、aof进程,且非 不更新key last access time时,更新last accesstime
if (server.rdb_child_pid == -1 &&
server.aof_child_pid == -1 &&
!(flags & LOOKUP_NOTOUCH))
{
if (server.maxmemory_policy & MAXMEMORY_FLAG_LFU) {
updateLFU(val); //走lfu算法
} else {
val->lru = LRU_CLOCK(); //走lru算法
}
}
return val;
} else {
return NULL;
}
}

关键点

  • 找key的值,如果没找到就返回NULL
  • LFU(Least Frequently Used):缓存淘汰算法之最少使用原则
  • LRU:淘汰最少被访问的key

参考https://blog.csdn.net/qq_35440678/article/details/53453107

LFU算法

淘汰最少被访问的key算法成为:LFU(Least Frequently Used),将来要被淘汰腾出新空间给新key。

理论上LFU的思想相当简单,只需要给每个key加一个访问计数器。每次访问就自增1,所以也就很容易知道哪些key被访问更频繁。

当然,LFU也会带起其他问题,不单单是针对redis,对于LFU实现:

1、不能使用“移除顶部元素”的方式,keys必须要根据访问计数器进行排序。每访问一次就得遍历所有key找出访问次数最少的key。

2、LFU不能仅仅是只增加每一此访问的计数器。正如我们所讲的,访问模式改变随时变化,因此一个有高访问次数的key,后面很可能没有人继续访问它,因此我们的算法必须要适应超时的情况。

在redis中,第一个问题很好解决:我们可以在LRU的方式一样:随机在缓存池中选举,淘汰其中某项。第二个问题redis还是存在,因此一般对于LFU的思想必须使用一些方式进行减少,或者定期把访问计数器减半。

更新LFU计算器结构

/* Update LFU when an object is accessed.
* Firstly, decrement the counter if the decrement time is reached.
* Then logarithmically increment the counter, and update the access time. */
void updateLFU(robj *val) {
unsigned long counter = LFUDecrAndReturn(val);
counter = LFULogIncr(counter);
val->lru = (LFUGetTimeInMinutes()<<8) | counter;
}

redis中,为每个对象额外新增24bit,来存放 上次缩减时间和计数器

16bit : last decr time //时间戳是精确到分钟

8bit:计数器,8bit最大255,如果访问100w此呢,根据随机因子,基于概率来进行计数器+1,服务长时间运行时,概率是趋同的

实际的counter:

你可以配置计数器增长的速率,如果使用默认配置,会发生:

  • 100次访问后,计数器=10;

  • 1000次访问是是18;

  • 10万次访问是142;

  • 100万次访问后达到255,不再继续增长;

    /* Logarithmically increment a counter. The greater is the current counter value

    • the less likely is that it gets really implemented. Saturate it at 255. /

      uint8_t LFULogIncr(uint8_t counter) {

      if (counter == 255) return 255;

      double r = (double)rand()/RAND_MAX;

      double baseval = counter - LFU_INIT_VAL; //LFU_INIT_VAL:5,初始化次数,key被添加就会有5次

      if (baseval < 0) baseval = 0;

      double p = 1.0/(baseval
      server.lfu_log_factor+1); //server.lfu_log_factor:10

      if (r < p) counter++;

      return counter;

      }

    /* Return the current time in minutes, just taking the least significant

    • 16 bits. The returned time is suitable to be stored as LDT (last decrement
    • time) for the LFU implementation. */

      unsigned long LFUGetTimeInMinutes(void) {

      return (server.unixtime/60) & 65535; //由此可见,unixtime的单位是秒

      }

    /* Update LFU when an object is accessed.

    • Firstly, decrement the counter if the decrement time is reached.
    • Then logarithmically increment the counter, and update the access time. */

      void updateLFU(robj *val) {

      unsigned long counter = LFUDecrAndReturn(val);

      counter = LFULogIncr(counter);

      val->lru = (LFUGetTimeInMinutes()<<8) | counter;

      }

接着看find

#define dictGetVal(he) ((he)->v.val)

dictEntry数据结构

dict.h
typedef struct dictEntry {
void *key;
union {
void *val;
uint64_t u64;
int64_t s64; //过期时间,到这个时间就过期了
double d;
} v;
struct dictEntry *next;
} dictEntry;

setExpire(c,c->db,key,mstime()+milliseconds)

/* Set an expire to the specified key. If the expire is set in the context
* of an user calling a command 'c' is the client, otherwise 'c' is set
* to NULL. The 'when' parameter is the absolute unix time in milliseconds
* after which the key will no longer be considered valid. */
void setExpire(client *c, redisDb *db, robj *key, long long when) {
dictEntry *kde, *de; /* Reuse the sds from the main dict in the expire dict */
kde = dictFind(db->dict,key->ptr);
serverAssertWithInfo(NULL,key,kde != NULL);
de = dictAddOrFind(db->expires,dictGetKey(kde));
dictSetSignedIntegerVal(de,when); int writable_slave = server.masterhost && server.repl_slave_ro == 0;
if (c && writable_slave && !(c->flags & CLIENT_MASTER))
rememberSlaveKeyWithExpire(db,key);
}

AddOrFind

/* Add or Find:
* dictAddOrFind() is simply a version of dictAddRaw() that always
* returns the hash entry of the specified key, even if the key already
* exists and can't be added (in that case the entry of the already
* existing key is returned.)
*
* See dictAddRaw() for more information. */
dictEntry *dictAddOrFind(dict *d, void *key) {
dictEntry *entry, *existing;
entry = dictAddRaw(d,key,&existing);
return entry ? entry : existing;
} #define dictSetSignedIntegerVal(entry, _val_) \
do { (entry)->v.s64 = _val_; } while(0)

get命令

void getCommand(client *c) {
getGenericCommand(c);
} int getGenericCommand(client *c) {
robj *o; if ((o = lookupKeyReadOrReply(c,c->argv[1],shared.nullbulk)) == NULL)
return C_OK; if (o->type != OBJ_STRING) {
//这个error并非是指响应的value type判断,而是数据类型的type判断,如这个key本身对应的type是list、set,不能直接用get命令
addReply(c,shared.wrongtypeerr);
return C_ERR;
} else {
addReplyBulk(c,o);
return C_OK;
}
} robj *lookupKeyReadOrReply(client *c, robj *key, robj *reply) {
robj *o = lookupKeyRead(c->db, key);
if (!o) addReply(c,reply);
return o;
} /* Like lookupKeyReadWithFlags(), but does not use any flag, which is the
* common case. */
robj *lookupKeyRead(redisDb *db, robj *key) {
return lookupKeyReadWithFlags(db,key,LOOKUP_NONE);
} /* Lookup a key for read operations, or return NULL if the key is not found
* in the specified DB.
*
* As a side effect of calling this function:
* 1. A key gets expired if it reached it's TTL.
* 2. The key last access time is updated.
* 3. The global keys hits/misses stats are updated (reported in INFO).
*
* This API should not be used when we write to the key after obtaining
* the object linked to the key, but only for read only operations.
*
* Flags change the behavior of this command:
*
* LOOKUP_NONE (or zero): no special flags are passed.
* LOOKUP_NOTOUCH: don't alter the last access time of the key.
*
* Note: this function also returns NULL is the key is logically expired
* but still existing, in case this is a slave, since this API is called only
* for read operations. Even if the key expiry is master-driven, we can
* correctly report a key is expired on slaves even if the master is lagging
* expiring our key via DELs in the replication link. */
robj *lookupKeyReadWithFlags(redisDb *db, robj *key, int flags) {
robj *val; if (expireIfNeeded(db,key) == 1) {
/* Key expired. If we are in the context of a master, expireIfNeeded()
* returns 0 only when the key does not exist at all, so it's safe
* to return NULL ASAP. */
if (server.masterhost == NULL) return NULL; /* However if we are in the context of a slave, expireIfNeeded() will
* not really try to expire the key, it only returns information
* about the "logical" status of the key: key expiring is up to the
* master in order to have a consistent view of master's data set.
*
* However, if the command caller is not the master, and as additional
* safety measure, the command invoked is a read-only command, we can
* safely return NULL here, and provide a more consistent behavior
* to clients accessign expired values in a read-only fashion, that
* will say the key as non exisitng.
*
* Notably this covers GETs when slaves are used to scale reads. */
if (server.current_client &&
server.current_client != server.master &&
server.current_client->cmd &&
server.current_client->cmd->flags & CMD_READONLY)
{
return NULL;
}
}
val = lookupKey(db,key,flags);
if (val == NULL)
server.stat_keyspace_misses++;
else
server.stat_keyspace_hits++;
return val;
}

robj:

typedef struct redisObject {
unsigned type:4;
unsigned encoding:4;
unsigned lru:LRU_BITS; /* LRU time (relative to global lru_clock) or
* LFU data (least significant 8 bits frequency
* and most significant 16 bits access time). */
int refcount;
void *ptr;
} robj;

Redis【二】 set|get那些事的更多相关文章

  1. Redis还可以做哪些事?

    在上一篇文章中,讲到了redis五大基本数据类型的使用场景,除了string,hash,list,set,zset之外,redis还提供了一些其他的数据结构(当然,严格意义上也不算数据结构),一起来看 ...

  2. 分布式数据存储 之 Redis(二) —— spring中的缓存抽象

    分布式数据存储 之 Redis(二) -- spring中的缓存抽象 一.spring boot 中的 StringRedisTemplate 1.StringRedisTemplate Demo 第 ...

  3. 深入理解MVC C#+HtmlAgilityPack+Dapper走一波爬虫 StackExchange.Redis 二次封装 C# WPF 用MediaElement控件实现视频循环播放 net 异步与同步

    深入理解MVC   MVC无人不知,可很多程序员对MVC的概念的理解似乎有误,换言之他们一直在错用MVC,尽管即使如此软件也能被写出来,然而软件内部代码的组织方式却是不科学的,这会影响到软件的可维护性 ...

  4. Redis(二十一):Redis性能问题排查解决手册(转)

    性能相关的数据指标 通过Redis-cli命令行界面访问到Redis服务器,然后使用info命令获取所有与Redis服务相关的信息.通过这些信息来分析文章后面提到的一些性能指标. info命令输出的数 ...

  5. Linux-NoSQL之Redis(二)

    一.Redis配置文件详解 1.通用配置 daemonize no  # 默认情况下,redis并不是以daemon形式来运行的.通过daemonize配置项可以控制redis的运行形式 pidfil ...

  6. 关于用友 U8-UAP二开的一些事

    这是关于一个刚刚接触用友U8的二次开发的一些小心得. 首先就是用友二开的论坛,http://u8dev.yonyou.com/ 当然这个论坛做得不怎么样,提出了好几个问题,都没有回复的. 以下是关于二 ...

  7. Redis(二)、Redis持久化RDB和AOF

    一.Redis两种持久化方式 对Redis而言,其数据是保存在内存中的,一旦机器宕机,内存中的数据会丢失,因此需要将数据异步持久化到硬盘中保存.这样,即使机器宕机,数据能从硬盘中恢复. 常见的数据持久 ...

  8. .NET中使用Redis (二)

    很久以前写了一篇文章 .NET中使用Redis 介绍了如何安装Redis服务端,以及如何在.NET中调用Redis读取数据.本文简单介绍如何设计NoSQL数据库,以及如何使用Redis来存储对象. 和 ...

  9. 转:.NET中使用Redis (二)

    原文来自于:http://blog.jobbole.com/83824/ 原文出处: 寒江独钓   欢迎分享原创到伯乐头条 很久以前写了一篇文章 .NET中使用Redis 介绍了如何安装Redis服务 ...

  10. redis(二)redis+TCMALLOC高性能的缓存服务器的安装配置

    安装  1准备编译环境    yum -y install gcc gcc+ gcc-c++ openssl openssl-devel pcre pcre-devel  2 下载源码包(由于goog ...

随机推荐

  1. linux学习(八)切换用户模式常用命令

    一.常用的切换用户命令 sudo 暂时切换到超级用户模式以执行超级用户权限,以系统管理者的身份执行指令,一般用在给命令提高权限. 经由 sudo 所执行的指令就好像是 root 亲自执行.默认为一次时 ...

  2. 读完这篇,让你真正理解Redis持久化

    什么叫持久化? 用一句话可以将持久化概括为:将数据(如内存中的对象)保存到可永久保存的存储设备中. 持久化的主要应用是将内存中的对象存储在数据库中,或者存储在磁盘文件中. XML 数据文件中等等. 也 ...

  3. Django-当前菜单激活状态-模版 request | slice

    如何满足这个需求? 1. view中传递过来一个当前页面的参数标识,通过模版语言进行判断 {% if current_page == 'index' %}active{% endif %} # 每一个 ...

  4. Linux学习笔记-vi(一)

    vim编辑命令 vim命令的三种模式: 1.命令模式: vi file.txt  进入vi模式,默认为命令模式,命令模式移动光标. 2.插入模式 i (insert):在光标前插入内容 a(appen ...

  5. 剑指Offer(一):二维数组中的查找

    一.前言 刷题平台:牛客网 二.题目 在一个二维数组中,每一行都按照从左到右递增的顺序排序,每一列都按照从上到下递增的顺序排序.请完成一个函数,输入这样的一个二维数组和一个整数,判断数组中是否含有该整 ...

  6. CF149D Coloring Brackets

    CF149D Coloring Brackets Link 题面: 给出一个配对的括号序列(如"\((())()\)"."\(()\)"等, "\() ...

  7. JavaScript倒计时效果

    实现思路: 输入的时间减去现在的时间就是剩余的时间,但是不能拿着时分秒相减,比如05分减去25分,结果会是负的. 可以用时间戳来做,用户输入时间总的毫秒数减去现在时间的总的毫秒数,得到的就是剩余时间的 ...

  8. 一、Vuforia_AR

    一.AR概念: 增强现实(Augmented Reality,简称AR),是一种将虚拟信息与真实世界巧妙融合的技术,广泛运用了多媒体.三维建模.实时跟踪及注册.智能交互.传感等多种技术手段,将计算机生 ...

  9. RHSA-2018:0007-重要: 内核 安全更新(需要重启、存在EXP)

    [root@localhost ~]# cat /etc/redhat-release CentOS Linux release 7.2.1511 (Core) 修复命令: 使用root账号登陆She ...

  10. package.json文件配置说明

    1.什么是package.json package.json文件是Node.js项目中的一个描述文件,执行npm init命令初始化项目后,在项目的根目录下自动生成该文件.package.json包含 ...