基础的string相关的命令

get——getCommand

示例: GET KEY_NAME

  • 返回 key 的值,如果 key 不存在时,返回 nil。 如果 key 不是字符串类型,那么返回一个错误。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
//t_string.c
void getCommand(redisClient *c) {
getGenericCommand(c);
}

int getGenericCommand(redisClient *c) {
robj *o;
// 尝试从数据库中取出键 c->argv[1] 对应的值对象
// 如果键不存在时,向客户端发送回复信息,并返回 NULL
if ((o = lookupKeyReadOrReply(c,c->argv[1],shared.nullbulk)) == NULL)
return REDIS_OK;
// 值对象存在,检查它的类型
if (o->type != REDIS_STRING) {
// 类型错误
addReply(c,shared.wrongtypeerr);
return REDIS_ERR;
} else {
// 类型正确,向客户端返回对象的值
addReplyBulk(c,o);
return REDIS_OK;
}
}

说明:先判断db的expires字典中查找key并判断是否已过期,如果已过期,则返回空;如果未过期,则继续在db的dict中查找key,找到则返回,若未找到则返回null。

set——setCommand

示例: SET key value [NX] [XX] [EX ] [PX ]

  • EX second :设置键的过期时间为 second 秒。 SET key value EX second 效果等同于 SETEX key second value 。
  • PX millisecond :设置键的过期时间为 millisecond 毫秒。 SET key value PX millisecond 效果等同于 PSETEX keymillisecond value 。
  • NX :只在键不存在时,才对键进行设置操作。 SET key value NX 效果等同于 SETNX key value 。
  • XX :只在键已经存在时,才对键进行设置操作。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
//t_string.c
/* SET key value [NX] [XX] [EX <seconds>] [PX <milliseconds>] */
void setCommand(redisClient *c) {
int j;
robj *expire = NULL;
int unit = UNIT_SECONDS;
int flags = REDIS_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 |= REDIS_SET_NX;
} else if ((a[0] == 'x' || a[0] == 'X') &&
(a[1] == 'x' || a[1] == 'X') && a[2] == '\0') {
flags |= REDIS_SET_XX;
} else if ((a[0] == 'e' || a[0] == 'E') &&
(a[1] == 'x' || a[1] == 'X') && a[2] == '\0' && next) {
unit = UNIT_SECONDS;
expire = next;
j++;
} else if ((a[0] == 'p' || a[0] == 'P') &&
(a[1] == 'x' || a[1] == 'X') && a[2] == '\0' && next) {
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);
}



void setGenericCommand(redisClient *c, int flags, robj *key, robj *val, robj *expire, int unit, robj *ok_reply, robj *abort_reply) {
long long milliseconds = 0; /* initialized to avoid any harmness warning */
// 取出过期时间
if (expire) {
// 取出 expire 参数的值
// T = O(N)
if (getLongLongFromObjectOrReply(c, expire, &milliseconds, NULL) != REDIS_OK)
return;
// expire 参数的值不正确时报错
if (milliseconds <= 0) {
addReplyError(c,"invalid expire time in SETEX");
return;
}
// 不论输入的过期时间是秒还是毫秒
// Redis 实际都以毫秒的形式保存过期时间
// 如果输入的过期时间为秒,那么将它转换为毫秒
if (unit == UNIT_SECONDS) milliseconds *= 1000;
}
// 如果设置了 NX 或者 XX 参数,那么检查条件是否不符合这两个设置
// 在条件不符合时报错,报错的内容由 abort_reply 参数决定
if ((flags & REDIS_SET_NX && lookupKeyWrite(c->db,key) != NULL) ||
(flags & REDIS_SET_XX && lookupKeyWrite(c->db,key) == NULL))
{
addReply(c, abort_reply ? abort_reply : shared.nullbulk);
return;
}
// 将键值关联到数据库
setKey(c->db,key,val);
// 将数据库设为脏
server.dirty++;
// 为键设置过期时间
if (expire) setExpire(c->db,key,mstime()+milliseconds);
// 发送事件通知
notifyKeyspaceEvent(REDIS_NOTIFY_STRING,"set",key,c->db->id);
// 发送事件通知
if (expire) notifyKeyspaceEvent(REDIS_NOTIFY_GENERIC,
"expire",key,c->db->id);
// 设置成功,向客户端发送回复
// 回复的内容由 ok_reply 决定
addReply(c, ok_reply ? ok_reply : shared.ok);
}

