“ElasticSearch查询:全文搜索”的版本间差异
		
		
		
		
		
		跳到导航
		跳到搜索
		
				
		
		
	
 (建立内容为“category:ElasticSearch  == 关于 ==  <syntaxhighlight lang="java" highlight="">  </syntaxhighlight>”的新页面)  | 
				无编辑摘要  | 
				||
| 第2行: | 第2行: | ||
== 关于 ==  | == 关于 ==  | ||
<pre>  | |||
全文搜索是 ES 的关键特性之一,平时我们使用 SQL 的 like 语句,搜索一些文本、字符串是否包含指定的关键词。  | |||
<  | 但是如果两篇文章,都包含我们的关键词,具体那篇文章内容的相关度更高? 这是 SQL 的 like 语句是做不到的,更别说 like 语句的性能问题了。  | ||
</pre>  | |||
ES 通过'''分词处理'''、'''相关度计算'''可以解决这个问题。  | |||
ES 内置了一些相关度算法,例如:'''TF/IDF 算法''',大体上思想就是:如果一个关键词在一篇文章出现的频率高,并且在其他文章中出现的少,那说明这个关键词与这篇文章的相关度很高。  | |||
=== 与 SQL 的差异 ===  | |||
SQL 中也有全文索引(通过倒排索引),也有 like 进行模糊查询。  | |||
但是:  | |||
# 如上所述,对于同一个关键词,两个 text 记录的相关度是 SQL 不能判断的;  | |||
# SQL 的性能问题:仅使用服务器认为代价最低的那个索引。所以不能保证全部条件都能够用到索引来优化。  | |||
=== 分词的目的 ===  | |||
主要就是为了'''提取搜索关键词''',理解搜索的意图。  | |||
在进行搜索内容的时候,输入的内容可能很长,但不是每个字都对搜索有帮助,所以通过分词算法,输入的关键词会进一步分解成多个关键词。  | |||
: 例如:搜索输入“上海陆家嘴在哪里?”,分词算法可能分解出:“上海、陆家嘴、哪里”,'''具体会分解出什么关键词,跟具体的分词算法有关'''。  | |||
== 默认全文搜索 ==  | |||
默认情况下,使用全文搜索很简单,只要将字段类型定义为 '''text''' 类型,然后用 '''match''' 语句匹配即可。  | |||
* ES 对于 text 类型的字段,在插入数据的时候,会进行分词处理,然后根据分词的结果索引文档。当我们搜索 text 类型字段的时候,也会先对搜索关键词进行分词处理、然后根据分词的结果去搜索。  | |||
* ES 默认的分词器是 '''standard'''。  | |||
** 对英文比较友好,例如:“hello world”会被分解成“hello”和“world”;  | |||
** 但如果是中文会全部分解成单字,例如:“上海大学”会分解成: “上”、“海”、“大”、“学”。  | |||
语法:  | |||
<syntaxhighlight lang="JSON" highlight="">  | |||
GET /_analyze  | |||
{  | |||
   "text": "需要分词的内容",  | |||
   "analyzer": "分词器"  | |||
}  | |||
</syntaxhighlight>  | |||
示例 1:  | |||
<syntaxhighlight lang="JSON" highlight="">  | |||
GET /_analyze  | |||
{  | |||
  "text": "hello wolrd",  | |||
  "analyzer": "standard"  | |||
}  | |||
</syntaxhighlight>  | |||
输出:  | |||
<syntaxhighlight lang="JSON" highlight="">  | |||
{  | |||
  "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  | |||
    }  | |||
  ]  | |||
}  | |||
</syntaxhighlight>  | |||
* '''token''' 就是分解出来的关键词。  | |||
示例 2:  | |||
<syntaxhighlight lang="JSON" highlight="">  | |||
GET /_analyze  | |||
{  | |||
  "text": "上海大学",  | |||
  "analyzer": "standard"  | |||
}  | |||
</syntaxhighlight>  | |||
输出:  | |||
<syntaxhighlight lang="JSON" highlight="">  | |||
{  | |||
  "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  | |||
    }  | |||
  ]  | |||
}  | |||
</syntaxhighlight>  | |||
== 中文分词器 ==  | |||
不过 ES 支持安装'''分词插件''',增加新的分词器。  | |||
目前中文分词器比较常用的有:'''smartcn''' 和 '''ik''' 两种。  | |||
=== 指定 analyzer ===  | |||
默认的分词器不满足需要,可以在'''定义索引映射的时候,指定 text 字段的分词器'''(analyzer)。  | |||
示例:  | |||
<syntaxhighlight lang="JSON" highlight="">  | |||
PUT /article  | |||
{  | |||
  "mappings": {  | |||
    "properties": {  | |||
      "title":   {   | |||
          "type": "text",  | |||
          "analyzer": "smartcn"  | |||
      }  | |||
    }  | |||
  }  | |||
}  | |||
</syntaxhighlight>  | |||
=== smartcn 分词器 ===  | |||
smartcn 是目前 ES 官方推荐的中文分词插件,不过'''目前不支持自定义词库'''。  | |||
* smartcn 的分词器名字就叫做:'''smartcn'''  | |||
插件安装:  | |||
<syntaxhighlight lang="bash" highlight="">  | |||
{ES安装目录}/bin/elasticsearch-plugin install analysis-smartcn  | |||
</syntaxhighlight>  | </syntaxhighlight>  | ||
* 安装完成后,重启 ES 即可。  | |||
示例:  | |||
<syntaxhighlight lang="JSON" highlight="">  | |||
GET /_analyze  | |||
{  | |||
  "text": "红烧牛肉面",  | |||
  "analyzer": "smartcn"  | |||
}  | |||
</syntaxhighlight>  | |||
输出:  | |||
<syntaxhighlight lang="JSON" highlight="">  | |||
{  | |||
  "tokens" : [  | |||
    {  | |||
      "token" : "红烧",  | |||
      "start_offset" : 0,  | |||
      "end_offset" : 2,  | |||
      "type" : "word",  | |||
      "position" : 0  | |||
    },  | |||
    {  | |||
      "token" : "牛肉面",  | |||
      "start_offset" : 2,  | |||
      "end_offset" : 5,  | |||
      "type" : "word",  | |||
      "position" : 1  | |||
    }  | |||
  ]  | |||
}  | |||
</syntaxhighlight>  | |||
=== ik 分词器 ===  | |||
ik 支持'''自定义扩展词库''':有时候分词的结果不满足我们业务需要,需要根据业务设置专门的词库,词库的作用就是自定义一批关键词,分词的时候优先根据词库设置的关键词分割内容。  | |||
: 例如:词库中包含 “上海大学” 关键词,如果对“上海大学在哪里?”进行分词,“上海大学” 会做为一个整体被切割出来。  | |||
ik 分词插件支持两种分词器:  | |||
# '''ik_smart''':粗粒度的分词;  | |||
# '''ik_max_word''':尽可能的枚举可能的关键词;  | |||
插件安装:  | |||
# 找到自己 ES 版本一致的插件:[https://github.com/medcl/elasticsearch-analysis-ik/releases 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 https://github.com/medcl/elasticsearch-analysis-ik/releases/download/v7.5.1/elasticsearch-analysis-ik-7.5.1.zip]  | |||
# 安装命令:  | |||
<syntaxhighlight lang="bash" highlight="">  | |||
{ES安装目录}/bin/elasticsearch-plugin install https://github.com/medcl/elasticsearch-analysis-ik/releases/download/v7.5.1/elasticsearch-analysis-ik-7.5.1.zip  | |||
</syntaxhighlight>  | |||
* 安装完成后,重启 ES 即可。  | |||
示例 1:  | |||
<syntaxhighlight lang="JSON" highlight="">  | |||
GET /_analyze  | |||
{  | |||
  "text": "上海人民广场麻辣烫",  | |||
  "analyzer": "ik_smart"  | |||
}  | |||
</syntaxhighlight>  | |||
输出:  | |||
<syntaxhighlight lang="JSON" highlight="">  | |||
{  | |||
  "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  | |||
    }  | |||
  ]  | |||
}  | |||
</syntaxhighlight>  | |||
示例 2:  | |||
<syntaxhighlight lang="JSON" highlight="">  | |||
GET /_analyze  | |||
{  | |||
  "text": "上海人民广场麻辣烫",  | |||
  "analyzer": "ik_max_word"  | |||
}  | |||
</syntaxhighlight>  | |||
输出:  | |||
<syntaxhighlight lang="JSON" highlight="">  | |||
{  | |||
  "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  | |||
    }  | |||
  ]  | |||
}  | |||
</syntaxhighlight>  | |||
=== ik 自定义词库 ===  | |||
自定义扩展词库步骤如下:  | |||
# '''创建词库文件''',以 '''dic''' 作为扩展名:  | |||
#* '''一行一个词条'''。  | |||
#: 如,词库文件:<code>{ES安装目录}/analysis-ik/config/demo.dic</code>  | |||
#: <syntaxhighlight lang="xml" highlight="">  | |||
上海大学  | |||
复旦大学  | |||
人民广场  | |||
</syntaxhighlight>  | |||
# '''创建或者修改配置文件''':  | |||
#* 路径:<code>{ES安装目录}/analysis-ik/config/IKAnalyzer.cfg.xml</code>  | |||
#* 内容:  | |||
#*: <syntaxhighlight lang="xml" highlight="">  | |||
<?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>  | |||
</syntaxhighlight>  | |||
# 重启 ES。  | |||
* Ik 新增扩展词库,支持'''热更新''',不用重启 ES,使用 '''remote_ext_dict''' 和 '''remote_ext_stopwords''' 配置远程词库地址即可。  | |||
** 词库地址需要返回两个头部(header),一个是 Last-Modified,一个是 ETag,ES 靠这两个头识别是否需要更新词库,不了解这两个 HTTP 头,可以搜一下。【???】  | |||
2021年5月21日 (五) 00:07的版本
关于
全文搜索是 ES 的关键特性之一,平时我们使用 SQL 的 like 语句,搜索一些文本、字符串是否包含指定的关键词。 但是如果两篇文章,都包含我们的关键词,具体那篇文章内容的相关度更高? 这是 SQL 的 like 语句是做不到的,更别说 like 语句的性能问题了。
ES 通过分词处理、相关度计算可以解决这个问题。
ES 内置了一些相关度算法,例如:TF/IDF 算法,大体上思想就是:如果一个关键词在一篇文章出现的频率高,并且在其他文章中出现的少,那说明这个关键词与这篇文章的相关度很高。
与 SQL 的差异
SQL 中也有全文索引(通过倒排索引),也有 like 进行模糊查询。
但是:
- 如上所述,对于同一个关键词,两个 text 记录的相关度是 SQL 不能判断的;
 - SQL 的性能问题:仅使用服务器认为代价最低的那个索引。所以不能保证全部条件都能够用到索引来优化。
 
