你们若是是作后端开发的,想必都实现过列表查询的接口,固然有的查询条件很简单,一条 SQL 就搞定了,但有的查询条件极其复杂,再加上库表中设计的各类不合理,致使查询接口特别难写,而后加班什么的就不用说了(不知各位有没有这种感觉呢~)。java
下面以一个例子开始,这是某购物网站的搜索条件,若是让你实现这样的一个搜索接口,你会如何实现?(固然你说借助搜索引擎,像 Elasticsearch 之类的,你彻底能够实现。但我这里想说的是,若是要你本身实现呢?)git
从上图中能够看出,搜索总共分为6大类,每大类中又分了各个子类。这中间,各大类条件之间是取的交集,各子类中有单选、多选、以及自定义的状况,最终输出符合条件的结果集。github
好了,既然需求很明确了,咱们就开始来实现。redis
率先登场是小A同窗,他是写 SQL 方面的“专家”。小A信心满满的说:“不就是一个查询接口吗?看着条件不少,但凭着我丰富的 SQL 经验,这点仍是难不倒个人。”sql
因而乎就写出了下面这段代码(这里以 MYSQL 为例):数据库
select ... from table_1 left join table_2 left join table_3 left join (select ... from table_x where ...) tmp_1 ... where ... order by ... limit m,n
代码在测试环境跑了一把,结果好像都匹配上了,因而准备上预发。这一上预发,问题就开始暴露出来。预发为了尽量的逼真线上环境,因此数据量天然而然要比测试大的多。因此这么一个复杂的 SQL,它的执行效率可想而知。测试同窗果断把小A的代码给打了回来。后端
总结了小A失败的教训,小B开始对SQL进行了优化,先是经过了explain关键字进行SQL性能分析,对该加索引的地方都加上了索引。同时将一条复杂SQL拆分红了多条SQL,计算结果在程序内存中进行计算。缓存
伪代码以下:性能优化
$result_1 = query('select ... from table_1 where ...'); $result_2 = query('select ... from table_2 where ...'); $result_3 = query('select ... from table_3 where ...'); ... $result = array_intersect($result_1, $result_2, $result_3, ...);
这种方案从性能上明显比第一种要好不少,但是在功能验收的时候,产品经理仍是以为查询速度不够快。小B本身也知道,每次查询都会向数据库查询屡次,并且有些历史缘由,部分条件是作不到单表查询的,因此查询等待的时间是避免不了的。数据结构
小C从上面的方案中看到了优化的空间。他发现小B在思路上是没问题的,将复杂条件拆分,计算各个子维度的结果集,最后将全部的子结果集进行一个汇总合并,获得最终想要的结果。
因而他突发奇想,可否事先将各个子维度的结果集给缓存起来,这要查询的时候直接去取想要的子集,而不用每次去查库计算。
这里小C采用 Redis 来存储缓存数据,用它的主要缘由是,它提供了多种数据结构,而且在 Redis 中进行集合的交并集操做是一件很容易的事情。
具体方案,如图所示:
这里每一个条件都事先将计算好的结果集ID存入对应的key中,选用的数据结构是集合(Set)。查询操做包括:
这其实就是所谓的反向索引。
这里会发现,漏了一个价格的条件。从需求中可知,价格条件是个区间,而且是无穷举的。因此上述的这种穷举条件的 Key-Value 方式是作不到的。这里咱们采用 Redis 的另外一种数据结构进行实现,有序集合(Sorted Set):
将全部商品加入 Key 为价格的有序集合中,值为商品ID,每一个值对应的分数为商品价格的数值。这样在 Redis 的有序集合中就能够经过ZRANGEBYSCORE命令,根据分数(价格)区间,获取相应结果集。
至此,方案三的优化已所有结束,将数据的查询与计算经过缓存的手段,进行了分离。在每次查找时,只须要简单的查找 Redis 几回就能得出结果。查询速度上符合了验收的要求。
这里你或许发现了一个严重的功能缺陷,列表查询怎么能没有分页。是的,咱们立刻来看 Redis 是如何实现分页的。
分页主要涉及排序,这里简单起见,就以建立时间为例。
如图所示:
图中蓝色部分是以建立时间为分值的商品有序集合,蓝色下方的结果集即为条件计算而得的结果,经过ZINTERSTORE命令,赋结果集权重为0,商品时间结果为1,取交集而得的结果集赋予建立时间分值的新有序集合。对新结果集的操做即能获得分页所需的各个数据:
关于索引数据更新的问题,有两种方式来进行。一种是经过商品数据的修改,来即时触发更新操做,一种是经过定时脚原本进行批量更新。
这里要注意的是,关于索引内容的更新,若是暴力的删除 Key,再从新设置 Key。由于 Redis 中两个操做不会是原子性进行的,因此中间可能存在空白间隙,建议采用仅移除集合中失效元素,添加新元素的方式进行。
Redis 是内存级操做,因此单次的查询会很快。可是若是咱们的实现中会进行屡次的 Redis 操做,Redis 的屡次链接时间多是没必要要时间消耗。经过使用MULTI命令,开启一个事务,将 Redis 的屡次操做放在一个事务中,最后经过EXEC来进行原子性执行(注意:这里所谓的事务,只是将多个操做在一次链接中执行,若是执行过程当中遇到失败,是不会回滚的)。
这里只是一个采用 Redis 优化查询搜索的一个简单 Demo,和现有的开源搜索引擎相比,它更轻量,学习成本页相应低些。其次,它的一些思想与开源搜索引擎是相似的,若是再加上词语解析,也能够实现相似全文检索的功能。
来源:https://github.com/jasonGeng8...