说明:

  1. 先尝试对value进行编码节省内存(只有编码为RAW或者EMBSTR时尝试编码),EMBSTR 转 INT(字节长度小于21且可以转成整数), RAW 转 EMBSTR(字符串长度小于长度39)
  2. 在dict中,如果没有该key,则添加key,value;否则覆盖,其中覆盖后,需要是否原有value
  3. 如果EX,需要添加到expires字典中;如果是NX,则判断如果已有key,则不进行第二步的dict添加操作操作

setnx——setnxCommand

示例:SETNX KEY_NAME VALUE

  • 返回 key 的值,如果 key 不存在时,返回 nil。 如果 key 不是字符串类型,那么返回一个错误。
1
2
3
4
5
//t_string.c
void setnxCommand(redisClient *c) {
c->argv[2] = tryObjectEncoding(c->argv[2]);
setGenericCommand(c,REDIS_SET_NX,c->argv[1],c->argv[2],NULL,0,shared.cone,shared.czero);
}

说明:复用“setGenericCommand”方法,通过flags=REDIS_SET_NX,标识

setex——setexCommand

示例:SETEX KEY_NAME TIMEOUT VALUE

  • 设置成功时返回 OK 。
1
2
3
4
5
//t_string.c
void setexCommand(redisClient *c) {
c->argv[3] = tryObjectEncoding(c->argv[3]);
setGenericCommand(c,REDIS_SET_NO_FLAGS,c->argv[1],c->argv[3],c->argv[2],UNIT_SECONDS,NULL,NULL);
}

说明:复用“setGenericCommand”方法,通过 expire =UNIT_SECONDS 设置过期时间

psetex——psetexCommand

示例:PSETEX key1 EXPIRY_IN_MILLISECONDS value1

  • 设置成功时返回 OK 。
1
2
3
4
5
//t_string.c
void psetexCommand(redisClient *c) {
c->argv[3] = tryObjectEncoding(c->argv[3]);
setGenericCommand(c,REDIS_SET_NO_FLAGS,c->argv[1],c->argv[3],c->argv[2],UNIT_MILLISECONDS,NULL,NULL);
}

说明:复用“setGenericCommand”方法,通过 expire =UNIT_MILLISECONDS 设置过期时间

append——appendCommand

示例:APPEND KEY_NAME NEW_VALUE

  • 追加指定值之后, key 中字符串的长度。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
//t_string.c
void appendCommand(redisClient *c) {
size_t totlen;
robj *o, *append;
// 取出键相应的值对象
o = lookupKeyWrite(c->db,c->argv[1]);
if (o == NULL) {
// 键值对不存在。。。
/* Create the key */
// 键值对不存在,创建一个新的
c->argv[2] = tryObjectEncoding(c->argv[2]);
dbAdd(c->db,c->argv[1],c->argv[2]);
incrRefCount(c->argv[2]);
totlen = stringObjectLen(c->argv[2]);
} else {
// 键值对存在。。。
/* Key exists, check type */
// 检查类型
if (checkType(c,o,REDIS_STRING))
return;
/* "append" is an argument, so always an sds */
// 检查追加操作之后,字符串的长度是否符合 Redis 的限制
append = c->argv[2];
totlen = stringObjectLen(o)+sdslen(append->ptr);
if (checkStringLength(c,totlen) != REDIS_OK)
return;
/* Append the value */
// 执行追加操作
o = dbUnshareStringValue(c->db,c->argv[1],o);
o->ptr = sdscatlen(o->ptr,append->ptr,sdslen(append->ptr));
totlen = sdslen(o->ptr);
}
// 向数据库发送键被修改的信号
signalModifiedKey(c->db,c->argv[1]);
// 发送事件通知
notifyKeyspaceEvent(REDIS_NOTIFY_STRING,"append",c->argv[1],c->db->id);
// 将服务器设为脏
server.dirty++;
// 发送回复
addReplyLongLong(c,totlen);
}

