文档章节

Elasticsearch实现类百度搜索引擎搜索功能ES5.5.0v

BakerZhu
 BakerZhu
发布于 2017/09/27 09:50
字数 1675
阅读 2.5K
收藏 80

3 月,跳不动了?>>>

源码地址: GitHub

业务需求(使用背景):

  1. 实现搜索引擎前缀搜索功能(中文,拼音前缀查询及简拼前缀查询功能)
  2. 实现摘要全文检索功能,及标题加权处理功能(按照标题权值高内容权值相对低的权值分配规则,按照索引的相关性进行排序,列出前20条相关性最高的文章)

一、搜索引擎前缀搜索功能:

中文搜索:
1、搜索“刘”,匹配到“刘德华”、“刘斌”、“刘德志”
2、搜索“刘德”,匹配到“刘德华”、“刘德志”
小结:搜索的文字需要匹配到集合中所有名字的子集。
全拼搜索:
1、搜索“li”,匹配到“刘德华”、“刘斌”、“刘德志”
2、搜索“liud”,匹配到“刘德华”、“刘德”
3、搜索“liudeh”,匹配到“刘德华”
小结:搜索的文字转换成拼音后,需要匹配到集合中所有名字转成拼音后的子集

简拼搜索:
1、搜索“w”,匹配到“我是中国人”,“我爱我的祖国”
2、搜索“wszg”,匹配到“我是中国人”
小结:搜索的文字取拼音首字母进行组合,需要匹配到组合字符串中前缀匹配的子集

解决方案:

方案一:将“like”搜索的字段的中、英简拼、英全拼 分别用索引的三个字段来进行存储并且不进行分词,最简单直接(倒排索引存储它们本身数据),检索索引数据的时候进行 通配符查询(like查询),从这三个字段中分别进行搜索,查询匹配的记录然后返回。(优势:存储格式简单,倒排索引存储的数据量最少。缺点:like索引数据的时候开销比较大 prefix 查询比 term 查询开销大得多)

方案二:将中、中简拼、中全拼 用一个字段衍生出三个字段(multi-field)来存储三种数据,并且分词器filter采用edge_ngram类型对分词的数据进行,然后处理存储到倒排索引中,当检索索引数据时,检索所有字段的数据。(优势:格式紧凑,检索索引数据的时候采用term 全匹配规则,也无需对入参进行分词,查询效率高。缺点:采用以空间换时间的策略,但是对索引来说可以接受。采用衍生字段来存储,增加了存储及检索的复杂度,对于三个字段搜索会将相关度相加,容易混淆查询相关度结果)

方案三:将索引数据存储在一个不需分词的字段中(keyword), 生成倒排索引时进行三种类型倒排索引的生成,倒排索引生成的时候采用edge_ngram 对倒排进一步拆分,以满足业务场景需求,检索时不对入参进行分词。(优势:索引数据存储简单,,检索索引数据的时只需对一个字段 采用term 全匹配查询规则,查询效率极高。缺点:采用以空间换时间的策略——比方案二要少,对索引数据来说可以接受。)

ES 针对这一业务场景解决方案还有很多种,先列出比较典型的这三种方案,选择方案三来进行处理。

准备工作:

  • pinyin分词插件安装及参数解读
  • ElasticSearch edge_ngram 使用
  • ElasticSearch multi-field 使用
  • ElasticSearch 多种查询特性熟悉

代码:

baidu_settings.json:

{
  "refresh_interval":"2s",
  "number_of_replicas":1,
  "number_of_shards":2,
  "analysis":{
    "filter":{
      "autocomplete_filter":{
        "type":"edge_ngram",
        "min_gram":1,
        "max_gram":15
      },
      "pinyin_first_letter_and_full_pinyin_filter" : {
        "type" : "pinyin",
        "keep_first_letter" : true,
        "keep_full_pinyin" : false,
        "keep_joined_full_pinyin": true,
        "keep_none_chinese" : false,
        "keep_original" : false,
        "limit_first_letter_length" : 16,
        "lowercase" : true,
        "trim_whitespace" : true,
        "keep_none_chinese_in_first_letter" : true
      },
      "full_pinyin_filter" : {
        "type" : "pinyin",
        "keep_first_letter" : true,
        "keep_full_pinyin" : false,
        "keep_joined_full_pinyin": true,
        "keep_none_chinese" : false,
        "keep_original" : true,
        "limit_first_letter_length" : 16,
        "lowercase" : true,
        "trim_whitespace" : true,
        "keep_none_chinese_in_first_letter" : true
      }
    },
    "analyzer":{
      "full_prefix_analyzer":{
        "type":"custom",
        "char_filter": [
          "html_strip"
        ],
        "tokenizer":"keyword",
        "filter":[
          "lowercase",
          "full_pinyin_filter",
          "autocomplete_filter"
        ]
      },
      "chinese_analyzer":{
        "type":"custom",
        "char_filter": [
          "html_strip"
        ],
        "tokenizer":"keyword",
        "filter":[
          "lowercase",
          "autocomplete_filter"
        ]
      },
      "pinyin_analyzer":{
        "type":"custom",
        "char_filter": [
          "html_strip"
        ],
        "tokenizer":"keyword",
        "filter":[
          "pinyin_first_letter_and_full_pinyin_filter",
          "autocomplete_filter"
        ]
      }
    }
  }
}

