1,概念
1)结构
Relational DB -> Databases -> Tables -> Rows -> Columns
Elasticsearch -> ->Indices -> Types -> Documents -> Fields
2)Elasticsearch原理
3)win安装与使用
1>es的安装和可视化
运行elasticsearch-5.1.1、kibana-5.1.1。
2>java yml配置
首先添加依赖;
yml文件的配置如下:
#es的默认名称,如果安装es时没有做特殊的操作名字都是此名称
spring.data.elasticsearch.cluster-name=elasticsearch
# Elasticsearch 集群节点服务地址,用逗号分隔,如果没有指定其他就启动一个客户端节点,默认java访问端口9300
spring.data.elasticsearch.cluster-nodes=localhost:9300
# 设置连接超时时间
spring.data.elasticsearch.properties.transport.tcp.connect_timeout=120s
4)shell安装与使用
5)mac安装与使用
- mac首先安装homebrew
- 终端执行命令,下载elastic 仓库:
brew tap elastic/tap
- 安装es:
brew install elastic/tap/elasticsearch-full
- 查看是否安装成功:
elasticsearch --version
- es启动:
elasticsearch
- 启动成功后,浏览器可访问:
localhost:9200
- 安装目录介绍
elasticsearch : /usr/local/Cellar/elasticsearch/7.10.1
Data: /usr/local/var/elasticsearch/elasticsearch_xuchen/
Logs: /usr/local/var
Config: /usr/local/etc/elasticsearch/
2,查询汇总
1)查询关键字
查询 | 说明 | sql类比 | es关键字 | java关键字 | 备注 |
---|---|---|---|---|---|
term query | 精确查询一个字段 | = | term | QueryBuilders.termQuery | |
terms query | 精确查询多个字段 | in | terms | QueryBuilders.termsQuery | |
range query | 范围查询 | between ... and ... | range | QueryBuilders.rangeQuery | |
match query | 全文模糊检索 | like '%a%' | match | QueryBuilders.matchQuery | match在匹配时会对所查找的关键词进行分词,然后按分词匹配查找 |
Bool query | 多条件查询 | - | bool | QueryBuilders.boolQuery | bool查询包含四个子句:must (所有子条件都要满足),filter (过滤查询),should (表示可以满足也可以不满足,相当于or),must_not (不能满足该条件),mininum_should_match= 1 ,表示should最小匹配度,可以设置为百分百。Elasticsearch在2.x版本将filter query去掉,并入bool query。 |
match_phrase_prefix query | 前缀检索 | like 'a%' | match_phrase_prefix | QueryBuilders.prefixQuery | |
分页查询 | limit ...offset | setFrom(10).setSize(20) | |||
select field | 筛选返回结果 | select a,b | _source | fetchSource(@Nullable String include, @Nullable String exclude) | include 表示返回结果里要包含哪个字段;exclude 是不包含哪个字段 |
查询total获取 | count(*) | .getHits().totalHits() | |||
sort | 排序 | order by | sort | .sort("date", SortOrder.DESC) | 按照某个字段排序的话,hit.getScore()将会失效。 |
查询指定索引下的所有文档 | match_all | QueryBuilders.matchAllQuery() | |||
exists | 查找存在某个字段的文档 | is not null |
2)查询结果
接口访问链接:127.0.0.1:9200/_search
返回数据含义:
返回参数 | 说明 | 备注 |
---|---|---|
found | 表示查询的数据是否存在 | |
took | 耗费时间(毫秒)。 | |
timed_out | 是否超时 | 默认无timeout |
_source | 表示查询到的数据 | |
_shards | shards fail的条件(primary和replica全部挂掉),不影响其他shard | 默认情况下来说,一个搜索请求,会打到一个index的所有primary shard上去,当然了,每个primary shard都可能会有一个或多个replic shard,所以请求也可以到primary shard的其中一个replica shard上去 |
_shards.total | 表示应执行索引操作的分片(主分片和副本分片)的数量 | |
_shards.successful | 表示索引操作成功的分片数 | |
_shards.failed | 返回一个数组,这个数组是在副本分片上索引操作失败的情况下相关错误的数组 | 如果没有失败的分片,failed将会为0。 |
hits.total | 本次搜索返回了几条结果 | |
hits.max_score | score的含义,就是document对于一个search的相关度的匹配分数,越相关,就越匹配,分数也高 | |
hits.hits | 包含了匹配搜索的document的详细数据,默认查询前10条数据,按_score降序排序 | 在java api中,可以通过client.setSize设置返回数量。 |
hits.hits._index | 索引名,对应sql的库 | |
hits.hits._type | 类型,对应sql的表 | |
hits.hits._id | 搜索的id | |
hits.hits._score | 描述搜索结果的匹配度,得分越高,文档匹配度越高,得分越低,文档的匹配度越低。 | |
hits.hits._source | 搜索到的具体数据 | |
hits.hits._source.fields | 搜索到的具体字段 | 在java api中通过hit.getSource().get(“field_name”)获取 |
可以通过设置timeout这个值,来定时返回已经搜索到的数据。timeout机制,指定每个shard,就只能在timeout时间范围内,将搜索到的部分数据(也可能是搜索到的全部数据),直接返回给client,而不是等到所有数据全部搜索出来后再返回。可以通过如下方式进行设置:
timeout=10ms,timeout=1s,timeout=1m
GET /_search?timeout=10m
3)索引返回结果
curl -XGET http://localhost:9200/_cat/indices?pretty
health: green代表健康;yellow代表分配了所有主分片,但至少缺少一个副本,此时集群数据仍旧完整;red代表部分主分片不可用,可能已经丢失数据。
pri:primary缩写,主分片数量
rep:副分片数量
docs.count: Lucene 级别的文档数量
docs.deleted: 删除的文档
store.size:全部分片大小(包含副本)
pri.store.size:主分片大小
3,rest api
es主要支持的是rest api。
- 检查集群、节点和索引的健康信息、状态以及各种统计信息
- 管理集群、节点、索引数据以及元数据
- 对索引进行 CRUD(创建、读取、更新和删除)和搜索操作
- 执行高级的搜索操作, 例如分页、排序、过滤、脚本编写(scripting)、聚合(aggregations)以及其它操作
1)语法格式
curl -X <HTTP Verb> /<Index>/<Type>/<ID>
#?v表示带表头输出
#?h=index 表示输出index列
#?pretty 美化格式化输出 或者:?pretty=true
#?v&pretty 多个参数组合
#-s 忽略curl调试信息,避免一些奇怪的输入
也可以直接通过浏览器、postman访问。
2)集群检查
1>集群信息
功能 | 说明 | 结果说明 |
---|---|---|
检查存储使用情况 | du -sh /data*/es | 如果出现磁盘写满的情况,ES集群很可能会变成yellow,red,可能是删除历史索引的程序出问题 |
显示集群系统信息 | curl -XGET localhost:9200/_cluster/stats?pretty | 包括CPU JVM、节点、分片等等 |
获取集群堆积的任务 | curl -XGET localhost:9200/_cluster/pending_tasks?pretty |
集群健康检查:
curl localhost:9200/_cluster/health?pretty
{
"cluster_name" : "es", # 集群名称
# 集群状态:
# green代表健康
# yellow表示部分副分片未分配。集群所有数据都是可用的,集群功能也齐全,但存在某些复制没有被分配;
# red表示部分索引的主副分片未分配。集群的部分数据不可用,集群的功能也是不全的,但是集群还是可以运行的,它可以继续处理搜索请求,不过开发者要尽快修复它。一般这种情况下es已经基本挂掉了。
"status" : "green",
"timed_out" : false, #
"number_of_nodes" : 3, # 在线的节点数
"number_of_data_nodes" : 3, # 在线的数据节点数
"active_primary_shards" : 549, # 活跃的主分片数量
"active_shards" : 1098, # 活跃的分片数量,包括主分片和副本
"relocating_shards" : 2, # 正在移动的分片数量。
"initializing_shards" : 0, # 正在初始化的分片的数量。
"unassigned_shards" : 0, # 未分配的分片数量
"delayed_unassigned_shards" : 0, # 由于节点离线导致延迟分配的分片数量。
"number_of_pending_tasks" : 0, #
"number_of_in_flight_fetch" : 0, #
"task_max_waiting_in_queue_millis" : 0, #
"active_shards_percent_as_number" : 100.0 #所有活跃分片/打开的所有索引的分片总数。
}
2>结点信息
_nodes
命令都可以通过/_nodes/${nodeId}/
、/_nodes/${nodeip}
、/_nodes/${nodeattribute}/
形式对指定节点操作。
功能 | 说明 | 结果说明 |
---|---|---|
查看节点的详细信息 | curl -X GET localhost:9200/_cat/nodes?v | |
看file descriptor 这个信息 | curl -X GET localhost:9200/_nodes/process | |
统计信息(内存、CPU能) | curl -X GET localhost:9200/_nodes/process/stats | |
获得各节点的虚拟机统计和配置信息 | curl -X GET localhost:9200/_nodes/jvm | |
更详细的虚拟机信息 | curl -X GET localhost:9200/_nodes/jvm/stats | |
获得各个节点的http信息(如ip地址) | curl -X GET localhost:9200/_nodes/http | |
获得各个节点处理http请求的统计情况 | curl -X GET localhost:9200/_nodes/http/stats | |
获得各种类型的线程池的配置信息 | curl -X GET localhost:9200/_nodes/thread_pool | es分别对不同的操作提供不同的线程池 |
获得各种类型的线程池的统计信息 | curl -X GET localhost:9200/_nodes/thread_pool/stats |
3>集群问题排查
- 查询第一个索引的未分配原因
curl localhost:9200/_cluster/allocation/explain
Elasticsearch 5.0发布了 _cluster / allocation / explain,帮助诊断未分配分片的原因。
- 查询指定索引索引名未分配的原因。
#查询指定索引索引名未分配的原因。
curl -XGET localhost:9200/_cluster/allocation/explain?pretty -d'
{
"index": "skyeye-dns-2018.12.21",
"shard": 1,
"primary": true
}'
返回值 | 说明 | 备注 |
---|---|---|
assigned | 分片是已分配还是未分配 | |
shard_state_fetch_pending | 是否仍在获取有关分片的信息 | |
unassigned_info-reason | 分片最初未分配的原因 | |
allocation_delay_in_millis | 分片可分配之前的已配置延迟 | |
remaining_delay_in_millis | 可以分配分片之前的剩余延迟 | |
nodes-es.…….com.1-node_attributes | 节点具有用户添加的属性 | |
nodes-es.…….com.1-store-shard_copy | 该节点的分片副本信息和错误(如果适用) | |
nodes-es.…….com.1-final_decision 、final_explanation | 分片是否可以分配给该节点的最终决定和解释 | |
nodes-es.…….com.1-weight | 分配器想要向该节点分配分片的权重 | |
nodes-es.…….com.1-decisions | 节点决策列表,这些决策将成为有关分片的最终决策 |
根据final_explanation进行问题排查:
final_explanation内容 | 说明 | 备注
i>RED-there is no copy of the shard available(主分片数据丢失)
一般是文件系统上对应分片的文件被清理了,这个是会丢数据的,但是丢的只是这一个分片的数据。
当有分片处于red状态时,会影响到这个索引的数据写入,手动分配空分片等于在通知集群可以重新给这个索引的该分片分配一个新的空分片,然后该索引就可以正常进行数据写入了。
分配一个空的主分片:
curl -XPOST 'localhost:9200/_cluster/reroute?pretty' -H 'Content-Type: application/json' -d'
{
"commands": [
{
"allocate_empty_primary": {
"index": "original_a_20200329",
"shard": 1,
"node": "es.a.com.1",
"accept_data_loss": true
}
},
{
"allocate_empty_primary": {
"index": "original_a_20200329",
"shard": 1,
"node": "es.a.com.0",
"accept_data_loss": true
}
}
]
}
如果red索引比较多,那么一个个分片这样处理比较麻烦,用脚本进行批处理。
ii>RED-the copy of the shard is stale, allocation ids do not match(主分片数据陈旧)
可以手动将该主分片分配到es.IP上:
curl -XPOST 'localhost:9200/_cluster/reroute?pretty' -H 'Content-Type: application/json' -d'
{
"commands": [
{
"allocate_stale_primary": {
"index": "a-2020.05.21",
"shard": 1,
"node": "es.a.3",
"accept_data_loss": true
}
}
]
}
4>YELLOW-initializing_shards > 0
5>YELLOW-initializing_shards = 0
6>delayed_unassigned_shards > 0
3)索引操作
功能 | 说明 | 结果说明 |
---|---|---|
创建index | curl -X PUT localhost:9200/customer?pretty curl -X PUT localhost:9200/customer/student/1 -d '{"name":"jack","age":30,"info":"Ilove you"}' | student为type |
删除index | curl -X DELETE localhost::9200/index02 | |
查询所有索引 | curl -XGET 'localhost:9200/_cat/indices?v&pretty' | |
查询具体索引 | curl -X GET localhost:9200/bank,bank2/_search?q=*&sort=account_number:asc&pretty&ignore_unavailable=true | 1. 支持多索引查询 bank,bank2为索引 2. 使用 _all 表示所有索引 3. 支持通配符,如 test*或 *test或 te*t或 *test* 等 4. 支持排除能力,例如: test*,-test3 5. 其他参数 ignore_unavailable 多索引时,如果有索引不可用(不存在或者已经关闭)那么是否忽略该索引;默认为false。&allow_no_indices=false 允许通配符匹配索引,默认为true。expand_wildcards 查询索引的范围:open表示查询所有匹配并open的索引,closed则表示查询所有匹配的索引 format=yaml 以yaml格式响应human=true 增强可读方式。true则返回{"exists_time":"1h"} |
打开/关闭index | curl -XPOST localhost:9200/index01/_close | 同理有/_open |
获取或操作索引的别名 | curl -XPOST localhost:9200/index01/_aliases | |
创建或操作类型 | curl -XPOST localhost:9200/index01/type | |
创建或操作mapping | curl -XPOST localhost:9200/index01/_mapping | |
创建或操作设置 | curl -XPOST localhost:9200/index01/_settings | number_of_shards是不可更改的 |
刷新索引 | curl -XPOST localhost:9200/index01/_refresh | 使新加内容对搜索可见 |
刷新索引,将变动提交到lucene索引文件中,并清空elasticsearch的transaction log | curl -XPOST localhost:9200/index01/_flush | |
优化segement,主要是对segement进行合并 | curl -XPOST localhost:9200/index01/_optimize | |
获得索引的状态信息 | curl -XPOST localhost:9200/index01/_status | |
获得索引的segments的状态信息 | curl -XPOST localhost:9200/index01/_segments | |
不执行实际搜索,而返回解释信息 | curl -XPOST localhost:9200/index01/_explain | |
不执行实际搜索,根据输入的参数进行文本分析 | curl -XPOST localhost:9200/index01/_analyze | |
操作指定文档 | curl -XPOST localhost:9200/index01/type/id | |
创建一个文档,如果该文件已经存在,则返回失败 | curl -XPOST localhost:9200/index01/type/id/_create | |
更新一个文件,如果改文件不存在,则返回失败 | curl -XPOST localhost:9200/index01/type/id/_update |
4)Document操作
1>创建/全量更新Document
不存在则创建,存在则覆盖。
curl -X PUT "localhost:9200/customer/_doc/1?pretty" -H 'Content-Type: application/json' -d'
{
"name": "John Doe"
}'
- document是不可变的,如果要修改document的内容,可以通过全量替换,直接对document重新建立索引,替换里面所有的内容。
- es会将老的document标记为deleted(逻辑删除),然后新增我们给定的一个document,当我们创建越来越多的document的时候,es会在适当的时机在后台自动删除(物理删除)标记为deleted的document。
- 替换必须带上所有的field,否则其他数据会丢失。
- Elasticsearch中,并不强制要求显式的创建索引,即前面案例中,如果开发者在添加文档之前,还没有创建customer索引,那么该文档一样也会创建成功的(此时索引会被自动创建)。
未指定id时,系统会自动创建id。
POST /_bulk
{ "index": { "_index": "ecommerce", "_type":"product"}}
{ "name": "test yagao", "desc": "youxiao fangzhu"}
强制创建文档create
POST /_bulk
{ "create": { "_index": "ecommerce", "_type": "product", "_id": "4" }}
{ "test_field": "test12" }
## 查询Document
curl -X GET localhost:9200/customer/_doc/1?pretty
## 更新Document
POST /ecommerce/product/1/_update
{
"doc": {
"name": "jiaqiangban gaolujie yagao"
}
}
POST /_bulk
{ "update": { "_index": "ecommerce", "_type": "product", "_id": "4","retry_on_conflict" : 3 }}
{ "doc" : {"test_field" : "test update"} }
2>删除Document
curl -X DELETE "localhost:9200/customer?pretty"
在删除一个document之后,我们可以从侧面证明,它不是立即物理删除的,因为它的一些版本号等信息还是保留的。
POST /_bulk
{ "delete": { "_index": "ecommerce", "_type": "product", "_id": "4"}}
由于删除只需要被删除文档的ID,所以并没有对应的源文档。
bulk API按顺序执行这些操作。如果其中一个操作因为某些原因失败了,它将会继续处理后面的操作。当bulk API返回时,它将提供每个操作的状态(按照同样的顺序),所以开发者能够看到每个操作成功与否。
5)查询操作
通过post进行查询,eg:
curl -XPOST 'localhost:9200/logstash-zhifubao-2018.09.18/_search?pretty' -d '{"query": { "match_all": {} },"from": 10,"size": 10}'
下午列举了body内容:
1>term query、terms query、 range query、match query、sort
{
"query":{
"term": {"title":"你好"},
"terms":{
# json中必须包含数组
"tag":["search","nosql","hello"]
},
"range":{
"age":{
"gte":"30",
"lte":"20"
}
},
"match": {"__type": "info"},
"sort": [{ "price": "desc" }]}
}
}
2>match_all、分页查询
GET /ecommerce/product/_search
{
"query": { "match_all": {} },
## _source表示自定义返回字段。默认会返回文档的所有字段。
"_source": ["name", "price"]
## size表示返回的文档个数为1,默认为10
"size": 1,
## from表示从第10个开始查询,主要用于分页查询
"from": 10
}
3>scroll查询
- scroll原理
scroll查询快于单纯的分页查询
scroll搜索会在第一次搜索时生成视图快照,之后查询通过第一次的快照ID来查询。查询期间数据变更不会查到。
采用基于_doc(不使用_score)进行排序的方式,性能较高。如果文档不需要特定排序,可以指定按照文档创建的时间返回会使迭代更高效。
每次发送scroll请求,需要指定一个scroll参数作为时间窗口,每次搜索请求超过时间窗口会被es自动清理。 srcoll_id 的存在会耗费大量的资源来保存快照,同时会占用文件描述符。用完之后要及时清理:CLEAR_API。- 基于 scroll 解决深度分页问题
原理:查询时生成一个游标 scroll_id , 后续查询根据游标去取数据,直到结果集中返回的 hits 字段为空为止。
注意:scroll_id 的生成可以理解为建立了一个临时的历史快照,在此之后的增删改查等操作不会影响到这个快照的结果。
# 第一次查询会生成快照
GET /lib3/user/_search?scroll=1m #这一批查询在一分钟内完成
{
"query":{
"match":{}
},
"sort":[
"_doc"
],
"size":3
}
# 返回结果
{
"_scroll_id": "DnF1ZXJ5VGhIbkXIdGNoAwAAAAAAAAAdFkEwRENOVTdnUUJPWVZUd1p2WE5hV2cAAAAAAAAAHhZBMERDTIU3Z1FCT1|WVHdadIhOYVdnAAAAAAAAAB8WQTBEQ05VN2dRQk9ZVIR3WnZYTmFXZw==",
"took": 1,
"timed_out": false,
"_shards": {
"total": 1,
"successful": 1,
"failed": 0
},
"hits":{...}
}
# 第二次查询通过第一次的快照ID来查询,后面以此类推
GET /product/info/_search?scroll=DnF1ZXJ5VGhIbkXIdGNoAwAAAAAAAAAdFkEwRENOVTdnUUJPWVZUd1p2WE5hV2cAAAAAAAAAHhZBMERDTIU3Z1FCT1|WVHdadIhOYVdnAAAAAAAAAB8WQTBEQ05VN2dRQk9ZVIR3WnZYTmFXZw==
{
"query":{
"match_all":{
}
},
"sort":["_doc"]
}
# 返回结果
{
"_scroll_id": "DnF1ZXJ5VGhIbkXIdGNoAwAAAAAAAAAdFkEwRENOVTdnUUJPWVZUd1p2WE5hV2cAAAAAAAAAHhZBMERDTIU3Z1FCT1|WVHdadIhOYVdnAAAAAAAAAB8WQTBEQ05VN2dRQk9ZVIR3WnZYTmFXZw==",
"took": 106,
"_shards": {
"total": 1,
"successful": 1,
"failed": 0
},
"hits": {
"total": 22424,
"max_score": 1.0,
"hits": [{
"_index": "product",
"_type": "info",
"_id": "did-519392_pdid-2010",
"_score": 1.0,
"_routing": "519392",
"_source": {
....
}
}
]
}
}
# 删掉指定的多个 srcoll_id
DELETE /_search/scroll -d
{
"scroll_id":[
"cXVlcnlBbmRGZXRjaDsxOzg3OTA4NDpTQzRmWWkwQ1Q1bUlwMjc0WmdIX2ZnOzA7"
]
}
# 删除掉所有索引上的 scroll_id
DELETE /_search/scroll/_all
# 查询当前所有的scroll 状态
GET /_nodes/stats/indices/_search?pretty
# 返回结果
{
"cluster_name" : "200.200.107.232",
"nodes" : {
"SC4fYi0CT5mIp274ZgH_fg" : {
"timestamp" : 1514346295736,
"name" : "200.200.107.232",
"transport_address" : "200.200.107.232:9300",
"host" : "200.200.107.232",
"ip" : [ "200.200.107.232:9300", "NONE" ],
"indices" : {
"search" : {
"open_contexts" : 0,
"query_total" : 975758,
"query_time_in_millis" : 329850,
"query_current" : 0,
"fetch_total" : 217069,
"fetch_time_in_millis" : 84699,
"fetch_current" : 0,
"scroll_total" : 5348,
"scroll_time_in_millis" : 92712468,
"scroll_current" : 0
}
}
}
}
}
4>基于 search_after 实现深度分页
search_after:
ES5.0 提出,它提供一个活动的游标,通过上一次查询最后一条数据来进行下一次查询;在分页请求的过程中,如果有索引数据的增删改查,这些变更也会实时的反映到游标上。
注意:如果 search_after 中的关键字为654,那么654323的文档也会被搜索到,所以在选择 search_after 的排序字段时需要谨慎,可以使用比如文档的id或者时间戳等。
search_after 适用于深度分页+ 排序,因为每一页的数据依赖于上一页最后一条数据,所以无法跳页请求。
返回的始终是最新的数据,在分页过程中数据的位置可能会有变更。这种分页方式更加符合 moa 的业务场景。
- 第一页的请求和正常的请求一样。
GET /order/info/_search
{
"size": 10,
"query": {
"match_all" : {
}
},
"sort": [
{"date": "asc"}
]
}
# 返回结果
{
"_index": "zmrecall",
"_type": "recall",
"_id": "60310505115909",
"_score": null,
"_source": {
...
"date": 1545037514
},
"sort": [
1545037514
]
}
- 第二页的请求,使用第一页返回结果的最后一个数据的值,加上 search_after 字段来取下一页。注意:使用 search_after 的时候要将 from 置为 0 或 -1。
curl -XGET 127.0.0.1:9200/order/info/_search
{
"size": 10,
"query": {
"match_all" : {
}
},
"search_after": [1463538857], # 这个值与上次查询最后一条数据的sort值一致,支持多个
"sort": [
{"date": "asc"}
]
}
3>Bool query
{
"bool":{
"must":{
"term":{"user":"lucy"}
},
"filter":{
"term":{"tag":"teach"}
},
"should":[
{"term":{"tag":"wow"}},
{"term":{"tag":"elasticsearch"}}
],
"mininum_should_match":1,
"boost":1.0
}
}
4>match_phrase_prefix query
{
"query":{
"match_phrase_prefix":{
"title":{
"query":"crime punish",
"slop":1
}
}
}
}
5>multi match 多值匹配查询
{
"query": {
"multi_match": {
"query": "运动 上衣",
"fields": [
"brandName^100",
"brandName.brandName_pinyin^100",
"brandName.brandName_keyword^100",
"sortName^80",
"sortName.sortName_pinyin^80",
"productName^60",
"productKeyword^20"
],
"type": <multi-match-type>,
"operator": "AND"
}
}
}
6>match_phrase(短语查询)
场景:精确查找所有字段。
match(全文检索)会将输入的搜索串拆解开来,去倒排索引里面去一一匹配,只要能匹配上任意一个拆解后的单词,就可以作为结果返回。
match_phrase要求输入的搜索串,必须在指定的字段文本中,完全包含一模一样的,才可以算匹配,才能作为结果返回。
GET /ecommerce/product/_search
{
"query" : {
"match_phrase" : {
"producer" : "yagao producer"
}
}
}
7>批量查询
优点:能够大大减少网络的请求次数,缩减网络开销。
自定义设置index、type以及document id:(id为1的没有查到(found为false))
GET /_mget
{
"docs" : [
{
"_index" : "ecommerce",
"_type" : "product",
"_id" : 1
},
{
"_index" : "ecommerce",
"_type" : "product",
"_id" : 2
}
]
}
在对应的index、type下进行批量查询:(注意:在ElasticSearch6.0以后一个index下只能有一个type,否则会报错)
GET /ecommerce/product/_mget
{
"ids": [2, 3]
}
或者:
GET /ecommerce/product/_mget
{
"docs" : [
{
"_id" : 2
},
{
"_id" : 3
}
]
}
8>通配符查询
{
'query':{
'wildcard':{
'title':'cr?me'
}
}
}
9>正则表达式查询
{
'query':{
'regex':{
'title':{
'value':'cr.m[ae]',
'boost':10.0
}
}
}
}
10>query_string
{
'query':{
'query_string':{
'query':'title:crime^10 +title:punishment -otitle:cat +author:(+Fyodor +dostoevsky)'
}
}
}
11>exists
过滤掉字段不存在或者为null的文档。
GET account/account/_search
{
"query": {
"bool": {
"must": [
{
"exists": {
"field": "sex"
}
}
]
}
}
}
6)更新删除操作
#更新部分字段
crul –XPUT http:localhost:9200/shb01/student/1/_update?version=1
–d ‘{“doc”:{“name”:”updatename”}
#根据id删除
curl -XDELETE http://localhost:9200/shb01/student/AVad05EExskBS1Rg2tdq
#删除所有的索引库中名称为tom的文档
curl -XDELETE http://localhost:9200/_all/_query?q=name:tom
7)聚合查询
- bucket(桶):group by 分组之后,相同的数据放进一个bucket。
- metric(度量/指标):对一个数据分组执行的统计。如:avg\max\min
group by缺点:
- 涉及group by的查询会降低查询速率
- group by之后无法拿到其它信息(通过后文讲解的tophits可以拿到)
- group by之后无法排序
terms聚合
terms根据字段值项分组聚合。field按什么字段分组,size指定返回多少个分组,shard_size指定每个分片上返回多少个分组,order排序方式。可以指定include和exclude正则筛选表达式的值,指定missing设置缺省值。
【terms】 java api见【max/min/avg/sum/stats】中的例子。
计算每个tag下的商品数量:
GET /ecommerce/product/_search
{
"size": 0, //size=0,表示只获取聚合结果,而不要执行聚合的原始数据。
"aggs": { //aggs:固定语法,要对一份数据执行分组聚合操作
"all_tags": { //all_tags:自定义对每个aggs取名。
"terms": { "field": "tags" } //terms根据字段的值进行分组;field:根据指定的字段的值进行分组将文本
}
}
}
返回结果:
{
"took": 53,
"timed_out": false,
"_shards": {
"total": 5,
"successful": 5,
"failed": 0
},
"hits": {
"total": 2,
"max_score": 0,
"hits": []
},
"aggregations": {
"all_tags": {
"doc_count_error_upper_bound": 0,
"sum_other_doc_count": 0,
"buckets": [
{
"key": "fangzhu",
"doc_count": 2
},
{
"key": "meibai",
"doc_count": 2
}
]
}
}
}
hits.hits:我们指定了size是0,所以hits.hits就是空的,否则会把执行聚合的那些原始数据给你返回回来
aggregations:聚合结果
all_tags:我们指定的某个聚合的名称
buckets:根据我们指定的field划分出的buckets
key:每个bucket对应的分组字段的值
doc_count:这个bucket分组内,有多少个数据
默认的排序规则:按照doc_count降序排序
max/min/avg/sum/stats
stats:bucket,terms,自动就会有一个doc_count,就相当于是数量。
avg:avg aggs,求平均值
max:求一个bucket内,指定field值最大的那个数据
min:求一个bucket内,指定field值最小的那个数据
sum:求一个bucket内,指定field值的总和先分组,再算每组的平均值
GET /ecommerce/product/_search
{
"size": 0,
"aggs" : {
"group_by_tags" : {
"terms" : { "field" : "tags" },
"aggs" : {
"avg_price": { "avg": { "field": "price" } },
"min_price" : { "min": { "field": "price"} },
"max_price" : { "max": { "field": "price"} },
"sum_price" : { "sum": { "field": "price" } }
}
}
}
{
"aggs":{
"avg_fees":{
"avg":{
"field":"fees"
}
}
}
}
聚合操作主要是调用了SearchRequestBuilder的addAggregation方法,通常是传入一个TermsBuilder。
多字段上的聚合操作需要用到子聚合(subAggregation),子聚合调用TermsBuilder的subAggregation方法,可以添加的子聚合有TermsBuilder、SumBuilder、AvgBuilder、MaxBuilder、MinBuilder等常见的聚合操作。
从实现上来讲,SearchRequestBuilder在内部保持了一个私有的 SearchSourceBuilder实例, SearchSourceBuilder内部包含一个List,每次调用addAggregation时会调用 SearchSourceBuilder实例,添加一个AggregationBuilder。
同样的,TermsBuilder也在内部保持了一个List,调用addAggregation方法(来自父类addAggregation)时会添加一个AggregationBuilder。
聚合操作
例如要计算每个球队年龄最大/最小/总/平均的球员年龄,如果使用SQL语句,应表达如下:
select team, max(age) as max_age from player group by team;
ES的java api:
TermsBuilder teamAgg= AggregationBuilders.terms("player_count ").field("team");
MaxBuilder ageAgg= AggregationBuilders.max("max_age").field("age");
sbuilder.addAggregation(teamAgg.subAggregation(ageAgg));
SearchResponse response = sbuilder.execute().actionGet();
子聚合
例如要计算每个球队球员的平均年龄,同时又要计算总年薪,如果使用SQL语句,应表达如下:
select team, avg(age)as avg_age, sum(salary) as total_salary from player group by team;
ES的java api:
TermsBuilder teamAgg= AggregationBuilders.terms("team");
AvgBuilder ageAgg= AggregationBuilders.avg("avg_age").field("age");
SumBuilder salaryAgg= AggregationBuilders.avg("total_salary ").field("salary");
sbuilder.addAggregation(teamAgg.subAggregation(ageAgg).subAggregation(salaryAgg));
SearchResponse response = sbuilder.execute().actionGet();
一次计算出count max min avg sum
public void stats(){
SearchResponse response = client.prepareSearch(indexName).setTypes(typeName)
.addAggregation(AggregationBuilders.stats("ageAgg").field("age"))
.get();
Stats ageAgg = response.getAggregations().get("ageAgg");
System.out.println("总数:"+ageAgg.getCount());
System.out.println("最小值:"+ageAgg.getMin());
System.out.println("最大值:"+ageAgg.getMax());
System.out.println("平均值:"+ageAgg.getAvg());
System.out.println("和:"+ageAgg.getSum());
}
group by多个field
例如要计算每个球队每个位置的球员数,如果使用SQL语句,应表达如下:
select team, position, count(*) as pos_count from player group by team, position;
ES的java api:
TermsBuilder teamAgg= AggregationBuilders.terms("player_count ").field("team");
TermsBuilder posAgg= AggregationBuilders.terms("pos_count").field("position");
sbuilder.addAggregation(teamAgg.subAggregation(posAgg));
SearchResponse response = sbuilder.execute().actionGet();
group by/count
例如要计算每个球队的球员数,如果使用SQL语句,应表达如下:
select team, count(*) as player_count from player group by team;
TermsBuilder teamAgg= AggregationBuilders.terms("player_count ").field("team");
sbuilder.addAggregation(teamAgg);
SearchResponse response = sbuilder.execute().actionGet();
CountResponse response = client.prepareCount("library")
.setQuery(QueryBuilders.termQuery("title", "elastic"))
.execute().actionGet();
聚合后对Aggregation结果排序
例如要计算每个球队总年薪,并按照总年薪倒序排列,如果使用SQL语句,应表达如下:
select team, sum(salary) as total_salary from player group by team order by total_salary desc;
ES的java api:
TermsBuilder teamAgg= AggregationBuilders.terms("team").order(Order.aggregation("total_salary ", false);
SumBuilder salaryAgg= AggregationBuilders.avg("total_salary ").field("salary");
sbuilder.addAggregation(teamAgg.subAggregation(salaryAgg));
SearchResponse response = sbuilder.execute().actionGet();
需要特别注意的是,排序是在TermAggregation处执行的,Order.aggregation函数的第一个参数是aggregation的名字,第二个参数是boolean型,true表示正序,false表示倒序。
Aggregation结果条数的问题
默认情况下,search执行后,仅返回10条聚合结果,如果想反悔更多的结果,需要在构建TermsBuilder 时指定size:
TermsBuilder teamAgg= AggregationBuilders.terms("team").size(15);
Aggregation结果的解析/输出
得到response后:
Map<String, Aggregation> aggMap = response.getAggregations().asMap();
StringTerms teamAgg= (StringTerms) aggMap.get("keywordAgg");
Iterator<Bucket> teamBucketIt = teamAgg.getBuckets().iterator();
while (teamBucketIt .hasNext()) {
Bucket buck = teamBucketIt .next(); //分桶
//球队名
String team = buck.getKey();
//记录数
long count = buck.getDocCount();
//得到所有子聚合
Map subaggmap = buck.getAggregations().asMap();
//avg值获取方法
double avg_age= ((InternalAvg) subaggmap.get("avg_age")).getValue();
//sum值获取方法
double total_salary = ((InternalSum) subaggmap.get("total_salary")).getValue();
//...
//max/min以此类推
}
Top Hits Aggregation
i> 作用
Top Hits聚合主要用于桶聚合后查询分组后的其它数据。
比如对于下表,通过max(time)group by ip
进行分组后,我们还想知道每一组数据hostname等其它字段内容,则需要使用Top Hits,再每个bucket中查询对应的数据,具体代码如下:
score | ip | hostname | time |
---|
TermsAggregationBuilder depIpGroup = AggregationBuilders.terms("group_by_ip").field("dip").size(10000);//一次最多拿到10000条数据,要拿到更多的数据参考后文scroll的相关讲解
TopHitsAggregationBuilder detail = AggregationBuilders.topHits("detail").size(1);//用于拿到分组以外的其它详情数据。size来确定数量,默认返回3条数据。sort用于组内排序。
MaxAggregationBuilder maxTime = AggregationBuilders.max("max_time").field("time");
SearchRequestBuilder searchRequestBuilder = client
.prepareSearch(indexExistsList.toArray(new String[indexNameList.size()]))//通过变长数组查询多个index
.setTypes(indexType).addAggregation(depIpGroup.subAggregation(maxTime).subAggregation(detail));
SearchResponse searchResponse = searchRequestBuilder
.execute()
.actionGet();
Terms ipTerms = searchResponse.getAggregations().get("group_by_ip");
for (Terms.Bucket bucket : ipTerms.getBuckets()) {//分桶
ListEntity listEntity = new ListEntity();
listEntity.setIpAddress(bucket.getKey().toString());
TopHits topHits = bucket.getAggregations().get("detail");
SearchHit hit = topHits.getHits().getHits()[0];//????没看懂原理,先这样用吧。返回的是一个id不同、其它数据相同的数组,由size决定长度。
listEntity.setHostname(hit.getSource().get("hostname").toString());
listEntity.setScore(Integer.parseInt(hit.getSource().get("score").toString()));
dataList.add(listEntity);
}
cardinality去重
{
"size": 0,
"aggs": {
"count_type": {
"cardinality": {
"field": "__type"
}
}
}
}
cardinality
percentiles百分比
percentiles对指定字段(脚本)的值按从小到大累计每个值对应的文档数的占比(占所有命中文档数的百分比),返回指定占比比例对应的值。默认返回[ 1, 5, 25, 50, 75, 95, 99 ]分位上的值。
{
"size": 0,
"aggs": {
"age_percents":{
"percentiles": {
"field": "age",
"percents": [
1,
5,
25,
50,
75,
95,
99
]
}
}
}
}
{
"size": 0,
"aggs": {
"states": {
"terms": {
"field": "gender"
},
"aggs": {
"banlances": {
"percentile_ranks": {
"field": "balance",
"values": [
20000,
40000
]
}
}
}
}
}
percentiles rank
统计小于等于指定值的文档比。
{
"size": 0,
"aggs": {
"tests": {
"percentile_ranks": {
"field": "age",
"values": [
10,
15
]
}
}
}
}
filter聚合
场景:对不同的bucket下的aggs,进行filter。
filter对满足过滤查询的文档进行聚合计算,在查询命中的文档中选取过滤条件的文档进行聚合,先过滤在聚合。
如果放query里面的filter,是全局的,会对所有的数据都有影响。
但是,如果,比如说,你要统计,长虹电视,最近1个月的平均值; 最近3个月的平均值; 最近6个月的平均值,用bucket filter。
{
"size": 0,
"aggs": {
"agg_filter":{
"filter": {
"match":{"gender":"F"}
},
"aggs": {
"avgs": {
"avg": {
"field": "age"
}
}
}
}
}
}
filtters聚合
多个过滤组聚合计算。
{
"size": 0,
"aggs": {
"message": {
"filters": {
"filters": {
"errors": {
"exists": {
"field": "__type"
}
},
"warring":{
"term": {
"__type": "info"
}
}
}
}
}
}
}
range聚合
{
"aggs": {
"agg_range": {
"range": {
"field": "cost",
"ranges": [
{
"from": 50,
"to": 70
},
{
"from": 100
}
]
},
"aggs": {
"bmax": {
"max": {
"field": "cost"
}
}
}
}
}
}
date_range聚合
{
"aggs": {
"date_aggrs": {
"date_range": {
"field": "accepted_time",
"format": "MM-yyy",
"ranges": [
{
"from": "now-10d/d",
"to": "now"
}
]
}
}
}
}
date_histogram聚合(时间直方图聚合)
按天、月、年等进行聚合统计。可按 year (1y), quarter (1q), month (1M), week (1w), day (1d), hour (1h), minute (1m), second (1s) 间隔聚合或指定的时间间隔聚合。
{
"aggs": {
"sales_over_time": {
"date_histogram": {
"field": "accepted_time",
"interval": "quarter",
"min_doc_count" : 0, //可以返回没有数据的月份
"extended_bounds" : { //强制返回数据的范围
"min" : "2014-01-01",
"max" : "2014-12-31"
}
}
}
}
}
missing聚合
{
"aggs": {
"account_missing": {
"missing": {
"field": "__type"
}
}
}
}
global bucket
将所有数据纳入聚合的scope,而不管之前的query。
aggregation,scope,一个聚合操作,必须在query的搜索结果范围内执行
出来两个结果,一个结果,是基于query搜索结果来聚合的; 一个结果,是对所有数据执行聚合的。
GET /tvs/sales/_search
{
"size": 0,
"query": {
"term": {
"brand": {
"value": "长虹"
}
}
},
"aggs": {
"single_brand_avg_price": {
"avg": {
"field": "price"
}
},
"all": {
"global": {},
"aggs": {
"all_brand_avg_price": {
"avg": {
"field": "price"
}
}
}
}
}
}
返回结果:
{
"took": 4,
"timed_out": false,
"_shards": {
"total": 5,
"successful": 5,
"failed": 0
},
"hits": {
"total": 3,
"max_score": 0,
"hits": []
},
"aggregations": {
"all": {
"doc_count": 8,
"all_brand_avg_price": {
"value": 2650
}
},
"single_brand_avg_price": {
"value": 1666.6666666666667
}
}
}
single_brand_avg_price:就是针对query搜索结果,执行的,拿到的,就是长虹品牌的平均价格
all.all_brand_avg_price:拿到所有品牌的平均价格
top_hits 按搜索结果聚合
top_hits 获取前几个doc_(即分组内前几个doc_,由size指定,默认为3个)
source 返回指定field(主要用于group by之后不能查看其它字段详情)。
GET /ecommerce/product/_search
{
"size": 0,
"aggs" : {
"group_by_tags" : {
"terms" : { "field" : "tags" },
"aggs" : {
"top_tags": {
"top_hits": {
"_source": {
"include": "name"
},
"size": 1
}
}
}
}
}
}
collect_mode 子聚合计算
depth_first
直接进行子聚合的计算
计算每个tag下的商品的平均价格,并且按照平均价格降序排序:
"order": { "avg_price": "desc" }
GET /ecommerce/product/_search
{
"size": 0,
"aggs" : {
"all_tags" : {
"terms" : { "field" : "tags", "collect_mode" : "breadth_first", "order": { "avg_price": "desc" } },
"aggs" : {
"avg_price" : {
"avg" : { "field" : "price" }
}
}
}
}
}
vii> breadth_first
先计算出当前聚合的结果,针对这个结果在对子聚合进行计算。
"ranges": [{},{}]
按照指定的价格范围区间进行分组,然后在每组内再按照tag进行分组,最后再计算每组的平均价格:
GET /ecommerce/product/_search
{
"size": 0,
"aggs": {
"group_by_price": {
"range": {
"field": "price",
"ranges": [
{
"from": 0,
"to": 20
},
{
"from": 20,
"to": 40
},
{
"from": 40,
"to": 50
}
]
},
"aggs": {
"group_by_tags": {
"terms": {
"field": "tags"
},
"aggs": {
"average_price": {
"avg": {
"field": "price"
}
}
}
}
}
}
}
}
histogram
类似于terms,也是进行bucket分组操作,接收一个field,按照这个field的值的各个范围区间,进行bucket分组操作
interval:10,划分范围,010,1020,20~30
GET /ecommerce/product/_search
{
"size" : 0,
"aggs":{
"price":{
"histogram":{
"field": "price",
"interval": 10
},
"aggs":{
"revenue": {
"sum": {
"field" : "price"
}
}
}
}
}
}
date histogram
按照我们指定的某个date类型的日期field,以及日期interval,按照一定的日期间隔,去划分bucket
date interval = 1m,
2017-01-01~2017-01-31,就是一个bucket
2017-02-01~2017-02-28,就是一个bucket
然后会去扫描每个数据的date field,判断date落在哪个bucket中,就将其放入那个bucket
min_doc_count:即使某个日期interval,2017-01-01~2017-01-31中,一条数据都没有,那么这个区间也是要返回的,不然默认是会过滤掉这个区间的
extended_bounds,min,max:划分bucket的时候,会限定在这个起始日期,和截止日期内
GET /tvs/sales/_search
{
"size" : 0,
"aggs": {
"sales": {
"date_histogram": {
"field": "sold_date",
"interval": "month",
"format": "yyyy-MM-dd",
"min_doc_count" : 0,
"extended_bounds" : {
"min" : "2016-01-01",
"max" : "2017-12-31"
}
}
}
}
}
8)基于bulk的增删改
Elasticsearch也提供了相关操作的批处理功能,这些批处理功能通过使用_bulk API实现。通过批处理可以非常高效的完成多个文档的操作,同时可以减少不必要的网络请求。
bulk语法:
- delete:删除一个文档,只要1个json串就可以了
- create:PUT /index/type/id/_create,强制创建
- index:普通的put操作,可以是创建文档,也可以是全量替换文档
- update:执行的partial update操作
注意点:
- bulk api对json的语法有严格的要求,除了delete外,每一个操作都要两个json串,且每个json串内不能换行,非同一个json串必须换行,否则会报错;
- bulk操作中,任意一个操作失败,是不会影响其他的操作的,但是在返回结果里,会告诉你异常日志;
bulk api奇特的json格式
目前处理流程:
- 直接按照换行符切割json,不用将其转换为json对象,不会出现内存中的相同数据的拷贝;
- 对每两个一组的json,读取meta,进行document路由;
- 直接将对应的json发送到node上去;
换成良好json格式的处理流程:
- 将json数组解析为JSONArray对象,这个时候,整个数据,就会在内存中出现一份一模一样的拷贝,一份数据是json文本,一份数据是JSONArray对象;
- 解析json数组里的每个json,对每个请求中的document进行路由;
- 为路由到同一个shard上的多个请求,创建一个请求数组;
- 将这个请求数组序列化;
- 将序列化后的请求数组发送到对应的节点上去;
9)Mapping和Template
类似db中的schema,主要作用:
- 定义索引中的字段的名称
- 定义字段的数据类型,比如字符串、数字、布尔
- 字段,倒排索引的相关配置,比如设置某个字段为不被索引、记录 position 等
1>es数据类型
ES 7.x.
分类 | 类型 | 关键字 | 说明 | 备注 |
---|---|---|---|---|
核心类型 | 字符串类型 | text | 会被 Lucene 分词器(Analyzer)处理为一个个词项,并使用 Lucene 倒排索引存储; 不能被用于排序 | 适用于需要被全文检索的字段,例如新闻正文、邮件内容等比较长的文字 |
keyword | 可以用于过滤、排序、聚合检索,也可以用于精确查询 | 适合简短、结构化字符串,例如主机名、姓名、商品名称等 | ||
数字类型 | long | 字段长度越短,搜索效率越高 | ||
integer | ||||
short | ||||
byte | ||||
double | ||||
float | ||||
half_float | ||||
scaled_float | ||||
日期类型 | date | 主要分为: 1. 格式化的日期字符串,例如 2020-03-17 00:00、2020/03/17 2.时间戳(和 1970-01-01 00:00:00 UTC 的差值),单位毫秒或者秒 | 即使是格式化的日期字符串,ES 底层依然采用的是时间戳的形式存储。 | |
date_nanos | ||||
布尔类型 | boolean | |||
二进制类型 | binary | 接受 BASE64 编码的字符串,默认 store 属性为 false; 不可以被搜索 | ||
范围类型 | integer_range | |||
float_range | ||||
long_range | ||||
double_range | ||||
date_range | ||||
复杂类型 | 对象类型 | object | ||
嵌套类型 | nested | |||
地理类型 | 经纬度类型 | geo_point | ||
地理区域类型 | geo_shape | |||
特殊类型 | IP类型 | ip | ||
JOIN类型 | join |
2>模板类型
dynamic=true
动态映射:动态添加新的字段(或缺省)。
文档可索引、字段可索引、Mapping被更新。
Dynamic Mapping机制
当字段无法匹配导Mapping时,ES 会自动根据文档信息来判断字段合适的类型。影响:数据可能判断不准确、自动推断消耗性能。一般建议使用strict模式。
dynamic=false
静态映射:忽略新的字段。
存在新增字段的数据写入,该数据可以被索引,但是新增字段被丢弃。
不会主动的添加新的映射关系,只作为查询结果出现在查询中。dynamic=strict
严格模式:如果遇到新的字段,就抛出异常。
数据写入直接出错。
3>使用
在 ES 早期版本,一个索引下是可以有多个 Type 的,从 7.0 开始,一个索引只有一个 Type(文档),也可以说一个 Type 有一个 Mapping 定义(统一用_doc
即可)。
- 模板全量查询
GET 127.0.0.1:9200/_cat/templates?v&name=xx_template
- 模板具体查询
GET 127.0.0.1:9200/_template/xx_template
3>模板修改
参数说明:
参数 | 说明 | 默认值 | 备注 |
---|---|---|---|
Dynamic | 模板类型 | 具体见上文 | |
index | 控制当前字段是否被索引 | true | true-该字段可搜索 |
index_options | 控制倒排索引记录的内容 | text字段默认为positions 其他类型默认为 doc | 记录内容越多,占用存储空间越大: 1. doc 只记录 doc id 2. reqs :记录 doc id 和 term frequencies 3. positions :记录 doc id 、term frequencies 和 term position 4. offsets :记录 doc id 、term frequencies 、term position 和 character offects |
PUT 127.0.0.1:9200/_template/xx_template
{
"order": 0,
"template": "xx_template_*",
"settings": {
"index": {
"mapping": {
"total_fields": {
"limit": "3000"
}
},
"routing": {
"allocation": {
"total_shards_per_node": "3"
}
},
"refresh_interval": "120s",
"number_of_shards": "3",
"translog": {
"flush_threshold_size": "150mb",
"sync_interval": "120s",
"durability": "async"
},
"merge": {
"policy": {
"max_merged_segment": "500m"
}
},
"max_result_window": "50000",
"number_of_replicas": "1"
}
},
"mappings": {
"_doc": {
"dynamic_templates": [
{
"strings": {
"mapping": {
"type": "keyword"
},
"match_mapping_type": "string"
}
}
],
"properties": {
"timestamp": {
"type": "long"
}
}
}
},
"aliases": {}
}
4,sql api
支持sql查询,将es结构对应关系数据库进行sql的查询。
1)查询语法
低版本支持GET接口:
http://127.0.0.1:9200/_sql?sql=select count(*) from abc_index*
高版本不再支持GET,仅支持POST:
http://127.0.0.1:9200/_sql
body:{"sql": "select * from student"}
2)缺点
仅用于验证es查询结果,因为sql的执行在内存中进行,非常耗时。
3)举例
1>term query
http://ip:9200/indexName/_search?q=title:你好
5,JAVA API
1)集群健康信息查看
ClusterHealthResponse response = client.admin().cluster()
.prepareHealth("library")
.execute().actionGet();
2)TransportClient
import org.elasticsearch.client.transport.TransportClient;
import org.elasticsearch.common.settings.Settings;
Settings settings = Settings.builder()
.put("cluster.name", "elasticsearch").build();
TransportClient client == new PreBuiltTransportClient(settings). addTransportAddress(new TransportAddress(InetAddress.getByName("XXX.XXX.XX.XX"), 9300));
3)查询Document
queryBuilder = QueryBuilders.matchAllQuery().boost(11f).normsField("title");
boost参数被用来增加一个子句的相对权重(当boost大于1时),或者减小相对权重(当boost介于0到1时),但是增加或者减小不是线性的。换言之,boost设为2并不会让最终的_score加倍。
相反,新的_score会在适用了boost后被归一化(Normalized)。每种查询都有自己的归一化算法(Normalization Algorithm)。但是能够说一个高的boost值会产生一个高的_score。
如果你在实现你自己的不基于TF/IDF的相关度分值模型并且你需要对提升过程拥有更多的控制,你可以使用function_score查询,它不通过归一化步骤对文档的boost进行操作。
4)判断index是否存在
在es查询之前,判断index是否存在,以免抛出异常。
private Client client;
private void init() throws Exception{
Settings settings = Settings.settingsBuilder().put("cluster.name", "log-test")
.build();
client = TransportClient.builder().settings(settings)
.addPlugin(DeleteByQueryPlugin.class)
.build()
.addTransportAddress(new InetSocketTransportAddress(InetAddress.getByName("127.0.0.1"), 9300))
.addTransportAddress(new InetSocketTransportAddress(InetAddress.getByName("127.0.0.1"), 9300));
}
public boolean indexExists(String index){
IndicesExistsRequest request = new IndicesExistsRequest(index);
IndicesExistsResponse response = client.admin().indices().exists(request).actionGet();
if (response.isExists()) {
return true;
}
return false;
}
5)索引查询操作
1>多索引检索、term query、分页
SearchResponse response = client.prepareSearch("index1", "index2")
.setTypes("type1", "type2")
.setSearchType(SearchType.DFS_QUERY_THEN_FETCH)
.setQuery(QueryBuilders.termQuery("multi", "test")) //term Query
// setPostFilter:过滤条件不影响聚合(agg)结果,而只是改变hits结果
.setPostFilter(QueryBuilders.termQuery("age", 12))
.setFrom(10) //跳过前10个文档
.setSize(20) //获取20个文档
.setExplain(true)
.get();
2>range query
QueryBuilders.rangeQuery("age").from(12).to(18)
QueryBuilders.rangeQuery("time").gte(start).lt(end)
3>查询总数获取:hits
response.getHits().totalHits()//SearchResponse 可以统计当前匹配到的结果数
4>Bool query、 sort、fetchSource、prefixQuery
sourceBuilder
.query(QueryBuilders.boolQuery()
.must(QueryBuilders.termsQuery("age", Arrays.asList(0, 1)))
.mustNot(QueryBuilders.prefixQuery("name", "未"))
)
.sort("time", SortOrder.ASC)
//过滤需要哪些行
.fetchSource(new String[]{"name", "age", "time"}, null)
.size(10000);
5> operator、zeroTermsQuery
queryBuilder = QueryBuilders
.operator(Operator.AND)
.zeroTermsQuery(ZeroTermsQuery.ALL);
6)滚动(翻页)查询
滚动搜索(Scroll API)
String scrollId = "";
QueryBuilder queryBuilder = QueryBuilders.matchAllQuery();
SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();
sourceBuilder.query(queryBuilder).size(10000);
SearchRequest request = Requests.searchRequest(indexName);
request.scroll("1s");
request.source(sourceBuilder);
SearchResponse response = client.search(request).actionGet();
severityCount = deelHits(severityCount, response.getHits());
scrollId = response.getScrollId();
while (true) {
SearchScrollRequestBuilder searchScrollRequestBuilder = client.prepareSearchScroll(scrollId);
searchScrollRequestBuilder.setScroll("1s");
// 请求
SearchResponse response1 = searchScrollRequestBuilder.get();
SearchHits hits = response1.getHits();
if (hits.getHits().length == 0) {
break;
}else {
severityCount = deelHits(severityCount, hits); //hit.getSource().get("detail").toString()读取数据
//下一批处理
scrollId = response1.getScrollId();
}
}
public class Scroll {
public static void main(String[] args) {
try{
long startTime = System.currentTimeMillis();
/*创建客户端*/
//client startup
//设置集群名称
Settings settings = Settings.builder()
.put("cluster.name", "elsearch")
.put("client.transport.sniff", true)
.build();
//创建client
TransportClient client = new PreBuiltTransportClient(settings)
.addTransportAddress(new InetSocketTransportAddress(
InetAddress.getByName("54.223.232.95"),9300));
List<String> result = new ArrayList<>();
String scrollId = "";
//第一次请求
SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();
//TODO: 设置查询条件
RangeQueryBuilder rangequerybuilder = QueryBuilders
.rangeQuery("inputtime")
.from("2016-12-14 02:00:00").to("2016-12-14 07:59:59");
sourceBuilder.query(QueryBuilders.boolQuery()
.must(QueryBuilders
.matchPhraseQuery("pointid","W3.UNIT1.10HFC01CT013"))
.must(rangequerybuilder))
.size(100)//如果开启游标,则滚动获取
.sort("inputtime", SortOrder.ASC);
//查询
SearchRequest request = Requests.searchRequest("pointdata");
request.scroll("2m");
request.source(sourceBuilder);
SearchResponse response = client.search(request).actionGet();
//TODO:处理数据
SearchHits hits = response.getHits();
for(int i = 0; i < hits.getHits().length; i++) {
//System.out.println(hits.getHits()[i].getSourceAsString());
result.add(hits.getHits()[i].getSourceAsString());
}
//记录滚动ID
scrollId = response.getScrollId();
while(true){
//后续的请求
//scrollId = query.getScollId();
SearchScrollRequestBuilder searchScrollRequestBuilder = client
.prepareSearchScroll(scrollId);
// 重新设定滚动时间
//TimeValue timeValue = new TimeValue(30000);
searchScrollRequestBuilder.setScroll("2m");
// 请求
SearchResponse response1 = searchScrollRequestBuilder.get();
//TODO:处理数据
SearchHits hits2 = response1.getHits();
if(hits2.getHits().length == 0){
break;
}
for(int i = 0; i < hits2.getHits().length; i++) {
result.add(hits2.getHits()[i].getSourceAsString());
}
//下一批处理
scrollId = response1.getScrollId();
}
System.out.println(result.size());
long endTime = System.currentTimeMillis();
System.out.println("Java程序运行时间:" + (endTime - startTime) + "ms");
}catch(Exception e){
e.printStackTrace();
}
}
7)RestHighLevelClient API
1>依赖
<dependency>
<groupId>org.elasticsearch</groupId>
<artifactId>elasticsearch</artifactId>
</dependency>
<dependency>
<groupId>org.elasticsearch.client</groupId>
<artifactId>elasticsearch-rest-client</artifactId>
</dependency>
<dependency>
<groupId>org.elasticsearch.client</groupId>
<artifactId>elasticsearch-rest-high-level-client</artifactId>
</dependency>
2>初始化连接 RestHighLevelClient
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.http.HttpHost;
import org.apache.http.impl.nio.reactor.IOReactorConfig;
import org.elasticsearch.client.RequestOptions;
import org.elasticsearch.client.RestClient;
import org.elasticsearch.client.RestHighLevelClient;
@Slf4j
@RequiredArgsConstructor
public class ESConfiguration {
/**
* IO线程数,即为处理IO请求和数据传输的线程数
*/
private static final int ioThreadCount = 10;
/**
* 套接字超时时间,即等待请求响应的最长时间
*/
private static final int socketTimeoutMs = 60000;
/**
* 连接超时时间,即客户端从创建请求到建立连接的最长时间
*/
private static final int connectTimeoutMs = 5000;
/**
* 连接请求超时时间,即从连接池获取连接的最长时间
*/
private static final int connectionRequestTimeoutMs = 5000;
private final static RequestOptions options = RequestOptions.DEFAULT;
public static RestHighLevelClient getClient() {
//ElasticSearch 连接地址地址
HttpHost[] httpHosts = getElasticSearchHttpHosts();
return new RestHighLevelClient(
RestClient.builder(httpHosts)
.setRequestConfigCallback(
builder -> builder.setConnectTimeout(connectTimeoutMs)
.setSocketTimeout(socketTimeoutMs)
.setConnectionRequestTimeout(connectionRequestTimeoutMs))
.setHttpClientConfigCallback(
builder -> builder.setDefaultIOReactorConfig(IOReactorConfig.custom()
.setIoThreadCount(ioThreadCount)
.setConnectTimeout(connectTimeoutMs)
.setSoTimeout(socketTimeoutMs)
.build()))
);
}
/**
* ElasticSearch 连接地址
* 多个逗号分隔
* 示例:127.0.0.1:9201,127.0.0.1:9202,127.0.0.1:9203
*/
private static HttpHost[] getElasticSearchHttpHosts() {
String ip = String.format("test:%s", getEsIp());
String[] esNode = {ip};
String[] hosts = new String[esNode.length];
for (int i = 0; i < esNode.length; i++) {
hosts[i] = esNode[i].split(":")[1] + ":9200";
}
HttpHost[] httpHosts = new HttpHost[hosts.length];
for (int i = 0; i < httpHosts.length; i++) {
String host = hosts[i];
host = host.replaceAll("http://", "").replaceAll("https://", "");
httpHosts[i] = new HttpHost(host.split(":")[0], Integer.parseInt(host.split(":")[1]), "http");
}
return httpHosts;
}
}
如果需要es验证:
public static RestHighLevelClient restHighLevelClient() {
//ElasticSearch 连接地址地址
HttpHost[] httpHosts = getElasticSearchHttpHosts("127.0.0.1:9200");
RestClientBuilder restClientBuilder = RestClient.builder(httpHosts).setRequestConfigCallback(requestConfigBuilder -> {
//设置连接超时时间
requestConfigBuilder.setConnectTimeout(1000);
requestConfigBuilder.setSocketTimeout(30000);
//连接的超时时间
requestConfigBuilder.setConnectionRequestTimeout(500);
return requestConfigBuilder;
}).setHttpClientConfigCallback(httpClientBuilder -> {
httpClientBuilder.disableAuthCaching();
httpClientBuilder.setKeepAliveStrategy((response, context) -> 60 * 1000);
//设置账密
return getHttpAsyncClientBuilder(httpClientBuilder, "username", "password");
});
return new RestHighLevelClient(restClientBuilder);
}
/**
* ElasticSearch 连接地址
* 多个逗号分隔
* 示例:127.0.0.1:9201,127.0.0.1:9202,127.0.0.1:9203
*/
private static HttpHost[] getElasticSearchHttpHosts(String address) {
String[] hosts = address.split(",");
HttpHost[] httpHosts = new HttpHost[hosts.length];
for (int i = 0; i < httpHosts.length; i++) {
String host = hosts[i];
host = host.replaceAll("http://", "").replaceAll("https://", "");
httpHosts[i] = new HttpHost(host.split(":")[0], Integer.parseInt(host.split(":")[1]), "http");
}
return httpHosts;
}
private static HttpAsyncClientBuilder getHttpAsyncClientBuilder(HttpAsyncClientBuilder httpClientBuilder, String username, String password) {
if (StringUtils.isBlank(username) || StringUtils.isEmpty(password)) {
return httpClientBuilder;
}
//账密设置
CredentialsProvider credentialsProvider = new BasicCredentialsProvider();
//es账号密码(一般使用,用户elastic)
credentialsProvider.setCredentials(AuthScope.ANY, new UsernamePasswordCredentials(username, password));
httpClientBuilder.setDefaultCredentialsProvider(credentialsProvider);
return httpClientBuilder;
}
3>创建索引
public static boolean createIndex(RestHighLevelClient client, String indexName) {
try {
if (!checkIndex(client, indexName)) {
CreateIndexRequest request = new CreateIndexRequest(indexName);
//mapping 主要是定于字段的类型,若无定义则ES会自动映射
request.mapping("{\"properties\":{\"eventId\":{\"type\":\"keyword\"}}}", XContentType.JSON);
client.indices().create(request, options);
return Boolean.TRUE;
}
} catch (IOException e) {
e.printStackTrace();
}
return Boolean.FALSE;
}
4>判断索引是否存在
public static boolean checkIndex(RestHighLevelClient client, String index) {
try {
return client.indices().exists(new GetIndexRequest(index), options);
} catch (IOException e) {
e.printStackTrace();
}
return Boolean.FALSE;
}
5>删除索引
public static boolean deleteIndex(RestHighLevelClient client, String indexName) {
try {
if (checkIndex(client, indexName)) {
DeleteIndexRequest request = new DeleteIndexRequest(indexName);
AcknowledgedResponse response = client.indices().delete(request, options);
return response.isAcknowledged();
}
} catch (Exception e) {
e.printStackTrace();
}
return Boolean.FALSE;
}
6>插入单条数据
public static boolean insert(RestHighLevelClient client, String indexName, String dataJson) {
try {
//ID 可以自己指定,无非是个唯一键,关联业务上的唯一键也可
IndexRequest source = new IndexRequest(indexName, "_doc").id("1")
.source(dataJson, XContentType.JSON);
client.index(source, options);
return Boolean.TRUE;
} catch (Exception e) {
e.printStackTrace();
}
return Boolean.FALSE;
}
7>批量写入数据
public static boolean batchInsert(RestHighLevelClient client, String indexName, List<EventInfo> dataJsons) {
try {
BulkRequest request = new BulkRequest();
for (EventInfo data : dataJsons) {
request.add(new IndexRequest(indexName, "_doc").id(data.getEventId())
.opType("create").source(JSONObject.toJSONString(data), XContentType.JSON));
}
client.bulk(request, options);
return Boolean.TRUE;
} catch (Exception e) {
e.printStackTrace();
}
return Boolean.FALSE;
}
8>修改数据
public static void update(RestHighLevelClient client, String indexName, String id) throws IOException {
//根据 id 来修改
UpdateRequest updateRequest = new UpdateRequest(indexName, id);
Map<String, Object> kvs = new HashMap<>();
kvs.put("eventAdress", "杭州");
updateRequest.doc(kvs);
updateRequest.timeout(TimeValue.timeValueSeconds(1));
updateRequest.setRefreshPolicy(WriteRequest.RefreshPolicy.WAIT_UNTIL);
client.update(updateRequest, RequestOptions.DEFAULT);
}
9>条件修改
public static void updateQuery(RestHighLevelClient client, String indexName, String id) throws IOException {
UpdateByQueryRequest updateByQueryRequest = new UpdateByQueryRequest();
updateByQueryRequest.indices(indexName);
updateByQueryRequest.setQuery(new TermQueryBuilder("id", id));
设置要修改的内容可以多个值多个用;隔开
updateByQueryRequest.setScript(new Script(ScriptType.INLINE,
"painless",
"eventAdress='杭州市'", Collections.emptyMap()));
client.updateByQuery(updateByQueryRequest, RequestOptions.DEFAULT);
}
10>批量修改
private static void batchUpdate(RestHighLevelClient client, String indexName) throws IOException {
BulkRequest bulkRequest = new BulkRequest();
bulkRequest.timeout("1m"); // 设置超时时间
List<UpdateRequest> updateRequests=new ArrayList<>();
//更新的数据
List<EventInfo> params=new ArrayList<>();
params.add(new EventInfo());
params.forEach(e->{
//获取id
UpdateRequest updateRequest = new UpdateRequest();
updateRequest.index(indexName);
//更新的id
updateRequest.id(e.getEventId());
//更新的数据
Map<String,Object> map=new HashMap<>();
map.put("eventAdress","杭州市");
updateRequest.doc(map);
bulkRequest.add(updateRequest);
});
// 执行批量更新
BulkResponse bulkResponse = client.bulk(bulkRequest, RequestOptions.DEFAULT);
// 检查结果
if (bulkResponse.hasFailures()) {
// 处理失败的情况
System.out.println("Bulk update failed for: " + bulkResponse.buildFailureMessage());
} else {
System.out.println("Bulk update succeeded");
}
}
11>删除数据
public static boolean deleteByQuery(RestHighLevelClient client, String indexName, String key, String value) {
try {
//DeleteRequest 根据id删除
// DeleteRequest request = new DeleteRequest(indexName, "id");
//BulkRequest 批量删除,参考批量修改一般建议是1000-5000个文档,如果你的文档很大,
// 可以适当减少队列,大小建议是5-15MB,默认不能超过100M,可以在es的配置文件(即$ES_HOME下的config下的elasticsearch.yml)中。
//条件删除
DeleteByQueryRequest deleteRequest = new DeleteByQueryRequest(indexName);
deleteRequest.setQuery(new TermQueryBuilder(key, value));
client.deleteByQuery(deleteRequest, options);
return Boolean.TRUE;
} catch (Exception e) {
e.printStackTrace();
}
return Boolean.FALSE;
}
12>总数查询
public static Long count(RestHighLevelClient client, String indexName) {
// 指定创建时间
BoolQueryBuilder queryBuilder = QueryBuilders.boolQuery();
queryBuilder.must(QueryBuilders.termQuery("createTime", "2024-04-19T20:23:26"));
SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();
sourceBuilder.query(queryBuilder);
CountRequest countRequest = new CountRequest(indexName);
countRequest.source(sourceBuilder);
try {
CountResponse countResponse = client.count(countRequest, options);
return countResponse.getCount();
} catch (Exception e) {
e.printStackTrace();
}
return 0L;
}
13>简单分页查询
public static List<EventInfo> page(RestHighLevelClient client, String indexName, Integer offset, Integer size) {
// 查询条件,指定时间并过滤指定字段值
SearchRequest searchRequest = new SearchRequest(indexName);
SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();
//构建查询条件
BoolQueryBuilder boolQueryBuilder = new BoolQueryBuilder();
// boolQueryBuilder.must(QueryBuilders.termsQuery("eventId", Arrays.asList("SJ999995191", "SJ999995193", "SJ999995194", "SJ999995195")));
boolQueryBuilder.must(QueryBuilders.termsQuery("eventType", Arrays.asList("cem.hygiene_health1", "cem.city_management35")));
// boolQueryBuilder.must(QueryBuilders.termsQuery("regionCode", Arrays.asList("65411ca27f3946538545ab2deaa8162d")));
sourceBuilder.query(boolQueryBuilder);
//offset 从 0 开始
sourceBuilder.from(offset);
sourceBuilder.size(size);
sourceBuilder.sort("createTime", SortOrder.DESC);
searchRequest.source(sourceBuilder);
try {
SearchResponse searchResp = client.search(searchRequest, options);
List<EventInfo> data = new ArrayList<>();
SearchHit[] searchHitArr = searchResp.getHits().getHits();
for (SearchHit searchHit : searchHitArr) {
String sourceAsString = searchHit.getSourceAsString();
data.add(JSONObject.parseObject(sourceAsString, EventInfo.class));
}
return data;
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
14>滚动查询
private static void scrollQuery(RestHighLevelClient restHighLevelClient, String indexName) throws IOException {
//1. 创建SearchRequest
SearchRequest request = new SearchRequest(indexName);
//2. 指定scroll信息!
request.scroll(TimeValue.timeValueMinutes(1L));//指定生存时间为1m,1分钟
//3. 指定查询条件
SearchSourceBuilder builder = new SearchSourceBuilder();
builder.size(2);//每页4条数据
// builder.sort("age", SortOrder.DESC);//按年龄排序
// builder.fetchSource(new String[]{"userName", "age"}, null);//只返回userName和age两个字段
builder.query(QueryBuilders.matchAllQuery());
request.source(builder);
//4. 获取返回结果scrollId,source
SearchResponse resp = restHighLevelClient.search(request, RequestOptions.DEFAULT);
String scrollId = resp.getScrollId();
System.out.println("----------首页---------" + scrollId);
for (SearchHit hit : resp.getHits().getHits()) {
System.out.println(hit.getSourceAsMap());
}
//模拟下一页
while (true) {
//5. 循环 - 创建SearchScrollRequest
SearchScrollRequest scrollRequest = new SearchScrollRequest(scrollId);//根据前面得到的scorll_id去指定
//6. 指定scrollId的生存时间!
scrollRequest.scroll(TimeValue.timeValueMinutes(1L));
//7. 执行查询获取返回结果
SearchResponse scrollResp = restHighLevelClient.scroll(scrollRequest, RequestOptions.DEFAULT);
//8. 判断是否查询到了数据,输出
SearchHit[] hits = scrollResp.getHits().getHits();
if (hits != null && hits.length > 0) {
System.out.println("----------下一页---------");
for (SearchHit hit : hits) {
System.out.println(hit.getSourceAsMap());
}
} else {
//9. 判断没有查询到数据-退出循环
System.out.println("----------结束---------");
break;
}
}
//10. 创建CLearScrollRequest
ClearScrollRequest clearScrollRequest = new ClearScrollRequest();
//11. 指定ScrollId
clearScrollRequest.addScrollId(scrollId);
//12. 删除ScrollId
ClearScrollResponse clearScrollResponse = restHighLevelClient.clearScroll(clearScrollRequest, RequestOptions.DEFAULT);
//13. 输出结果
System.out.println("删除scroll:" + clearScrollResponse.isSucceeded());
restHighLevelClient.close();
}
15>分页查询的常见条件匹配查询
public static List<EventInfo> pageSerach(RestHighLevelClient client, String indexName) {
//1.创建 SearchRequest搜索请求
SearchRequest searchRequest = new SearchRequest();
searchRequest.indices(indexName);//指定要查询的索引
//2.创建 SearchSourceBuilder条件构造。builder模式这里就先不简写了
SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
MatchAllQueryBuilder matchAllQueryBuilder = QueryBuilders.matchAllQuery();
searchSourceBuilder.query(matchAllQueryBuilder);
//分页参数
searchSourceBuilder.from(0);
searchSourceBuilder.size(100);
//createTime倒序
searchSourceBuilder.sort("createTime", SortOrder.DESC);
//数据过滤---指定需要返回或者排除的字段
// String[] includes = {
// "eventId", "createTime","eventAddress"};
// String[] excludes = {
// };
// searchSourceBuilder.fetchSource(includes, excludes);
//match 查找match在匹配时会对所查找的关键词进行分词,然后按分词匹配查找。
// MatchQueryBuilder matchQueryBuilder = QueryBuilders.matchQuery("eventAddress", "13001");
// matchQueryBuilder.operator(Operator.OR);
// searchSourceBuilder.query(matchQueryBuilder);
//多字段查询multi_match
// MultiMatchQueryBuilder multiMatchQuery = QueryBuilders.multiMatchQuery("处理流程","eventAddress", "dutyName");
// multiMatchQuery.operator(Operator.OR);
// searchSourceBuilder.query(multiMatchQuery);
//Term 查找精确查询Term
// TermQueryBuilder termQueryBuilder = QueryBuilders.termQuery("eventAddress.keyword", "浙江省");
// searchSourceBuilder.query(termQueryBuilder);
//Range 查找 范围查询range
// RangeQueryBuilder rangeQueryBuilder = QueryBuilders.rangeQuery("createTime");
// rangeQueryBuilder.gte("2024-03-20T10:07:43");
// rangeQueryBuilder.lt("2024-03-20T22:07:43");
// searchSourceBuilder.query(rangeQueryBuilder);
//多个id 查找
// IdsQueryBuilder idsQueryBuilder = QueryBuilders.idsQuery();
// idsQueryBuilder.addIds("2", "5", "1111");
// searchSourceBuilder.query(idsQueryBuilder);
//WildcardQueryBuilder 查找
// WildcardQueryBuilder wildcardQueryBuilder = QueryBuilders.wildcardQuery("dutyName.keyword", "*道及门前*");
// searchSourceBuilder.query(wildcardQueryBuilder);
//3.将 SearchSourceBuilder 添加到 SearchRequest中
searchRequest.source(searchSourceBuilder);
//4.执行查询
try {
SearchResponse searchResp = client.search(searchRequest, options);
List<EventInfo> data = new ArrayList<>();
SearchHit[] searchHitArr = searchResp.getHits().getHits();
for (SearchHit searchHit : searchHitArr) {
String sourceAsString = searchHit.getSourceAsString();
data.add(JSONObject.parseObject(sourceAsString, EventInfo.class));
}
return data;
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
16>分组、半径搜索
public static List<EventInfo> group(RestHighLevelClient client, String indexName) {
//1.创建 SearchRequest搜索请求
SearchRequest searchRequest = new SearchRequest();
searchRequest.indices(indexName);//指定要查询的索引
//2.创建 SearchSourceBuilder条件构造。builder模式这里就先不简写了
SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
// 构建查询
// size(0) :不需要 查询的结果 , 只需要分组的结果
searchSourceBuilder.size(0) ;
searchSourceBuilder.query(QueryBuilders.matchAllQuery()) ;
// // 构建Agg
// TermsAggregationBuilder eventTypeAgg = AggregationBuilders.terms("group_by_eventType").field("eventType.keyword");
// // agg结合进 searchSourceBuilder
// searchSourceBuilder.aggregation(eventTypeAgg);
// group --1
// TermsAggregationBuilder oneBuilder = AggregationBuilders.terms("one").field("eventType.keyword");
// TermsAggregationBuilder twoBuilder = AggregationBuilders.terms("two").field("regionCode.keyword");
// oneBuilder.subAggregation(twoBuilder);
// searchSourceBuilder.aggregation(oneBuilder);
// group --2这种方式查出来的数据更扁平化,容易被接受
// script{
//"key": "cem.hygiene_health1-split-root00000000",
//"doc_count": 5
// Script script = new Script(ScriptType.INLINE, "groovy",
// "doc['flowCode.keyword'].value+'-split-'+doc['stepExecuteId'].value", new HashMap<>());
// TermsAggregationBuilder aggregationBuilder = AggregationBuilders.terms("result").script(script);
// 设置GeoDistanceQueryBuilder
// "location":{ --字段
// "type": "geo_point"
// }
// "location":{ --字段
// "lat": "39.6"
// "lon": "118"
// }
// GeoDistanceQueryBuilder geoDistanceQueryBuilder = QueryBuilders
// .geoDistanceQuery("location")
// .point(centerLat, centerLon) //原点
// .distance(distance, DistanceUnit.KILOMETERS); // 设置半径和单位
//
// searchSourceBuilder.query(geoDistanceQueryBuilder);
//
// // 可以添加排序,按距离排序
// searchSourceBuilder.sort(new GeoDistanceSortBuilder("location", centerLat, centerLon)
// .unit(DistanceUnit.KILOMETERS)
// .order(SortOrder.ASC));
//3.将 SearchSourceBuilder 添加到 SearchRequest中
searchRequest.source(searchSourceBuilder);
List<EventInfo> data = new ArrayList<>();
//4.执行查询 ---以上条件决定了返回的值,需要修改
try {
SearchResponse searchResp = client.search(searchRequest, options);
//3.处理返回结果
Aggregations aggregations = searchResp.getAggregations();
//他是按照Terms 进行聚合的 所以取出来也需要 Terms 获取运行结果
Terms colorAggRes = aggregations.get("group_by_eventType");
List<? extends Terms.Bucket> colorBuckets = colorAggRes.getBuckets();
// 遍历结果
for (Terms.Bucket colorBucket : colorBuckets) {
System.out.println("key: " + colorBucket.getKeyAsString());
System.out.println("docCount: " + colorBucket.getDocCount());
System.out.println("=================");
}
} catch (IOException e) {
e.printStackTrace();
}
return data;
}
6,数据录入
可以将json数据(可以在http://www.json-generator.com/网站上自动生成)放到当前用户目录下,然后执行如下命令,将数据导入到Elasticsearch中,如下:
curl -H "Content-Type: application/json" -XPOST "localhost:9200/bank/_doc/_bulk?pretty&refresh" --data-binary "@accounts.json"
参考文献
ElasticSearch教程——汇总篇
elasticsearch文档Delete By Query API(二)
Elasticsearch-基础介绍及索引原理分析
ElasticSearch教程——Java常用操作
ElasticSearch AggregationBuilders java api常用聚合查询