说明:查找dict中的key是否存在,如果不存在,则直接新增key=>value;如果存在,则追加value。

strlen——strlenCommand

示例:STRLEN KEY_NAME

  • 字符串值的长度。 当 key 不存在时,返回 0。
1
2
3
4
5
6
7
8
9
//string.c
void strlenCommand(redisClient *c) {
robj *o;
// 取出值对象,并进行类型检查
if ((o = lookupKeyReadOrReply(c,c->argv[1],shared.czero)) == NULL ||
checkType(c,o,REDIS_STRING)) return;
// 返回字符串值的长度
addReplyLongLong(c,stringObjectLen(o));
}

说明:查找dict中的key是否存在,如果存在则获取value,计算value的长度

del——delCommand

示例:DEL key [key …]

  • 被删除 key 的数量。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
//db.c
void delCommand(redisClient *c) {
int deleted = 0, j;
// 遍历所有输入键
for (j = 1; j < c->argc; j++) {
// 先删除过期的键
expireIfNeeded(c->db,c->argv[j]);
// 尝试删除键
if (dbDelete(c->db,c->argv[j])) {
// 删除键成功,发送通知
signalModifiedKey(c->db,c->argv[j]);
notifyKeyspaceEvent(REDIS_NOTIFY_GENERIC,
"del",c->argv[j],c->db->id);
server.dirty++;
// 成功删除才增加 deleted 计数器的值
deleted++;
}
}
// 返回被删除键的数量
addReplyLongLong(c,deleted);
}

说明:遍历每个要删除的key,先删除expires的过期key,再删除dict中的key

exists——existsCommand

示例:EXISTS KEY_NAME

  • 若 key 存在返回 1 ,否则返回 0 。
1
2
3
4
5
6
7
8
9
10
11
12
//db.c
void existsCommand(redisClient *c) {
// 检查键是否已经过期,如果已过期的话,那么将它删除
// 这可以避免已过期的键被误认为存在
expireIfNeeded(c->db,c->argv[1]);
// 在数据库中查找
if (dbExists(c->db,c->argv[1])) {
addReply(c, shared.cone);
} else {
addReply(c, shared.czero);
}
}

说明:先判断expires是否有该key,如果key已过期,则删除该key;查询dict中是否有该key,如果有则返回1,否认返回0

incr——incrCommand

示例:INCR KEY_NAME

  • 执行 INCR 命令之后 key 的值。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
//string.c
void incrCommand(redisClient *c) {
incrDecrCommand(c,1);
}
void incrDecrCommand(redisClient *c, long long incr) {
long long value, oldvalue;
robj *o, *new;
// 取出值对象
o = lookupKeyWrite(c->db,c->argv[1]);
// 检查对象是否存在,以及类型是否正确
if (o != NULL && checkType(c,o,REDIS_STRING)) return;
// 取出对象的整数值,并保存到 value 参数中
if (getLongLongFromObjectOrReply(c,o,&value,NULL) != REDIS_OK) return;
// 检查加法操作执行之后值释放会溢出
// 如果是的话,就向客户端发送一个出错回复,并放弃设置操作
oldvalue = value;
if ((incr < 0 && oldvalue < 0 && incr < (LLONG_MIN-oldvalue)) ||
(incr > 0 && oldvalue > 0 && incr > (LLONG_MAX-oldvalue))) {
addReplyError(c,"increment or decrement would overflow");
return;
}
// 进行加法计算,并将值保存到新的值对象中
// 然后用新的值对象替换原来的值对象
value += incr;
new = createStringObjectFromLongLong(value);
if (o)
dbOverwrite(c->db,c->argv[1],new);
else
dbAdd(c->db,c->argv[1],new);
// 向数据库发送键被修改的信号
signalModifiedKey(c->db,c->argv[1]);
// 发送事件通知
notifyKeyspaceEvent(REDIS_NOTIFY_STRING,"incrby",c->argv[1],c->db->id);
// 将服务器设为脏
server.dirty++;
// 返回回复
addReply(c,shared.colon);
addReply(c,new);
addReply(c,shared.crlf);
}

说明:如果对象不存在,则初始化为0,获取value后,执行value+1。执行过程符合原子性特点

decr——delCommand

