对象类型与编码方式
对于字符串类型的命令,redis数据库会为每个对象创建一个字符串类型(REDIS_STRING)的对象。
对于字符串类型的对象,可以支持三种编码方式:
#define REDIS_ENCODING_RAW 0 /* Raw representation */
#define REDIS_ENCODING_INT 1 /* Encoded as integer */
#define REDIS_ENCODING_EMBSTR 8 /* Embedded sds string encoding */
只有对于纯数字,才会将字符串编码为int,如执行BITCOUNT命令时。
由于一个embstr方式编码的对象的内存结构为:
对象的robject和具体数据buf都是紧凑相连的,其中robject为16字节,sdshdr为8字节,buf数组的末尾还需要加上1字节的’\0’表示结束。在redis中,要求embstr编码的对象最大为64字节,因此数据部分的长度最多为:64-(16+8+1)=39字节。
因此当字符串长度不超过39字节时,会采用embstr编码方式,否则会采用raw编码方式。
1)int编码方式
int编码方式用的比较少,当数据为纯数字时,才可能会使用int编码方式
其中ptr直接存储数据值
2)raw编码方式
eg:set num “123456789101112131415161718192021222324252627282930”
命令:set
key:url
value:123456789101112131415161718192021222324252627282930
由于value的长度非常长(超过了39个字节),因此采用raw编码方式
3)embstr编码方式
eg: set msg “hello”
value为字符串“hello”,采用embstr编码方式,最后生成的对象为:
可以发现robject、sds在内存空间中是连续的
命令:
位操作命令(bitops.c)
- GETBIT 获取一个键值的二进制位的指定位置的值(0/1),用法:
GETBIT key offset
- SETBIT 设置一个键值的二进制位的指定位置的值(0/1),用法:
SETBIT key offset value
- BITCOUNT 获取一个键值的一个范围内的二进制表示的1的个数,用法:
BITCOUNT key [start end]
- BITOP 该命令可以对多个字符串类型键进行位运算,并将结果存储到指定的键中,BITOP支持的运算包含:OR,AND,XOR,NOT,用法:
BITOP OP desKey key1
key2 - BITPOS 获取指定键的第一个位值为0或者1的位置,用法:
BITPOS key 0/1 [start, end]
1)SETBIT
SETBIT key offset value
将key的第offset位设置为value,因此value只能为0或1,默认为0。
注意offset是以bit位为单位的,且offset 参数必须在[0,2^32)之间。 (key的长度被限制在 512 MB 之内)。
/* SETBIT key offset bitvalue */
void setbitCommand(redisClient *c) {
///解析offset和value,并检查是否合法
if (getBitOffsetFromArgument(c,c->argv[2],&bitoffset) != REDIS_OK)
return;
if (getLongFromObjectOrReply(c,c->argv[3],&on,err) != REDIS_OK)
return;
if (on & ~1) { //value只能为0或1,否则错误
addReplyError(c,err);
return;
}
o = lookupKeyWrite(c->db,c->argv[1]); //在数据库中查找key
if (o == NULL) { //当key不存在时,将key插入到数据库中,此时value为null
o = createObject(REDIS_STRING,sdsempty());
dbAdd(c->db,c->argv[1],o);
} else { //当key不为字符串时,直接返回,否则获得key对应的value
if (checkType(c,o,REDIS_STRING)) return;
o = dbUnshareStringValue(c->db,c->argv[1],o);
}
byte = bitoffset >> 3; //offset位对应的字节数
o->ptr = sdsgrowzero(o->ptr,byte+1); //扩展value的空间
byteval = ((uint8_t*)o->ptr)[byte];
bit = 7 - (bitoffset & 0x7);
bitval = byteval & (1 << bit); //得到对应bit位原来的值
/* 更新该bit位,并将该bit位原来的值返回给客户端 */
byteval &= ~(1 << bit); //先讲该位清零
byteval |= ((on & 0x1) << bit); //再设置该位
((uint8_t*)o->ptr)[byte] = byteval;
}
2)GETBIT
GETBIT key offset
获取key中第offset位的值,key中bit位是从左向右递增的,即最左为第0位。
当 offset 比字符串值的长度大,或者 key 不存在时,返回 0 。
同样offset 参数必须在[0,2^32)之间
/* GETBIT key offset */
void getbitCommand(redisClient *c) {
//解析offset,并检查是否合法
if (getBitOffsetFromArgument(c,c->argv[2],&bitoffset) != REDIS_OK)
return;
if ((o = lookupKeyReadOrReply(c,c->argv[1],shared.czero)) == NULL ||
checkType(c,o,REDIS_STRING)) return; //当key不存在或key不为字符串时,直接返回
byte = bitoffset >> 3; //offset位所在的字节
bit = 7 - (bitoffset & 0x7); //offset在byte字节的第bit位
if (sdsEncodedObject(o)) { //得到第offset位的值
if (byte < sdslen(o->ptr))
bitval = ((uint8_t*)o->ptr)[byte] & (1 << bit);
} else {
if (byte < (size_t)ll2string(llbuf,sizeof(llbuf),(long)o->ptr))
bitval = llbuf[byte] & (1 << bit);
}
}
3)BITOP
BITOP operation destkey key [key …]
对一个或多个保存二进制位的字符串 key 进行位操作,并将结果保存到 destkey
operation 可以是 AND 、 OR 、 NOT 、 XOR 这四种操作中的任意一种,NOT只接受一个key
/* BITOP op_name target_key src_key1 src_key2 src_key3 ... src_keyN */
void bitopCommand(redisClient *c) {
/* 解析位运算命令 */
if ((opname[0] == ‘a‘ || opname[0] == ‘A‘) && !strcasecmp(opname,"and"))
op = BITOP_AND;
else if((opname[0] == ‘o‘ || opname[0] == ‘O‘) && !strcasecmp(opname,"or"))
op = BITOP_OR;
else if((opname[0] == ‘x‘ || opname[0] == ‘X‘) && !strcasecmp(opname,"xor"))
op = BITOP_XOR;
else if((opname[0] == ‘n‘ || opname[0] == ‘N‘) && !strcasecmp(opname,"not"))
op = BITOP_NOT;
else {
addReply(c,shared.syntaxerr);
return;
}
if (op == BITOP_NOT && c->argc != 4) { /* NOT操作只接受一个key */
addReplyError(c,"BITOP NOT must be called with a single source key.");
return;
}
for (j = 0; j < numkeys; j++) {
o = lookupKeyRead(c->db,c->argv[j+3]);
if (o == NULL) { //key不存在时,当做空字符串处理
}
if (checkType(c,o,REDIS_STRING)) { //key不为字符串时,返回
return;
}
}
if (maxlen) {
if (minlen >= sizeof(unsigned long)*4 && numkeys <= 16) {
/* 依次对每个key执行位运算 */
if (op == BITOP_AND) {
......
} else if (op == BITOP_OR) {
} else if (op == BITOP_XOR) {
} else if (op == BITOP_NOT) {
}
}
for (; j < maxlen; j++) {
output = (len[0] <= j) ? 0 : src[0][j];
if (op == BITOP_NOT) output = ~output;
for (i = 1; i < numkeys; i++) {
byte = (len[i] <= j) ? 0 : src[i][j];
switch(op) {
case BITOP_AND: output &= byte; break;
case BITOP_OR: output |= byte; break;
case BITOP_XOR: output ^= byte; break;
}
}
res[j] = output;
}
}
}
4)BITCOUNT
BITCOUNT key [start] [end]
计算第[start,end]个字节中被设置为 1 的比特位的数量。
其中start和end可以为负数,如 -1 表示最后一位,而 -2 表示倒数第二位
注意第end个字节也在计算范围内,且BITCOUNT 是对字节进行计数的。setbit函数是以bit为单位的。
/* BITCOUNT key [start end] */
void bitcountCommand(redisClient *c) {
if ((o = lookupKeyReadOrReply(c,c->argv[1],shared.czero)) == NULL ||checkType(c,o,REDIS_STRING)) return; //当key不存在,或key为字符串时,直接返回
//将key转换为字符串形式
if (o->encoding == REDIS_ENCODING_INT) {
p = (unsigned char*) llbuf;
strlen = ll2string(llbuf,sizeof(llbuf),(long)o->ptr);
} else {
p = (unsigned char*) o->ptr;
strlen = sdslen(o->ptr);
}
/* 如果参数个数为4时,解析start、end参数 */
if (c->argc == 4) { //
if (getLongFromObjectOrReply(c,c->argv[2],&start,NULL) != REDIS_OK)
return;
if (getLongFromObjectOrReply(c,c->argv[3],&end,NULL) != REDIS_OK)
return;
/* 当start、end为负值时,转换为正值 */
if (start < 0) start = strlen+start;
if (end < 0) end = strlen+end;
if (start < 0) start = 0;
if (end < 0) end = 0;
if (end >= strlen) end = strlen-1;
} else if (c->argc == 2) { //否则就是对整个字符串进行计数
start = 0;
end = strlen-1;
} else { //其它参数个数为错误
addReply(c,shared.syntaxerr);
return;
}
if (start > end) { //start>end时,结果为0
addReply(c,shared.czero);
} else { //否则计算从p+start起的bytes个字节中bit为1的个数
long bytes = end-start+1;
addReplyLongLong(c,redisPopcount(p+start,bytes));
}
}
5)BITPOS
BITPOS key bit [start [end]]
返回key中第[start,end]个字节中,第一个bit位为bit的位。
同样start、end参数可以为负值
/* BITPOS key bit [start [end]] */
void bitposCommand(redisClient *c) {
//解析参数bit,并检查合法性
if (getLongFromObjectOrReply(c,c->argv[2],&bit,NULL) != REDIS_OK)
return;
if (bit != 0 && bit != 1) {
addReplyError(c, "The bit argument must be 1 or 0.");
return;
}
//当key不存在时,认为key是一串无限长的0,当查找0时,返回第0位,当查找1时,返回-1;
if ((o = lookupKeyRead(c->db,c->argv[1])) == NULL) {
addReplyLongLong(c, bit ? -1 : 0);
return;
}
if (checkType(c,o,REDIS_STRING)) return;
//获取key
if (o->encoding == REDIS_ENCODING_INT) {
p = (unsigned char*) llbuf;
strlen = ll2string(llbuf,sizeof(llbuf),(long)o->ptr);
} else {
p = (unsigned char*) o->ptr;
strlen = sdslen(o->ptr);
}
/* 解析start、end参数 */
if (c->argc == 4 || c->argc == 5) {
......
} else if (c->argc == 3) { /* The whole string. */
start = 0;
end = strlen-1;
} else { /* Syntax error. */
addReply(c,shared.syntaxerr);
return;
}
if (start > end) {
addReplyLongLong(c, -1);
} else {
long bytes = end-start+1;
long pos = redisBitpos(p+start,bytes,bit);
if (end_given && bit == 0 && pos == bytes*8) {
addReplyLongLong(c,-1);
return;
}
if (pos != -1) pos += start*8; /* Adjust for the bytes we skipped. */
addReplyLongLong(c,pos);
}
}
字符串命令(t_string.c)
- SET 赋值,用法:
SET key value
- GET 取值,用法:
GET key
- INCR 递增数字,仅仅对数字类型的键有用,相当于Java的i++运算,用法:
INCR key
- INCRBY 增加指定的数字,仅仅对数字类型的键有用,相当于Java的i+=3,用法:
INCRBY key increment
,意思是key自增increment,increment可以为负数,表示减少。 - DECR 递减数字,仅仅对数字类型的键有用,相当于Java的i–,用法:
DECR key
- DECRBY 减少指定的数字,仅仅对数字类型的键有用,相当于Java的i-=3,用法:
DECRBY key decrement
,意思是key自减decrement,decrement可以为正数,表示增加。 - INCRBYFLOAT 增加指定浮点数,仅仅对数字类型的键有用,用法:
INCRBYFLOAT key increment
- APPEND 向尾部追加值,相当于Java中的”hello”.append(“ world”),用法:
APPEND key value
- STRLEN 获取字符串长度,用法:
STRLEN key
- MSET 同时设置多个key的值,用法:
MSET key1 value1 [key2 value2 ...]
- MGET 同时获取多个key的值,用法:
MGET key1 [key2 ...]
由于字符串命令非常多,因此只以其中几个为例进行简单说明
1)APPEND
APPEND key value
若 key 不存在, 则将给定 key 设为 value ,就像执行 SET key value 一样。
若 key 已经存在并且是一个字符串, 则将 value 追加到 key 原来对应的值的末尾。
//APPEND key value
void appendCommand(redisClient *c) {
robj *o, *append;
o = lookupKeyWrite(c->db,c->argv[1]);
if (o == NULL) { //1)key不存在时,向数据库中插入key-value对
c->argv[2] = tryObjectEncoding(c->argv[2]);
dbAdd(c->db,c->argv[1],c->argv[2]);
} else { //2)key存在时
if (checkType(c,o,REDIS_STRING)) //2.1)key不为字符串时,直接返回
return;
o = dbUnshareStringValue(c->db,c->argv[1],o);
o->ptr = sdscatlen(o->ptr,append->ptr,sdslen(append->ptr)); //2.2)key为字符串时,将value追加到key原来对应的值的末尾
}
}
2)SET
SET key value [EX seconds] [PX milliseconds] [NX|XX]
将字符串值 value 关联到 key 。
如果 key 已经持有其他值, SET 就覆写旧值,无视类型。
/* SET key value [NX] [XX] [EX <seconds>] [PX <milliseconds>] */
void setCommand(redisClient *c) {
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); //真正操作
}
//真正执行set命令的函数
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) { //当有超时时间时,计算超时时间
if (getLongLongFromObjectOrReply(c, expire, &milliseconds, NULL) != REDIS_OK)
return;
if (milliseconds <= 0) {
addReplyErrorFormat(c,"invalid expire time in %s",c->cmd->name);
return;
}
if (unit == UNIT_SECONDS) milliseconds *= 1000;
}
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); //将key-val对插入到数据库中
if (expire) setExpire(c->db,key,mstime()+milliseconds); //设置超时时间
}
本文所引用的源码全部来自Redis3.0.7版本
redis学习参考资料:
https://github.com/huangz1990/redis-3.0-annotated
Redis 设计与实现(第二版)
http://blog.csdn.net/hechurui/article/details/49508735