一、索引操作
1.1 创建索引
变量index_name就是创建的目标索引名称;可以在settings子句内部填写索引相关的设置项,如主分片个数和副分片个数等;可以在mappings子句内部填写数据组织结构,即数据映射。在第1章中曾介绍过创建索引hotel的语句,但是当时的主分片个数使用的是系统默认值(默认值为5),并且没有使用副分片个数(默认值为0)。假设设置主分片个数为15,副分片个数为2,则相应的DSL如下:
1.2 删除索引
1.3 关闭索引
在有些场景下,某个索引暂时不使用,但是后期可能又会使用,这里的使用是指数据写入和数据搜索。这个索引在某一时间段内属于冷数据或者归档数据,这时可以使用索引的关闭功能。索引关闭时,只能通过ES的API或者监控工具看到索引的元数据信息,但是此时该索引不能写入和搜索数据,待该索引被打开后,才能写入和搜索数据。
根据上面的信息可知,索引关闭时写入数据将会报错。印证了索引在关闭时不能提供搜索服务的规定。
1.4 打开索引
1.5 索引别名
二、映射操作
在使用数据之前,需要构建数据的组织结构。这种组织结构在关系型数据库中叫作表结构,在ES中叫作映射。
2.1 查看映射
在ES中写入文档请求的类型是GET,其请求形式如下:
2.2 扩展映射
映射中的字段类型是不可以修改的,但是字段可以扩展。最常见的扩展方式是增加字段和为object(对象)类型的数据新增属性。下面的DSL示例为扩展hotel索引,并增加tag字段。
三、文档操作
3.1 单条写入文档
由以上结果可知,向hotel索引中写入文档成功。另外,ES在返回结果中还会显示文档的版本,这里因为文档刚刚建立,所以当前值为1。当然,用户也可以不指定文档_id,该_id值将由ES自动生成,其请求形式如下:
3.2 批量写入文档
在ES中批量写入文档请求的类型是POST,其请求形式如下:
请求体的第一行表示写入的第一条文档对应的元数据,其中,index_name表示写入的目标索引,第2行表示数据体,第3行表示写入的第二条文档对应的元数据,第4行表示数据体。以此类推,在一次请求里可以写入多条数据。下面将向hotel索引中批量写入两条酒店数据:
上面的DSL写入索引中的文档_id是ES自动生成的。如果需要指定_id,则应该在元数据中添加_id。例如,下面的DSL将向酒店索引中添加文档_id为001和002两条文档:
3.3 更新单条文档
在ES中更新索引的请求类型是POST,其请求形式如下:
上面的_id就是将要修改的ES文档中的_id,修改后的字段和值将会填写到大括号中,其格式是JSON形式。例如把_id为001的文档修改成下面的数据:
通过结果可知,已经成功更新文档信息,并且本次修改后文档的版本变为2。下面根据_id搜索文档的命令进行验证:
upsert
除了普通的update功能,ES还提供了upsert。upsert即是update和insert的合体字,表示更新/插入数据。如果目标文档存在,则执行更新逻辑;否则执行插入逻辑。以下DSL演示了upsert的应用:
执行以上DSL后,如果文档001存在,则执行更新逻辑,将doc内容更新到文档中;否则执行插入逻辑,将upsert的内容写入文档中。
3.4 批量更新文档
与批量写入文档相似,批量更新文档的请求形式如下:
注意,与批量写入文档不同的是,批量更新文档必须在元数据中填写需要更新的文档_id。下面的DSL将批量更新_id为001和002的文档:
3.5 根据条件更新文档
在索引数据的更新操作中,有些场景需要根据某些条件同时更新多条数据,类似于在RDBMS中使用update table table_name set…where…更新一批数据。为了满足这样的需求,ES为用户提供了_update_by_query功能,其请求形式如下:
执行以上DSL后,ES将先搜索城市为“北京”的酒店,然后把这些酒店的城市字段的值改为“上海”。
如果更新所有文档中的某个字段应该如何操作呢?其实,_update_by_query中的query子句可以不定义,这种情况下ES会选中所有的文档执行script中的内容。以下为修改所有酒店中城市为“上海”的DSL:
3.6 删除单条文档
在ES中删除文档的请求的类型是DELETE,其请求形式如下:
3.7 批量删除文档
例如,下面的DSL将批量删除_id为001和002的文档:
3.8 根据条件删除文档
_delete_by_query
和条件更新操作类似,有些场景需要根据某些条件同时删除多条数据,类似于在RDBMS中使用delete table_name where…删除一批数据。为了满足这样的需求,ES为用户提供了_delete_by_query功能,其请求形式如下:
四、基础搜索操作
4.1 搜索辅助功能
4.1.1 指定返回字段
在ES中,通过_source子句可以设定返回结果的字段。_source指向一个JSON数组,数组中的元素是希望返回的字段名称。定义酒店索引的结构如下:
为方便演示,向酒店索引中新增如下数据:下面的DSL指定搜索结果只返回title和city字段:
4.1.2 结果计数
ES提供了_count API功能,在该API中,用户提供query子句用于结果匹配,ES会返回匹配的文档条数。下面的DSL将返回城市为“北京”的酒店个数:
由结果可知,ES不仅返回了匹配的文档数量(值为3),并且还返回了和分片相关的元数据,如总共扫描的分片个数,以及成功、失败、跳过的分片个数等。
4.1.3 结果分页
在默认情况下,ES返回前10个搜索匹配的文档。用户可以通过设置from和size来定义搜索位置和每页显示的文档数量,from表示查询结果的起始下标,默认值为0,size表示从起始下标开始返回的文档个数,默认值为10。下面的DSL将返回下标从0开始的20个结果。
在默认情况下,用户最多可以取得10 000个文档,即from为0时,size参数最大为10 000,如果请求超过该值,ES返回如下报错信息:
对于普通的搜索应用来说,size设为10 000已经足够用了。如果确实需要返回多于10 000条的数据,可以适当修改max_result_window的值。以下示例将hotel索引的最大窗口值修改为了20 000。
注意,如果将配置修改得很大,一定要有足够强大的硬件作为支撑。
作为一个分布式搜索引擎,一个ES索引的数据分布在多个分片中,而这些分片又分配在不同的节点上。一个带有分页的搜索请求往往会跨越多个分片,每个分片必须在内存中构建一个长度为from+size的、按照得分排序的有序队列,用以存储命中的文档。然后这些分片对应的队列数据都会传递给协调节点,协调节点将各个队列的数据进行汇总,需要提供一个长度为number_of_shards*(from+size)的队列用以进行全局排序,然后再按照用户的请求从from位置开始查找,找到size个文档后进行返回。
基于上述原理,ES不适合深翻页。什么是深翻页呢?简而言之就是请求的from值很大。假设在一个3个分片的索引中进行搜索请求,参数from和size的值分别为1000和10,其响应过程如图4.1所示。
当深翻页的请求过多时会增加各个分片所在节点的内存和CPU消耗。尤其是协调节点,随着页码的增加和并发请求的增多,该节点需要对这些请求涉及的分片数据进行汇总和排序,过多的数据会导致协调节点资源耗尽而停止服务。
4.1.4 性能分析
在使用ES的过程中,有的搜索请求的响应可能比较慢,其中大部分的原因是DSL的执行逻辑有问题。ES提供了profile功能,该功能详细地列出了搜索时每一个步骤的耗时,可以帮助用户对DSL的性能进行剖析。开启profile功能只需要在一个正常的搜索请求的DSL中添加"profile":"true"即可。以下查询将开启profile功能:
执行以上DSL后ES返回了一段比较冗长的信息,下面是省略一些信息的返回数据。
如上所示,在带有profile的返回信息中,除了包含搜索结果外,还包含profile子句,在该子句中展示了搜索过程中各个环节的名称及耗时情况。需要注意的是,使用profile功能是有资源损耗的,建议用户只在前期调试的时候使用该功能,在生产中不要开启profile功能。
4.1.5 评分分析
_explain
在使用搜索引擎时,一般都会涉及排序功能。如果用户不指定按照某个字段进行升序或者降序排列,那么ES会使用自己的打分算法对文档进行排序。有时我们需要知道某个文档具体的打分详情,以便于对搜索DSL问题展开排查。ES提供了explain功能来帮助使用者查看搜索时的匹配详情。explain的使用形式如下:
以下示例为按照标题进行搜索的explain查询请求:
执行上述explain查询请求后,ES返回的信息如下:
4.2 搜索匹配功能
针对不同的数据类型,ES提供了很多搜索匹配功能:既有进行完全匹配的term搜索,也有按照范围匹配的range搜索;既有进行分词匹配的match搜索,也有按照前缀匹配的suggest搜索。
4.2.1 查询所有文档
的match_all查询可以完成类似的功能。使用match_all查询文档时,ES不对文档进行打分计算,默认情况下给每个文档赋予1.0的得分。用户可以通过boost参数设定该分值。以下示例使用match_all查询所有文档,并设定所有文档的分值为2.0:
4.2.2 term级别查询
1.term查询
term查询是结构化精准查询的主要查询方式,用于查询待查字段和查询值是否完全匹配,其请求形式如下:
其中,FIELD和VALUE分别代表字段名称和查询值,FIELD的数据类型可以是数值型、布尔型、日期型、数组型及关键字等
2.terms查询
terms查询是term查询的扩展形式,用于查询一个或多个值与待查字段是否完全匹配,其请求形式如下:
以下是使用terms查询城市为“北京”或“天津”
的文档
3.range查询
range查询用于范围查询,一般是对数值型和日期型数据的查询。使用range进行范围查询时,用户可以按照需求中是否包含边界数值进行选项设置,
可供组合的选项如下:·
gt:大于;
·lt:小于;
·gte:大于或等于;
·lte:小于或等于。
以下是数值类型的查询示例,查询住宿价格在300~500(包含边界值)元的酒店:
4.exists查询
在某些场景下,我们希望找到某个字段不为空的文档,则可以用exists搜索。
字段不为空的条件有:·值存在且不是null;·
值不是空数组;·值是数组,但不是[null]。
为方便测试,创建索引hotel_1,DSL如下:
4.2.3 布尔查询
复合搜索,顾名思义是一种在一个搜索语句中包含一种或多种搜索子句的搜索。布尔查询是常用的复合查询,它把多个子查询组合成一个布尔表达式,这些子查询之间的逻辑关系是“与”,即所有子查询的结果都为true时布尔查询的结果才为真。布尔查询还可以按照各个子查询的具体匹配程度对文档进行打分计算。
布尔查询支持的子查询有四种,各子查询的名称和功能如表4.1所示。
1.must查询
当查询中包含must查询时,相当于逻辑查询中的“与”查询。命中的文档必须匹配该子查询的结果,并且ES会将该子查询与文档的匹配程度值加入总得分里。must搜索包含一个数组,可以把其他的term级别的查询及布尔查询放入其中。
以下示例使用must查询城市为北京并且价格在350~400元的酒店:
2.should查询
当查询中包含should查询时,表示当前查询为“或”查询。命中的文档可以匹配该查询中的一个或多个子查询的结果,并且ES会将该查询与文档的匹配程度加入总得分里。should查询包含一个数组,可以把其他的term级别的查询及布尔查询放入其中。
以下示例使用should查询城市为北京或者天津的酒店。
3.must not查询
当查询中包含must not查询时,表示当前查询为“非”查询。命中的文档不能匹配该查询中的一个或多个子查询的结果,ES会将该查询与文档的匹配程度加入总得分里。must not查询包含一个数组,可以把其他term级别的查询及布尔查询放入其中。
以下示例中使用must not查询城市不是北京也不是天津的酒店:
4.filter查询
filter查询即过滤查询,该查询是布尔查询里非常独特的一种查询。其他布尔查询关注的是查询条件和文档的匹配程度,并按照匹配程度进行打分;而filter查询关注的是查询条件和文档是否匹配,不进行相关的打分计算,但是会对部分匹配结果进行缓存。
4.2.4 filter查询原理
4.2.5 Constant Score查询
如果不想让检索词频率TF(Term Frequency)对搜索结果排序有影响,只想过滤某个文本字段是否包含某个词,可以使用Constant Score将查询语句包装起来。
假设需要查询amenities字段包含关键词“停车场”的酒店,则请求的DSL如下:
4.2.6 Function Score查询
当使用ES进行搜索时,命中的文档默认按照相关度进行排序。有些场景下用户需要干预该“相关度”,此时就可以使用Function Score查询。使用时,用户必须定义一个查询以及一个或多个函数,这些函数为每个文档计算一个新分数。
下面使用一个随机函数对查询结果进行排序:
上述请求使用了Function Score查询,其中,query子句负责对文档进行匹配,本例使用了简单的term查询,functions子句负责输出对文档的排序分值,此处使用了random_score随机函数,使得每个文档的分数都是随机生成的。每次执行上述查询时生成的文档分数都不同。以下为某次搜索的结果:
4.2.7 全文搜索
1.match查询
对于最基本的math搜索来说,只要分词中的一个或者多个在文档中存在即可。读者可能会有疑问,为什么“金都酒店”被切分成4个字而不是“金都”“酒店”两个词呢?这是因为在默认情况下,match查询使用的是标准分词器。该分词器比较适用于英文,如果是中文则按照字进行切分,因此默认的分词器不适合做中文搜索,在后面的章节中将介绍如何安装和使用中文分词器。
match搜索可以设置operator参数,该参数决定文档按照分词后的词集合进行“与”还是“或”匹配。在默认情况下,该参数的值为“或”关系,即operator的值为or,这也解释了搜索结果中包含部分匹配的文档。如果希望各个词之间的匹配结果是“与”关系,则可以设置operator参数的值为and。
下面的请求示例设置查询词之间的匹配结果为“与”关系:
有时搜索多个关键字,关键词和文档在某一个比例上匹配即可,如果使用“与”操作过于严苛,如果使用“或”操作又过于宽松。这时可以采用minimum_should_match
参数,该参数叫作最小匹配参数,其值为一个数值,意义为可以匹配上的词的个数。在一般情况下将其设置为一个百分数,因为在真实场景中并不能精确控制具体的匹配数量。以下示例设置最小匹配为80%的文档:
2.multi_match查询
有时用户需要在多个字段中查询关键词,除了使用布尔查询封装多个match查询之外,可替代的方案是使用multi_match。可以在multi_match的query子句中组织数据匹配规则,并在fields子句中指定需要搜索的字段列表。
下面的示例在title和amenities两个字段中同时搜索“假日”关键词:
根据结果可以看到,命中的文档要么在title中包含“假日”关键词,要么在amenities字段中包含“假日”关键词。
3.match_phrase查询
match_phrase用于匹配短语,与match查询不同的是,match_phrase用于搜索确切的短语或邻近的词语。假设在酒店标题中搜索“文雅酒店”,希望酒店标题中的“文雅”与“酒店”紧邻并且“文雅”在“酒店”前面,则使用match_phrase查询的DSL如下:
根据上述结果可知,使用match_phrase查询后,只有文档001命中,而文档005(酒店标题为“文雅精选酒店”)没有命中,这是为什么呢?
ES在构建索引时,文档001的title字段被切分为“文雅”“酒店”,文档005的title字段被切分为“文雅”“精选”“酒店”。
使用match_phrase进行查询时,ES将查询文本“精选酒店”切分为“精选”“酒店”,“文雅”匹配时命中了文档001和文档005,但是“酒店”匹配时要求“酒店”必须在“文雅”之后并且索引位置和“文雅”之差为1,而文档001符合匹配要求但是文档005不符合要求。
如果需要文档005也命中上述查询,则可以设置match_phrase查询的slop参数,它用来调节匹配词之间的距离阈值。下面的DSL将slop设置为2:
4.2.8 地理位置查询
对应于geo_point字段类型的查询方式有3种,分别为geo_distance查询、geo_bounding_box查询和geo_polygon。
geo_distance
查询方式需要用户指定一个坐标点,在指定距离该点的范围后,ES即可查询到相应的文档。
假设北京天安门的经纬度为[116.4039,39.915143],以下为使用geo_distance查询所找到的天安门5km范围内的酒店:
geo_shape
查询提供的是矩形内的搜索,需要用户给出左上角的顶点地理坐标和右下角的顶点地理坐标。假设定义国贸商圈为一个矩形,其左上角顶点的经纬度为[116.457044,39.922821],右下角顶点的经纬度为[116.479466,39.907104],则在国贸商圈内搜索酒店的DSL如下:
geo_polygon
比geo_shape提供的地理范围功能更加灵活,它支持多边形内的文档搜索,使用该查询需要提供多边形所有顶点的地理坐标。假设北京地坛公园商圈的地形为三角形,该三角形的三个顶点的经纬度分别为[116.417088,39.959829]、[116.432035,39.960272]和[116.421399,39.965802],则在地坛公园商圈内搜索酒店的DSL语句如下:
4.2.9 搜索建议
搜索建议,顾名思义,即在用户输入搜索关键词的过程中系统进行自动补全,用户可以根据自己的需求单击搜索建议的内容直接进行搜索。在搜索时,用户每输入一个字符,前端就需要向后端发送一次查询请求对匹配项进行查询,因此这种场景对后端响应速度的要求比较高。通过协助用户进行搜索,可以避免用户输入错误的关键词,引导用户使用更合适的关键词,提升用户的搜索体验和搜索效率。
ES中的Completion Suggester
是比较合适的。为了使用CompletionSuggester,其对应的字段类型需要定义为completion
类型。在以下示例中定义了一个酒店搜索建议的索引:
在上述查询中,hotel_zh_sug定义的是搜索建议的名称,prefix定义的是用户输入的关键词,completion.field定义的是搜索建议的候选集对应的字段名称。
执行上述DSL后,ES返回的结果如下:
和普通搜索不同的是,搜索建议的结果不是封装在hits中,而是单独封装在suggest中。在suggest.hotel_zh_sug.options中可以看到每一个候选集的文档信息。
4.3 按字段值排序
4.3.1 普通字段值排序
使用sort
子句对字段值进行排序时需要指定排序的字段。ES默认是按照字段值进行升序排序,可以设置order参数为asc或desc,指定按照字段值进行升序或者降序排序。以下示例为搜索名称包含“金都”的酒店,并对酒店按照价格进行降序排列。
也可以使用sort对搜索结果按照多个字段进行排序。例如,用户可以按照价格进行降序排列,然后再按照口碑值进行降序排列,对应的DSL如下:
4.3.2 按地理距离排序
使用geo_distance查询,配合sort可以指定另一种排序规则,即按照文档坐标与指定坐标的距离对结果进行排序。使用时,需要在sort内部指定排序名称为geo_distanc,并指定目的地坐标。除了可以指定升序或者降序排列外,还可以指定排序结果中sort子句中的距离的计量单位,默认值为km即千米。在进行距离计算时,系统默认使用的算法为arc,该算法的特点是计算精准但是耗费时间较长,用户可以使用distance_type参数选择另一种计算速度快但经度略差的算法,名称为plane。如下示例使用geo_distance查询天安门5km范围内的酒店,并按照距离由近及远进行排序: