资料篇
基础篇
简介
Elasticsearch是一个使用JAVA开发,基于Apache Lucene(TM)的开源搜索引擎。
- 分布式的实时文件存储,每个字段都被索引并可被搜索
- 分布式的实时分析搜索引擎
- 可以扩展到上百台服务器,处理PB级结构化或非结构化数据
索引(_index)
ES中索引概念的区分
- 索引(名词) 它是相关文档存储的地方,一个索引(index)就像是传统关系数据库中的数据库。
- 索引(动词) 「索引一个文档」表示把一个文档存储到索引(名词)里,以便它可以被检索或者查询。这很像SQL中的INSERT关键字,差别是,如果文档已经存在,新的文档将覆盖旧的文档。
- 倒排索引 传统数据库为特定列增加一个索引,例如B-Tree索引来加速检索。Elasticsearch和Lucene使用一种叫做倒排索引(inverted index)的数据结构来达到相同目的。默认情况下,文档中的所有字段都会被索引(拥有一个倒排索引),只有这样他们才是可被搜索的(我们可以通过设置让某些字段不被索引)。
安装篇
windows下安装ES
直接下载ZIP压缩包并解压,通过/bin/elasticsearch.in.bat脚本启动
配置
config/elasticsearch.yml
安装IK中文分词器
IKAnalyzer是一个开源的,基于java语言开发的轻量级的中文分词工具包。GitHub地址
- 下载源码
- 编译源码:
mvn clean package
- 在ES_HOME/plugins路径下创建analysis-ik文件夹
- 将编译得到的elasticsearch-analysis-ik-1.9.1.zip解压到analysis-ik
- 编辑ES_HOME/config/elasticsearch.yml
index.analysis.analyzer.ik.type : "ik"
测试IK分词器
默认的分词器
curl GET http://localhost:9200/_analyze?text=我是中国人&pretty
结果
HTTP/1.1 200 OK { "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 }, { "token" : "人", "start_offset" : 4, "end_offset" : 5, "type" : "<ideographic>", "position" : 4 } ] } </ideographic></ideographic></ideographic></ideographic></ideographic>
使用ik分词器
curl GET http://localhost:9200/_analyze?analyzer=ik&text=我是中国人&pretty
结果
HTTP/1.1 200 OK { "tokens" : [ { "token" : "我", "start_offset" : 0, "end_offset" : 1, "type" : "CN_CHAR", "position" : 0 }, { "token" : "中国人", "start_offset" : 2, "end_offset" : 5, "type" : "CN_WORD", "position" : 1 }, { "token" : "中国", "start_offset" : 2, "end_offset" : 4, "type" : "CN_WORD", "position" : 2 }, { "token" : "国人", "start_offset" : 3, "end_offset" : 5, "type" : "CN_WORD", "position" : 3 } ] }
编程篇
索引管理
自定义分析器
PUT http://host:port/my_index { "settings": { "analysis": { "char_filter": { "&_to_and": { "type": "mapping", "mappings": ["&=> and "] } }, "filter": { "my_stopwords": { "type": "stop", "stopwords": ["the","a"] } }, "analyzer": { "my_analyzer": { "type": "custom", "char_filter": ["html_strip", "&_to_and"], "tokenizer": "standard", "filter": [ "lowercase","my_stopwords"] } } } } }
创建索引
我们可以简单的通过添加一个文档的方式创建一个索引。这个索引使用默认设置,新的属性通过动态映射添加到分类中。
禁用默认创建索引
# config/elasticsearch.yml action.auto_create_index: false
我们可以对创建索引的过程进行更多的控制
GET http://host/port/_index { "settings": { "number_of_shards" : 1, "number_of_replicas" : 0, "analysis" : { "analyzer" : { "stem" : { "tokenizer" : "standard", "filter" : ["standard", "lowercase", "stop", "porter_stem"] } } } }, "mappings": { " _default_ ":{ }, "my_type_blog": { "_id" : { "path": "doc_id" }, "_source": { "enabled": false }, "_all": { "enabled": false }, "include_in_all": false, "dynamic" : false , "date_detection" : false, "dynamic_templates" : { "str" : { "match": "*_str", "match_mapping_type": "string", "mapping": { "type": "string", "analyzer": "spanish" } }, "number" : { "match": "*", "match_mapping_type": "string", "mapping": { "type": "string", "analyzer": "english" } } }, "properties"{ "title": { "type" : "long", "index" : "not_analyzed", "analyzer" : "", "include_in_all": true } } } }
- number_of_shards:定义一个索引的主分片个数,默认值是 `5`。这个配置在索引创建后不能修改。
- number_of_replicas:每个主分片的复制分片个数,默认是 `1`。这个配置可以随时在活跃的索引上修改。
- analysis:分析器配置
- mappings:文档类型映射
- _default_:索引的默认映射,可以方便的指定公用设置。类型映射中明可以确覆盖这些配置。
- my_type_blog:映射根对象(自定义类型名称)
- _id:文档id策略
- path:告诉文档以哪个字段作为_id的值。
- _source:是否禁用_source字段(最好始终是开启状态)。默认情况下,ES把用JSON字符串表示的文档主体保存在_source字段中。
- _all:是否禁用_all字段。
- include_in_all:字段是否包含在_all字段中。
- dynamic:控制新增字段的处理方式, true :自动添加字段(默认),false :忽略字段, strict :当遇到未知字段时抛出异常。它可以用在根对象或任何 object 对象上。你可以将dynamic默认设置为strict ,而在特定内部对象上启用它:
- date_detection:false关闭自动检测日期。
- dynamic_templates:动态模板,新字段通过字段名或数据类型应用一个完全不同的映射。
- match:字段名匹配规则。
- match_mapping_type:字段数据类型匹配规则。
- mapping:字段映射。
- unmatch和path_unmatch规则将用于排除未被匹配的字段。
- properties:列出了文档中可能包含的每个字段的映射。
- _id:文档id策略
删除索引
DELETE http://host/port/my_index DELETE /my_index_one,my_index_two DELETE /index_* DELETE /_all
索引别名(alias)
创建索引my_index_v1,使用别名my_index指向它
PUT http://host/prot/my_index_v1 PUT http://host/prot/my_index_v1/_alias/my_index
检测索引
检测my_index指向那些索引
GET http://host/prot/*/_alias/my_index
检测my_index_v1被那些别名指着。
GET http://host/prot/my_index_v1/_alias/*
aliases从新索引中添加别名的同时从旧索引中删除它
POST http://host/prot/_aliases { "actions": [ { "remove": { "index": "my_index_v1", "alias": "my_index" }}, { "add": { "index": "my_index_v2", "alias": "my_index" }} ] }
修改复制分片
PUT http://host/port/my_index/_settings { "number_of_replicas": 1 }
映射和分析
- 映射(mapping)机制用于进行字段类型确认,将每个字段匹配为一种确定的数据类型( string , number , booleans , date 等)。
- 分析(analysis)机制用于进行全文文本(Full Text)的分词,以建立供搜索用的反向索引。
查看文档的映射结构
GET http://host:port/sina/_mapping/bolg
Es对核心数据类型(strings, numbers, booleans及dates)以不同的方式进行索引,但更大的区别在于确切值(exact values)(比如 string 类型)及全文文本(full text)之间
分析器
分析器是三个顺序执行的组件的结合(字符过滤器,分词器,标记过滤器)。
- 字符过滤器
- 字符过滤器是让字符串在被分词前变得更加“整洁”。例如,如果我们的文本是 HTML 格式,它可能会包含一些我们不想被索引的 HTML 标签,字符过滤器可以删除这些标签或者进行转义。一个分析器可能包含零到多个字符过滤器。
- 分词器
- 一个分析器 必须 包含一个分词器。分词器将字符串分割成单独的词(terms)或标记(tokens)。
- 标记过滤器
- 分词结果的 标记流 会根据各自的情况,传递给特定的标记过滤器。标记过滤器可能修改,添加或删除标记。
默认字符过滤器
- html_strip:删除所有的 HTML 标签,并且将 HTML 实体转换成对应的 Unicode 字符比如将 á 转成 á 。
默认分词器
- standard:将字符串分割成单独的字词,删除大部分标点符号,
- keyword:分词器输出和它接收到的相同的字符串,不做任何分词处理
- whitespace:只通过空格来分割文本。
- pattern:可以通过正则表达式来分割文本。
默认标记过滤器
- lowercase:
- stop:
- stemmer:将单词转化为他们的根形态(root form)
- ascii_folding:删除变音符号,比如从 très 转为 tres
- ngram和edge_ngram:让标记更适合特殊匹配情况或自动完成。
自定义字段映射
{ "mappings": { "bolg": { "properties": { "title": { "type": "string", "index": "analyzed" } } } } }
- type:字段数据类型
- index:控制字符串以何种方式被索引
- analyzed:首先分析这个字符串,然后索引。换言之,以全文形式索引此字段。
- not_analyzed:索引这个字段,使之可以被搜索,但是索引内容和指定值一样。不分析此字段。
- no:不索引这个字段。这个字段不能为搜索到。
指定分析器
{ "mappings": { "bolg": { "properties": { "title": { "type": "string", "index":"analyzed", "analyzer": "my_ik_test" } } } } }
默认的,ES使用standard分析器,但是你可以通过analyzer指定一个分析器
内建分析器
- standard:用于全文字段的默认分析器,对于大部分西方语系来说是一个不错的选择。
- whitespace
- simple
- english
预防类型陷阱
sina索引下存在两种类型中文博客和英文博客,两种类型都有title字段,但是中文博客的title字段使用中文的分析器,英文博客的title字段使用英文分词器。我们在两种类型中搜索title字段,ES会采用第一个被找到的title字段使用的分析器,这对于这个字段的文档来说是正确的,但对另一个来说却是错误的。
一种解决方案是在查询时指定字段类型
GET http://host/port/_search { "query": { "multi_match": { "query": "The quick brown fox", "fields": [ "blog_en.title", "blog_es.title" ] } } }
还有一种冲突是统一索引下不同类型的同名字段存储不同类型的数据,
?类型: user { "login": "john_smith" } ?类型: event { "login": "2014-06-01" }
假如我们试图排序event.login字段,可能会有预想不到的结果或者以失败告终。为了保证你不会遇到这些冲突,建议在同一个索引的每一个类型中,确保用同样的方式映射同名的字段
更新映射
更新映射指的是为新增字段添加映射
注意:已经存在的类型映射是不能修改的。果你改变了已有字段映射,那已经被索引的数据将错误并且不能被正确的搜索到。
测试分词结果
GET http://host:port/_analyze?text=王祖国&pretty
GET http://host:port/_analyze?analyzer=ik&text=王祖国&pretty
其他字段数据类型
除了之前提到的简单的标量类型,JSON还有 null 值,数组和对象,所有这些ES都支持。
对于数组ES将使用第一个值的类型来确定这个新字段的类型,所以数组中所有值必须为同一类型
空字段
这四个字段将被识别为空字段而不被索引
"empty_string": "", "null_value": null, "empty_array": [], "array_with_null_value": [ null ]
多层对象索引
{ "mappings": { "bolg": { "properties": { "title": { "type": "string", "index":"analyzed", "analyzer": "my_ik_test" }, "user": { "name" : { "type": "string" }, "age" : { "type": "long" } } } } } }
CRUD操作
新增文档
RESTful方式创建
POST http://host:port/sina/blog/1 { "title" : "中国式大片", "author" : "王钢蛋", "classification" : ['中国','影视','时评'], "content" : "今年暑期档,《变形金刚4》在中国内地狂揽3.15亿美元票房,打破其在美国本土2.3亿美元的纪录,使中国成为该片全球最高票房收获地。紧接着,《敢死队3》《猩球崛起2:黎明之战》《不惧风暴》以及最近的《银河护卫队》等美国大片纷至沓来,让观众目不暇接。面对中国越来越庞大的市场和消费能力,近几年好莱坞制片方们纷纷将中国作为市场开拓的重点,为了讨好中国观众而各显其能。2016年,预计将有《蝙蝠侠大战超人》《美国队长3》《超凡蜘蛛侠3》《变形金刚5》《X战警:天启》等登陆中国市场。毫不夸张地说,好莱坞的“超级英雄们”简直是组团来中国“抢钱”。而这一年,根据WTO协议,内地电影市场将向好莱坞全面开放。喊了很多年,狼似乎真的要来了,中国电影,尤其是代表着中国电影工业发展最高水平的“中国式大片”应该如何应对。", "releaseTime":"2016-04-24" }
名字 | 说明 |
---|---|
sina | 索引名 |
blog | 类型名 |
1 | 这个员工的ID(如果不指定,ES会自动生成一个ID。) |
JAVA 客户端方式创建
// 创建客户端 Client client = TransportClient.builder().build() .addTransportAddress(new InetSocketTransportAddress(InetAddress.getByName(host), port)); String [] classification = {"中国","影视","时评"}; XContentBuilder json = jsonBuilder().startObject() .field("title", "中国式大片") .field("author","王钢蛋") .field("classification",classification) .field("content","今年暑期档,《变形金刚4》在中国内地狂揽3.15亿美元票房,打破其在美国本土2.3亿美元的纪录,使中国成为该片全球最高票房收获地。紧接着,《敢死队3》《猩球崛起2:黎明之战》《不惧风暴》以及最近的《银河护卫队》等美国大片纷至沓来,让观众目不暇接。面对中国越来越庞大的市场和消费能力,近几年好莱坞制片方们纷纷将中国作为市场开拓的重点,为了讨好中国观众而各显其能。2016年,预计将有《蝙蝠侠大战超人》《美国队长3》《超凡蜘蛛侠3》《变形金刚5》《X战警:天启》等登陆中国市场。毫不夸张地说,好莱坞的“超级英雄们”简直是组团来中国“抢钱”。而这一年,根据WTO协议,内地电影市场将向好莱坞全面开放。喊了很多年,狼似乎真的要来了,中国电影,尤其是代表着中国电影工业发展最高水平的“中国式大片”应该如何应对。") .field("releaseTime",new Date()) .endObject(); IndexResponse response = client.prepareIndex("sina", "bog","1").setSource(json).get();
检索文档
RESTful方式检索
GET http://host:port/sina/blog/1
JAVA 客户端方式检索
// 创建客户端 Client client = TransportClient.builder().build() .addTransportAddress(new InetSocketTransportAddress(InetAddress.getByName(host), port)); etResponse prepareGet = client.prepareGet("sina", "blog", "1").get();
删除一个文档
RESTful方式删除
DELETE http://host:port/sina/blog/1
JAVA 客户端方式删除
// 创建客户端 Client client = TransportClient.builder().build() .addTransportAddress(new InetSocketTransportAddress(InetAddress.getByName(host), port)); DeleteResponse deleteResponse = client.prepareDelete("sina", "bog", "1").get();
修改文档
更新整个文档
- 文档在Elasticsearch中是不可变的,我们不能修改他们。
- 如果需要更新已存在的文档,我们可以使用index API重建索引(reindex) 或者替换掉它。
- 更新后在内部,Elasticsearch已经标记旧文档为删除并添加了一个完整的新文档。旧版本文档不会立即消失,但你也不能去访问它。Elasticsearch会在你继续索引更多数据时清理被删除的文档。
ES更新的过程
- 从旧文档中检索JSON
- 修改它
- 删除旧文档(标记删除,不是物理删除)
- 索引新文档
使用update API 进行局部更新
RESTful方式修改
POST http://host:port/sina/blog/1/_update { "doc" :{ "releaseTime":"2016-05-29" } }
JAVA 客户端方式修改
// 创建客户端 Client client = TransportClient.builder().build() .addTransportAddress(new InetSocketTransportAddress(InetAddress.getByName(host), port)); UpdateRequest updateRequest = new UpdateRequest("sina", "blog", "1"); updateRequest.doc(jsonBuilder().startObject() .field("releaseTime","2016-05-29") .endObject()); UpdateResponse updateResponse = client.update(request).get();
更新一个不存在的文档
文档存在执行更新操作,文档不存在执行其他操作(比如创建)
RESTful方式更新有可能不存在的文档
POST /website/pageviews/1/_update { "doc" : { "releaseTime":"2016-05-29" }, "upsert": { "title" : "中国式大片", "author" : "王钢蛋" } }
JAVA 客户端方式更新有可能不存在的文档
Client client = TransportClient.builder().build() .addTransportAddress(new InetSocketTransportAddress(InetAddress.getByName(host), port)); IndexRequest indexRequest = new IndexRequest("twitter", "tweet", "1"); indexRequest.source(jsonBuilder().startObject() .field("title", "中国式大片") .field("author", "王钢蛋") .endObject()); UpdateRequest updateRequest = new UpdateRequest("twitter", "tweet", "1"); updateRequest.doc(jsonBuilder().startObject() .field("releaseTime", "2016-05-29") .endObject()).upsert(indexRequest); Client client = TransportClientApiTest.transportClient(); UpdateResponse updateResponse = client.update(updateRequest).get();
搜索
空搜索
RESTful方式
GET http://host/prot/_search
JAVA 客户端
Client client = TransportClient.builder().build() .addTransportAddress(new InetSocketTransportAddress(InetAddress.getByName(host), port)); SearchResponse response = client.prepareSearch().execute().actionGet();
响应说明
名字 | 说明 |
---|---|
hits | 响应中最重要的部分是 hits ,它包含了 total 字段来表示匹配到的文档总数, hits 数组还包含了匹配到的前10条数据。 |
took | 告诉我们整个搜索请求花费的毫秒数。 |
_shards | _shards 节点告诉我们参与查询的分片数( total 字段),有多少是成功的( successful 字段),有多少的是失败的( failed 字段)。 |
time_out | 告诉我们查询超时与否 |
多索引、多类型
RESTful方式
GET http://host/prot/sina,netease/bog,news/_search GET http://host/prot/s*,n*/bog,news/_search GET http://host/prot/_all/bog,news/_search
JAVA 客户端
Client client = TransportClient.builder().build() .addTransportAddress(new InetSocketTransportAddress(InetAddress.getByName(host), port)); SearchResponse response = client.prepareSearch("sina", "netease") .setTypes("bog", "news") .actionGet();
分页
RESTful方式
GET http://host/prot/_search?size=10&form=0
JAVA 客户端
Client client = TransportClient.builder().build() .addTransportAddress(new InetSocketTransportAddress(InetAddress.getByName(host), port)); SearchResponse response = client.prepareSearch() .setFrom(0).setSize(10) .execute().actionGet();
size每页显示的数量,当前页起始位置
注意:应该当心分页太深或者一次请求太多的结果。结果在返回前会被排序,在分布式系统中,排序结果的花费随着分页的深入而成倍增长。假设我们请求第1000页——结果10001到10010。每个分片都必须产生顶端的10010个结果。然后请求节点排序这50050个结果并丢弃50040个!这也是为什么网络搜索引擎中任何语句不能返回多于1000个结果的原因。
_All字段
当你索引一个文档,ES把所有字符串字段值连接起来放在一个大字符串中,它被索引为一个特殊的字段 _all 。
查询字符串在其他字段被定以前使用 _all 字段搜索
注意: _all 字段对于开始一个新应用时是一个有用的特性。之后,如果你定义字段来代替 _all 字段,你的搜索结果将更加可控。当 _all 字段不再使用,你可以停用它。
结构化查询 Query DSL
查询
RESTful方式
GET http://host/prot/_search { "query": { "match": { "author": "王钢蛋" } } }
JAVA 客户端
Client client = TransportClient.builder().build() .addTransportAddress(new InetSocketTransportAddress(InetAddress.getByName(host), port)); SearchResponse response = client.prepareSearch() .setQuery(QueryBuilders.matchQuery("author", "王钢蛋")) .execute().actionGet();
合并查询
RESTful方式
GET http://host/prot/_search { "query": { "bool": { "must": { "match": { "author": "王钢蛋" } }, "should": { "match": { "content": "变形金刚4" } } } } }
JAVA 客户端
Client client = TransportClient.builder().build() .addTransportAddress(new InetSocketTransportAddress(InetAddress.getByName(host), port)); String[] classification = {"美国"}; SearchResponse response = client.prepareSearch().setQuery(QueryBuilders.boolQuery() .must(QueryBuilders.matchQuery("author","王钢蛋")) .mustNot(QueryBuilders.matchQuery("classification",classification)) .should(QueryBuilders.matchQuery("content", "变形金刚4"))) .get();
过滤
RESTful方式
GET http://host/prot/_search { "query": { "filtered": { "filter": { "range": { "releaseTime": { "gte": "2016-04-01", "lt": "2016-05-01" } } } } } }
JAVA 客户端
Client client = TransportClient.builder().build() .addTransportAddress(new InetSocketTransportAddress(InetAddress.getByName(host), port)); SearchResponse searchResponse = client.prepareSearch() .setPostFilter(QueryBuilders.rangeQuery("releaseTime").from("2016-04-01").to("2016-05-01")) .get();
查询和过滤混合
RESTful方式
GET http://host/prot/_search { "query": { "filtered": { "filter": { "range": { "releaseTime": { "gte": "2016-04-01", "lt": "2016-05-01" } } }, "query": { "bool": { "must": { "match": { "author": "王钢蛋" } }, "should": { "match": { "content": "变形金刚4" } } } } } } }
JAVA 客户端
Client client = TransportClient.builder().build() .addTransportAddress(new InetSocketTransportAddress(InetAddress.getByName(host), port)); SearchResponse searchResponse = client.prepareSearch() .setQuery(QueryBuilders.boolQuery() .must(QueryBuilders.matchQuery("author", "王钢蛋")) .should(QueryBuilders.matchQuery("content", "变形金刚4"))) .setPostFilter(QueryBuilders.rangeQuery("releaseTime").from("2016-04-01").to("2016-05-01")) .get();
查询与过滤
过滤语句会询问每个文档的字段值是否包含着特定值
- created 的日期范围是否在 2013 到 2014 ?
- status 字段中是否包含单词 "published" ?
- lat_lon 字段中的地理位置与目标点相距是否不超过10km ?
查询语句会询问每个文档的字段值与特定值的匹配程度如何?
- 查找与 full text search 这个词语最佳匹配的文档
- 查找包含单词 run ,但是也包含 runs , running , jog 或 sprint 的文档
- 同时包含着 quick , brown 和 fox --- 单词间离得越近,该文档的相关性越高
- 标识着 lucene , search 或 java --- 标识词越多,该文档的相关性越高
一条查询语句会计算每个文档与查询语句的相关性,会给出一个相关性评分 _score ,并且 按照相关性对匹配到的文档进行排序。 这种评分方式非常适用于一个没有完全配置结果的全文本搜索。
查询与过滤使用
原则上来说做全文本搜索或其他需要进行相关性评分的时候使用查询语句,剩下的全部用过滤语句
重要的过滤语句
- term过滤:term主要用于精确匹配哪些值,比如数字,日期,布尔值或 not_analyzed 的字符串(未经分析的文本数据类型):
- terms过滤:terms跟term有点类似,但terms允许指定多个匹配条件。 如果某个字段指定了多个值,那么文档需要一起去做匹配:
- range过滤:range过滤允许我们按照指定范围查找一批数据
- exists和missing过滤: exists和missing过滤可以用于查找文档中是否包含指定字段或没有某个字段,类似于SQL语句中的 IS_NULL 条件
- bool过滤:bool过滤可以用来合并多个过滤条件查询结果的布尔逻辑。
过滤的可选值
- bool过滤可选操作符
- must : 多个查询条件的完全匹配,相当于 and。
- must_not : 多个查询条件的相反匹配,相当于 not。
- should : 至少有一个查询条件匹配, 相当于 or。
- range过滤范围操作符
- gt : 大于
- gte : 大于等于
- lt : 小于
- lte : 小于等于
注意
对于文本term和terms是包含操作,而不是相等操作。实际上他是使用你的查询字符串去字段的倒排索引中查询,这意味着我们使用“王钢蛋”作为查询条件查询author字段是查不出结果的。默认分词器将author字段的值“王钢蛋”分成,“王”、“钢”、“蛋”的倒排索引。如果你需要使用准确值来查找文本,可以采用将字段设置为not_analyzed。(或者通过过短句查询)
range日期范围操作除支持正常日期之外还支持日期数学操作符,例如now-1h表示当前时间之前的一小时,2014-01-01 00:00:00||+1M表示2014年1月1号加一个月。now-1h/d 表示到取整到昨夜凌晨日期格式手册
过滤器缓存
不会被缓存的过滤器
- bool这类的组合过滤器
- 脚本过滤器
- Geo 过滤器
- 日期范围(now-1h这种结果值精确到毫秒的不被缓存,日期取整时会被缓存now/d取最近一天)
- 使用_cache明确指定为false的过滤器
过滤器的顺序:更详细的过滤条件应该被放置在其他过滤器之前,以便更早的排除更多的文档。缓存的过滤器非常快,所以它们需要被放在不能缓存的过滤器之前。
使用了缓存的过滤器的性能(且不用计算关联性)要比查询快,所以我们应该尽量多的使用过滤器。与关系数据库中尽量多使用索引列同理。
重要的查询语句
- match_all查询:使用 match_all 可以查询到所有文档,是没有查询条件下的默认语句。
- match查询:match查询是一个标准查询,不管你需要全文本查询还是精确查询基本上都要用到它。
- multi_match查询: multi_match相比match允许同时搜索多个字段
- bool查询:bool查询与bool过滤相似,用于合并多个查询子句。不同的是,bool 过滤可以直接给出是否匹配成功,而bool查询要计算每一个查询子句的_score(相关性分值)。
查询的可选值
- bool过滤可选操作符
- must : 查询指定文档一定要被包含。
- must_not : 查询指定文档一定不要被包含。
- should : 查询指定文档,有则可以为文档相关性加分。
注意
如果bool查询下没有must子句,那至少应该有一个should子句。但是如果有must子句,那么没有should子句也可以进行查询。
注意
如果用match查询一个全文本字段,它会在真正查询之前用分析器先分析一下 match 查询字符
如果用match查询一个确切值,在遇到数字,日期,布尔值或者not_analyzed的字符串时,它将为你搜索你给定的值
做精确匹配搜索时,你最好用过滤语句,因为过滤语句可以缓存数据
验证查询
查询语句可以变得非常复杂,特别是与不同的分析器和字段映射相结合后,就会有些难度。
validate API可以验证一条查询语句是否合法。
RESTful方式
GET http://host/prot/_validate/query?explain { "query": { "tweet" : { "match" : "really powerful" } } }
JAVA 客户端
Client client = TransportClient.builder().build() .addTransportAddress(new InetSocketTransportAddress(InetAddress.getByName(host), port)); ValidateQueryResponse valResponse = new ValidateQueryRequestBuilder(client, ValidateQueryAction.INSTANCE) .setQuery(QueryBuilders.matchQuery("User", "Jon snow")) .setExplain(true) .get(); for (QueryExplanation qe : valResponse.getQueryExplanation()) { System.out.println(String.format("索引:%s", qe.getIndex())); System.out.println(String.format("解释:%s", qe.getExplanation())); System.out.println(String.format("错误信息:%s", qe.getError())); }
全文检索
全文检索最重要的两个方面是
- 相关度(Relevance):根据文档与查询的相关程度对结果集进行排序的能力。相关度可以使用TF/IDF、地理位置相近程度、模糊相似度或其他算法计算。
- 分析(Analysis):将一段文本转换为一组唯一的、标准化了的标记(token),用以(a)创建倒排索引,(b)查询倒排索引。
一旦提到相关度和分析,指的都是查询(queries)而非过滤器(filters)
文本查询的分类
- 基于短语(Term-based)的查询
- 像term或fuzzy一类的查询是低级查询,它们没有分析阶段。这些查询在单一的短语上执行。
- 全文(Full-text)检索
- match和query_string这样的查询是高级查询,它们会对字段进行分析。
字段权重
下面的查询的title和author的权重是一样的(1/2)
{ "query": { "bool": { "should": [ { "match": { "title": "War and Peace" } }, { "match": { "author": "Leo Tolstoy" } } ] } } }
下面的查询中title、author、translator的权重是一样的(1/3)
{ "query": { "bool": { "should": [ { "match": { "title": "War and Peace" } }, { "match": { "author": "Leo Tolstoy" } }, { "bool": { "should": [ { "match": { "translator": "Constance Garnett" } }, { "match": { "translator": "Louise Maude" } } ] } } ] } } }
下面的查询中title、author、translator、translator的权重是一样的(1/4)
{ "query": { "bool": { "should": [ { "match": { "title": "War and Peace" } }, { "match": { "author": "Leo Tolstoy" } }, { "match": { "translator": "Constance Garnett" } }, { "match": { "translator": "Louise Maude" } } ] } } }
通过boost提高字段权重,下面的查询中author字段最重要
{ "query": { "bool": { "should": [ { "match": { "title": "War and Peace", "boost": 1 } }, { "match": { "author": "Leo Tolstoy", "boost": 2 } } ] } } }
批量操作
Mget检索多个文档
RESTful方式
GET http://host/prot/_mget { "docs" : [ { "_index" : "sina", "_type" : "blog", "_id" : 2 }, { "_index" : "netease", "_type" : "blog", "_id" : 1, "_source": "views" } ] }
JAVA 客户端方式
Client client = TransportClient.builder().build() .addTransportAddress(new InetSocketTransportAddress(InetAddress.getByName(host), port)); MultiGetResponse multiGetItemResponses = client.prepareMultiGet() .add("sina", "blog", "1") .add("netease", "blog", "1") .get(); for (MultiGetItemResponse itemResponse : multiGetItemResponses) { if (itemResponse.isFailed()) {// 是否是失败的 break; } GetResponse response = itemResponse.getResponse(); if (response.isExists()) {// 检查文件是否存在 String json = response.getSourceAsString();//访问_source字段 System.out.println(json); } }
bulk批量更新、创建、删除
RESTful方式
POST http://host/prot/_bulk { action: { metadata }}\n { request body }\n
bulk的格式比较特殊,每行必须以"\n"符号结尾,包括最后一行。
action是行为,request body是请求体,删除操作不需要请求体。
行为(action)必须是以下几种
行为 | 解释 |
---|---|
create | 当文档不存在时创建之。详见《创建文档》 |
index | 创建新文档或替换已有文档。见《索引文档》和《更新文档》 |
update | 局部更新文档。见《局部更新》 |
delete | 删除一个文档。见《删除文档》 |
JAVA 客户端方式
Client client = TransportClient.builder().build() .addTransportAddress(new InetSocketTransportAddress(InetAddress.getByName(host), port)); BulkRequestBuilder bulkRequest = client.prepareBulk(); String [] classification = {"中国","影视","时评"}; bulkRequest.add(client.prepareIndex("sina", "bolg", "1") .setSource(jsonBuilder() .startObject() .field("title", "中国式大片") .field("author","王钢蛋") .field("classification",classification) .field("content","今年暑期档,《变形金刚4》在中国内地狂揽3.15亿美元票房,打破其在美国本土2.3亿美元的纪录,使中国成为该片全球最高票房收获地。紧接着,《敢死队3》《猩球崛起2:黎明之战》《不惧风暴》以及最近的《银河护卫队》等美国大片纷至沓来,让观众目不暇接。面对中国越来越庞大的市场和消费能力,近几年好莱坞制片方们纷纷将中国作为市场开拓的重点,为了讨好中国观众而各显其能。2016年,预计将有《蝙蝠侠大战超人》《美国队长3》《超凡蜘蛛侠3》《变形金刚5》《X战警:天启》等登陆中国市场。毫不夸张地说,好莱坞的“超级英雄们”简直是组团来中国“抢钱”。而这一年,根据WTO协议,内地电影市场将向好莱坞全面开放。喊了很多年,狼似乎真的要来了,中国电影,尤其是代表着中国电影工业发展最高水平的“中国式大片”应该如何应对。") .field("releaseTime",new Date()) .field("User","Jon snow") .endObject() ) ); bulkRequest.add(client.prepareDelete("sina", "bolg", "2")); BulkResponse bulkResponse = bulkRequest.get(); for (BulkItemResponse response : bulkResponse.getItems()) { String _index = response.getIndex(); String _type = response.getType(); String _id = response.getId(); long _version = response.getVersion(); System.out.println(String.format("_index:%s,_type:%s,_id:%s,_version:%s", _index,_type,_id,_version)); }
统计
最小值
Client client = TransportClient.builder().build() .addTransportAddress(new InetSocketTransportAddress(InetAddress.getByName(host), port)); MetricsAggregationBuilder<?> minAggregation = AggregationBuilders.min("minAge").field("age"); SearchResponse sr = client.prepareSearch() .setQuery(QueryBuilders.matchAllQuery()) .addAggregation(minAggregation);
最大值
Client client = TransportClient.builder().build() .addTransportAddress(new InetSocketTransportAddress(InetAddress.getByName(host), port)); MetricsAggregationBuilder<?> maxAggregation = AggregationBuilders.max("maxAge").field("age"); SearchResponse sr = client.prepareSearch() .setQuery(QueryBuilders.matchAllQuery()) .addAggregation(maxAggregation);
和
Client client = TransportClient.builder().build() .addTransportAddress(new InetSocketTransportAddress(InetAddress.getByName(host), port)); MetricsAggregationBuilder<?> sumAggregation = AggregationBuilders.sum("sumAge").field("age"); SearchResponse sr = client.prepareSearch() .setQuery(QueryBuilders.matchAllQuery()) .addAggregation(sumAggregation);
平均值
Client client = TransportClient.builder().build() .addTransportAddress(new InetSocketTransportAddress(InetAddress.getByName(host), port)); MetricsAggregationBuilder<?> avgAggregation = AggregationBuilders.avg("avgAge").field("age"); SearchResponse sr = client.prepareSearch() .setQuery(QueryBuilders.matchAllQuery()) .addAggregation(avgAggregation);
统计(最小值,最大值,和,平均值,次数)
Client client = TransportClient.builder().build() .addTransportAddress(new InetSocketTransportAddress(InetAddress.getByName(host), port)); MetricsAggregationBuilder<?> statsAggregation = AggregationBuilders.stats("statsAge").field("age"); SearchResponse sr = client.prepareSearch() .setQuery(QueryBuilders.matchAllQuery()) .addAggregation(statsAggregation);