分词的目的
主要就是为了提取搜索关键词,理解搜索的意图。
在进行搜索内容的时候,输入的内容可能很长,但不是每个字都对搜索有帮助,所以通过分词算法,输入的关键词会进一步分解成多个关键词。
- 例如:搜索输入“上海陆家嘴在哪里?”,分词算法可能分解出:“上海、陆家嘴、哪里”,具体会分解出什么关键词,跟具体的分词算法有关。
 
默认全文搜索
默认情况下,使用全文搜索很简单,只要将字段类型定义为 text 类型,然后用 match 语句匹配即可。
- ES 对于 text 类型的字段,在插入数据的时候,会进行分词处理,然后根据分词的结果索引文档。当我们搜索 text 类型字段的时候,也会先对搜索关键词进行分词处理、然后根据分词的结果去搜索。
 - ES 默认的分词器是 standard。
- 对英文比较友好,例如:“hello world”会被分解成“hello”和“world”;
 - 但如果是中文会全部分解成单字,例如:“上海大学”会分解成: “上”、“海”、“大”、“学”。
 
 
语法:
GET /_analyze
{
   "text": "需要分词的内容",
   "analyzer": "分词器"
}
示例 1:
GET /_analyze
{
  "text": "hello wolrd",
  "analyzer": "standard"
}
输出:
{
  "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 就是分解出来的关键词。
 
示例 2:
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
    }
  ]
}
中文分词器
不过 ES 支持安装分词插件,增加新的分词器。
目前中文分词器比较常用的有:smartcn 和 ik 两种。
指定 analyzer
默认的分词器不满足需要,可以在定义索引映射的时候,指定 text 字段的分词器(analyzer)。
示例:
PUT /article
{
  "mappings": {
    "properties": {
      "title":   { 
          "type": "text",
          "analyzer": "smartcn"
      }
    }
  }
}
smartcn 分词器
smartcn 是目前 ES 官方推荐的中文分词插件,不过目前不支持自定义词库。
- smartcn 的分词器名字就叫做:smartcn
 
