Redis的集合操作

实话说,Redis提供的集合操作是我选择它成为内存数据库的一个主要理由,它弥补了传统关系型数据库在这方面带来的复杂度,使得只需要简单的一个命令就可以完成一个复杂SQL任务,并且交、并、差操作在实际的业务场景中应用非常广泛,比如快速检索出具备一系列标签属性的一个集合,本篇文章将主要介绍对于求交集操作结果缓存的设计方案。

Redis命令

对于set类型,提供了sinter、sinterstore进行交集操作,对于sortedset,提供了zinter、zinterstore进行交集操作。命令十分便捷,对于不保存结果的方法sinter和zinter只需要输入待求交集的集合的key的数组就可以得到求交集后的结果集合,对于保存结果的方法,你可以将求交集后的结果集合保存到你指定key的一个集合中。

设计方案

目标

计算给定集合群的交集集合,并对计算过程中所产生的中间结果集合进行缓存,从而达到下次计算给定集合群的一些子群集时,先查询是否存在交集key,如果存在直接读取,避免重复计算。

原始方法(Only)

以下我们以Redis Java客户端库Jedis进行举例,Jedis提供了与Redis命令的一致接口方法,实现了交集操作,如下代码:

public Set<String> inter(String[] keys, String keycached){int size = keys.length;Jedis jedis = new Jedis("127.0.0.1");if(size < 2){try{return jedis.smembers(keys[0]);} finally {jedis.close();}}try{jedis.sinterstore(keycached, keys);return jedis.smembers(keycached);} finally {jedis.close();}}

原始方法的问题在于只对最终的交集key进行了缓存,简洁方便,但每次变更给定集合群时,都需要重新在此计算。

原始方法上的改造方案(All)

在原始方法上进行改造,我们可以在计算过程中依次增加计算集合群的集合数量,比如给定的集合群key{A,B,C,D},我们先计算A、B,保存一个{A,B}的交集结果,再依次计算A、B、C和A、B、C、D并对结果进行保存。 
显然,这是个糟糕的方案,但确实完成了我们设定的目标,参考代码如下:

private Set<String> interByAll(String... keys){Jedis jedis = new Jedis("127.0.0.1");Set<String> value = null;int interNum = 2;for(int i = 0; i < (keys.length - 1); i++){String keystored = "";String[] keyintered = new String[interNum];for(int j = 0; j < interNum; j++){keystored += (keys[j] + "&");keyintered[j] = keys[j];}if(jedis.sinterstore(keystored, keyintered) == 0){jedis.sadd(keystored, "nocache");}if(interNum == keys.length){value = jedis.smembers(keystored);}interNum++;}jedis.close();return value;}

递归方案(Recursive)

根据上面糟糕的设计方案,你应该改进实现一种递归方案,递归方案的好处是你每次只求一对集合的交集,逐步完成对整个给定集合群的交集计算,计算过程如下图所示:

private boolean isEnd = false;private Set<String> value = null;private Set<String> getKeysWithInner(String[] keys, String srckey, Jedis jedis, int i){String key = null;if(srckey == null){//表示为第一次求交集srckey = keys[i++];key = keys[i++];} else {key = keys[i++];}String keystored = srckey + "&" + key;//生成缓存keyif(jedis.sinterstore(keystored, srckey, key) == 0){jedis.sadd(keystored, "nocache");}if(i == keys.length){ //当与最后一个key求交集后,返回结果,并跳出递归调用value = jedis.smembers(keystored);isEnd = true;}while(!isEnd){//递归调用,一对key集合求交集
            getKeysWithInner(keys, keystored, jedis, i);}return value;}public Set<String> interByRecursive(String... keys){int size = keys.length;Jedis jedis = new Jedis("127.0.0.1");if(size < 2){try{return jedis.smembers(keys[0]);} finally {jedis.close();}}try{return getKeysWithInner(keys, null, jedis, 0);} finally {isEnd = false;jedis.close();}}

该方案的优势不仅仅是对计算过程进行了缓存,而且,每次都只是完成一对集合的计算,计算量显著降低。

