0.前言

redis对无序集合的操作几个命令,本文介绍几个命令实际操作过程。

1.sadd命令

2.求差集和求并集命令

3.求交集命令

1.sadd命令

  1. void saddCommand(redisClient *c) {
  2. robj *set;
  3. int j, added = 0;
  4. /*查找集合,如果不存在创建新的集合*/
  5. set = lookupKeyWrite(c->db,c->argv[1]);
  6. if (set == NULL) {
  7. /*
  8. *创建集合,如果添加的元素可以转换为longlong类型,则存储格式采用intset数据结构,否则采用hash table数据结构进行存储
  9. */
  10. set = setTypeCreate(c->argv[2]);
  11. dbAdd(c->db,c->argv[1],set);
  12. } else {
  13. if (set->type != REDIS_SET) {
  14. addReply(c,shared.wrongtypeerr);
  15. return;
  16. }
  17. }
  18. for (j = 2; j < c->argc; j++) {
  19. c->argv[j] = tryObjectEncoding(c->argv[j]);
  20. /*元素添加进集合中*/
  21. if (setTypeAdd(set,c->argv[j])) added++;
  22. }
  23. if (added) {
  24. signalModifiedKey(c->db,c->argv[1]);
  25. notifyKeyspaceEvent(REDIS_NOTIFY_SET,"sadd",c->argv[1],c->db->id);
  26. }
  27. server.dirty += added;
  28. addReplyLongLong(c,added);
  29. }
  30. int setTypeAdd(robj *subject, robj *value) {
  31. long long llval;
  32. if (subject->encoding == REDIS_ENCODING_HT) {
  33. if (dictAdd(subject->ptr,value,NULL) == DICT_OK) {
  34. incrRefCount(value);
  35. return 1;
  36. }
  37. } else if (subject->encoding == REDIS_ENCODING_INTSET) {
  38. /*如果添加元素可以转换为longlong类型,保存至intset中,否则需要转换存储结构为hash table*/
  39. if (isObjectRepresentableAsLongLong(value,&llval) == REDIS_OK) {
  40. uint8_t success = 0;
  41. subject->ptr = intsetAdd(subject->ptr,llval,&success);
  42. if (success) {
  43. /* 为了防止intset过大,set_max_intset_entries值作为一个阀值,占用空间大于此值,则将存储结构转换为hash table类型*/
  44. if (intsetLen(subject->ptr) > server.set_max_intset_entries)
  45. setTypeConvert(subject,REDIS_ENCODING_HT);
  46. return 1;
  47. }
  48. } else {
  49. /* 转换为longlong失败,需要转换为hash table*/
  50. setTypeConvert(subject,REDIS_ENCODING_HT);
  51. /* 新元素添加至hash table中*/
  52. redisAssertWithInfo(NULL,value,dictAdd(subject->ptr,value,NULL) == DICT_OK);
  53. incrRefCount(value);
  54. return 1;
  55. }
  56. } else {
  57. redisPanic("Unknown set encoding");
  58. }
  59. return 0;
  60. }

2.求差集和并集命令(sdiff,sdiffstore,sunion,sunionstore)

