如何用redis实现“搜索历史”和“自动补全”搜索框

标签: redis 搜索 历史 | 发表时间:2017-11-16 19:31 | 作者:
出处:http://www.iteye.com

搜索框的附加功能

 

在日常的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等大数据平台。最后通过各种算法计算,为用户进行精准推荐,这些数据都是“智能推荐”的基础。

 

为自己的系统做一个好的搜索框,在增加用户体验的同时,还可以做一些附件的推荐类产品。建议有类似场景的系统,都可以尝试下。

 

 



已有 0 人发表留言,猛击->> 这里<<-参与讨论


ITeye推荐



相关 [redis 搜索 历史] 推荐:

如何用redis实现“搜索历史”和“自动补全”搜索框

- - ITeye博客
在日常的web开发中,经常有搜索框功--在一批数据中检索自己需要的数据. 现在的百度以及各大电商的搜索框都做得很人性化,主要体现在两个方面:. 一、搜索框的“搜索历史”:为了方便用户下次搜索,搜索框通常会提供“搜索历史”功能 即:记录下用户的搜索历史,用户下次点击搜索框就会立即展示你最近的搜索记录列表.

Redis 构建高性能的实时搜索 . 笔记 #知识梳理#

- - 张沈鹏 zuroc.42qu.com
http://42qu.us/oHwH Rails App 运用 Redis 构建高性能的实时搜索 (. http://42qu.us/oHwI 的解决方案 ). 索引前缀 , 存放id, 按照rank排序 (对于标签). We use every prefix as the key name for a sorted set, executing the a ZINCRBY 1 news for each key..

开源分布式搜索平台ELK(Elasticsearch+Logstash+Kibana)+Redis+Syslog-ng实现日志实时搜索

- - C1G军火库
ElasticSearch是一个基于Lucene构建的开源,分布式,RESTful搜索引擎. 设计用于云计算中,能够达到实时搜索,稳定,可靠,快速,安装使用方便. 支持通过HTTP使用JSON进行数据索引. logstash是一个应用程序日志、事件的传输、处理、管理和搜索的平台. 你可以用它来统一对应用程序日志进行收集管理,提供 Web 接口用于查询和统计.

Android搜索功能的案例,本地保存搜索历史记录。

- - CSDN博客推荐文章
同事负责开发的APP有一个搜索功能,并且需要显示搜索的历史记录,我闲暇之余帮她开发了这个功能,现把该页面抽取成一个demo分享给大家. 本案例实现起来很简单,所以可以直接拿来嵌入项目中使用,涉及到的知识点:. - 数据库的增删改查操作. - ListView和ScrollView的嵌套冲突解决.

谷奥: 【快乐周末】漫画:搜索的历史

- 舞愿~cc - 谷奥聚合——谷奥主站+谷安 aggregator
以下是Geek & Poke带来的漫画:“搜索的历史”. 镜子啊镜子,谁是世界上最美的人. ──于是,世界上第一次SEO(搜索引擎优化)也诞生了. © musiXboy 发表于 谷奥——探寻谷歌的奥秘 ( http://www.guao.hk ), 2011. | 3 条评论 | 永久链接 | 关于谷奥 | 投稿/爆料.

Redis 负载监控——redis-monitor

- - ITeye资讯频道
redis-monitor是一个Web可视化的 redis 监控程序. 使用 Flask 来开发的,代码结构非常简单,适合移植到公司内网使用. redis 服务器信息,包括 redis 版本、上线时间、 os 系统信息等等. 实时的消息处理信息,例如处理 command 数量、连接总数量等. 内存占用、 cpu 消耗实时动态图表.

Redis 起步

- - 博客园_首页
Rdis和JQuery一样是纯粹为应用而产生的,这里记录的是在CentOS 5.7上学习入门文章:. Redis是一个key-value存储系统. 和Memcached类似,但是解决了断电后数据完全丢失的情况,而且她支持更多无化的value类型,除了和string外,还支持lists(链表)、sets(集合)和zsets(有序集合)几种数据类型.

redis 配置

- - 谁主沉浮
# 当配置中需要配置内存大小时,可以使用 1k, 5GB, 4M 等类似的格式,其转换方式如下(不区分大小写). # 内存配置大小写是一样的.比如 1gb 1Gb 1GB 1gB. # daemonize no 默认情况下,redis不是在后台运行的,如果需要在后台运行,把该项的值更改为yes. # 当redis在后台运行的时候,Redis默认会把pid文件放在/var/run/redis.pid,你可以配置到其他地址.

Cassandra代替Redis?

- - Tim[后端技术]
最近用Cassandra的又逐渐多了,除了之前的360案例,在月初的QCon Shanghai 2013 篱笆网也介绍了其使用案例. 而这篇 百万用户时尚分享网站feed系统扩展实践文章则提到了Fashiolista和Instagram从Redis迁移到Cassandra的案例. 考虑到到目前仍然有不少网友在讨论Redis的用法问题,Redis是一个数据库、内存、还是Key value store?以及Redis和memcache在实际场景的抉择问题,因此简单谈下相关区别.

redis 部署

- - CSDN博客云计算推荐文章
一、单机部署 tar xvf redis-2.6.16.tar.gz cd redis-2.6.16 make make PREFIX=/usr/local/redis install  #指定安装目录为/usr/local/redis,默认安装安装到/usr/local/bin. # chkconfig: 2345 80 10       #添加redhat系列操作系统平台,开机启动需求项(运行级别,开机时服务启动顺序、关机时服务关闭顺序) # description:  Starts, stops redis server.