Fork-Join方案(Fork-Join)

一写递归方案,你就可以直接想到使用Fork-Join框架进行改造,并使其并行化,计算过程如下图所示:

InterTask类:

public class InterTask extends RecursiveTask<String>{private String[] keys = null;private static final int THRESHOLD = 3;public InterTask(String[] keys){this.keys = keys;}private static String genKeyinnered(String... keys){StringBuilder sb = new StringBuilder();for(String key : keys){sb.append(key);sb.append("&");}return sb.toString().substring(0, sb.length() - 1);}@Overrideprotected String compute() {int size = keys.length;if(size < THRESHOLD){//当keys数组中元素数为2个或1个时,计算交集,并退出递归if(size < 2){return keys[0];}Jedis jedis = new Jedis("127.0.0.1");try{jedis.sinterstore(genKeyinnered(keys), keys[0], keys[1]);} finally {jedis.close();}return genKeyinnered(keys);} else {//取keys数组的中值进行分治算法String[] leftkeys = new String[size / 2];String[] rightkeys = new String[size - (size / 2)];//按中值拆分keys数组for(int i = 0; i < size; i++){if(i < leftkeys.length){leftkeys[i] = keys[i];} else {rightkeys[i - leftkeys.length] =  keys[i];}}InterTask lefttask = new InterTask(leftkeys);InterTask righttask = new InterTask(rightkeys);lefttask.fork();righttask.fork();//取得从递归中返回的一对存储交集结果的keyString left = lefttask.join();String right = righttask.join();Jedis jedis = new Jedis("127.0.0.1");try{jedis.sinterstore(left + "&" + right, left, right);} finally {jedis.close();}return left + "&" + right;}}}

这里运用了最基础的分治算法思想,逐步将一个大的给定集合拆解为若干个成对的集合进行交集计算。 
调用方法:

public Set<String> interByForkJoin(String... keys){Set<String> value = null;Jedis jedis = new Jedis("127.0.0.1");InterTask task = new InterTask(keys);ForkJoinPool forkJoinPool = new ForkJoinPool();Future<String> result = forkJoinPool.submit(task);try {String key = result.get();value = jedis.smembers(key);} catch (InterruptedException e) {e.printStackTrace();} catch (ExecutionException e) {e.printStackTrace();} finally {jedis.close();}return value;}

测试

测试数据准备

我们这里准备100个集合,每个给定集合包含1000000个元素,参考如下代码:

Jedis jedis = new Jedis("127.0.0.1");jedis.flushAll();String token = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";Random rand = new Random();String[] keys = new String[100];for(int i = 0; i < 100; i++){keys[i] = "ct" + i;String[] values = new String[1000000];for(int j = 0; j < 1000000; j++){StringBuilder sb = new StringBuilder();for(int k = 0;k < 2; k++){sb.append(token.charAt(rand.nextInt(62)));}values[j] = sb.toString();}jedis.sadd(keys[i], values);}jedis.close();

测试结果

我们分别对25、50、75、100的给定集合进行交集计算,测试结果如下:

我们可以清楚的看到,All方案是多么的糟糕,剔除All方案的结果:

总体来说Only方案和Recursive方案不分伯仲,但在相对较小的给定合集计算场景下,Recursive存在优势,而且其进行了计算过程结果的缓存。 
对于Fork-Join方案表示比较遗憾,当然这里可以采用另外一种更优的分解算法完成并行过程,但是就Redis本身作为通过单线程epoll模型实现的异步IO来说,可能客户端的并行计算在服务端仍然被串行化处理,另外,分治算法拆分数组的时间损耗也不能忽略。

转载:https://blog.csdn.net/xreztento/article/details/53289193

Redis实现求交集操作结果缓存的设计方案相关推荐

  1. Redis介绍 Java客户端操作Redis

    Redis介绍 && Java客户端操作Redis 本文内容 redis介绍 redis的 shell 客户端简介 redis的 java 客户端简介 环境配置 redis 2.8.1 ...

  2. Redis介绍 Java客户端操作Redis

