七redis的set数据类型常见命令、内部编码、场景

七redis的set数据类型常见命令、内部编码、场景

保存多个不重复的字符串的集合。

常见命令

sadd

  • 解释

    往集合中添加元素,如果值已经存在,则忽略。如果键不存在,则创建键后往键里添加元素。

  • 用法 sadd key value

  • 示例


127.0.0.1:6379> sadd key1 1
(integer) 1
127.0.0.1:6379> sadd key2
(error) ERR wrong number of arguments for 'sadd' command

  • 源码
/**
** redis.c
**/ 
struct redisCommand readonlyCommandTable[] = {
 {"sadd",saddCommand,3,REDIS_CMD_DENYOOM,NULL,1,1,1}
}

1.查询键是否存在
2.如果键不存在则创建数据类型为集合的键(这里完成后键的没有元素)
3.加入到dict字典中
4.键已经存在,如果不是集合类型则返回错误
5.元素加入集合中,返回1表示添加成功,0表示添加失败或元素已存在

/**
** t_set.c
**/
void saddCommand(redisClient *c) {
    robj *set;
	//1.查询键是否存在
    set = lookupKeyWrite(c->db,c->argv[1]);
    c->argv[2] = tryObjectEncoding(c->argv[2]);
    if (set == NULL) {
	//2.如果键不存在则创建数据类型为集合的键(这里完成后键的没有元素)
        set = setTypeCreate(c->argv[2]);
	//3.加入到dict字典中
        dbAdd(c->db,c->argv[1],set);
    } else {
	//4.键已经存在,如果不是集合类型则返回错误
        if (set->type != REDIS_SET) {
            addReply(c,shared.wrongtypeerr);
            return;
        }
    }
	//5. 元素加入集合中,返回1表示添加成功,0表示添加失败或元素已存在
    if (setTypeAdd(set,c->argv[2])) {
        touchWatchedKey(c->db,c->argv[1]);
        server.dirty++;
        addReply(c,shared.cone);
    } else {
        addReply(c,shared.czero);
    }
}


scard

  • 解释

    返回集合中元素的个数,如果键不存在返回0。

  • 用法 scard key

  • 示例


127.0.0.1:6379> sadd key1 1
(integer) 1
127.0.01:6379> sadd key1 2
(integer) 1
127.0.0.1:6379> scard key1 
(integer) 2
127.0.0.1:6379> scard key2
(integer) 0

  • 源码
/**
** redis.c
**/ 
struct redisCommand readonlyCommandTable[] = {
 {"scard",scardCommand,2,0,NULL,1,1,1}
}

1.检查键是否存在以及是否数据类型是set集合,否则返回0
2.返回集合中的元素个数
2.1 计算内部编码是hashtable的键的元素个数
2.2 计算内部编码是intset的键的元素个数
2.3 未知的集合内部编码,返回错误信息

/**
** t_set.c
**/

void scardCommand(redisClient *c) {
    robj *o;
	//1.检查键是否存在以及是否数据类型是set集合,否则返回0
    if ((o = lookupKeyReadOrReply(c,c->argv[1],shared.czero)) == NULL ||
        checkType(c,o,REDIS_SET)) return;
	//2. 返回集合中的元素个数
    addReplyLongLong(c,setTypeSize(o));
}

/** 
** t_set.c
** 返回集合中的元素个数
**/
unsigned long setTypeSize(robj *subject) {
	//1.计算内部编码是hashtable的键的元素个数
    if (subject->encoding == REDIS_ENCODING_HT) {
        return dictSize((dict*)subject->ptr);
    } else if (subject->encoding == REDIS_ENCODING_INTSET) {
	//2.计算内部编码是intset的键的元素个数
        return intsetLen((intset*)subject->ptr);
    } else {
	//3. 未知的集合内部编码,返回错误信息
        redisPanic("Unknown set encoding");
    }
}