插件安装:
{ES安装目录}/bin/elasticsearch-plugin install analysis-smartcn
- 安装完成后,重启 ES 即可。
 
示例:
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
    }
  ]
}
ik 分词器
ik 支持自定义扩展词库:有时候分词的结果不满足我们业务需要,需要根据业务设置专门的词库,词库的作用就是自定义一批关键词,分词的时候优先根据词库设置的关键词分割内容。
- 例如:词库中包含 “上海大学” 关键词,如果对“上海大学在哪里?”进行分词,“上海大学” 会做为一个整体被切割出来。
 
ik 分词插件支持两种分词器:
- ik_smart:粗粒度的分词;
 - ik_max_word:尽可能的枚举可能的关键词;
 
插件安装:
- 找到自己 ES 版本一致的插件:https://github.com/medcl/elasticsearch-analysis-ik/releases
 - 安装命令:
 
{ES安装目录}/bin/elasticsearch-plugin install https://github.com/medcl/elasticsearch-analysis-ik/releases/download/v7.5.1/elasticsearch-analysis-ik-7.5.1.zip
- 安装完成后,重启 ES 即可。
 
示例 1:
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:
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
    }
  ]
}
ik 自定义词库
自定义扩展词库步骤如下:
- 创建词库文件,以 dic 作为扩展名:
- 一行一个词条。
 
- 如,词库文件:
{ES安装目录}/analysis-ik/config/demo.dic 上海大学 复旦大学 人民广场
 - 创建或者修改配置文件:
- 路径:
{ES安装目录}/analysis-ik/config/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 头,可以搜一下。【???】