sdiff求差集, sdiffstore求差集并保存结果, sunion求并集, sunionstore求并集并保存结果, 几种运算过程都是通过sunionDiffGenericCommand函数进行,此处将几个命令全部列出.

  1. /*求并集*/
  2. void sunionCommand(redisClient *c) {
  3. sunionDiffGenericCommand(c,c->argv+1,c->argc-1,NULL,REDIS_OP_UNION);
  4. }
  5. /*求并集并保存结果*/
  6. void sunionstoreCommand(redisClient *c) {
  7. sunionDiffGenericCommand(c,c->argv+2,c->argc-2,c->argv[1],REDIS_OP_UNION);
  8. }
  9. /*求差集*/
  10. void sdiffCommand(redisClient *c) {
  11. sunionDiffGenericCommand(c,c->argv+1,c->argc-1,NULL,REDIS_OP_DIFF);
  12. }
  13. /*求差集并保存结果*/
  14. void sdiffstoreCommand(redisClient *c) {
  15. sunionDiffGenericCommand(c,c->argv+2,c->argc-2,c->argv[1],REDIS_OP_DIFF);
  16. }
  17. /*通用的求差集和并集函数*/
  18. void sunionDiffGenericCommand(redisClient *c, robj **setkeys, int setnum, robj *dstkey, int op) {
  19. robj **sets = zmalloc(sizeof(robj*)*setnum);
  20. setTypeIterator *si;
  21. robj *ele, *dstset = NULL;
  22. int j, cardinality = 0;
  23. int diff_algo = 1;
  24. /*取出需要操作的集合*/
  25. for (j = 0; j < setnum; j++) {
  26. robj *setobj = dstkey ?
  27. lookupKeyWrite(c->db,setkeys[j]) :
  28. lookupKeyRead(c->db,setkeys[j]);
  29. if (!setobj) {
  30. sets[j] = NULL;
  31. continue;
  32. }
  33. if (checkType(c,setobj,REDIS_SET)) {
  34. zfree(sets);
  35. return;
  36. }
  37. sets[j] = setobj;
  38. }
  39. /*
  40. *依据待运算集合中元素数量,选择计算差集算法, 其中算法1时间复杂度:O(N*M), N是第一个集合中元素个数, M是参与运算的集合数量.
  41. *算法2时间复杂度:O(N), N是所有集合中元素数量总和
  42. */
  43. if (op == REDIS_OP_DIFF && sets[0]) {
  44. long long algo_one_work = 0, algo_two_work = 0;
  45. for (j = 0; j < setnum; j++) {
  46. if (sets[j] == NULL) continue;
  47. algo_one_work += setTypeSize(sets[0]);
  48. algo_two_work += setTypeSize(sets[j]);
  49. }
  50. /*
  51. *algo_one_work值即为算法1中N*M, algo_two_work值即为算法2中N. 考虑到如果参与运算集合为intset时, 算法1的时间复杂度稳定性要好于算法2,
  52. *因此没有直接比较两者大小选择算法, 而是算法1理论时间复杂度一半大于算法2时, 才使用算法2
  53. */
  54. algo_one_work /= 2;
  55. diff_algo = (algo_one_work <= algo_two_work) ? 1 : 2;
  56. if (diff_algo == 1 && setnum > 1) {
  57. /*为了提高算法1速度, 尽快找到重复元素, 对集合列表按照元素数量进行了降序排序*/
  58. qsort(sets+1,setnum-1,sizeof(robj*),
  59. qsortCompareSetsByRevCardinality);
  60. }
  61. }
  62. /*创建一个临时集合存放计算结果*/
  63. dstset = createIntsetObject();
  64. if (op == REDIS_OP_UNION) {
  65. /* 求并集很简单了, 直接遍历所有元素, 添加进dstset集合中即可*/
  66. for (j = 0; j < setnum; j++) {
  67. if (!sets[j]) continue; /* non existing keys are like empty sets */
  68. si = setTypeInitIterator(sets[j]);
  69. while((ele = setTypeNextObject(si)) != NULL) {
  70. if (setTypeAdd(dstset,ele)) cardinality++;
  71. decrRefCount(ele);
  72. }
  73. setTypeReleaseIterator(si);
  74. }
  75. } else if (op == REDIS_OP_DIFF && sets[0] && diff_algo == 1) {
  76. /*
  77. *算法1对集合1进行遍历, 并判断集合1中的元素是否在其他集合中出现, 没有出现则添加到dstset集合中, 作为差集的一个元素
  78. */
  79. si = setTypeInitIterator(sets[0]);
  80. /*
  81. *循环外层对集合1进行遍历, 内层对其他参与运算的集合进行遍历
  82. */
  83. while((ele = setTypeNextObject(si)) != NULL) {
  84. for (j = 1; j < setnum; j++) {
  85. if (!sets[j]) continue; /* no key is an empty set. */
  86. if (sets[j] == sets[0]) break; /* same set! */
  87. if (setTypeIsMember(sets[j],ele)) break;
  88. }
  89. if (j == setnum) {
  90. /* 其他集合中没有找到该元素, 添加到差集集合中*/
  91. setTypeAdd(dstset,ele);
  92. cardinality++;
  93. }
  94. decrRefCount(ele);
  95. }
  96. setTypeReleaseIterator(si);
  97. } else if (op == REDIS_OP_DIFF && sets[0] && diff_algo == 2) {
  98. /*
  99. *算法2将集合1中元素直接copy进dstset集合中, 通过遍历其他所有集合, 然后确认其他集合中的元素没有在dstset中出现, 出现则从dstset中删除, 最终获取差集
  100. */
  101. for (j = 0; j < setnum; j++) {
  102. if (!sets[j]) continue; /* non existing keys are like empty sets */
  103. si = setTypeInitIterator(sets[j]);
  104. while((ele = setTypeNextObject(si)) != NULL) {
  105. if (j == 0) {
  106. /*集合1中元素添加进dstset中*/
  107. if (setTypeAdd(dstset,ele)) cardinality++;
  108. } else {
  109. /*其他集合中元素出现在dstset中,则删除该元素*/
  110. if (setTypeRemove(dstset,ele)) cardinality--;
  111. }
  112. decrRefCount(ele);
  113. }
  114. setTypeReleaseIterator(si);
  115. if (cardinality == 0) break;
  116. }
  117. }
  118. if (!dstkey) {
  119. /*运算结果不需要存储,直接返回结果元素至客户端*/
  120. addReplyMultiBulkLen(c,cardinality);
  121. si = setTypeInitIterator(dstset);
  122. while((ele = setTypeNextObject(si)) != NULL) {
  123. addReplyBulk(c,ele);
  124. decrRefCount(ele);
  125. }
  126. setTypeReleaseIterator(si);
  127. decrRefCount(dstset);
  128. } else {
  129. /* 需要存储, 首先删除原来可能已经存在dstkey的集合*/
  130. int deleted = dbDelete(c->db,dstkey);
  131. if (setTypeSize(dstset) > 0) {
  132. dbAdd(c->db,dstkey,dstset);
  133. addReplyLongLong(c,setTypeSize(dstset));
  134. notifyKeyspaceEvent(REDIS_NOTIFY_SET,
  135. op == REDIS_OP_UNION ? "sunionstore" : "sdiffstore",
  136. dstkey,c->db->id);
  137. } else {
  138. decrRefCount(dstset);
  139. addReply(c,shared.czero);
  140. if (deleted)
  141. notifyKeyspaceEvent(REDIS_NOTIFY_GENERIC,"del",
  142. dstkey,c->db->id);
  143. }
  144. signalModifiedKey(c->db,dstkey);
  145. server.dirty++;
  146. }
  147. zfree(sets);
  148. }

