深入底层C源码 Redis核心设计原理( 九 )


// set添加元素的处理函数 , 在文件t_set.c中//过程汇总//检查set是否存在不存在则创建一个set结合 。//根据传入的set集合一个个进行添加 , 添加的时候需要进行内存压缩 。//setTypeAdd执行set添加过程中会判断是否进行编码转换 。void saddCommand(client *c) {robj *set;int j, added = 0;// 取出集合对象set = lookupKeyWrite(c->db,c->argv[1]);// 对象不存在 , 创建一个新的 , 并将它关联到数据库if (set == NULL) {set = setTypeCreate(c->argv[2]->ptr);dbAdd(c->db,c->argv[1],set);}// 对象存在 , 检查类型else {if (set->type != OBJ_SET) {addReply(c,shared.wrongtypeerr);return;}}// 将所有输入元素添加到集合中for (j = 2; j < c->argc; j++) {// set 类型 添加元素if (setTypeAdd(set,c->argv[j]->ptr)) added++;}// 如果有至少一个元素被成功添加 , 那么执行以下程序if (added) {// 发送键修改信号signalModifiedKey(c,c->db,c->argv[1]);// 发送事件通知notifyKeyspaceEvent(NOTIFY_SET,"sadd",c->argv[1],c->db->id);}// 将数据库设为脏server.dirty += added;// 返回添加元素的数量addReplyLongLong(c,added);}//元素已经存在 直接返回 0  ,  否则添加元素 返回 1//过程汇总//如果能够转成int的对象(isObjectRepresentableAsLongLong) , 那么就用intset保存 。//如果用intset保存的时候 , 如果长度超过512(REDIS_SET_MAX_INTSET_ENTRIES)就转为hashtable编码 。//其他情况统一用hashtable进行存储 。int setTypeAdd(robj *subject, sds value) {long long llval;// 字典if (subject->encoding == OBJ_ENCODING_HT) {// 将 value 作为键 ,  NULL 作为值 , 将元素添加到字典中dict *ht = subject->ptr;dictEntry *de = dictAddRaw(ht,value,NULL);if (de) {dictSetKey(ht,de,sdsdup(value));dictSetVal(ht,de,NULL);return 1;}}// intsetelse if (subject->encoding == OBJ_ENCODING_INTSET) {// 判断是否可以用整形编码 , 可以的话用intset 编码if (isSdsRepresentableAsLongLong(value,&llval) == C_OK) {uint8_t success = 0;subject->ptr = intsetAdd(subject->ptr,llval,&success);if (success) {//如果元素个数超过set-max-intset-entries[ 默认 512 ]时 , 将转化为 hashtable 数据结构if (intsetLen(subject->ptr) > server.set_max_intset_entries)setTypeConvert(subject,OBJ_ENCODING_HT);return 1;}} else {//转整形失败 , 直接用hashtable存储setTypeConvert(subject,OBJ_ENCODING_HT);// 执行添加操作serverAssert(dictAdd(subject->ptr,sdsdup(value),NULL) == DICT_OK);return 1;}} else {// 未知编码serverPanic("Unknown set encoding");}return 0;}7.ZSet 数据结构1)介绍
【1】ZSet  为有序的 , 自动去重的集合数据类型 , ZSet 数据结构底层实现为 字典(dict) + 跳表(skiplist) ,当数据比较少时 , 用ziplist编码结构存储 。
zset-max-ziplist-entries128// 元素个数超过128  , 将用skiplist编码zset-max-ziplist-value64//单个元素大小超过 64 byte, 将用 skiplist编码【2】数据比较少时 , 用ziplist编码结构存储的图示:

深入底层C源码 Redis核心设计原理

文章插图
2)skiplist 分析解析
【1】数据结构代码
// 创建zset 数据结构: 字典 + 跳表robj *createZsetObject(void) {    zset *zs = zmalloc(sizeof(*zs));    robj *o;    // dict用来查询数据到分数的对应关系 ,  如 zscore 就可以直接根据 元素拿到分值     zs->dict = dictCreate(&zsetDictType,NULL);        // skiplist用来根据分数查询数据(可能是范围查找)    zs->zsl = zslCreate();  // 设置对象类型     o = createObject(OBJ_ZSET,zs);  // 设置编码类型     o->encoding = OBJ_ENCODING_SKIPLIST;    return o;}//位于edis/src/server.h 中#define ZSKIPLIST_MAXLEVEL 32 /* Should be enough for 2^64 elements */#define ZSKIPLIST_P 0.25/* Skiplist P = 1/4 */typedef struct zskiplistNode {sds ele;//存储字符串类型数据 redis3.0版本中使用robj类型表示 , 但是在redis4.0.1中直接使用sds类型表示double score;//存储排序的分值struct zskiplistNode *backward;//指向上一个节点,用于zrevrange命令struct zskiplistLevel {struct zskiplistNode *forward;//指向下一个节点unsigned long span;//到达后一个节点的跨度(两个相邻节点span为1)} level[];//该节点在各层的信息 , 柔性数组成员} zskiplistNode;typedef struct zskiplist {struct zskiplistNode *header, *tail;// 跳跃表头尾节点unsigned long length;//节点个数int level;//除头结点外最大的层数} zskiplist;typedef struct zset {dict *dict;zskiplist *zsl;} zset;

经验总结扩展阅读