baidu_mapping.json

{
  "baidu_type": {
    "properties": {
      "full_name": {
        "type":  "text",
        "analyzer": "full_prefix_analyzer"
      },
      "age": {
        "type":  "integer"
      }
    }
  }
}

public class PrefixTest {

    @Test
    public void testCreateIndex() throws Exception{
        TransportClient client = ESConnect.getInstance().getTransportClient();
        //定义索引
        BaseIndex.createWithSetting(client,"baidu_index","esjson/baidu_settings.json");
        //定义类型及字段详细设计
        BaseIndex.createMapping(client,"baidu_index","baidu_type","esjson/baidu_mapping.json");
    }
    @Test
    public void testBulkInsert() throws Exception{
        TransportClient client = ESConnect.getInstance().getTransportClient();
        List<Object> list = new ArrayList<>();
        list.add(new BulkInsert(12l,"我们都有一个家名字叫中国",12));
        list.add(new BulkInsert(13l,"兄弟姐妹都很多景色也不错 ",13));
        list.add(new BulkInsert(14l,"家里盘着两条龙是长江与黄河",14));
        list.add(new BulkInsert(15l,"还有珠穆朗玛峰儿是最高山坡",15));
        list.add(new BulkInsert(16l,"我们都有一个家名字叫中国",16));
        list.add(new BulkInsert(17l,"兄弟姐妹都很多景色也不错",17));
        list.add(new BulkInsert(18l,"看那一条长城万里在云中穿梭",18));
        boolean flag = BulkOperation.batchInsert(client,"baidu_index","baidu_type",list);
        System.out.println(flag);
    }
}

不要意思,代码封装了,java生成索引网上查方式即可:重点不在java代码怎么实现。而是上面的思想。

接下来查看下定义的分词器效果:

http://192.168.20.114:9200/baidu_index/_analyze?text=刘德华AT2016&analyzer=full_prefix_analyzer
{
    "tokens": [
        {
            "token": "刘",
            "start_offset": 0,
            "end_offset": 9,
            "type": "word",
            "position": 0
        },
        {
            "token": "刘德",
            "start_offset": 0,
            "end_offset": 9,
            "type": "word",
            "position": 0
        },
        {
            "token": "刘德华",
            "start_offset": 0,
            "end_offset": 9,
            "type": "word",
            "position": 0
        },
        {
            "token": "刘德华a",
            "start_offset": 0,
            "end_offset": 9,
            "type": "word",
            "position": 0
        },
        {
            "token": "刘德华at",
            "start_offset": 0,
            "end_offset": 9,
            "type": "word",
            "position": 0
        },
        {
            "token": "刘德华at2",
            "start_offset": 0,
            "end_offset": 9,
            "type": "word",
            "position": 0
        },
        {
            "token": "刘德华at20",
            "start_offset": 0,
            "end_offset": 9,
            "type": "word",
            "position": 0
        },
        {
            "token": "刘德华at201",
            "start_offset": 0,
            "end_offset": 9,
            "type": "word",
            "position": 0
        },
        {
            "token": "刘德华at2016",
            "start_offset": 0,
            "end_offset": 9,
            "type": "word",
            "position": 0
        },
        {
            "token": "l",
            "start_offset": 0,
            "end_offset": 9,
            "type": "word",
            "position": 0
        },
        {
            "token": "li",
            "start_offset": 0,
            "end_offset": 9,
            "type": "word",
            "position": 0
        },
        {
            "token": "liu",
            "start_offset": 0,
            "end_offset": 9,
            "type": "word",
            "position": 0
        },
        {
            "token": "liud",
            "start_offset": 0,
            "end_offset": 9,
            "type": "word",
            "position": 0
        },
        {
            "token": "liude",
            "start_offset": 0,
            "end_offset": 9,
            "type": "word",
            "position": 0
        },
        {
            "token": "liudeh",
            "start_offset": 0,
            "end_offset": 9,
            "type": "word",
            "position": 0
        },
        {
            "token": "liudehu",
            "start_offset": 0,
            "end_offset": 9,
            "type": "word",
            "position": 0
        },
        {
            "token": "liudehua",
            "start_offset": 0,
            "end_offset": 9,
            "type": "word",
            "position": 0
        },
        {
            "token": "l",
            "start_offset": 0,
            "end_offset": 9,
            "type": "word",
            "position": 0
        },
        {
            "token": "ld",
            "start_offset": 0,
            "end_offset": 9,
            "type": "word",
            "position": 0
        },
        {
            "token": "ldh",
            "start_offset": 0,
            "end_offset": 9,
            "type": "word",
            "position": 0
        },
        {
            "token": "ldha",
            "start_offset": 0,
            "end_offset": 9,
            "type": "word",
            "position": 0
        },
        {
            "token": "ldhat",
            "start_offset": 0,
            "end_offset": 9,
            "type": "word",
            "position": 0
        },
        {
            "token": "ldhat2",
            "start_offset": 0,
            "end_offset": 9,
            "type": "word",
            "position": 0
        },
        {
            "token": "ldhat20",
            "start_offset": 0,
            "end_offset": 9,
            "type": "word",
            "position": 0
        },
        {
            "token": "ldhat201",
            "start_offset": 0,
            "end_offset": 9,
            "type": "word",
            "position": 0
        },
        {
            "token": "ldhat2016",
            "start_offset": 0,
            "end_offset": 9,
            "type": "word",
            "position": 0
        }
    ]
}