3.求交集命令(sinter,sinterstore)

sinter求交集, sinterstore求交集并保存结果, 都是通过sinterGenericCommand函数进行相应的操作

  1. /*求交集*/
  2. void sinterCommand(redisClient *c) {
  3. sinterGenericCommand(c,c->argv+1,c->argc-1,NULL);
  4. }
  5. /*求交集并保存结果*/
  6. void sinterstoreCommand(redisClient *c) {
  7. sinterGenericCommand(c,c->argv+2,c->argc-2,c->argv[1]);
  8. }
  9. /*通用求交集函数*/
  10. void sinterGenericCommand(redisClient *c, robj **setkeys, unsigned long setnum, robj *dstkey) {
  11. robj **sets = zmalloc(sizeof(robj*)*setnum);
  12. setTypeIterator *si;
  13. robj *eleobj, *dstset = NULL;
  14. int64_t intobj;
  15. void *replylen = NULL;
  16. unsigned long j, cardinality = 0;
  17. int encoding;
  18. /*遍历所有key, 读出所有传入的所有集合*/
  19. for (j = 0; j < setnum; j++) {
  20. robj *setobj = dstkey ?
  21. lookupKeyWrite(c->db,setkeys[j]) :
  22. lookupKeyRead(c->db,setkeys[j]);
  23. if (!setobj) {
  24. zfree(sets);
  25. if (dstkey) {
  26. if (dbDelete(c->db,dstkey)) {
  27. signalModifiedKey(c->db,dstkey);
  28. server.dirty++;
  29. }
  30. addReply(c,shared.czero);
  31. } else {
  32. addReply(c,shared.emptymultibulk);
  33. }
  34. return;
  35. }
  36. if (checkType(c,setobj,REDIS_SET)) {
  37. zfree(sets);
  38. return;
  39. }
  40. sets[j] = setobj;
  41. }
  42. /* 按照集合中元素数量升序排列, 提高后面算法性能, 尽快决定元素是否是交集元素*/
  43. qsort(sets,setnum,sizeof(robj*),qsortCompareSetsByCardinality);
  44. /* The first thing we should output is the total number of elements...
  45. * since this is a multi-bulk write, but at this stage we don't know
  46. * the intersection set size, so we use a trick, append an empty object
  47. * to the output list and save the pointer to later modify it with the
  48. * right length */
  49. if (!dstkey) {
  50. replylen = addDeferredMultiBulkLength(c);
  51. } else {
  52. /* If we have a target key where to store the resulting set
  53. * create this key with an empty set inside */
  54. dstset = createIntsetObject();
  55. }
  56. /* Iterate all the elements of the first (smallest) set, and test
  57. * the element against all the other sets, if at least one set does
  58. * not include the element it is discarded */
  59. si = setTypeInitIterator(sets[0]);
  60. while((encoding = setTypeNext(si,&eleobj,&intobj)) != -1) {
  61. for (j = 1; j < setnum; j++) {
  62. if (sets[j] == sets[0]) continue;
  63. /*
  64. *依据不同的编码进行相应的操作
  65. */
  66. if (encoding == REDIS_ENCODING_INTSET) {
  67. /* 编码均为intset时,则直接进行查找 */
  68. if (sets[j]->encoding == REDIS_ENCODING_INTSET &&
  69. !intsetFind((intset*)sets[j]->ptr,intobj))
  70. {
  71. break;
  72. /* 编码为hash table时, 重新创建object进行比较 */
  73. } else if (sets[j]->encoding == REDIS_ENCODING_HT) {
  74. eleobj = createStringObjectFromLongLong(intobj);
  75. if (!setTypeIsMember(sets[j],eleobj)) {
  76. decrRefCount(eleobj);
  77. break;
  78. }
  79. decrRefCount(eleobj);
  80. }
  81. } else if (encoding == REDIS_ENCODING_HT) {
  82. /*待查集合为intset, 则可以直接安卓long类型进行查找, 否则只能object在hash table中查找*/
  83. if (eleobj->encoding == REDIS_ENCODING_INT &&
  84. sets[j]->encoding == REDIS_ENCODING_INTSET &&
  85. !intsetFind((intset*)sets[j]->ptr,(long)eleobj->ptr))
  86. {
  87. break;
  88. } else if (!setTypeIsMember(sets[j],eleobj)) {
  89. break;
  90. }
  91. }
  92. }
  93. /* 查找到最后一个集合表示此元素在所有集合中均出现, 作为交集结果 */
  94. if (j == setnum) {
  95. if (!dstkey) {
  96. if (encoding == REDIS_ENCODING_HT)
  97. addReplyBulk(c,eleobj);
  98. else
  99. addReplyBulkLongLong(c,intobj);
  100. cardinality++;
  101. } else {
  102. if (encoding == REDIS_ENCODING_INTSET) {
  103. eleobj = createStringObjectFromLongLong(intobj);
  104. setTypeAdd(dstset,eleobj);
  105. decrRefCount(eleobj);
  106. } else {
  107. setTypeAdd(dstset,eleobj);
  108. }
  109. }
  110. }
  111. }
  112. setTypeReleaseIterator(si);
  113. /*判断是否需要存储交集结果, 并进行相应操作*/
  114. if (dstkey) {
  115. int deleted = dbDelete(c->db,dstkey);
  116. if (setTypeSize(dstset) > 0) {
  117. dbAdd(c->db,dstkey,dstset);
  118. addReplyLongLong(c,setTypeSize(dstset));
  119. notifyKeyspaceEvent(REDIS_NOTIFY_SET,"sinterstore",
  120. dstkey,c->db->id);
  121. } else {
  122. decrRefCount(dstset);
  123. addReply(c,shared.czero);
  124. if (deleted)
  125. notifyKeyspaceEvent(REDIS_NOTIFY_GENERIC,"del",
  126. dstkey,c->db->id);
  127. }
  128. signalModifiedKey(c->db,dstkey);
  129. server.dirty++;
  130. } else {
  131. setDeferredMultiBulkLength(c,replylen,cardinality);
  132. }
  133. zfree(sets);
  134. }

