一架梯子,一头程序猿,仰望星空!

Elasticsearch 全文搜索


全文搜索是ES的关键特性之一,平时我们使用SQL的like语句,搜索一些文本、字符串是否包含指定的关键词,但是如果两篇文章,都包含我们的关键词,具体那篇文章内容的相关度更高? 这个SQL的like语句是做不到的,更别说like语句的性能问题了。

ES通过分词处理、相关度计算可以解决这个问题,ES内置了一些相关度算法,例如:TF/IDF算法,大体上思想就是,如果一个关键词在一篇文章出现的频率高,并且在其他文章中出现的少,那说明这个关键词与这篇文章的相关度很高。

分词的目的:

主要就是为了提取搜索关键词,理解搜索的意图,我们平时在百度搜索内容的时候,输入的内容可能很长,但不是每个字都对搜索有帮助,所以通过分词算法,我们输入的搜索关键词,会进一步分解成多个关键词,例如:搜索输入 "上海陆家嘴在哪里?",分词算法可能分解出:“上海、陆家嘴、哪里”,具体会分解出什么关键词,跟具体的分词算法有关。

1.默认全文搜索

默认情况下,使用全文搜索很简单,只要将字段类型定义为text类型,然后用match语句匹配即可。

ES对于text类型的字段,在插入数据的时候,会进行分词处理,然后根据分词的结果索引文档,当我们搜索text类型字段的时候,也会先对搜索关键词进行分词处理、然后根据分词的结果去搜索。

ES默认的分词器是standard,对英文比较友好,例如:hello world 会被分解成 hello和world两个关键词,如果是中文会分解成一个一个字,例如:上海大学 会分解成: 上、海、大、学。

在ES中,我们可以通过下面方式测试分词效果:

语法:

GET /_analyze
{
  "text": "需要分词的内容",
  "analyzer": "分词器"
}

例如:

GET /_analyze
{
  "text": "hello wolrd",
  "analyzer": "standard"
}

使用standard分词器,对hello world进行分词,下面是输出结果:

{
  "tokens" : [
    {
      "token" : "hello",
      "start_offset" : 0,
      "end_offset" : 5,
      "type" : "<ALPHANUM>",
      "position" : 0
    },
    {
      "token" : "wolrd",
      "start_offset" : 6,
      "end_offset" : 11,
      "type" : "<ALPHANUM>",
      "position" : 1
    }
  ]
}

token就是分解出来的关键词。

下面是对中文分词的结果:

GET /_analyze
{
  "text": "上海大学",
  "analyzer": "standard"
}

输出

{
  "tokens" : [
    {
      "token" : "上",
      "start_offset" : 0,
      "end_offset" : 1,
      "type" : "<IDEOGRAPHIC>",
      "position" : 0
    },
    {
      "token" : "海",
      "start_offset" : 1,
      "end_offset" : 2,
      "type" : "<IDEOGRAPHIC>",
      "position" : 1
    },
    {
      "token" : "大",
      "start_offset" : 2,
      "end_offset" : 3,
      "type" : "<IDEOGRAPHIC>",
      "position" : 2
    },
    {
      "token" : "学",
      "start_offset" : 3,
      "end_offset" : 4,
      "type" : "<IDEOGRAPHIC>",
      "position" : 3
    }
  ]
}

明显被切割成一个个的字了。

中文关键词被分解成一个个的字的主要问题就是搜索结果可能不太准确。

例如:

搜索:上海大学

分词结果:上、海、大、学

下面的内容都会被搜到:

  • 上海大学
  • 海上有条船
  • 上海有好吃的
  • 这条船又大又好看

基本上包含这四个字的内容都会被搜到,区别就是相关度的问题,这里除了第一条是相关的,后面的内容基本上跟搜索目的没什么关系。

2.中文分词器

ES默认的analyzer(分词器),对英文单词比较友好,对中文分词效果不好。不过ES支持安装分词插件,增加新的分词器。

2.0. 如何指定analyzer

默认的分词器不满足需要,可以在定义索引映射的时候,指定text字段的分词器
(analyzer)。

例子:

PUT /article
{
  "mappings": {
    "properties": {
      "title":   { 
          "type": "text",
          "analyzer": "smartcn"
      }
    }
  }
}

只要在定义text字段的时候,增加一个analyzer配置,指定分词器即可,这里指定的分词器是smartcn,后面会介绍怎么安装smartcn插件。

目前中文分词器比较常用的有:smartcn和ik两种, 下面分别介绍这两种分词器。

2.1. smartcn分词器

smartcn是目前ES官方推荐的中文分词插件,不过目前不支持自定义词库。

插件安装方式:

{ES安装目录}/bin/elasticsearch-plugin install analysis-smartcn

安装完成后,重启ES即可。

smartcn的分词器名字就叫做:smartcn

2.2. smartcn中文分词效果

测试分词效果:

GET /_analyze
{
  "text": "红烧牛肉面",
  "analyzer": "smartcn"
}

输出:

{
  "tokens" : [
    {
      "token" : "红烧",
      "start_offset" : 0,
      "end_offset" : 2,
      "type" : "word",
      "position" : 0
    },
    {
      "token" : "牛肉面",
      "start_offset" : 2,
      "end_offset" : 5,
      "type" : "word",
      "position" : 1
    }
  ]
}