1
示例:DECR KEY_NAME 
  • 执行命令之后 key 的值。
1
2
3
4
//t_string.c
void decrCommand(redisClient *c) {
incrDecrCommand(c,-1);
}

说明:同上一个方法,执行value-1

incrby——incrbyCommand

示例:INCRBY KEY_NAME INCR_AMOUNT

  • 加上指定的增量值之后, key 的值。
1
2
3
4
5
6
//t_string.c
void incrbyCommand(redisClient *c) {
long long incr;
if (getLongLongFromObjectOrReply(c, c->argv[2], &incr, NULL) != REDIS_OK) return;
incrDecrCommand(c,incr);
}

说明:同上面的方法,value + incr

decrby——decrbyCommand

示例:DECRBY KEY_NAME DECREMENT_AMOUNT

  • 减去指定减量值之后, key 的值。
1
2
3
4
5
6
//t_string.c
void decrbyCommand(redisClient *c) {
long long incr;
if (getLongLongFromObjectOrReply(c, c->argv[2], &incr, NULL) != REDIS_OK) return;
incrDecrCommand(c,-incr);
}

说明:同上面的方法,value - incr

incrbyfloat——incrbyfloatCommand

1
示例:INCRBYFLOAT KEY_NAME INCR_AMOUNT
  • 执行命令之后 key 的值。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
//t_string.c
void incrbyfloatCommand(redisClient *c) {
long double incr, value;
robj *o, *new, *aux;
// 取出值对象
o = lookupKeyWrite(c->db,c->argv[1]);
// 检查对象是否存在,以及类型是否正确
if (o != NULL && checkType(c,o,REDIS_STRING)) return;
// 将对象的整数值保存到 value 参数中
// 并取出 incr 参数的值
if (getLongDoubleFromObjectOrReply(c,o,&value,NULL) != REDIS_OK ||
getLongDoubleFromObjectOrReply(c,c->argv[2],&incr,NULL) != REDIS_OK)
return;
// 进行加法计算,并检查是否溢出
value += incr;
if (isnan(value) || isinf(value)) {
addReplyError(c,"increment would produce NaN or Infinity");
return;
}
// 用一个包含新值的新对象替换现有的值对象
new = createStringObjectFromLongDouble(value);
if (o)
dbOverwrite(c->db,c->argv[1],new);
else
dbAdd(c->db,c->argv[1],new);
// 向数据库发送键被修改的信号
signalModifiedKey(c->db,c->argv[1]);
// 发送事件通知
notifyKeyspaceEvent(REDIS_NOTIFY_STRING,"incrbyfloat",c->argv[1],c->db->id);
// 将服务器设为脏
server.dirty++;
// 回复
addReplyBulk(c,new);
/* Always replicate INCRBYFLOAT as a SET command with the final value
* in order to make sure that differences in float precision or formatting
* will not create differences in replicas or after an AOF restart. */
// 在传播 INCRBYFLOAT 命令时,总是用 SET 命令来替换 INCRBYFLOAT 命令
// 从而防止因为不同的浮点精度和格式化造成 AOF 重启时的数据不一致
aux = createStringObject("SET",3);
rewriteClientCommandArgument(c,0,aux);
decrRefCount(aux);
rewriteClientCommandArgument(c,2,new);
}

说明:同上类似,int改成float

mget——mgetCommand

示例:MGET KEY1 KEY2 .. KEYN

  • 一个包含所有给定 key 的值的列表。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
//t_string.c
void mgetCommand(redisClient *c) {
int j;
addReplyMultiBulkLen(c,c->argc-1);
// 查找并返回所有输入键的值
for (j = 1; j < c->argc; j++) {
// 查找键 c->argc[j] 的值
robj *o = lookupKeyRead(c->db,c->argv[j]);
if (o == NULL) {
// 值不存在,向客户端发送空回复
addReply(c,shared.nullbulk);
} else {
if (o->type != REDIS_STRING) {
// 值存在,但不是字符串类型
addReply(c,shared.nullbulk);
} else {
// 值存在,并且是字符串
addReplyBulk(c,o);
}
}
}
}

说明:从dict中查找,如果存在则返回;如果没有则返回空。这里没有调用过期函数。