sdiff

  • 解释

    返回第一个键其他键的差集

  • 用法

    sdiff key1 key2 [key3…]
    需要注意: 算法复杂度是O(N),如果集合中的元素过多,会导致卡住redis。

  • 示例


127.0.0.1:6379> sadd key1 a
(integer) 1
127.0.0.1:6379> sadd key1 b
(integer) 1
127.0.0.1:6379> sadd key1 c
(integer) 1
127.0.0.1:6379> sadd key1 d
(integer) 1
127.0.0.1:6379> sadd key2 c
(integer) 1
127.0.0.1:6379> sadd key3 a
(integer) 1
127.0.0.1:6379> sadd key3 c
(integer) 1
127.0.0.1:6379> sadd key3 e
(integer) 1
127.0.0.1:6379> sdiff key1 key2 key3
1) "b"
2) "d"


  • 源码
/**
** redis.c
**/ 
struct redisCommand readonlyCommandTable[] = {
  {"sdiff",sdiffCommand,-2,REDIS_CMD_DENYOOM,NULL,1,-1,1}
}

/**
** t_set.c
**/

#define REDIS_OP_UNION 0
#define REDIS_OP_DIFF 1
#define REDIS_OP_INTER 2

void sdiffCommand(redisClient *c) {
    sunionDiffGenericCommand(c,c->argv+1,c->argc-1,NULL,REDIS_OP_DIFF);
}


void sunionDiffGenericCommand(redisClient *c, robj **setkeys, int setnum, robj *dstkey, int op) {
    robj **sets = zmalloc(sizeof(robj*)*setnum);
    setTypeIterator *si;
    robj *ele, *dstset = NULL;
    int j, cardinality = 0;

    for (j = 0; j < setnum; j++) {
        robj *setobj = dstkey ?
            lookupKeyWrite(c->db,setkeys[j]) :
            lookupKeyRead(c->db,setkeys[j]);
        if (!setobj) {
            sets[j] = NULL;
            continue;
        }
        if (checkType(c,setobj,REDIS_SET)) {
            zfree(sets);
            return;
        }
        sets[j] = setobj;
    }

    /* We need a temp set object to store our union. If the dstkey
     * is not NULL (that is, we are inside an SUNIONSTORE operation) then
     * this set object will be the resulting object to set into the target key*/
    dstset = createIntsetObject();

    /* Iterate all the elements of all the sets, add every element a single
     * time to the result set */
    for (j = 0; j < setnum; j++) {
        if (op == REDIS_OP_DIFF && j == 0 && !sets[j]) break; /* result set is empty */
        if (!sets[j]) continue; /* non existing keys are like empty sets */

        si = setTypeInitIterator(sets[j]);
        while((ele = setTypeNextObject(si)) != NULL) {
            if (op == REDIS_OP_UNION || j == 0) {
                if (setTypeAdd(dstset,ele)) {
                    cardinality++;
                }
            } else if (op == REDIS_OP_DIFF) {
                if (setTypeRemove(dstset,ele)) {
                    cardinality--;
                }
            }
            decrRefCount(ele);
        }
        setTypeReleaseIterator(si);

        /* Exit when result set is empty. */
        if (op == REDIS_OP_DIFF && cardinality == 0) break;
    }

    /* Output the content of the resulting set, if not in STORE mode */
    if (!dstkey) {
        addReplyMultiBulkLen(c,cardinality);
        si = setTypeInitIterator(dstset);
        while((ele = setTypeNextObject(si)) != NULL) {
            addReplyBulk(c,ele);
            decrRefCount(ele);
        }
        setTypeReleaseIterator(si);
        decrRefCount(dstset);
    } else {
        /* If we have a target key where to store the resulting set
         * create this key with the result set inside */
        dbDelete(c->db,dstkey);
        if (setTypeSize(dstset) > 0) {
            dbAdd(c->db,dstkey,dstset);
            addReplyLongLong(c,setTypeSize(dstset));
        } else {
            decrRefCount(dstset);
            addReply(c,shared.czero);
        }
        touchWatchedKey(c->db,dstkey);
        server.dirty++;
    }
    zfree(sets);
}



