上两篇我们讲了hash和list数据类型相关的主要实现方法,同时加上前面对框架服务和string相关的功能介绍,已揭开了大部分redis的实用面纱。
现在还剩下两种数据类型: set, zset.
本篇咱们继续来看redis中的数据类型的实现: set 相关操作实现。
研究过jdk的hashmap和hashset实现的同学,肯定都是知道,set其实就是一个简化版的map,只要将map的 k->v 的形式变为 k->1 的形式就可以了。所以set只是map的一个简单包装类。
同理,对于 redis的 hash 和 set 数据类型,我们是否可以得出这么个结论呢?(如果是那样的话,我们就只需看几个set提供的特殊功能即可)
同样,我们从功能列表开始,到数据结构,再到具体实现的这么个思路,来探索redis set的实现吧。
零、redis set相关操作方法
Redis 的 Set 是 String 类型的无序集合。集合成员是唯一的,这就意味着集合中不能出现重复的数据。可根据应用场景需要选用该数据类型。(比如:好友/关注/粉丝/感兴趣的人/黑白名单)
从官方的手册中可以查到相关的使用方法。
1> SADD key member1 [member2]
功能: 向集合添加一个或多个成员
返回值: 本次添加到redis的member数量(不包含已存在的member)2> SCARD key
功能: 获取集合的成员数
返回值: set的元素数量或者03> SDIFF key1 [key2]
功能: 返回给定所有集合的差集
返回值: 差集的数组列表4> SDIFFSTORE destination key1 [key2]
功能: 返回给定所有集合的差集并存储在 destination 中
返回值: 差集元素个数5> SINTER key1 [key2]
功能: 返回给定所有集合的交集
返回值: 交集的数组列表6> SINTERSTORE destination key1 [key2]
功能: 返回给定所有集合的交集并存储在 destination 中
返回值: 交集的元素个数7> SISMEMBER key member
功能: 判断 member 元素是否是集合 key 的成员
返回值: 1:如果member是key的成员, 0:如果member不是key的成员或者key不存在8> SMEMBERS key
功能: 返回集合中的所有成员
返回值: 所有成员列表9> SMOVE source destination member
功能: 将 member 元素从 source 集合移动到 destination 集合
返回值: 1:移动操作成功, 0:移动不成功(member不是source的成员)10> SPOP key [count]
功能: 移除并返回集合中的一个随机元素(因为set是无序的)
返回值: 被移除的元素列表或者nil11> SRANDMEMBER key [count]
功能: 返回集合中一个或多个随机数
返回值: 1个元素或者count个元素数组列表或者nil12> SREM key member1 [member2]
功能: 移除集合中一个或多个成员
返回值: 实际移除的元素个数13> SUNION key1 [key2]
功能: 返回所有给定集合的并集
返回值: 并集元素数组列表14> SUNIONSTORE destination key1 [key2]
功能: 所有给定集合的并集存储在 destination 集合中
返回值: 并集元素个数15> SSCAN key cursor [MATCH pattern] [COUNT count]
功能: 迭代集合中的元素
返回值: 元素数组列表
一、set 相关数据结构
redis使用dict和intset 两种数据结构保存set数据。
// 1. inset 数据结构,在set数据量小且都是整型数据时使用 typedef struct intset { // 编码范围,由具体存储值决定 uint32_t encoding; // 数组长度 uint32_t length; // 具体存储元素的容器 int8_t contents[]; } intset; // 2. dict 相关数据结构,即是 hash 的实现相关的数据结构 /* This is our hash table structure. Every dictionary has two of this as we * implement incremental rehashing, for the old to the new table. */ typedef struct dictht { dictEntry **table; unsigned long size; unsigned long sizemask; unsigned long used; } dictht; typedef struct dict { dictType *type; void *privdata; dictht ht[2]; long rehashidx; /* rehashing not in progress if rehashidx == -1 */ unsigned long iterators; /* number of iterators currently running */ } dict; /* If safe is set to 1 this is a safe iterator, that means, you can call * dictAdd, dictFind, and other functions against the dictionary even while * iterating. Otherwise it is a non safe iterator, and only dictNext() * should be called while iterating. */ typedef struct dictIterator { dict *d; long index; int table, safe; dictEntry *entry, *nextEntry; /* unsafe iterator fingerprint for misuse detection. */ long long fingerprint; } dictIterator; typedef struct dictEntry { void *key; union { void *val; uint64_t u64; int64_t s64; double d; } v; struct dictEntry *next; } dictEntry; typedef struct dictType { unsigned int (*hashFunction)(const void *key); void *(*keyDup)(void *privdata, const void *key); void *(*valDup)(void *privdata, const void *obj); int (*keyCompare)(void *privdata, const void *key1, const void *key2); void (*keyDestructor)(void *privdata, void *key); void (*valDestructor)(void *privdata, void *obj); } dictType;
对于set相关的命令的接口定义:
{ "sadd",saddCommand,-3,"wmF",0,NULL,1,1,1,0,0}, { "srem",sremCommand,-3,"wF",0,NULL,1,1,1,0,0}, { "smove",smoveCommand,4,"wF",0,NULL,1,2,1,0,0}, { "sismember",sismemberCommand,3,"rF",0,NULL,1,1,1,0,0}, { "scard",scardCommand,2,"rF",0,NULL,1,1,1,0,0}, { "spop",spopCommand,-2,"wRsF",0,NULL,1,1,1,0,0}, { "srandmember",srandmemberCommand,-2,"rR",0,NULL,1,1,1,0,0}, { "sinter",sinterCommand,-2,"rS",0,NULL,1,-1,1,0,0}, { "sinterstore",sinterstoreCommand,-3,"wm",0,NULL,1,-1,1,0,0}, { "sunion",sunionCommand,-2,"rS",0,NULL,1,-1,1,0,0}, { "sunionstore",sunionstoreCommand,-3,"wm",0,NULL,1,-1,1,0,0}, { "sdiff",sdiffCommand,-2,"rS",0,NULL,1,-1,1,0,0}, { "sdiffstore",sdiffstoreCommand,-3,"wm",0,NULL,1,-1,1,0,0}, { "smembers",sinterCommand,2,"rS",0,NULL,1,1,1,0,0}, { "sscan",sscanCommand,-3,"rR",0,NULL,1,1,1,0,0},
二、sadd 添加成员操作
一般我们都会以添加数据开始。从而理解数据结构的应用。
// 用法: SADD key member1 [member2] // t_set.c, 添加member void saddCommand(client *c) { robj *set; int j, added = 0; // 先从当前db中查找set实例 set = lookupKeyWrite(c->db,c->argv[1]); if (set == NULL) { // 1. 新建set实例并添加到当前db中 set = setTypeCreate(c->argv[2]->ptr); dbAdd(c->db,c->argv[1],set); } else { if (set->type != OBJ_SET) { addReply(c,shared.wrongtypeerr); return; } } // 对于n个member,一个个地添加即可 for (j = 2; j < c->argc; j++) { // 2. 只有添加成功, added 才会加1 if (setTypeAdd(set,c->argv[j]->ptr)) added++; } // 命令传播 if (added) { signalModifiedKey(c->db,c->argv[1]); notifyKeyspaceEvent(NOTIFY_SET,"sadd",c->argv[1],c->db->id); } server.dirty += added; // 响应添加成功的数量 addReplyLongLong(c,added); } // 1. 创建新的set集合实例(需根据首次的参数类型判定) // t_set.c, 创建set实例 /* Factory method to return a set that *can* hold "value". When the object has * an integer-encodable value, an intset will be returned. Otherwise a regular * hash table. */ robj *setTypeCreate(sds value) { // 如果传入的value是整型,则创建 intset 类型的set // 否则使用dict类型的set // 一般地,第一个数据为整型,后续数据也应该为整型,所以这个数据结构相对稳定 // 而hash的容器创建时,只使用了一 ziplist 创建,这是不一样的实现 if (isSdsRepresentableAsLongLong(value,NULL) == C_OK) return createIntsetObject(); return createSetObject(); } // 1.1. 创建 intset 型的set // object.c robj *createIntsetObject(void) { intset *is = intsetNew(); robj *o = createObject(OBJ_SET,is); o->encoding = OBJ_ENCODING_INTSET; return o; } // intset.c, new一个空的intset对象 /* Create an empty intset. */ intset *intsetNew(void) { intset *is = zmalloc(sizeof(intset)); is->encoding = intrev32ifbe(INTSET_ENC_INT16); is->length = 0; return is; } // 1.2. 创建dict 型的set robj *createSetObject(void) { dict *d = dictCreate(&setDictType,NULL); robj *o = createObject(OBJ_SET,d); o->encoding = OBJ_ENCODING_HT; return o; } // dict.c /* Create a new hash table */ dict *dictCreate(dictType *type, void *privDataPtr) { dict *d = zmalloc(sizeof(*d)); _dictInit(d,type,privDataPtr); return d; } /* Initialize the hash table */ int _dictInit(dict *d, dictType *type,