大功告成。

参考:

http://blog.csdn.net/napoay/article/details/53907921
https://elasticsearch.cn/question/407
http://blog.csdn.net/xifeijian/article/details/51095762
http://www.cnblogs.com/xing901022/p/5910139.html
http://www.cnblogs.com/clonen/p/6674492.html

https://github.com/medcl/elasticsearch-analysis-pinyin

https://github.com/medcl/elasticsearch-analysis-ik

全文检索后续有时间再进行整理。

 

 

© 著作权归作者所有

BakerZhu
粉丝 109
博文 517
码字总数 423077
作品 0
通州
程序员
私信 提问
加载中

评论(2)

苍穹自由无限
苍穹自由无限
楼主可以描述其中容易错误的点么,细节方面的描述
kakai
kakai
先收藏了,谢谢博主!!!
Java搜索引擎选择: Elasticsearch与Solr(转)

Elasticsearch简介 Elasticsearch是一个实时的分布式搜索和分析引擎。它可以帮助你用前所未有的速度去处理大规模数据。 它可以用于全文搜索,结构化搜索以及分析,当然你也可以将这三者进行组...

easonjim
2017/11/13
0
0
搜索引擎选择: Elasticsearch与Solr

搜索引擎选型调研文档 Elasticsearch简介* Elasticsearch是一个实时的分布式搜索和分析引擎。它可以帮助你用前所未有的速度去处理大规模数据。 它可以用于全文搜索,结构化搜索以及分析,当然...

开源中国首席码农
2016/11/15
1.4K
4
Elasticsearch初探(1)——基本介绍与环境搭建

版权声明:本文版权归Jitwxs所有,欢迎转载,但未经作者同意必须保留原文链接。 https://blog.csdn.net/yuanlaijike/article/details/82966110 一、Elasticsearch简介 1.1 什么是Elasticsear...

Jitwxs
2018/10/08
0
0
Django Haystack 全文检索与关键词高亮

作者:HelloGitHub-追梦人物 文中所涉及的示例代码,已同步更新到 HelloGitHub-Team 仓库 博客提供 RSS 订阅应该是标配,这样读者就可以通过一些聚合阅读工具订阅你的博客,时时查看是否有文...

HelloGitHub
02/14
0
0
ES(elasticsearch)搜索引擎安装和使用(windows And Linux)

大数据时代,搜索无处不在。搜索技术是全栈工程师必备技术之一,如今是开源时代,数不尽的资源供我们利用,如果要自己写一套搜索引擎无疑是浪费绳命。本节主要介绍搜索引擎开源项目elasticSe...

ZhangLG
2018/09/17
109
0

没有更多内容

加载失败,请刷新页面

加载更多

JavaScript等同于printf / String.Format - JavaScript equivalent to printf/String.Format

问题: I'm looking for a good JavaScript equivalent of the C/PHP printf() or for C#/Java programmers, String.Format() ( IFormatProvider for .NET). 我正在寻找一个等效于C / PHP p......

javail
34分钟前
23
0
什么是Android上的“上下文”? - What is 'Context' on Android?

问题: In Android programming, what exactly is a Context class and what is it used for? 在Android编程中, Context类到底是什么?它的用途是什么? I read about it on the developer......

技术盛宴
今天
20
0
OkHttp配置HTTPS访问+服务器部署

1 概述 OkHttp配置HTTPS访问,核心为以下三个部分: sslSocketFactory() HostnameVerifier X509TrustManager 第一个是ssl套接字工厂,第二个用来验证主机名,第三个是证书信任器管理类.通过OkHtt...

氷泠
今天
20
0
华为P40发布:搭载HMS硬刚谷歌,未涨价抢全球高端机市场

  文连线 Insight,作者向阳,编辑水笙   3 月 26 日晚,华为消费者业务 CEO 余承东登上台,以熟悉的英文口音开启了华为发布会,他说,“这就是我们的 P40 系列。”   以往华为P系列通...

水果黄瓜
今天
28
0
OSChina 周一乱弹 —— 小姐姐,这tm不是犬耳娘吗!你认错了吧

Osc乱弹歌单(2020)请戳(这里) 【今日歌曲】 @薛定谔的兄弟 :分享洛神有语创建的歌单「我喜欢的音乐」: 《Drip Drip Drip》- 音乐治疗 手机党少年们想听歌,请使劲儿戳(这里) @-Eric- ...

小小编辑
今天
56
1

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部