sdiffstore

  • 解释

    和sdiff命令的效果一样,不同的是,sdiffstore会存储差集的结果到指定的键,
    如果键已经存在,则覆盖键之前的值。

  • 用法 sdiffstore key key1 key2 [key3…]

  • 示例


127.0.0.1:6379> sadd key1 a
(integer) 1
127.0.0.1:6379> sadd key1 b
(integer) 1
127.0.0.1:6379> sadd key1 c
(integer) 1
127.0.0.1:6379> sadd key1 d
(integer) 1
127.0.0.1:6379> sadd key2 c
(integer) 1
127.0.0.1:6379> sadd key3 a
(integer) 1
127.0.0.1:6379> sadd key3 c
(integer) 1
127.0.0.1:6379> sadd key3 e
(integer) 1
127.0.0.1:6379> sdiff key4 key1 key2 key3
(integer) 2

127.0.0.1:6379> smembers key4
1) "d"
2) "b"

  • 源码

参见sdiff源码解析

sinter

  • 解释

    返回集合之间的交集

  • 用法

    sinter key1 key2
    需要注意: 算法复杂度是O(N*M),如果集合中的元素过多,会导致卡住redis。

  • 示例


127.0.0.1:6379> sadd key1 a
(integer) 1
127.0.0.1:6379> sadd key1 b
(integer) 1
127.0.0.1:6379> sadd key1 c
(integer) 1
127.0.0.1:6379> sadd key1 d
(integer) 1
127.0.0.1:6379> sadd key2 c
(integer) 1
127.0.0.1:6379> sadd key3 a
(integer) 1
127.0.0.1:6379> sadd key3 c
(integer) 1
127.0.0.1:6379> sadd key3 e
(integer) 1
127.0.0.1:6379> sinter  key1 key2 key3
1) "c"


  • 源码
/**
** redis.c
**/ 
struct redisCommand readonlyCommandTable[] = {
  {"sinter",sinterCommand,-2,REDIS_CMD_DENYOOM,NULL,1,-1,1}
}


/**
** t_set.c
**/

void sinterCommand(redisClient *c) {
    sinterGenericCommand(c,c->argv+1,c->argc-1,NULL);
}


sinterstore

  • 解释

    和sinter命令的下效果一样,不同的是,sinterstore会存储交集的结果到指定的键,
    如果键已经存在,则覆盖键之前的值。

  • 用法 sinterstore key key1 key2 [key3…]

  • 示例


127.0.0.1:6379> sadd key1 a
(integer) 1
127.0.0.1:6379> sadd key1 b
(integer) 1
127.0.0.1:6379> sadd key1 c
(integer) 1
127.0.0.1:6379> sadd key1 d
(integer) 1
127.0.0.1:6379> sadd key2 c
(integer) 1
127.0.0.1:6379> sadd key3 a
(integer) 1
127.0.0.1:6379> sadd key3 c
(integer) 1
127.0.0.1:6379> sadd key3 e
(integer) 1
127.0.0.1:6379> sinterstore key4  key1 key2 key3
(integer) 1
127.0.0.1:6379> smembers key4
1) "c"

sismember

  • 解释

    判断元素是否存在于集合中,如果返回1表示存在,0表示不存在,
    如果键不存在则直接返回0

  • 用法 sismember key member

  • 示例


127.0.0.1:6379> sadd key1 a
(integer) 1
127.0.0.1:6379> sadd key1 b
(integer) 1
127.0.0.1:6379> sadd key1 c
(integer) 1
127.0.0.1:6379> sadd key1 d
(integer) 1
127.0.0.1:6379> sismember key1 c
(integer) 1
127.0.0.1:6379> sismember key1 f
(integer) 0

  • 源码
