下面主要是源码,标题也都是各个方法,所以不建议跳着看,而是从头开始看会,
文章目录
setCommand
在
server.c
中的redisCommand redisCommandTable[]
,里面有很多命令行操作的命令
这里只看set String
的
{"set",setCommand,-3,
"write use-memory @string",
0,NULL,1,1,1,0,0,0},
在t_string.c
中就有setCommand
方法
/* SET key value [NX] [XX] [KEEPTTL] [GET] [EX <seconds>] [PX <milliseconds>]
* [EXAT <seconds-timestamp>][PXAT <milliseconds-timestamp>] */
void setCommand(client *c) {
robj *expire = NULL;
int unit = UNIT_SECONDS;
int flags = OBJ_NO_FLAGS;
if (parseExtendedStringArgumentsOrReply(c,&flags,&unit,&expire,COMMAND_SET) != C_OK) {
return;
}
c->argv[2] = tryObjectEncoding(c->argv[2]);
//执行set操作的实际方法 setGenericCommand(c,flags,c->argv[1],c->argv[2],expire,unit,NULL,NULL);
}
tryObjectEncoding(尝试对字符串对象进行编码以节省空间)
/* 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.
我们仅针对 RAW 或 EMBSTR 编码的对象尝试一些专门的编码,换句话说,仍然由实际的字符数组表示的对象*/
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.
对共享对象进行编码是不安全的:共享对象可以在 Redis 的“对象空间”中的任何地方共享,
并且可能在它们未被处理的地方结束。我们仅将它们作为键空间中的值来处理*/
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.
检查我们是否可以将此字符串表示为长整数。请注意,我们确信大于 20 个字符的字符串不能表示为 32 位或 64 位整数*/
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.
此对象可编码为 long。尝试使用共享对象。请注意,当使用 maxmemory 时,我们避免使用共享整数,
因为每个对象都需要有一个私有的 LRU 字段才能使 LRU 算法正常工作*/
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 {
//raw
if (o->encoding == OBJ_ENCODING_RAW) {
sdsfree(o->ptr);
o->encoding = OBJ_ENCODING_INT;
o->ptr = (void*) value;
return o;
} else if (o->encoding == OBJ_ENCODING_EMBSTR) {//embstr
decrRefCount(o);
return createStringObjectFromLongLongForValue(value);
}
}
}
/* 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.
如果字符串很小并且仍然是 RAW 编码,请尝试更有效的 EMBSTR 编码。在这种表示中,对象和 SDS 字符串被分配在同一块内存中,以节省空间和缓存未命中。
*/
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.
*做最后一次尝试,至少优化字符串对象内部的 SDS 字符串,使其占用空间很小,以防 SDS 字符串末尾有超过 10% 的可用空间。
* 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.
*我们仅对相对较大的字符串执行此操作,因为仅当字符串的长度大于 OBJ_ENCODING_EMBSTR_SIZE_LIMIT 时才会进入此分支
*/
trimStringObjectIfNeeded(o);
/* Return the original object.
返回原始对象*/
return o;
}
setGenericCommand(set 字符串实际的执行流程)
/* The setGenericCommand() function implements the SET operation with different
* options and variants. This function is called in order to implement the
* following commands: SET, SETEX, PSETEX, SETNX, GETSET.
*setGenericCommand() 函数以不同的方式实现 SET 操作 选项和变体。调用此函数是为了实现以下命令:SET、SETEX、PSETEX、SETNX、GETSET
* 'flags' changes the behavior of the command (NX, XX or GET, see below).
* 'flags' 改变命令的行为(NX、XX 或 GET,见下文)。
* 'expire' represents an expire to set in form of a Redis object as passed
* by the user. It is interpreted according to the specified 'unit'.
*'expire' 表示要以用户传递的 Redis 对象的形式设置的过期时间。它根据指定的“单位”进行解释
* 'ok_reply' and 'abort_reply' is what the function will reply to the client
* if the operation is performed, or when it is not because of NX or
* XX flags.
*'ok_reply' 和 'abort_reply' 是函数在执行操作或不是因为 NX 或 XX 标志时将回复客户端的内容
* If ok_reply is NULL "+OK" is used. 如果 ok_reply 为 NULL,则使用“+OK”
* If abort_reply is NULL, "$-1" is used. 如果 abort_reply 为 NULL,则使用“-1”。*/
#define OBJ_NO_FLAGS 0
#define OBJ_SET_NX (1<<0) /* Set if key not exists. 如果键不存在则 set*/
#define OBJ_SET_XX (1<<1) /* Set if key exists. 如果键存在则set*/
#define OBJ_EX (1<<2) /* Set if time in seconds is given 设置是否给出以秒为单位的时间*/
#define OBJ_PX (1<<3) /* Set if time in ms in given *设置是否在给定时间以毫秒为单位/
#define OBJ_KEEPTTL (1<<4) /* Set and keep the ttl */
#define OBJ_SET_GET (1<<5) /* Set if want to get key before set 如果想在设置之前获取密钥,请设置*/
#define OBJ_EXAT (1<<6) /* Set if timestamp in second is given 如果给出以秒为单位的时间戳,则设置*/
#define OBJ_PXAT (1<<7) /* Set if timestamp in ms is given 如果给出以毫秒为单位的时间戳,则设置*/
#define OBJ_PERSIST (1<<8) /* Set if we need to remove the ttl 设置我们是否需要删除 ttl*/
void setGenericCommand(client *c, int flags, robj *key, robj *val, robj *expire, int unit, robj *ok_reply, robj *abort_reply) {
long long milliseconds = 0, when = 0; /* initialized to avoid any harmness warning 初始化以避免任何危害警告*/
//转换过期时间
if (expire) {
if (getLongLongFromObjectOrReply(c, expire, &milliseconds, NULL) != C_OK)
return;
if (milliseconds <= 0 || (unit == UNIT_SECONDS && milliseconds > LLONG_MAX / 1000)) {
/* Negative value provided or multiplication is gonna overflow. */
addReplyErrorFormat(c, "invalid expire time in %s", c->cmd->name);
return;
}
if (unit == UNIT_SECONDS) milliseconds *= 1000;
when = milliseconds;
if ((flags & OBJ_PX) || (flags & OBJ_EX))
when += mstime();
if (when <= 0) {
/* Overflow detected. */
addReplyErrorFormat(c, "invalid expire time in %s", c->cmd->name);
return;
}
}
//key存在,则返回失败,符合NX语义,key不存在,返回失败,这符合XX语义
if ((flags & OBJ_SET_NX && lookupKeyWrite(c->db,key) != NULL) ||
(flags & OBJ_SET_XX && lookupKeyWrite(c->db,key) == NULL))
{
addReply(c, abort_reply ? abort_reply : shared.null[c->resp]);
return;
}
if (flags & OBJ_SET_GET) {
if (getGenericCommand(c) == C_ERR) return;
}
//核心set指令的操作
genericSetKey(c,c->db,key, val,flags & OBJ_KEEPTTL,1);
server.dirty++;
notifyKeyspaceEvent(NOTIFY_STRING,"set",key,c->db->id);
if (expire) {
setExpire(c,c->db,key,when);
notifyKeyspaceEvent(NOTIFY_GENERIC,"expire",key,c->db->id);
/* Propagate as SET Key Value PXAT millisecond-timestamp if there is EXAT/PXAT or
* propagate as SET Key Value PX millisecond if there is EX/PX flag.
*如果有 EXATPXAT,则作为 SET Key Value PXAT 毫秒时间戳传播,如果有 EXPX 标志,则作为 SET Key Value PX 毫秒传播
* Additionally when we propagate the SET with PX (relative millisecond) we translate
* it again to SET with PXAT for the AOF.
*此外,当我们使用 PX(相对毫秒)传播 SET 时,我们再次将其转换为带有 PXAT 的 SET 用于 AOF
* Additional care is required while modifying the argument order. AOF relies on the
* exp argument being at index 3. (see feedAppendOnlyFile)
修改参数顺序时需要额外小心。 AOF 依赖于索引 3 处的 exp 参数。(参见 feedAppendOnlyFile)
* */
robj *exp = (flags & OBJ_PXAT) || (flags & OBJ_EXAT) ? shared.pxat : shared.px;
robj *millisecondObj = createStringObjectFromLongLong(milliseconds);
rewriteClientCommandVector(c,5,shared.set,key,val,exp,millisecondObj);
decrRefCount(millisecondObj);
}
if (!(flags & OBJ_SET_GET)) {
addReply(c, ok_reply ? ok_reply : shared.ok);
}
/* Propagate without the GET argument (Isn't needed if we had expire since in that case we completely re-written the command argv)
在没有 GET 参数的情况下传播(如果我们已经过期则不需要,因为在这种情况下我们完全重写了命令 argv)
*/
if ((flags & OBJ_SET_GET) && !expire) {
int argc = 0;
int j;
robj **argv = zmalloc((c->argc-1)*sizeof(robj*));
for (j=0; j < c->argc; j++) {
char *a = c->argv[j]->ptr;
/* Skip GET which may be repeated multiple times. */
if (j >= 3 &&
(a[0] == 'g' || a[0] == 'G') &&
(a[1] == 'e' || a[1] == 'E') &&
(a[2] == 't' || a[2] == 'T') && a[3] == '\0')
continue;
argv[argc++] = c->argv[j];
incrRefCount(c->argv[j]);
}
replaceClientCommandVector(c, argc, argv);
}
}
genericSetKey(核心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),
* unless 'keepttl' is true.
* 重置密钥的过期时间(使密钥持久化),除非 'keepttl' 为真
* All the new keys in the database should be created via this interface.
* The client 'c' argument may be set to NULL if the operation is performed
* in a context where there is no clear client performing the operation. */
void genericSetKey(client *c, redisDb *db, robj *key, robj *val, int keepttl, int signal) {
//lookupKeyWrite 先尝试判断key是否过期,过期则删除
if (lookupKeyWrite(db,key) == NULL) {
//添加新键和值
dbAdd(db,key,val);
} else {
//说明key存在而且没有过期,覆盖老的value,但是不会修改key过期时间
dbOverwrite(db,key,val);
}
//这里对value的引用计算++,保证val不会被释放
incrRefCount(val);
//移除超时时间,因为setGenericCommand会重新设置超时时间,
//考虑一种情况,上面的覆盖旧val,没有修改新的超过时间,可能刚覆盖后key就过期了
if (!keepttl) removeExpire(db,key);
if (signal) signalModifiedKey(c,db,key);
}
lookupKeyWrite(先尝试判断key是否过期,过期则删除)
robj *lookupKeyWrite(redisDb *db, robj *key) {
return lookupKeyWriteWithFlags(db, key, LOOKUP_NONE);
}
/* Lookup a key for write operations, and as a side effect, if needed, expires
* the key if its TTL is reached.
*查找写操作的键,如果需要,如果达到 TTL,则使key过期。。
* Returns the linked value object if the key exists or NULL if the key
* does not exist in the specified DB. */
robj *lookupKeyWriteWithFlags(redisDb *db, robj *key, int flags) {
expireIfNeeded(db,key);
return lookupKey(db,key,flags);
}
lookupKey(查找key)
/* Low level key lookup API, not actually called directly from commands
* implementations that should instead rely on lookupKeyRead(),
* lookupKeyWrite() and lookupKeyReadWithFlags().
低级查找 API,实际上并不直接从命令实现中直接调用,而是应该依赖 lookupKeyRead()、lookupKeyWrite() 和 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. */
if (!hasActiveChildProcess() && !(flags & LOOKUP_NOTOUCH)){
if (server.maxmemory_policy & MAXMEMORY_FLAG_LFU) {
updateLFU(val);
} else {
val->lru = LRU_CLOCK();
}
}
return val;
} else {
return NULL;
}
}
dbAdd(正常添加键值对)
/* Add the key to the DB. It's up to the caller to increment the reference
* counter of the value if needed.
*
* The program is aborted if the key already exists. */
void dbAdd(redisDb *db, robj *key, robj *val) {
sds copy = sdsdup(key->ptr);
//正常dict添加操作,hash添加值
int retval = dictAdd(db->dict, copy, val);
serverAssertWithInfo(NULL,key,retval == DICT_OK);
signalKeyAsReady(db, key, val->type);
if (server.cluster_enabled) slotToKeyAdd(key->ptr);
}
dbOverwrite(覆盖旧的value,但是不修改过期时间)
因为在调用dbOverwrite方法之后,会有重置超时时间的操作
/* Overwrite an existing key with a new value. Incrementing the reference
* count of the new value is up to the caller.
* This function does not modify the expire time of the existing key.
* 用新值覆盖现有键。增加新值的引用计数取决于调用者。此函数不会修改现有键的过期时间
* The program is aborted if the key was not already present.
如果密钥不存在,程序将中止
*/
void dbOverwrite(redisDb *db, robj *key, robj *val) {
dictEntry *de = dictFind(db->dict,key->ptr);
serverAssertWithInfo(NULL,key,de != NULL);
dictEntry auxentry = *de;
robj *old = dictGetVal(de);
if (server.maxmemory_policy & MAXMEMORY_FLAG_LFU) {
val->lru = old->lru;
}
/* Although the key is not really deleted from the database, we regard
overwrite as two steps of unlink+add, so we still need to call the unlink
callback of the module. */
moduleNotifyKeyUnlink(key,old);
dictSetVal(db->dict, de, val);
if (server.lazyfree_lazy_server_del) {
freeObjAsync(key,old);
dictSetVal(db->dict, &auxentry, NULL);
}
dictFreeVal(db->dict, &auxentry);
}