总结

集合的几种操作都是比较耗时的, 使用时对于特别庞大的集合进行运算需要谨慎, 可能影响整体性能.

Redis之Set命令的更多相关文章

  1. NoSQL之Redis高级实用命令详解--安全和主从复制

    Android IOS JavaScript HTML5 CSS jQuery Python PHP NodeJS Java Spring MySQL MongoDB Redis NOSQL Vim ...

  2. 2016022611 - redis订阅发布命令集合

    redis消息订阅发布命令 参考地址:http://www.yiibai.com/redis/redis_pub_sub.html 消息发送者发送消息,通过redis的channal,消息接收者获取消 ...

  3. 通过redis的monitor命令排除故障

    项目里有10台服务器都在一个刀箱里,其中一台是redis缓存服务器,另外的是app服务器.通过监控发现这个刀箱的流量750M,其中缓存服务器的流量达105M,这么高的流量已经造成其它项目的服务器网络延 ...

  4. Jedis对Redis的常用命令操作

    本篇主要总结一些Jedis对Redis的常用命令操作: 1.对key操作命令 2.对String操作命令 3.对List操作命令 4.对Set操作命令 5.对Hash操作命令 6.排序操作指令 一.项 ...

  5. Redis安装、命令以及设置密码遇到的问题

    一.下载Redis 如果没有 安装wget先安装wget和gcc(使用make的时候会用上) wget http://download.redis.io/releases/redis-4.0.8.ta ...

  6. redis常用的命令总结

    redis常用的命令大全 1.基于内存的key-value数据库 2.基于c语言编写的,可以支持多种语言的api //set每秒11万次,取get 81000次 3.支持数据持久化 4.value可以 ...

  7. redis 的简单命令

    以下实例讲解了如何启动 redis 客户端: 启动 redis 客户端,打开终端并输入命令 redis-cli.该命令会连接本地的 redis 服务. $redis-cli redis > re ...

  8. Redis的KEYS命令引起宕机事件

    摘要: 使用 Redis 的开发者必看,吸取教训啊! 原文:Redis 的 KEYS 命令引起 RDS 数据库雪崩,RDS 发生两次宕机,造成几百万的资金损失 作者:陈浩翔 Fundebug经授权转载 ...

  9. Redis 入门 安装 命令

    win7 64位安装redis 及Redis Desktop Manager使用 引自:http://blog.csdn.net/joyhen/article/details/47358999 写基于 ...

  10. 【Redis数据库】命令学习笔记——发布订阅、事务、脚本、连接等命令汇总

    本篇基于redis 4.0.11版本,学习发布订阅.事务.脚本.连接的相关命令. Redis 发布订阅(pub/sub)是一种消息通信模式:发送者(pub)发送消息,订阅者(sub)接收消息. 序号 ...