/**
** redis.c
**/ 
struct redisCommand readonlyCommandTable[] = {
  {"sismember",sismemberCommand,3,0,NULL,1,1,1}
}

1.检查键是否存在以及是否数据类型是set集合,否则返回0
2.在键所有数据中查找这个元素是否存在,存在返回1,不存在返回0
1.在内部编码为hashtable中查找
2.在内部编码为intset中查找
3.未知的内部编码


/**
** t_set.c
**/
void sismemberCommand(redisClient *c) {
    robj *set;
	//1.检查键是否存在以及是否数据类型是set集合,否则返回0
    if ((set = lookupKeyReadOrReply(c,c->argv[1],shared.czero)) == NULL ||
        checkType(c,set,REDIS_SET)) return;
	//2.在键所有数据中查找这个元素是否存在,存在返回1,不存在返回0
    c->argv[2] = tryObjectEncoding(c->argv[2]);
    if (setTypeIsMember(set,c->argv[2]))
        addReply(c,shared.cone);
    else
        addReply(c,shared.czero);
}


int setTypeIsMember(robj *subject, robj *value) {
    long long llval;
    if (subject->encoding == REDIS_ENCODING_HT) {
		//1. 在内部编码为hashtable中查找
        return dictFind((dict*)subject->ptr,value) != NULL;
    } else if (subject->encoding == REDIS_ENCODING_INTSET) {
        if (isObjectRepresentableAsLongLong(value,&llval) == REDIS_OK) {
		//2. 在内部编码为intset中查找
            return intsetFind((intset*)subject->ptr,llval);
        }
    } else {
		//3.未知的内部编码
        redisPanic("Unknown set encoding");
    }
    return 0;
}


smembers

  • 解释

    返回集合中的所有元素

  • 用法 smembers key

  • 示例


127.0.0.1:6379> sadd key1 a
(integer) 1
127.0.0.1:6379> sadd key1 b
(integer) 1
127.0.0.1:6379> sadd key1 c
(integer) 1
127.0.0.1:6379> sadd key1 d
(integer) 1
127.0.0.1:6379> smembers key1
1) "a"
2) "b"
3) "c"
4) "d"


  • 源码
/**
** redis.c
**/ 
// 其中2 表示这个客户端只能输入两个字符串 
// 第一个字符串: smembers  第二个字符串: key
struct redisCommand readonlyCommandTable[] = {
   {"smembers",sinterCommand,2,0,NULL,1,1,1}
}


/**
** t_set.c 
**/
void sinterCommand(redisClient *c) {
    sinterGenericCommand(c,c->argv+1,c->argc-1,NULL);
}