    分享一下我老师大神的人工智能教程.零基础!通俗易懂!风趣幽默!还带黄段子!希望你也加入到我们人工智能的队伍中来!https://blog.csdn.net/jiangjunshow Redis介绍 & ...

  3. redis python_Redis之Python操作

    Redis简单介绍 如果简单地比较Redis与Memcached的区别,大多数都会得到以下观点: 1 Redis不仅仅支持简单的k/v类型的数据,同时还提供list,set,zset,hash等数据结 ...

  4. Redis知识点总结(操作入门级)

    Redis笔记目录 nosql 讲解 nosql 数据模型 Nosql 四大分类 CAP BASE Redis 入门 Redis安装(Window & Linux服务器) 五大基本数据类型 S ...

  5. Redis 笔记之 Java 操作 Redis(Jedis)

    Java 操作 Redis 环境准备 引入依赖 创建 jedis 对象 操作 Key 相关 API 操作 String 相关 API 操作 List 相关 API 操作 Set 的相关 API 操作 ...

  6. 高并发核心技术Redis系列(九)--------本地操作

    一.本地安装设置 1.本地测试安装 在电脑开始页面 输入 cmd 2.输入redis-cli打开redis系统 注意:redis里面会出现这种情况 是因为没有登录的原因 解决输入密码 auth 123 ...

  7. Redis常用命令和操作

    1)连接操作命令     quit:关闭连接(connection)     auth:简单密码认证     help cmd: 查看cmd帮助,例如:help quit          2)持久化 ...

  8. 杂谈记录——论Long类型的大数据量求交集

    一.优化内外层比较的结构 『外层遍历 + 内层遍历』查找: 复杂度 O (NM) ,一般使用 contains () 检查是否包含 『外层遍历 + 内层 Hash』查找: 复杂度 O (N),一般将内 ...

  9. wukong引擎源码分析之搜索——docid有序的数组里二分归并求交集,如果用跳表的话,在插入索引时会更快...

    searcher.Search(types.SearchRequest{Text: "百度中国"}) // 查找满足搜索条件的文档,此函数线程安全 func (engine *En ...

最新文章

  1. SQLite3简单C++包装类源码示例
  2. .fa .fna和.fasta是同一种格式文件吗?
  3. ZOOKEEPER安装及测试
  4. windows下添加yaf扩展,生成yaf框架
  5. [渝粤教育] 西南科技大学 英语口语 在线考试复习资料
  6. python_递归原理
  7. ubuntu nfs linux,Ubuntu的NFS功能配置
  8. shell 字符串删除特定字符
  9. 确保VDI顺利部署 试点项目是关键
  10. 华为鸿蒙联合品牌,魅族官宣:接入华为鸿蒙!这是国产智能手机品牌的首个公开表态!...
  11. linux系统镜像官方下载地址
  12. 个人计算机好用的pdf软件,win10好用的pdf阅读器推荐 推荐几款好用的pdf阅读器
  13. Instant类[java]
  14. 输入10个互不相同的数字并分成5对,问有多少种分法。
  15. 王者荣耀微信登陆不了服务器,王者荣耀微信区怎么登陆不了 王者荣耀微信区怎么登不上...
  16. mysql购买服务_云数据库MySQL购买须知
  17. SQLZOOL练习题答案和解析 第2关 SELECT from World
  18. javaweb练手项目jsp+servlet简易购物车系统
  19. 简单易懂之什么是重排和重绘?
  20. 什么是zkSNARKs:谜一般的“月亮数学”加密,Part-1

热门文章

  1. 博客构建工具推荐(文本送书)
  2. Flutter创建圆圈图标按钮
  3. setsockopt()函数 参数详解
  4. java el表达式 if else_jsp EL表达式和JSTL标签if-else if-else用法
  5. 物流链云平台云ROS——看得见的成本节约
  6. linux查看java进程cpu占用过高
  7. 人工智能学习体系大纲(src:http://blog.sina.com.cn/s/blog_7dbb766f0102xdwu.html)
  8. linux虚拟机时间不准的问题
  9. package.json
  10. vmware安装mac