随机推荐

  1. 福州三中集训day4

    第6天写第4天的博客….可以说是很弱了…… 讲了一天的高级数据结构,可以说很迷,先是并查集,然后是树状数组,线段树,MAP函数,KMP算法. 很难……确实不是很清楚…但是很重要,回去以后这应该说是优先 ...

  2. date conversion

    SELECT to_char(sysdate,'yyyymmdd hh:mi:ss'), to_char(sysdate ,'yyyymmdd hh:mi:ss'), to_char(sysdate ...

  3. decode and CASE

    CASE

  4. 【最短路】【最大流】bzoj3931 [CQOI2015]网络吞吐量

    跑出最短路图,然后把结点拆点跑最大流. #include<cstdio> #include<queue> #include<cstring> #include< ...

  5. apk打包

    1.在导航栏中选择Builder->Generate Signed Apk 2.新建点击Creat new... 3.注意路径后面写apk的名字(这个名字将会显示在手机软件的下方)

  6. MathType如何插入竖直线

    不用键盘上的竖线,用左竖直线和右竖直线.

  7. RedisTemplate SerializationFailedException: Failed to deserialize payload 异常解决

    问题描述: 使用RedisTemplate(spring-data-redis )进行redis操作的封装 , 现有一个incr的key , 当调用incr后返回值一切正常, 当对此key进行get调 ...

  8. 【mybatis】count 计数查询 + List的IN查询

    mybatis中conut计数的sql怎么在mapper中写? Mapper.java类这么写 @Mapper public interface GoodsBindConfigMappingMappe ...

  9. Sublime Text:格式化插件HTML-CSS-JS Prettify

    Sublime Text:插件HTML-CSS-JS Prettify可以格式化HMTL/CSS/JS 1.安装Node.js 2.Sublime中ctrl+shift+p,输入ip: 3.点击Ins ...

  10. C#之Hello World(入门 )

    C#是一种简单.现代.面向对象和类型安全的编程语言. C#由C和C++发展而来.C#(英文发音C sharp)牢固地植根于C和C++语言族谱中,是Microsoft专门为使用.NET平台而创建的. • ...