void sinterGenericCommand(redisClient *c, robj **setkeys, unsigned long setnum, robj *dstkey) {
	// 1.暂存有效的键
    robj **sets = zmalloc(sizeof(robj*)*setnum);
    setTypeIterator *si;
    robj *eleobj, *dstset = NULL;
    int64_t intobj;
    void *replylen = NULL;
    unsigned long j, cardinality = 0;
    int encoding;

    for (j = 0; j < setnum; j++) {
	//2. 循环输入的参数,查询键是否存在
        robj *setobj = dstkey ?
            lookupKeyWrite(c->db,setkeys[j]) :
            lookupKeyRead(c->db,setkeys[j]);
        if (!setobj) {
            zfree(sets);
            if (dstkey) {
                if (dbDelete(c->db,dstkey)) {
                    touchWatchedKey(c->db,dstkey);
                    server.dirty++;
                }
                addReply(c,shared.czero);
            } else {
                addReply(c,shared.emptymultibulk);
            }
            return;
        }
        if (checkType(c,setobj,REDIS_SET)) {
            zfree(sets);
            return;
        }
        sets[j] = setobj;
    }
    /* Sort sets from the smallest to largest, this will improve our
     * algorithm's performace */
    qsort(sets,setnum,sizeof(robj*),qsortCompareSetsByCardinality);

    /* The first thing we should output is the total number of elements...
     * since this is a multi-bulk write, but at this stage we don't know
     * the intersection set size, so we use a trick, append an empty object
     * to the output list and save the pointer to later modify it with the
     * right length */
    if (!dstkey) {
        replylen = addDeferredMultiBulkLength(c);
    } else {
        /* If we have a target key where to store the resulting set
         * create this key with an empty set inside */
        dstset = createIntsetObject();
    }

    /* Iterate all the elements of the first (smallest) set, and test
     * the element against all the other sets, if at least one set does
     * not include the element it is discarded */
    si = setTypeInitIterator(sets[0]);
    while((encoding = setTypeNext(si,&eleobj,&intobj)) != -1) {
        for (j = 1; j < setnum; j++) {
            if (sets[j] == sets[0]) continue;
            if (encoding == REDIS_ENCODING_INTSET) {
                /* intset with intset is simple... and fast */
                if (sets[j]->encoding == REDIS_ENCODING_INTSET &&
                    !intsetFind((intset*)sets[j]->ptr,intobj))
                {
                    break;
                /* in order to compare an integer with an object we
                 * have to use the generic function, creating an object
                 * for this */
                } else if (sets[j]->encoding == REDIS_ENCODING_HT) {
                    eleobj = createStringObjectFromLongLong(intobj);
                    if (!setTypeIsMember(sets[j],eleobj)) {
                        decrRefCount(eleobj);
                        break;
                    }
                    decrRefCount(eleobj);
                }
            } else if (encoding == REDIS_ENCODING_HT) {
                /* Optimization... if the source object is integer
                 * encoded AND the target set is an intset, we can get
                 * a much faster path. */
                if (eleobj->encoding == REDIS_ENCODING_INT &&
                    sets[j]->encoding == REDIS_ENCODING_INTSET &&
                    !intsetFind((intset*)sets[j]->ptr,(long)eleobj->ptr))
                {
                    break;
                /* else... object to object check is easy as we use the
                 * type agnostic API here. */
                } else if (!setTypeIsMember(sets[j],eleobj)) {
                    break;
                }
            }
        }

        /* Only take action when all sets contain the member */
        if (j == setnum) {
            if (!dstkey) {
                if (encoding == REDIS_ENCODING_HT)
                    addReplyBulk(c,eleobj);
                else
                    addReplyBulkLongLong(c,intobj);
                cardinality++;
            } else {
                if (encoding == REDIS_ENCODING_INTSET) {
                    eleobj = createStringObjectFromLongLong(intobj);
                    setTypeAdd(dstset,eleobj);
                    decrRefCount(eleobj);
                } else {
                    setTypeAdd(dstset,eleobj);
                }
            }
        }
    }
    setTypeReleaseIterator(si);

    if (dstkey) {
        /* Store the resulting set into the target, if the intersection
         * is not an empty set. */
        dbDelete(c->db,dstkey);
        if (setTypeSize(dstset) > 0) {
            dbAdd(c->db,dstkey,dstset);
            addReplyLongLong(c,setTypeSize(dstset));
        } else {
            decrRefCount(dstset);
            addReply(c,shared.czero);
        }
        touchWatchedKey(c->db,dstkey);
        server.dirty++;
    } else {
        setDeferredMultiBulkLength(c,replylen,cardinality);
    }
    zfree(sets);
}


smove

  • 解释

    原子的移动集合(source)中的一个元素到另一个集合(destination)中。 如果元素或者source键不存在,命令将不做任何操作直接返回0;
    如果destionation不存在则新建键;
    如果元素在destination中已经存在,则只做source删除这个元素操作;
    如果destination不是集合类型,则报错。

  • 用法 smove source destination member

  • 示例