红烧牛肉面,被切割为: 红烧、牛肉面 两个词。

2.3. ik分词器

ik支持自定义扩展词库,有时候分词的结果不满足我们业务需要,需要根据业务设置专门的词库,词库的作用就是自定义一批关键词,分词的时候优先根据词库设置的关键词分割内容,例如:词库中包含 “上海大学” 关键词,如果对“上海大学在哪里?”进行分词,“上海大学” 会做为一个整体被切割出来。

安装ik插件:

// 到这里找跟自己ES版本一致的插件地址
https://github.com/medcl/elasticsearch-analysis-ik/releases

我本地使用的ES版本是7.5.1,所以选择的Ik插件版本地址是:

https://github.com/medcl/elasticsearch-analysis-ik/releases/download/v7.5.1/elasticsearch-analysis-ik-7.5.1.zip

当然你也可以尝试根据我给出这个地址,直接修改版本号,试试看行不行。

安装命令

{ES安装目录}/bin/elasticsearch-plugin install https://github.com/medcl/elasticsearch-analysis-ik/releases/download/v7.5.1/elasticsearch-analysis-ik-7.5.1.zip

2.4. ik中文分词效果

ik分词插件支持 ik_smart 和 ik_max_word 两种分词器

  • ik_smart - 粗粒度的分词
  • ik_max_word - 会尽可能的枚举可能的关键词,就是分词比较细致一些,会分解出更多的关键词

例1:

GET /_analyze
{
  "text": "上海人民广场麻辣烫",
  "analyzer": "ik_max_word"
}

输出:

{
  "tokens" : [
    {
      "token" : "上海人",
      "start_offset" : 0,
      "end_offset" : 3,
      "type" : "CN_WORD",
      "position" : 0
    },
    {
      "token" : "上海",
      "start_offset" : 0,
      "end_offset" : 2,
      "type" : "CN_WORD",
      "position" : 1
    },
    {
      "token" : "人民",
      "start_offset" : 2,
      "end_offset" : 4,
      "type" : "CN_WORD",
      "position" : 2
    },
    {
      "token" : "广场",
      "start_offset" : 4,
      "end_offset" : 6,
      "type" : "CN_WORD",
      "position" : 3
    },
    {
      "token" : "麻辣烫",
      "start_offset" : 6,
      "end_offset" : 9,
      "type" : "CN_WORD",
      "position" : 4
    },
    {
      "token" : "麻辣",
      "start_offset" : 6,
      "end_offset" : 8,
      "type" : "CN_WORD",
      "position" : 5
    },
    {
      "token" : "烫",
      "start_offset" : 8,
      "end_offset" : 9,
      "type" : "CN_CHAR",
      "position" : 6
    }
  ]
}

例2:

GET /_analyze
{
  "text": "上海人民广场麻辣烫",
  "analyzer": "ik_smart"
}

输出:

{
  "tokens" : [
    {
      "token" : "上海",
      "start_offset" : 0,
      "end_offset" : 2,
      "type" : "CN_WORD",
      "position" : 0
    },
    {
      "token" : "人民",
      "start_offset" : 2,
      "end_offset" : 4,
      "type" : "CN_WORD",
      "position" : 1
    },
    {
      "token" : "广场",
      "start_offset" : 4,
      "end_offset" : 6,
      "type" : "CN_WORD",
      "position" : 2
    },
    {
      "token" : "麻辣烫",
      "start_offset" : 6,
      "end_offset" : 9,
      "type" : "CN_WORD",
      "position" : 3
    }
  ]
}

2.5. ik自定义词库

自定义扩展词库步骤如下:

一、创建配词库文件,以dic作为扩展名

例如:

词库文件:{ES安装目录}/analysis-ik/config/demo.dic

上海大学
复旦大学
人民广场

一行一个词条即可

提示:config目录不存在创建一个即可。

二、创建或者修改配置文件

配置文件路径:{ES安装目录}/analysis-ik/config/IKAnalyzer.cfg.xml

IKAnalyzer.cfg.xml配置文件不存在,就创建一个。

配置文件内容:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE properties SYSTEM "http://java.sun.com/dtd/properties.dtd">
<properties>
	<comment>IK Analyzer 扩展配置</comment>
	<!--用户可以在这里配置自己的扩展字典 -->
	<entry key="ext_dict">{ES安装目录}/analysis-ik/config/demo.dic</entry>
	 <!--用户可以在这里配置自己的扩展停止词字典,没有可用删掉配置-->
	<entry key="ext_stopwords">custom/ext_stopword.dic</entry>
 	<!--用户可以在这里配置远程扩展字典,这个配置需要结合下面配置一起使用,没有可用删掉配置 -->
	<entry key="remote_ext_dict">location</entry>
 	<!--用户可以在这里配置远程扩展停止词字典,没有可用删掉-->
	<entry key="remote_ext_stopwords">http://xxx.com/xxx.dic</entry>
</properties>

三、重启ES即可

提示:Ik新增扩展词库,支持热更新,不用重启ES,使用remote_ext_dict和remote_ext_stopwords配置远程词库地址即可,词库地址需要返回两个头部(header),一个是 Last-Modified,一个是 ETag,ES靠这两个头识别是否需要更新词库,不了解这两个HTTP头,可以搜一下。