getset——getsetCommand

示例:GETSET KEY_NAME VALUE

  • 返回给定 key 的旧值。 当 key 没有旧值时,即 key 不存在时,返回 nil 。

    当 key 存在但不是字符串类型时,返回一个错误。

1
2
3
4
5
6
7
8
9
10
11
12
13
//t_string.c
void getsetCommand(redisClient *c) {
// 取出并返回键的值对象
if (getGenericCommand(c) == REDIS_ERR) return;
// 编码键的新值 c->argv[2]
c->argv[2] = tryObjectEncoding(c->argv[2]);
// 将数据库中关联键 c->argv[1] 和新值对象 c->argv[2]
setKey(c->db,c->argv[1],c->argv[2]);
// 发送事件通知
notifyKeyspaceEvent(REDIS_NOTIFY_STRING,"set",c->argv[1],c->db->id);
// 将服务器设为脏
server.dirty++;
}

说明:结合了get和set方法,无法解决并发计数问题

mset——msetCommand

示例:MSET key1 value1 key2 value2 .. keyN valueN

  • 总是返回 OK 。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
//t_string.c
void msetCommand(redisClient *c) {
msetGenericCommand(c,0);
}

void msetGenericCommand(redisClient *c, int nx) {
int j, busykeys = 0;
// 键值参数不是成相成对出现的,格式不正确
if ((c->argc % 2) == 0) {
addReplyError(c,"wrong number of arguments for MSET");
return;
}
/* Handle the NX flag. The MSETNX semantic is to return zero and don't
* set nothing at all if at least one already key exists. */
// 如果 nx 参数为真,那么检查所有输入键在数据库中是否存在
// 只要有一个键是存在的,那么就向客户端发送空回复
// 并放弃执行接下来的设置操作
if (nx) {
for (j = 1; j < c->argc; j += 2) {
if (lookupKeyWrite(c->db,c->argv[j]) != NULL) {
busykeys++;
}
}
// 键存在
// 发送空白回复,并放弃执行接下来的设置操作
if (busykeys) {
addReply(c, shared.czero);
return;
}
}
// 设置所有键值对
for (j = 1; j < c->argc; j += 2) {
// 对值对象进行解码
c->argv[j+1] = tryObjectEncoding(c->argv[j+1]);
// 将键值对关联到数据库
// c->argc[j] 为键
// c->argc[j+1] 为值
setKey(c->db,c->argv[j],c->argv[j+1]);
// 发送事件通知
notifyKeyspaceEvent(REDIS_NOTIFY_STRING,"set",c->argv[j],c->db->id);
}
// 将服务器设为脏
server.dirty += (c->argc-1)/2;
// 设置成功
// MSET 返回 OK ,而 MSETNX 返回 1
addReply(c, nx ? shared.cone : shared.ok);
}

说明:结合多个set方法

msetnx——msetnxCommand

示例:MSETNX key1 value1 key2 value2 .. keyN valueN

  • 当所有 key 都成功设置,返回 1 。 如果所有给定 key 都设置失败(至少有一个 key 已经存在),那么返回 0 。
1
2
3
4
//t_string.c
void msetnxCommand(redisClient *c) {
msetGenericCommand(c,1);
}

说明:同上

命令 作用 int编码时间复杂度 embstr编码时间复杂度 raw编码实现时间复杂度
SET 设置给定 key 的值 O(1) O(1) O(1)
GET 获取给定 key 的值 O(1) O(1) O(1)
APPEND 将值附加到key原有的string后面 O(n),n为被添加string的长度 O(n) O(n)
INCRBYFLOAT 为 key 中所储存的值加上指定的浮点数增量值 O(1) O(1) O(1)
INCRBY 为 key 所储存的值加上指定值 O(1) ERROR (不允许这种操作) ERROR
DECRBY 为 key 所储存的值减去指定额值 O(1) ERROR ERROR
STRLEN 计算字符串的长度 O(1) O(1) O(1)
SETRANGE 用value参数覆写给定key所储存的字符串值,从偏移量offset开始 O(n) O(n) O(n)
GETRANGE 获取存储在指定 key 中字符串的子字符串 O(n),N 为要返回的字符串的长度 O(n) O(n)