127.0.0.1:6379> sadd key1 a
(integer) 1
127.0.0.1:6379> sadd key1 b
(integer) 1
127.0.0.1:6379> sadd key1 c
(integer) 1
127.0.0.1:6379> sadd key1 d
(integer) 1
127.0.0.1:6379> smove key1 key2 a
(integer) 1
127.0.0.1:6379> smembers key2
1) "a" 

  • 源码
/**
** redis.c
**/ 
struct redisCommand readonlyCommandTable[] = {
  {"smove",smoveCommand,4,0,NULL,1,2,1}
}

1.在dict字典中查找source,destination的键,以及编码要转移的元素
2.如果source的键不存在则直接返回0
3.检查source和destination,如果类型不是集合返回错误
4.如果source和destination是同一键则不操作直接返回0
5.从source中移除元素
6.移除后source中的元素为空,则删除键
7.如果destination不存在创建这个键
8.destination插入被source移除的元素

/**
** t_set.c
**/
void smoveCommand(redisClient *c) {
	//1.在dict字典中查找source,destination的键,以及编码要转移的元素
    robj *srcset, *dstset, *ele;
    srcset = lookupKeyWrite(c->db,c->argv[1]);
    dstset = lookupKeyWrite(c->db,c->argv[2]);
    ele = c->argv[3] = tryObjectEncoding(c->argv[3]);

   	//2.如果source的键不存在则直接返回0
    if (srcset == NULL) {
        addReply(c,shared.czero);
        return;
    }


	 //3.检查source和destination,如果类型不是集合返回错误,
	 //	destination不存在不检查数据类型
    if (checkType(c,srcset,REDIS_SET) ||
        (dstset && checkType(c,dstset,REDIS_SET))) return;

    //4.如果source和destination是同一键则不操作直接返回0
    if (srcset == dstset) {
        addReply(c,shared.cone);
        return;
    }

    //5.从source中移除元素
    if (!setTypeRemove(srcset,ele)) {
        addReply(c,shared.czero);
        return;
    }
	
    //6.移除后source中的元素为空,则删除键
    if (setTypeSize(srcset) == 0) dbDelete(c->db,c->argv[1]);
    touchWatchedKey(c->db,c->argv[1]);
    touchWatchedKey(c->db,c->argv[2]);
    server.dirty++;

    //7.如果destination不存在创建这个键
    if (!dstset) {
        dstset = setTypeCreate(ele);
        dbAdd(c->db,c->argv[2],dstset);
    }

    //8.destination插入被source移除的元素
    if (setTypeAdd(dstset,ele)) server.dirty++;
    addReply(c,shared.cone);
}

spop

  • 解释

    随机移除集合中指定数量的元素并且被移除的元素作为这个命令的结果返回。
    如果没有指定数量,则移除一个元素。

  • 用法 spop key [count]

  • 示例

127.0.0.1:6379> sadd key1 a
(integer) 1
127.0.0.1:6379> sadd key1 b
(integer) 1
127.0.0.1:6379> sadd key1 c
(integer) 1
127.0.0.1:6379> sadd key1 d
(integer) 1
127.0.0.1:6379> spop key1 
"d"
127.0.0.1:6379> smembers key1
1) "c"
2) "b" 
3) "a"
127.0.0.1:6379> spop key1 2
1) "c"
2) "a"
127.0.0.1:6379> smembers key1
1) "b"

  • 源码
/**
** redis.c
**/ 
struct redisCommand readonlyCommandTable[] = {
  {"spop",spopCommand,2,0,NULL,1,1,1}
}

