Redis源码剖析和注释(九)--- 字符串命令的实现(t_string)
来源:互联网 发布:社交网络 电影 编辑:程序博客网 时间:2024/06/05 03:54
Redis 字符串键的实现(t_string)
1. 字符串命令介绍
redis中的所有字符串命令如下:字符串类型命令详解
2. 字符串命令的实现
字符串命令底层数据结构为 简单动态字符串SDS 。对于字符串命令,无论是命令本身还是参数,都是作为成一个对象对待的。关于redis的对象系统,请参考文章:redis对象系统源码剖析和注释。
在redis的对象系统中,字符串对象的底层实现类型有如下三种:
因此,一个字符串对象的结构定义如下:
typedef struct redisObject { //对象的数据类型,字符串对象应该为 OBJ_STRING unsigned type:4; //对象的编码类型,分别为OBJ_STRING、OBJ_ENCODING_INT或OBJ_ENCODING_EMBSTR unsigned encoding:4; //暂且不关心该成员 unsigned lru:LRU_BITS; /* lru time (relative to server.lruclock) */ //引用计数 int refcount; //指向底层数据实现的指针 void *ptr;} robj;
我们假设一个key的值为”Hello World” ,因此它的空间结构如图所示:
3. 字符串命令源码注释
所有命令详细实现可以上github下载:t_string.c注释。这里列出重要的命令。
3.1 SET 一类命令的最底层实现
#define OBJ_SET_NO_FLAGS 0#define OBJ_SET_NX (1<<0) /* Set if key not exists. */ //在key不存在的情况下才会设置#define OBJ_SET_XX (1<<1) /* Set if key exists. */ //在key存在的情况下才会设置#define OBJ_SET_EX (1<<2) /* Set if time in seconds is given */ //以秒(s)为单位设置键的key过期时间#define OBJ_SET_PX (1<<3) /* Set if time in ms in given */ //以毫秒(ms)为单位设置键的key过期时间//setGenericCommand()函数是以下命令: SET, SETEX, PSETEX, SETNX.的最底层实现//flags 可以是NX或XX,由上面的宏提供//expire 定义key的过期时间,格式由unit指定//ok_reply和abort_reply保存着回复client的内容,NX和XX也会改变回复//如果ok_reply为空,则使用 "+OK"//如果abort_reply为空,则使用 "$-1"void setGenericCommand(client *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 */ //初始化,避免错误 //如果定义了key的过期时间 if (expire) { //从expire对象中取出值,保存在milliseconds中,如果出错发送默认的信息给client if (getLongLongFromObjectOrReply(c, expire, &milliseconds, NULL) != C_OK) return; // 如果过期时间小于等于0,则发送错误信息给client if (milliseconds <= 0) { addReplyErrorFormat(c,"invalid expire time in %s",c->cmd->name); return; } //如果unit的单位是秒,则需要转换为毫秒保存 if (unit == UNIT_SECONDS) milliseconds *= 1000; } //lookupKeyWrite函数是为执行写操作而取出key的值对象 //如果设置了NX(不存在),并且在数据库中 找到 该key,或者 //设置了XX(存在),并且在数据库中 没有找到 该key //回复abort_reply给client 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.nullbulk); return; } //在当前db设置键为key的值为val setKey(c->db,key,val); //设置数据库为脏(dirty),服务器每次修改一个key后,都会对脏键(dirty)增1 server.dirty++; //设置key的过期时间 //mstime()返回毫秒为单位的格林威治时间 if (expire) setExpire(c->db,key,mstime()+milliseconds); //发送"set"事件的通知,用于发布订阅模式,通知客户端接受发生的事件 notifyKeyspaceEvent(NOTIFY_STRING,"set",key,c->db->id); //发送"expire"事件通知 if (expire) notifyKeyspaceEvent(NOTIFY_GENERIC, "expire",key,c->db->id); //设置成功,则向客户端发送ok_reply addReply(c, ok_reply ? ok_reply : shared.ok);}
3.2 GET 一类命令的最底层实现
//GET 命令的底层实现int getGenericCommand(client *c) { robj *o; //lookupKeyReadOrReply函数是为执行读操作而返回key的值对象,找到返回该对象,找不到会发送信息给client //如果key不存在直接,返回0表示GET命令执行成功 if ((o = lookupKeyReadOrReply(c,c->argv[1],shared.nullbulk)) == NULL) return C_OK; //如果key的值的编码类型不是字符串对象 if (o->type != OBJ_STRING) { addReply(c,shared.wrongtypeerr); //返回类型错误的信息给client,返回-1表示GET命令执行失败 return C_ERR; } else { addReplyBulk(c,o); //返回之前找到的对象作为回复给client,返回0表示GET命令执行成功 return C_OK; }}
3.3 DECR 和 INCR 底层实现
// DECR key// INCR key//INCR和DECR命令的底层实现void incrDecrCommand(client *c, long long incr) { long long value, oldvalue; robj *o, *new; o = lookupKeyWrite(c->db,c->argv[1]); //以写操作获取key的value对象 //找到了value对象但是value对象不是字符串类型,直接返回 if (o != NULL && checkType(c,o,OBJ_STRING)) return; //将字符串类型的value转换为longlong类型保存在value中 if (getLongLongFromObjectOrReply(c,o,&value,NULL) != C_OK) return; oldvalue = value; //备份旧的value //如果incr超出longlong类型所能表示的范围,发送错误信息 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; //计算新的value值 //value对象目前非共享,编码为整型类型,且新value值不在共享范围,且value处于long类型所表示的范围内 if (o && o->refcount == 1 && o->encoding == OBJ_ENCODING_INT && (value < 0 || value >= OBJ_SHARED_INTEGERS) && value >= LONG_MIN && value <= LONG_MAX) { new = o; o->ptr = (void*)((long)value); //设置vlaue对象的值 } else { //当不满足以上任意条件,则新创建一个字符串对象 new = createStringObjectFromLongLong(value); //如果之前的value对象存在 if (o) { dbOverwrite(c->db,c->argv[1],new); //用new对象去重写key的值 } else { dbAdd(c->db,c->argv[1],new); //如果之前的value不存在,将key和new组成新的key-value对 } } signalModifiedKey(c->db,c->argv[1]); //当数据库的键被改动,则会调用该函数发送信号 //发送"incrby"事件通知 notifyKeyspaceEvent(NOTIFY_STRING,"incrby",c->argv[1],c->db->id); //设置脏键 server.dirty++; //回复信息给client addReply(c,shared.colon); addReply(c,new); addReply(c,shared.crlf);}
3.4 APPEND 实现
// APPEND key value// APPEND命令的实现void appendCommand(client *c) { size_t totlen; robj *o, *append; o = lookupKeyWrite(c->db,c->argv[1]); //以写操作获取key的value对象 //如果没有获取到vlaue,则要创建一个 if (o == NULL) { /* Create the key */ c->argv[2] = tryObjectEncoding(c->argv[2]); //对参数value进行优化编码 dbAdd(c->db,c->argv[1],c->argv[2]); //将key和value组成新的key-value对 incrRefCount(c->argv[2]); //增加value的引用计数 totlen = stringObjectLen(c->argv[2]); //返回vlaue的长度 } else { //获取到value /* Key exists, check type */ if (checkType(c,o,OBJ_STRING)) //如果value不是字符串类型的对象直接返回 return; /* "append" is an argument, so always an sds */ //获得追加的值对象 append = c->argv[2]; //计算追加后的长度 totlen = stringObjectLen(o)+sdslen(append->ptr); //如果追加后的长度超出范围,则返回 if (checkStringLength(c,totlen) != C_OK) return; /* Append the value */ //因为要根据value修改key的值,因此如果key原来的值是共享的,需要解除共享,新创建一个值对象与key组对 o = dbUnshareStringValue(c->db,c->argv[1],o); //将vlaue对象的值后面追加上append的值 o->ptr = sdscatlen(o->ptr,append->ptr,sdslen(append->ptr)); //计算出追加后值的长度 totlen = sdslen(o->ptr); } signalModifiedKey(c->db,c->argv[1]);//当数据库的键被改动,则会调用该函数发送信号 //发送"append"事件通知 notifyKeyspaceEvent(NOTIFY_STRING,"append",c->argv[1],c->db->id); //设置脏键 server.dirty++; //发送追加后value的长度给client addReplyLongLong(c,totlen);}
0 0
- Redis源码剖析和注释(九)--- 字符串命令的实现(t_string)
- Redis源码剖析--字符串t_string
- Redis源码分析(九)——字符串类型t_string
- Redis源码分析(九)--- t_list,t_string的分析
- 结合redis设计与实现的redis源码学习-8.2-t_string(字符串键)
- Redis源码剖析和注释(十一)--- 哈希键命令的实现(t_hash)
- Redis源码剖析和注释(十四)---- Redis 数据库及相关命令实现(db)
- Redis源码剖析和注释(二十四)--- Redis Sentinel实现(哨兵操作的深入剖析)
- Redis源码剖析和注释(二)--- 简单动态字符串
- Redis源码剖析和注释(十)--- 列表键命令实现(t_list)
- Redis源码剖析和注释(二十三)--- Redis Sentinel实现(哨兵的执行过程和执行的内容)
- Redis源码剖析和注释(二十八)--- Redis 事务实现和乐观锁
- Redis源码剖析和注释(十九)--- Redis 事件处理实现
- Redis源码剖析和注释(二十)--- 网络连接库剖析(client的创建/释放、命令接收/回复、Redis通信协议分析等)
- Redis源码剖析和注释(三)--- Redis 字典结构
- Redis源码剖析和注释(十六)---- Redis输入输出的抽象(rio)
- Redis源码剖析和注释(十二)--- 集合类型键实现(t_set)
- Redis源码剖析和注释(十三)--- 有序集合类型键实现(t_zset)
- ajax 跨域请求
- 红黑树
- kerberos认证原理---讲的非常细致,易懂
- Cordova
- 低功耗蓝牙开发权威指南 之 安全
- Redis源码剖析和注释(九)--- 字符串命令的实现(t_string)
- JAVA学习笔记
- C#委托事件的使用
- 缓存更新的套路
- linux下gcc-编译多个文件为静态库
- MySQL批量SQL插入性能优化
- spoon软件快捷添加数据
- Java修改图片png格式透明背景大小分辨率
- Vue的计算属性