概述
搜索引擎应该同时支持精确搜索与全文搜索两种模式。精确搜索是指当文档指定字段的内容与关键字完全相等时,才会将文档作为结果返回;全文搜索的意思是只要文档中的内容包含关键字或近似于关键字的含义,这些文档都会按照与关键字的相关程度排序进行输出。
ES的全文搜索和精确搜索都是通过对索引设置不同的映射和分析来完成的,当我们创建一个索引时,同时应该制定这个索引相关的映射字段属性与分析方法。如果同一个索引的映射与分析的方式不同,执行搜索的结果可能大相径庭。
说明
请求说明
本系列文章对ES的REST请求都使用如下例的格式进行描述:
PUT http://ubuntu:9200/page <body> { "mappings":{ "books": { "properties": { "name":{ "type":"string" } } } } }
PUT代表请求方法,http://ubuntu:9200/page是请求地址,<body>
之后的内容代表HTTP请求体。对于带有请求体的请求,请求头信息中Content-Type
属性的值都是application/json
,另外编码方式统一采用UTF-8。
响应说明
本系列文章对REST响应信息都是用如下例的格式进行描述:
<code>200 ok <body> {"page":{"aliases":{}, "mappings":{"books":{"properties":{"name":{"type": "text"…}
<code>
后是本次请求的状态码 如200,ok代表的是状态。 <body>
后带表的是响应体,如有必要响应body体代码中会加注释说明。为了简洁起见,只会对必要的请求贴出响应信息。另外文章忽略了所有的响应头信息,这些被忽略的代码如下:
content-type: application/json; charset=UTF-8 content-encoding: gzip transfer-encoding: chunked
特殊字符
文章中可能会出现部分特殊字符,现对这些字符做出以下解释:
- {a} 代表a变量,如{host}代表主机名或ip地址,{index}代表索引名
- [] 代表可出现也可不出现,一般情况下带有[]标识的变量出现和不出现会有不同的含义。对于URL开头的[http[s]://]表达式,如果省略整个表达式则系统会默认使用http协议。
- ... 对返回结果或请求内容进行部分省略。
若有未尽之处,请留言。
问题
在搜索引擎的实际应用中,我们对这两种搜索模式都有强烈的需求。以我们的blogs索引为例,当我们需要按照文章的标题查找文章,此时对文章标题进行全文索引,找出标题中包含关键字,或者与关键字意思相近的文章更为合适。若是我们需要搜索点击量大于100,或者文章发布时间为2017年8月10日之后的文章,全文搜索就完全不能满足我们的需求了。比如一篇文章的点击量为1000,按照模糊匹配的原则它能够被搜索引擎检索到。但若其点击量为200,这个值明显大于100,但其中并未包含任何关于100的信息,这篇文章无法被搜索引擎检索到。
或许你已经发现了,到现在为止我们对每个字段所存储的数据类型都是string
类型,如果我们能将点击量存为int
这个问题就迎刃而解。我们已经知道,ES能够存储复杂结构的数据,而对于不同的数据类型的存储,恰好就是我们要探索的第一个问题-映射。
第二个问题是索引分析,所谓分析就是使用什么样的规则在在索引时对一句话分词,解析其含义。
方法
对于映射部分,文章会详细的分析映射和数据类型的理论,最后实验演示如何在ES中配置映射,这一部分也是后续文章的基础。
对于分析部分,文章会简要的概括ES所采用的分析方法,以及采用不同的分析对搜索结果的影响。文章会通过举例来说明ES是如何进行自然语言分析并建立索引。
映射
概念
索引中每个文档都有类型,每个类型都有其自己的映射,映射就是这个类型的模式定义。类型的映射定义了这个类型中所有的域,域的数据类型,以及对这个域的分析方法等元数据。
基本数据类型
ES中的映射描述了文档可能具有的字段或属性、每个字段的数据类型—比如 string, integer 或 date,下面是ES里的基本数据类型。
- 字符串: string
- 整数 : byte, short, integer, long
- 浮点数: float, double
- 布尔型: boolean
- 日期: date
当索引一个包含新域的文档时,ES会根据json的类型自动推断该域应该存储为哪种数据类型。例如'2017-09-21'
这个字符串,因其符合yyyy-MM-dd
的规则,其所在的域就会被自动映射为date类型。
类型原理
类型可以很好的抽象划分相似但是不相同的数据,所以ES中规定一个文档包括一个或多个字段,任何字段都有自己的数据类型。
而作为ES的底层Lucene认为,文档由简单的键值对组成。每个字段可以拥有多个值,但至少有一个值。所有类型的值都会被看做不透明的字节(Opaque bytes)。而在建立索引时,根据映射中规定的不同的分析办法,既可以根据全文索引的分词存在索引里,也可以直接将原始值存储。Lucene 没有文档类型的概念,每个文档的类型名被存储在一个叫 _type 的元数据字段上。 当我们要检索某个类型的文档时, Elasticsearch 通过在 _type 字段上使用过滤器限制只返回这个类型的文档。
类型可以很好的区分同一个集合中的不同细分。在不同的细分中数据的整体模式是相同的(或相似的)。如索引人
下可以分别有男人
、女人
类型。不过类型不适合完全不同类型的数据。 。如果两个类型的字段集是互不相同的,这就意味着索引中将有一半的数据是空的(字段将是 稀疏的 ),最终将导致性能问题。在这种情况下,最好是使用两个单独的索引。如索引人
中如果同时存在一个商品
类型,因为其本身与另外两个类型毫无关系,会造成很大的空间浪费。
简单的映射
在实例开始之前我们先看一下什么是映射,下面代码所示的是page索引的简单映射。
"mappings":{ "books":{//类型(映射)名称 "properties":{//类型所具有的域(属性) "name":{//域名称 "type": "text" //域的数据类型 } } } }
复杂的映射
JSON灵活方便,不但兼容基本数据类型还可以存储null、对象和数组。ES使用JSON存储文档,通过其内部特殊的处理方式也能支持这种复杂对象的存储。
空值
由于Lucene中不能存储空值,所以ES不对空值字段进行索引。ES的空值有两种,一是显式的null所声明的,另一种是该字段上完全没有值。第一种情况ES会使用一个占位符来代替null,第二种情况,es不存储此字段的任何值。
对象
ES中的字段可以是对象类型,在实际索引时,该对象会被转化成内部对象。
{ "user":{ "name" : "weiwei" }, "sample" : "a" }
在实际索引时,文档会被转化成如下所示的内容。
{ "user.name" : "weiwei", "sample" : "a" }
数组
ES中可以存储数组,但要求数组的每个元素的数据类型必须完全相同。不能是一个元素是string而另一个元素是int。数组如果没有元素就是一个空值。
作用
通过上面的介绍我们大致这么理解映射:
- 映射(动词),将具体的根系方法和数据类型与文档的数据结构进行关联。
- 映射(名字),文档的数据结构的模式定义。
建立合理的映射的重要性就如我们在RDBMS中建立表结构,其设计的好坏会直接影响到搜索的结果与性能。所幸ES不像RDBMS那么死板,可以支持我们在索引中有数据之后修改映射。
实例
本文的实例将从建立一个全新的索引page开始,逐步演示完善索引与映射信息。
数据类型映射
数据类型映射小节只会对类型(映射)的域与数据类型的对应关系进行探讨分析,所涉及的知识包括从映射的创建、修改、查看至使用和常见错误方面。
创建映射
创建索引的同时创建映射 下面的请求创建一个索引page,并包含类型books。books有一个name属性,其数据类型为string。
请求:
PUT http://ubuntu:9200/page <body> { "mappings":{ "books": { "properties": { "name":{ "type":"string" } } } } }
查看映射
- 语法
通过_mapping
可以查看一个或多个索引中的一个或多个映射,其语法如下。
GET http[s]://{host}:{port}/[{index}[,{index2}...]]/_mapping/[{type}[,{type2}...]]
本接口可以支持查询多个索引的多个映射,中间使用,隔开,支持_all
来表示查看所有索引或映射。
- 示例 查看a索引下的healthCareAndEducation类型的映射,该映射是从国家统计局获取的地区医疗和教育建设统计。 请求:
GET http://ubuntu:9200/a/_mapping/healthCareAndEducation
响应:
<code>200 ok <body> { "a":{ "mappings":{ "healthCareAndEducation":{ "properties":{ "cinema_num":{"type": "long"}, "city":{"type": "keyword"}, "doctor_num":{"type": "float"}, "from":{ "type": "text", "fields":{ "keyword":{ "type": "keyword", "ignore_above": 256 } } }, "high_level_school_num":{"type": "float"}, "hospital":{"type": "float"}, "year":{"type": "integer"} } } } } }
- 更多 除了可以查看整个索引或类型的映射,还可以通过以下语法查看某一字段的映射。
GET http[s]://{host}:{port}/{index}/{type}/_mapping/field/{field}
{index},{type},{field}可以使用 , 分割,也可以使用通配符。 示例如下:
GET http://ubuntu:9200/a/healthCareAndEducation/_mapping/field/from
自定义映射
字段的映射有很多奇妙的属性,利用这些属性我们可以指定字段的数据类型,索引方式等。
type
如果存储的字段不是string类型,一般只需简要的指明其 type
属性,如果该字段是一个string类型,为了更好的支持全文索引,一般需要对该字段的映射进行自定义配置。
{ "cinema_num":{"type": "long"} }
如上面的 cinema_num 字段就是一个简单的long类型的字段。
index
index属性声明如何索引分析字符串,它常用的三个值的含义分别如下:
- analyzed
首先分析字符串,然后索引它。换句话说,以全文索引这个域。string类型的字段默认的index属性就是analyzed。 - not_analyzed 索引这个域,所以它能够被搜索,但索引的是精确值。不会对它进行分析。
- no
不索引这个域。这个域不会被搜索到。
其它类型的字段也可以有index属性,但不会有任何效果。
- 示例 我们需要医疗和教育建设统计中的city是一个精确值,可以将索引的映射修改为下:
"city" : { "type" : "string", "index" : "not_analyzed" }
analyzer
analyzer属性用于为类型为字符串、index属性Wieanalyzed的字段执行在搜索和索引时所使用的分词器。默认使用 standard 分析器,另外还有 whitespace 、 simple 和 english 、chinese等语言分词器。但es自带的中文分词器很不好用,在实际使用时可以考虑使用ik等第三方中文分词器。
更新映射
如果要向索引类型中添加新字段,可以直接对映射进行更新。否则当更新的数据类型产生冲突时,ES会抛出异常信息。
- 语法
PUT http[s]://{host}:{port}/{index}/_mapping/{type} <body> { "properties" : { "from" : { "type" : "string", "index" : "analyzed", "analyzer" : "chinese" } } }
其中from为要修改或新增的字段。
- 错误示例 请求
PUT http://ubuntu:9200/a/_mapping/healthCareAndEducation <body> { "properties" : { "from" : { "type" : "string", "index" : "analyzed", "analyzer" : "chinese" } } }
以上请求会得到下面的结果:
{ "error":{ "root_cause":[ { "type": "illegal_argument_exception", "reason": "Mapper for [from] conflicts with existing mapping in other types:\n[mapper [from] has different [analyzer]]" } ], "type": "illegal_argument_exception", "reason": "Mapper for [from] conflicts with existing mapping in other types:\n[mapper [from] has different [analyzer]]" }, "status": 400 }
这是由于新 from 字段和以前的from字段采用了不同的分词器,在此产生了冲突,故而会抛出illegal_argument_exception
异常。如果一定要不计类型冲突修改索引的话,可以通过重建索引的方式来解决。
- 示例 请求
PUT http://ubuntu:9200/a/_mapping/healthCareAndEducation <body> { "properties" : { "city" : { "type" : "string", "index" : "not_analyzed" } } }
结果
{"acknowledged": true}
引用
本文是我在学习使用ES时的笔记,在本文的写过过程中参考了大量其它资料,有些材料来源于网络,我由衷的表示感谢,但由于原作者不明,恕不能一一记述。
- Elasticsearch 权威指南.——https://www.elastic.co/
- Elasticsearch技术解析与实战/朱林编著.——北京:机械工业出版社,2016.12(数据分析与决策技术丛书)
关于
本项目和文档中所用的内容仅供学习和研究之用,转载或引用时请指明出处。如果你对文档有疑问或问题,请在项目中给我留言或发email到 weiwei02@vip.qq.com 我的github: https://github.com/weiwei02/ 我相信技术能够改变世界 。
链接
- 上篇文章ElasticSearch2轻量级搜索
- 下篇文章(待更新)