搜索框的附加功能
在日常的web开发中,经常有搜索框功--在一批数据中检索自己需要的数据。现在的百度以及各大电商的搜索框都做得很人性化,主要体现在两个方面:
一、搜索框的“搜索历史”:为了方便用户下次搜索,搜索框通常会提供“搜索历史”功能 即:记录下用户的搜索历史,用户下次点击搜索框就会立即展示你最近的搜索记录列表。如果用户经常搜索的关键字,下次不用手动输入,直接选择即可。
用户的搜索历史相当关键,这是电商网站做“智能推荐”的基石。比如上述搜索列表,反映出三类信息:羽毛球相关、java、汽车周边。根据一系列智能算法 很快就能推算出该用户喜欢的商品。
这个功能即可以方便用户减少输入,又可以衍生出一些新服务,可谓一箭双雕。
二、搜索框的“自动匹配”:当用户输入关键词的头一个或几个字 就会出现一个自动匹配列表,用户直接选择自己想要搜索的关键字即可,不用输入完整的关键词。
上面这个截图来自百度搜索框的“自动匹配”,同时整合了“搜索历史”的功能,其中蓝色的关键词是用户的“搜索历史”。
“搜索历史”和“自动匹配”功能的本质是向前面页面返回一个“关键字列表”, 要实现一个体验好的搜索框,关键就是快速响应页面的获取“关键字列表”请求;否则如果太慢 用户都输入完成了,“关键字列表”还没有刷新出来,这个功能就没有意义了。
百度搜索框 把这两个功能都做到了极致,而且的用户量很庞大,关键词的存储量就更庞大了。百度的具体实现笔者不是很清楚,猜测是通过ES+各种Hash分组实现的。庆幸的是我们不必去实现一个百度,在一个普通的系统里没有太大的用户量和关键词(相对于百度),要实现这两个功能 使用redis就可以完全满足需求了。
采用redis的list实现“搜索历史”
采用redis的list存储每个用户的“搜索历史”列表,存储规则:最近搜索过的关键词的放在最前面(这里有两层含义:最近搜索新关键词、最近搜索老关键词),并且每个用户只保留最近访问过的5个关键词(5个太少,这里只是举例,可以根据自己业务调整)。List对应的key规则为:recent_search_{userId},在redis中存储示意图如下:
本示例采用java的Jedis进行代码实现,要实现这个功能只需要两个方法即可:一个是更新“搜索历史”方法(对应下列updateList方法);一个是匹配“搜索历史”方法(对应下列getAutoMatchs),具体实现如下:
/** * Created by gantianxing on 2017/11/16. */ @Component public class RecentSearchService { @Resource private Jedis redis; /** * 更新搜索历史列表 * @param userId 用户id * @param searchkey 本次搜索关键词 */ public void updateList(Integer userId,String searchkey){ String key = "recent_search_"+userId; //为了保证事务和性能,采用pipeline Pipeline pipeline = redis.pipelined(); pipeline.lrem(key,1,searchkey);//如果该关键词已存在先删除 pipeline.lpush(key, searchkey);//把该关键词放在最顶部(左边) pipeline.ltrim(key, 0, 4);//裁剪list 保留最近的5个关键词 pipeline.sync();//批量提交命令 } /** * 从搜索历史列表中获取匹配的列表 * @param userId * @param pre * @return */ public List<String> getAutoMatchs(Integer userId,String pre){ String key = "recent_search_"+userId; List<String> all = redis.lrange(key,0,-1);//获取该用户对应的“搜索历史列表” if(all == null){ return null; } if(pre!=null && pre.length()>0){ List<String> matchList = new ArrayList<>(); for(String one:all){ //前缀匹配 if(one.startsWith(pre)){ matchList.add(one); } } return matchList;//返回匹配到的“搜索历史列表” }else { return all;//用户还没有输入,就返回所有的“搜索历史列表” } } }
可以看到整个存储和查询都是在redis中进行,性能和效率是mysql等传统数据库无法比拟的。
采用redis的list实现“自动匹配”
在“搜索历史”的实现过程中已经实现了“自动匹配”, 即getAutoMatchs方法,这里只需把每个用户的“搜索历史”列表替换成“关键词词库”。“关键词词库”怎么来,通过一些分词工具分词,然后再归类后通过hash规则放到不同的服务器,百度词库的实现应该采用类似的技术手段。
如果只是一个普通的一个小系统,可以通过手动导入一些与自己系统相关的词库到redis即可。假设现在我们已经把词库导入到redis的list中了(对应key为all_key_words),稍微更下getAutoMatchs方法 就可以实现默认的“自动匹配”:
/** * 从词库列表中获取匹配的列表 * @param pre * @return */ public List<String> getDefaultAutoMatchs(String pre){ String key = "all_key_words"; List<String> all = redis.lrange(key,0,-1);//获取“关键词词库列表” if(all == null){ return null; } if(pre!=null && pre.length()>0){ List<String> matchList = new ArrayList<>(); for(String one:all){ //前缀匹配 if(one.startsWith(pre)){ matchList.add(one); } } return matchList;//返回匹配到的“关键词词库列表” }else { return all;//用户还没有输入,就返回所有的“关键词词库列表” } }
实现起来很简单,但是如果采用list存储“关键词词库列表”,需要注意的是all_key_words对应的“关键词词库列表”中的关键词数量不能太多(如果采用这种方式,建议词库中的数量不要超过100)。如果太多会导致匹配过程相当缓慢,严重影响该功能的性能。
如何优化呢?有两种办法:一、对词库进行分组,把一个list变为多个list,在匹配之前首先确定“关键词”所在的分组;二、采用redis的zset数据结构存储“关键词词库”,zset中每项成员名的存储内容为“关键词”,成员值 score都设置为0,此时zset的排序会按照成员名进行。其实还可以把两种方案合并使用,效果会更好一些 即:首先对词库进行分组,每个分组采用redis的zset数据结构存储。
为什么要使用zset数据结构,因为zset采用了“跳表”结构设计,可以快速的进行范围查询检索。但实用zset存储在进行检索时想对比较复杂:
1、先生成“前缀关键字”对应的起始值。
2、通过zadd命令把起始值插入到zset中。
3、通过zrank命令计算这两个位置的index:start_index、end_index。
4、通过zrem删除步骤2中插入的起始值(它们的作用仅仅为了找到start_index、end_index)。
5、最后通过zrange命令取出这个start_index、 end_index之间的所有 关键字即可。
这里就不再展示但实现,可以根据这个逻辑自行编码。
小结
现在的各大电商网站都有搜索框,而且“搜索历史”和“自动匹配功能”几乎都是标配。另外由于redis中会对搜索记录进行裁剪,一般会在用户搜索时还会进行日志上报,把用户的搜索记录流水全部记录到日志服务器,再通过日志整理汇集到hadoop等大数据平台。最后通过各种算法计算,为用户进行精准推荐,这些数据都是“智能推荐”的基础。
为自己的系统做一个好的搜索框,在增加用户体验的同时,还可以做一些附件的推荐类产品。建议有类似场景的系统,都可以尝试下。
相关推荐
autocomplete-redis 是基于redis的自动补全,他会自动索引你要自动补全的句子,然后根据你的输入返回包含这个输入的句子。这儿有一个完整的演示实例: http://ohbooklist.com/redis/ ,我们索引了3.7万本书的名字。 ...
java使用DelayQueue延迟队列和Redis缓存实现订单自动取消功能
主要给大家介绍了关于如何利用Redis如何实现自动补全功能的相关资料,文中通过示例代码介绍的非常详细,对大家学习或者使用Redis具有一定的参考学习价值,需要的朋友们下面来一起学习学习吧
基于SSM框架+Redis实现的线上秒杀系统项目源码 基于SSM框架+Redis实现的线上秒杀系统项目源码 基于SSM框架+Redis实现的线上秒杀系统项目源码 基于SSM框架+Redis实现的线上秒杀系统项目源码 基于SSM框架+Redis实现的...
redis实现简单排行榜,和消息处理。
基于mq和redis实现的秒杀系统基于mq和redis实现的秒杀系统
Qt 使用 Redis实现 消息队列,点对点 生产者-消费者 模式
tomcat-redis实现session共享
springboot整和jwt、shiro、redis实现token自动刷新
今天用了一天来搞定了ssm+redis集成和nginx实现负载均衡,这里只有ssm+redis简单d集成demo,希望大家一起来讨论
主要介绍了Java基于redis实现分布式锁代码实例,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下
redis页面缓存html使用redis实现页面缓存.docx
springboot+websocket+redis实现聊天室功能,可以实现私聊和群聊(并支持发送图片)
Spring Boot 使用 AOP 和 Redis 实现接口限流是一种高效且实用的方法,用于控制对特定接口的访问频率。以下是实现这个功能的基本步骤: 引入依赖:首先,在 Spring Boot 项目中引入 Redis 和 AOP 的相关依赖。这...
redis实现搜索功能。
本篇文章主要介绍了基于 Redis 实现分布式应用限流的方法,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧
redis实现分布式锁,自旋式加锁,lua原子性解锁
springboot+mybatis+druid+redis实现数据库读写分离和缓存