elasticsearch 操作实例技巧(三)
一:组合过滤
前面的两个例子展示了单个过滤器的使用。现实中,你可能需要过滤多个值或字段,例如,想在 Elasticsearch 中表达这句 SQL 吗?
SELECT product FROM products WHERE (price = 20 OR productID = "XHDK-A-1293-#fJ3") AND (price != 30)
这些情况下,你需要 bool 过滤器。这是以其他过滤器作为参数的组合过滤器,将它们结合成多种布尔组合
布尔过滤器
bool 过滤器由三部分组成:
{ "bool" : { "must" : [], "should" : [], "must_not" : [], } }
must:所有分句都必须匹配,与 AND 相同。
must_not:所有分句都必须不匹配,与 NOT 相同。
should:至少有一个分句匹配,与 OR 相同。
GET /my_store/products/_search { "query" : { "filtered" : { <1> "filter" : { "bool" : { "should" : [ { "term" : {"price" : 20}}, <2> { "term" : {"productID" : "XHDK-A-1293-#fJ3"}} <2> ], "must_not" : { "term" : {"price" : 30} <3> } } } } } }
<1> 注意我们仍然需要用 filtered 查询来包裹所有条件。
<2> 这两个 term 过滤器是 bool 过滤器的子节点,因为它们被放在 should分句下,所以至少他们要有一个条件符合。
<3> 如果一个产品价值 30,它就会被自动排除掉,因为它匹配了 must_not分句。
二:字段mapping 修改
查看索引描述
GET /my_store/_mapping/products
{
"my_store": {
"mappings": {
"products": {
"properties": {
"price": {
"type": "long"
},
"productID": {
"type": "string"
}
}
}
}
}
}
为了不被分割 productID字段,需要如下设置:
PUT /my_store <2> { "mappings" : { "products" : { "properties" : { "productID" : { "type" : "string", "index" : "not_analyzed" <3> } } } } }
<1> 必须首先删除索引,因为我们不能修改已经存在的映射。
<2> 删除后,我们可以用自定义的映射来创建它。
<3> 这里我们明确表示不希望 productID 被分析。
三:用于数字的 term 过滤器
POST /my_store/products/_bulk { "index": { "_id": 1 }} { "price" : 10, "productID" : "XHDK-A-1293-#fJ3" } { "index": { "_id": 2 }} { "price" : 20, "productID" : "KDKE-B-9947-#kL5" } { "index": { "_id": 3 }} { "price" : 30, "productID" : "JODL-X-1937-#pV7" } { "index": { "_id": 4 }} { "price" : 30, "productID" : "QQPX-R-3956-#aD8" }
我们的目标是找出特定价格的产品。假如你有关系型数据库背景,可能用 SQL 来表现这次查询比较熟悉,它看起来像这样:
SELECT document FROM products WHERE price = 20
在 Elasticsearch DSL 中,我们使用 term 过滤器来实现同样的事。term 过滤器会查找我们设定的准确值。term 过滤器本身很简单,它接受一个字段名和我们希望查找的值:
GET /my_store/products/_search
{
"query": {
"filtered": {
"query": {
"match_all": {}
},
"filter": {
"term": {
"price": "20"
}
}
}
}
}
<1> filtered 查询同时接受接受 query 与 filter。
<2> match_all 用来匹配所有文档,这是默认行为,所以在以后的例子中我们将省略掉 query 部分。
<3> 这是我们上面见过的 term 过滤器。注意它在 filter 分句中的位置。
执行之后,你将得到预期的搜索结果:只能文档 2 被返回了(因为只有 2 的价格是 20):
四:索引别名和零停机时间
前面提到的重新索引过程中的问题是必须更新你的应用,来使用另一个索引名。索引别名正是用来解决这个问题的!
索引 别名 就像一个快捷方式或软连接,可以指向一个或多个索引,也可以给任何需要索引名的 API 使用。别名带给我们极大的灵活性,允许我们做到:
在一个运行的集群上无缝的从一个索引切换到另一个
给多个索引分类(例如,last_three_months)
给索引的一个子集创建 视图
我们以后会讨论更多别名的使用场景。现在我们将介绍用它们怎么在零停机时间内从旧的索引切换到新的索引。
这里有两种管理别名的途径:_alias 用于单个操作,_aliases 用于原子化多个操作。
在这一章中,我们假设你的应用采用一个叫 my_index 的索引。而事实上,my_index 是一个指向当前真实索引的别名。真实的索引名将包含一个版本号:my_index_v1, my_index_v2 等等。
开始,我们创建一个索引 my_index_v1,然后将别名 my_index 指向它:
PUT /my_index_v1 <1> PUT /my_index_v1/_alias/my_index <2>
<1> 创建索引 my_index_v1。
<2> 将别名 my_index 指向 my_index_v1
你可以检测这个别名指向哪个索引:
GET /*/_alias/my_index
或哪些别名指向这个索引:
GET /my_index_v1/_alias/*
两者都将返回下列值:
{ "my_index_v1" : { "aliases" : { "my_index" : { } } } }
然后,我们决定修改索引中一个字段的映射。当然我们不能修改现存的映射,索引我们需要重新索引数据。首先,我们创建有新的映射的索引my_index_v2。
PUT /my_index_v2 { "mappings": { "my_type": { "properties": { "tags": { "type": "string", "index": "not_analyzed" } } } } }
然后我们从将数据从 my_index_v1 迁移到 my_index_v2,下面的过程在【重新索引】中描述过了。一旦我们认为数据已经被正确的索引了,我们就将别名指向新的索引。
别名可以指向多个索引,所以我们需要在新索引中添加别名的同时从旧索引中删除它。这个操作需要原子化,所以我们需要用 _aliases 操作:
POST /_aliases { "actions": [ { "remove": { "index": "my_index_v1", "alias": "my_index" }}, { "add": { "index": "my_index_v2", "alias": "my_index" }} ] }
这样,你的应用就从旧索引迁移到了新的,而没有停机时间。
提示:
即使你认为现在的索引设计已经是完美的了,当你的应用在生产环境使用时,还是有可能在今后有一些改变的。
所以请做好准备:在应用中使用别名而不是索引。然后你就可以在任何时候重建索引。别名的开销很小,应当广泛使用。