1.查找键是否存在以及是否是集合类型
2.返回内部编码,如果是hashtable/intset返回一个随机元素
2.1 如果内部编码是hashtable,返回一个随机元素
2.2 如果内部编码是intset,返回一个随机元素
2.3 如果内部编码都不是返回错误
3.内部编码是intset移除上面返回的索引元素
4.内部编码是hashtableu移除元素
5.没看明白为什么这里还要再删除一下元素呢(有看懂的朋友可以告知我一下哈
6.如果集合的元素个数为0,则删除键

/**
** t_set.c
**/
void spopCommand(redisClient *c) {
    robj *set, *ele, *aux;
    int64_t llele;
    int encoding;
	//1. 查找键是否存在以及是否是集合类型
    if ((set = lookupKeyWriteOrReply(c,c->argv[1],shared.nullbulk)) == NULL ||
        checkType(c,set,REDIS_SET)) return;
	//2. 返回内部编码,如果是hashtable/intset返回一个随机元素
    encoding = setTypeRandomElement(set,&ele,&llele);
    if (encoding == REDIS_ENCODING_INTSET) {
	//3.内部编码是intset移除上面返回的元素
        ele = createStringObjectFromLongLong(llele);
        set->ptr = intsetRemove(set->ptr,llele,NULL);
    } else {
	//4. 内部编码是hashtableu移除元素
        incrRefCount(ele);
        setTypeRemove(set,ele);
    }

	//5.没看明白为什么这里还要再删除一下元素呢(有看懂的朋友可以告知我一下哈)
    aux = createStringObject("SREM",4);
    rewriteClientCommandVector(c,3,aux,c->argv[1],ele);
    decrRefCount(ele);
    decrRefCount(aux);
	//6. 如果集合的元素个数为0,则删除键
    addReplyBulk(c,ele);
    if (setTypeSize(set) == 0) dbDelete(c->db,c->argv[1]);
    touchWatchedKey(c->db,c->argv[1]);
    server.dirty++;
}

int setTypeRandomElement(robj *setobj, robj **objele, int64_t *llele) {
    if (setobj->encoding == REDIS_ENCODING_HT) {
		//1.如果内部编码是hashtable,返回一个随机元素
        dictEntry *de = dictGetRandomKey(setobj->ptr);
        *objele = dictGetEntryKey(de);
    } else if (setobj->encoding == REDIS_ENCODING_INTSET) {
	   //2.如果内部编码是intset,返回一个随机元素
        *llele = intsetRandom(setobj->ptr);
    } else {
		//3.如果内部编码都不是返回错误
        redisPanic("Unknown set encoding");
    }
    return setobj->encoding;
}

srandmember

  • 解释

    随机返回集合中指定数量的元素(数量的绝对值个元素),
    如果未指定数量,则返回一个元素。

  • 用法 srandmember key [count]

  • 示例


127.0.0.1:6379> sadd key1 a
(integer) 1
127.0.0.1:6379> sadd key1 b
(integer) 1
127.0.0.1:6379> sadd key1 c
(integer) 1
127.0.0.1:6379> sadd key1 d
(integer) 1
127.0.0.1:6379> srandmember key1
1) "a"

  • 源码
/**
** redis.c
**/ 
struct redisCommand readonlyCommandTable[] = {
  {"srandmember",srandmemberCommand,2,0,NULL,1,1,1}
}

/**
** t_set.c
**/
void srandmemberCommand(redisClient *c) {
    robj *set, *ele;
    int64_t llele;
    int encoding;
	1. 查找键是否存在以及是否是集合类型
    if ((set = lookupKeyReadOrReply(c,c->argv[1],shared.nullbulk)) == NULL ||
        checkType(c,set,REDIS_SET)) return;
	2. 返回内部编码,如果是hashtable返回一个随机元素,如果是intset返回一个随机索引
    encoding = setTypeRandomElement(set,&ele,&llele);
    if (encoding == REDIS_ENCODING_INTSET) {
        addReplyBulkLongLong(c,llele);
    } else {
        addReplyBulk(c,ele);
    }
}

srem

  • 解释

    移除集合中的指定元素移除成功然后1
    如果元素不存在则返回0;
    如果键不存在则返回0;
    当键不是集合类型则返回错误信息。

  • 用法 srem key member

  • 示例


127.0.0.1:6379> sadd key1 a
(integer) 1
127.0.0.1:6379> sadd key1 b
(integer) 1
127.0.0.1:6379> sadd key1 c
(integer) 1
127.0.0.1:6379> sadd key1 d
(integer) 1
127.0.0.1:6379> srem key1 a
(integer) 1
127.0.0.1:6379> srem key1 r
(integer) 1


  • 源码
/**
** redis.c
**/ 
struct redisCommand readonlyCommandTable[] = {
  {"srem",sremCommand,3,0,NULL,1,1,1}
}

  1. 查找键是否存在以及是否是集合类型
  2. 移除元素
    2.1 内部编码是hashtable,在hashbable中删除元素
    2.2 hashtable如果需要重哈希则进行重哈希
    2.3 内部编码是intset,移除intset中的元素
  3. 如果移除元素后,集合的元素个数为0,则删除键
/**
** t_set.c
**/
void sremCommand(redisClient *c) {
    robj *set;
	1. 查找键是否存在以及是否是集合类型
    if ((set = lookupKeyWriteOrReply(c,c->argv[1],shared.czero)) == NULL ||
        checkType(c,set,REDIS_SET)) return;
    c->argv[2] = tryObjectEncoding(c->argv[2]);
	//2. 移除元素
    if (setTypeRemove(set,c->argv[2])) {
	//3. 如果移除元素后,集合的元素个数为0,则删除键
        if (setTypeSize(set) == 0) dbDelete(c->db,c->argv[1]);
        touchWatchedKey(c->db,c->argv[1]);
        server.dirty++;
        addReply(c,shared.cone);
    } else {
        addReply(c,shared.czero);
    }
}

int setTypeRemove(robj *setobj, robj *value) {
    long long llval;
    if (setobj->encoding == REDIS_ENCODING_HT) {
		//1.内部编码是hashtable,在hashbable中删除元素
        if (dictDelete(setobj->ptr,value) == DICT_OK) {
			//2. hashtable如果需要重哈希则进行重哈希
            if (htNeedsResize(setobj->ptr)) dictResize(setobj->ptr);
            return 1;
        }
    } else if (setobj->encoding == REDIS_ENCODING_INTSET) {
		//3.内部编码是intset,移除intset中的元素
        if (isObjectRepresentableAsLongLong(value,&llval) == REDIS_OK) {
            int success;
            setobj->ptr = intsetRemove(setobj->ptr,llval,&success);
            if (success) return 1;
        }
    } else {
		//3.内部编码不是hashtable或intset则返回错误
        redisPanic("Unknown set encoding");
    }
    return 0;
}


sunion

  • 解释

    对多个键进行并集操作并且返回结果

  • 用法 sunion key1 key2 [key3…]

  • 示例

127.0.0.1:6379> sadd key1 a
(integer) 1
127.0.0.1:6379> sadd key1 b
(integer) 1
127.0.0.1:6379> sadd key2 c
(integer) 1
127.0.0.1:6379> sadd key2 d
(integer) 1
127.0.0.1:6379> sadd key3 r
(integer) 1
127.0.0.1:6379> sadd key3 a
(integer) 1
127.0.0.1:6379> sunion key1 key2 key3
1) "a"
2) "b"
3) "c"
4) "d"
5) "r"

  • 源码

sunionstore

  • 解释

    存储sunion的结果到指定键

  • 用法 sunionstore key key1 key2 key3

内部编码

hashtable、intset

使用场景

场景一:黑名单

存储用户id黑名单、ip黑名单、设备黑名单等,通过sismember命令判断是否在set中。

场景二: 访问数据统计

统计每日访问UV,每日访问ip

随机抽奖

随机抽奖,参与用户加入集合:
1.抽中用户不能参与多轮抽奖: 使用spop命令随机移除用户
2.抽中用户能参与多轮抽奖: 使用srandmember命令随机返回用户

https://blog.csdn.net/wizblack/article/details/78796557

https://redis.io/commands/sadd

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值