es的官方中文文档
https://www.elastic.co/guide/cn/elasticsearch/guide/current/index.html
概念
es是基于Lucene,纯java语言编写的分布式,可扩展,近实时性的高性能搜索引擎和数据分析引擎
es基础构成
-
document
es中数据存储的最小单元,对应数据库的一行数据 -
Index
有具有相同字段的文档列表组成,对应数据库的一个表,一个索引由一个或者多个分片组成 -
type
文档类型,这个我们可以不用记,es后面的版本要把type去掉。之前的版本中,索引和文档中间还有个类型的概念,每个索引下可以建立多个类型,文档存储时需要指定index和type。从6.0.0开始单个索引中只能有一个类型,7.0.0以后将将不建议使用,8.0.0 以后完全不支持。在7.0开始,一个索引只能建一个Type为_doc。 -
shards
分片。当索引数据太大时,受限于单个节点的磁盘,内存,处理能力等资源,需要将索引的数据水平拆分,拆分后形成的每一个分片回落到不一样的服务节点上,这样显著提升了es的性能,索引创建时需要指定分片,一旦分片被指定就无法更改 -
replicas
副本。是主分片的备份分片,副本主要是用于主分片的数据备份,同时也对外提供数据查询服务。副本分片提升了es的高可用性
分片shard和es集群的高可用高性能
分片的概念和kafka的分区很像,既有副本又各自拥有不同的数据并且分散在不同的节点,index就是这些分片的topic。
一个索引中的数据保存通过hash取余决定保存在哪分片中,相当于水平分表也类似于kafka的分区,分片有主分片和副本分片,一个主分片可以设置多个副本分片,在es集群中每个主分片存储了es的部分数据,副本分片主要是用于备份主分片的数据,主分片分布在es的各个节点上,而各个主分片的副本分片也会分布到和主分片不同的节点上,当集群中某个节点宕机时,分布在其他节点的副本分片就会顶替原来的主分片,这个体现了es的高可用性。
由于分片分散在各个不同的节点上,每个节点都有能力处理任意请求, 每个节点都知道集群中任一文档位置,所以可以直接将请求转发到需要的节点上,所以其请求压力和数据分析压力也会分散到集群的各个节点上,实现了es的高性能。我们最好通过轮询的方式访问集群的节点
分片的原理就和数据库的水平分表和kafka的分区是一个道理的,既能保证高新南
下图的流程展示es集群的高可用
图一:es集群中有三个节点,三个主分片,三个副本分片,也就是每个分片拥有一个副本分片
图一图二:主节点宕机,node2通过选举称为新主节点
图二图三:挂掉的node1的副本分片存储在node3上,集群将副本分片P0调整为主分片,这样node1的数据就恢复回来了
图三图四:es集群将为创建P0的副本分片,和重新创建和node1一起挂掉的R1副本分片,并让它们保存在和自己主分片不一样的节点上,也就是P0副本分片保存在node2上,R1副本分片保存在node3上,下次就算是在宕机,其中的一个节点存在有其他备份分片,保证了数据的不丢失,这就是es的高可用性
图四数据被存储到哪个分片是由公式决定的:
shard = hash(routing) % number_of_primary_shards
routing 是一个可变值,默认是文档的 _id ,也可以设置成一个自定义的值
分片的实时性和查询性能
refresh:在 Elasticsearch 中,写入和打开一个新段的轻量的过程叫做 refresh,也就是数据在jvm内存写入文件系统缓存中并形成段的过程。 在Elasticsearch的jvm内和磁盘之间是文件系统缓存,写入的数据会先落入jvm内存中,在jvm内存中的数据是没有进行分段的无法提供搜索,当内存的数据到达一定量或者时间就和进行一次refresh,然后缓存数据到文件系统缓存中形成段,然后将jvm内存清理,此时数据才可以提供搜索, Elasticsearch定时刷新(flush)文件系统缓存中的数据到磁盘中形成提交点。es默认每秒中执行一次refresh
默认情况下每个分片会每秒自动刷新一次。这就是为什么我们说 Elasticsearch 是 近 实时搜索: 文档的变化并不是立即对搜索可见,但会在一秒之内变为可见可搜索。
flush:这种缓存的机制给es带来了性能上的提升,但是也带来了丢失数据的风险,所以es添加了事务日志,事务日志主要记录还未持久化到硬盘的数据,当日志文件大小达到一定量时(默认512MB),或者超过固定时间(默认5秒),es就会触发一次刷新(flush),将jvm内存和文件系统缓存的数据持久化,然后删除日志文件,并创建新的日志文件。
fsync:每个新段在文件系统缓存被持久化的过程叫做fsync
有时候我们创建了一个文档然后尝试搜索它,但却没有搜到。这个问题的解决办法是用 refresh API 执行一次手动刷新:
POST /_refresh //刷新所有的索引
POST /index_name/_refresh //只刷新(Refresh) 特定索引
es写性能优化
降低刷新频率(会影响实时性)
- 提高写性能,我们可以启动多线程去进行写操作
- 除了启动多线程外还可以通过调整刷新频率来提升性能。
虽然刷新(refresh)是比提交轻量很多的操作,但它还是会有性能开销,并不是所有的情况都需要每秒刷新。如果你想优化读写速度而不是近实时搜索,可以通过设置refresh_interval, 降低每个索引的刷新频率:
PUT /my_logs
{
"settings": {
"refresh_interval": "30s" //每30秒刷新 my_logs 索引,默认是每秒刷新
}
}
refresh_interval 是可以进行动态更新的,就是说在生产环境中,当你正在建立一个大的新索引时,可以先关闭自动刷新,待开始使用该索引时,再把它们调回来:
PUT /my_logs/_settings
{ "refresh_interval": -1 } //关闭实时更新
PUT /my_logs/_settings
{ "refresh_interval": "1s" }//开启每秒更新
refresh_interval 需要一个 持续时间 值, 例如 1s (1 秒) 或 2m (2 分钟)。 一个绝对值 1 表示的是 1毫秒 (会使你的集群陷入瘫痪)。
- 修改内存缓存大小(indices.memory.index_buffer_size)。
jvm内存缓存的数据满了或者是超过固定时间就会进行刷新操作,调整内存缓存大小可以有效的降低刷新频率
在elasticsearch.yml中可以这样设置:
indices.memory.index_buffer_size: 20%(默认是堆内存的10%)
indices.memory.min_index_buffer_size: 96mb(默认是48mb)
已经索引好的文档会先存放在内存缓存中,等待被写到到段(segment)中。缓存满的时候会触发段刷盘(吃i/o和cpu的操作)。默认最小缓存大小为48m,不太够,最大为堆内存的10%。对于大量写入的场景也显得有点小。
- 修改事务日志(translog)的刷新频率(fsync)(index.translog.sync_interval)和大小(index.translog.flush_threshold_size)
日志中记录了还在缓存中并未形成段的数据,会定时或者超过一定量后进行刷新
在elasticsearch.yml中可以这样设置:
index.translog.sync_interval:30s(默认5s)。
index.translog.flush_threshold_size:700mb(默认512mb)
index.translog.durability:async(默认request) //通过异步提交日志,不过可以会在宕机时丢失数据
- _id字段的使用,应尽可能避免自定义_id, 以避免针对ID的版本管理;建议使用ES的默认ID生成策略或使用数字类型ID做为主键。
es读性能优化
- 1.合理的分片的数量,分片数量越多每个分片的数据范围就越小,合理的分片数量能有效的提升每个分片的查询性能
- 2. 当机器内存小于64G时,遵循通用的原则,50%给ES,50%留给lucene。
内存对于 Elasticsearch 来说绝对是重要的,它可以被许多内存数据结构使用来提供更快的操作。但是说到这里, 还有另外一个内存消耗大户 非堆内存 (off-heap):Lucene。
Lucene 被设计为可以利用操作系统底层机制来缓存内存数据结构。 Lucene 的段是分别存储到单个文件中的。因为段是不可变的,这些文件也都不会变化,这是对缓存友好的,同时操作系统也会把这些段文件缓存起来,以便更快的访问。
Lucene 的性能取决于和操作系统的相互作用。如果你把所有的内存都分配给 Elasticsearch 的堆内存,那将不会有剩余的内存交给 Lucene。 这将严重地影响全文检索的性能。
标准的建议是把 50% 的可用内存作为 Elasticsearch 的堆内存,保留剩下的 50%。
-
- 当机器内存大于64G时,遵循以下原则:
- 如果主要的使用场景是全文检索, 那么建议给ES Heap分配 4~32G的内存即可;其它内存留给操作系统, 供lucene使用(segments cache), 以提供更快的查询性能。
- 如果主要的使用场景是聚合或排序, 并且大多数是numerics, dates, geo_points 以及not_analyzed的字符类型, 建议分配给ES Heap分配 4~32G的内存即可,其它内存留给操作系统,供lucene使用(doc values cache),提供快速的基于文档的聚类、排序性能。
- 如果使用场景是聚合或排序,并且都是基于analyzed 字符数据,这时需要更多的 heap size, 建议机器上运行多ES实例,每个实例保持不超过50%的ES heap设置(但不超过32G,堆内存设置32G以下时,JVM使用对象指标压缩技巧节省空间),50%以上留给lucene。
-
关闭交换分区,防止内存发生交换导致性能下降 通过执行swapoff -a或者通过: 在elasticsearch.yml 中 bootstrap.memory_lock: true, 以保持JVM锁定内存,保证ES的性能。
es的锁机制
由于es需要处理一定的并发量的写操作,es是使用的乐观锁,主要是使用java的CAS技术实现乐观锁
正排索引
就是利用文档id查询文档内容,也就是通过key去查value
倒排索引
就是通过value去查key,关于倒排索引这里有一篇不错的文章可以供大家参考https://blog.csdn.net/qq_21312297/article/details/102496833
es索引的过程
es是先通过term Dictionary将每个单词出现在文档的位置地址这个过程是倒排索引,然后通过地址去查询文档,这个是正排索引,es就是倒排索引和正排索引的结合term Dictionary(单词字典):记录了所有文档的单词,es储存的文档内容越大词典也就越大,还记录了单词是放在哪些文档上的关联信息,通常term Dictionary会被拆分成单词的前缀组成一颗B+树,每个叶子节点的单词会记录着单词所在文档的位置地址
Posting List:记录了单词对应文档的集合,由Posting组成,而Posting中包含了文档id,单词出现频率,文档中单词的位置,单词在文档开始和结束的位置
以下是倒排索引的过程文档字段类型
核心类型 字符串类型 string,text,keyword
整数类型 integer,long,short,byte
浮点类型 double,float,half_float,scaled_float
逻辑类型 boolean
日期类型 date
范围类型 range
二进制类型 binary
复合类型 数组类型 array
对象类型 object
嵌套类型 nested
地理类型 地理坐标类型 geo_point
地理地图 geo_shape
特殊类型 IP类型 ip
范围类型 completion
令牌计数类型 token_count
附件类型 attachment
抽取类型 percolator
基础语句
index操作
创建index:
put /index_name
{
"settings" : {
"number_of_shards" : 3,
"number_of_replicas" : 1
}
}
查看index:
get /_cat/indices
删除index:
delete /index_name
document操作
创建id为2的文档:
DELETE /index_name/doc/2
创建id为1的文档:
put /index_name/doc/1
{
"name":"es",
"user":"test"
}
创建自带id的文档:
post /index_name/doc/
{
"name":"es",
"user":"test"
}
批量创建文档:
put /u/_bulk
{"index":{"_id":3}}
{"username":"james","code":23,"age":32,"country":"usa","job":"basketball"}
{"index":{"_id":4}}
{"username":"haden","code":15,"age":30,"country":"usa","job":"basketball"}
{"index":{"_id":5}}
{"username":"bingbing","code":999,"age":35,"country":"chn","job":"act"}
{"index":{"_id":6}}
{"username":"susu","code":999,"age":25,"country":"chn","job":"great"}
{"index":{"_id":7}}
{"username":"hu","code":999,"age":36,"country":"chn","job":"act"}
批量修改文档:
POST _bulk
{"update":{"_index":"test","_type":"product","_id":1}}
{"doc":{ "price" : 5200, "productName" : "苹果11","memory":"128G" }}
{"update":{"_index":"test","_type":"product","_id": 2 }}
{"doc":{ "price" : 3100, "productName" : "小米10","memory":"268G" }}
{"update":{"_index":"test","_type":"product", "_id": 3 }}
{"doc":{ "price" : 1300, "productName" : "酷派大神","memory":"16G" }}
{"update":{"_index":"test","_type":"product", "_id": 4 }}
{"doc":{ "price" : 730, "productName" : "魅蓝note3","memory":"32G" }}
{"update":{"_index":"test","_type":"product", "_id": 6 }}
{"doc":{ "price" : 10000, "productName" : "macBook Pro","memory":"521G" }}
{"update":{"_index":"test","_type":"product", "_id": 7 }}
{"doc":{ "price" : 6500, "productName" : "华为P40","memory":"128G" }}
{"update":{"_index":"test","_type":"product","_id": 8 }}
{"doc":{ "price" : 3100, "productName" : "Ipad Air3","memory":"64G" }}
批量查询:
post /_mget
{
"docs": [
{
"_index": "test_index",
"_type": "doc",
"_id": "1"
},
{
"_index": "test_index",
"_type": "doc",
"_id": "Ai9cM3IBEyMN0HxifBpI"
}
]
修改文档:
POST /u/_doc/5/_update
{
"doc":{
"age":41,
"username":"kate"
}
}
根据查询的结果更改文档字段:
POST /u/_doc/_update_by_query
{
"script":{
"source":"ctx._source['brith']='1990-11-11 12:00:00'"
},
"query":{
"match_all":{
}
}
}
新增文档字段:
PUT /u/_mapping
{
"properties":{
"brith":{
"type":"date",
"format": "yyyy-MM-dd HH:mm:ss"
}
}
}
分词器
分词就是将文本转化为一系列单词的过程,也叫做文本分析,在es中称为Analysis,es默认的Analysis是standard
Analysis由三部分组成:
Character Filters:对原始文本进行处理,去掉一些特殊的符号标签,例如去除html的特殊标记符
Tokenizer:将原始文本按照一定的规则切分成单词
Toker Filters:对Tokenizer处理的单词进一步加工,例如大小写转换,删除没有意义的词等
分词器选择并测试:
post /_analyze
{
"analyzer":"simple",//选择simple分词器
"text":"elasticsearch是世界最好的搜索引擎"
}
es内置分词器:
Standard - 默认分词器,按词切分,小写处理
Simple - 按照非字母切分(符号被过滤), 小写处理
Stop - 小写处理,停用词过滤(the,a,is)
Whitespace - 按照空格切分,不转小写
Keyword - 不分词,直接将输入当作输出
Patter - 正则表达式,默认\W+(非字符分割)
Language - 提供了30多种常见语言的分词器
Customer 自定义分词器
自定义分词器:
put /index_name_2
{
"settings":{
"analysis":{
"analyzer":{
"my_analysis":{
"type":"custom",
"tokenizer":"standard",
"char_filter":["html_strip","to_and"],
"filter":["lowercase","my_stopwords"]
}
},
"char_filter": {
"to_and": {
"type": "mapping",
"mappings": ["& => and"]
}
},
"filter": {
"my_stopwords": {
"type": "stop",
"stopwords": ["the", "a"]
}
}
}
}
}
中文分词器:
IK - 支持中英文单词分词,支持ik_smart,ik_maxword的模式,支持自定义词库、支持热更新单词词典
jieba - python最流行的分词器,支持繁体分词,自定义词典等
Mapping
Mapping类似数据库表数据类型定义,Mapping可以定义index下的字段名,字段类型
这里不写了,属性太多,到官方网站看文档去吧
https://www.elastic.co/guide/en/elasticsearch/reference/master/mapping.html
Search API
URI Search
q:指定URL查询条件
df(default field):指定查询的字段,如果不写,es 会查询所有字段或者是q中指定的字段
sort:排序
timeout:指定超时时间,默认不超时
from,size:用于分页
GET /u/_search?q=basketball
GET /u/_search?q=job:basketball mvp//查询出所有job是basketball和mvp的文档
GET /u/_search?q=job:"basketball mvp"//查询出所有job是basketball mvp的文档
GET /u/_search?q=job:(basketball mvp)//查询job:basketball或者job:mvp
GET /u/_search?q=user.id:8a4f500d
GET /u/_search?q=basketball&df=job&sort=age:asc&from=1&size=3&timeout=1s//df指定q的值basketball从job字段查,分页从第一页开始,每页3条记录,查询时间不能超过1秒,超过一秒就退出查询
布尔操作:
要大写,不能小写
AND:&&
OR:||
NOT:!
GET /u/_search?q=basketball AND mvp //查询出所有包含basketball 并且同时有mvp 的字段
GET /u/_search?q=job:(basketball AND mvp)//查询出所有job是basketball并且有mvp的文档
GET /u/_search?q=job:(basketball NOT mvp)//查询出所有job是basketball并且没有mvp的文档
GET /u/_search?q=job:(basketball || mvp)//查询出所有job是basketball或者有mvp的文档
{
"profile":true"//查看es查询的过程
}
加减操作
+:在url写成%2B必须有 must
-:必须没有 must_not
GET /u/_search?q=basketball %2Bmvp//查询出所有是basketball 并且一定包含mvp的文档
GET /u/_search?q=(basketball %2Bmvp)//查询出所有是basketball 并且一定包含mvp的文档
GET /u/_search?q=basketball -2Bmvp//查询出所有是basketball 并且一定不包含mvp的文档
GET /u/_search?q=job:(basketball -2Bmvp)//查询出所有job是basketball 并且一定不包含mvp的文档
范围查询
闭区间 [ ] 开区间{ }
age:[1 TO 10]相当于1<=age<=10
age:[1 TO 10}相当于1<=age<10
age:[1 TO ]相当于age>=1
age:[* TO 10]相当于age<=10
GET /u/_search?q=basketball AND mvp age:>31 //查询出所有包含basketball 并且同时有mvp 的字段或者age大于31
GET /u/_search?q=basketball AND mvp AND age:>31//查询出所有包含basketball 并且同时有mvp 的字段并且age大于31
通配符查询
username:j?mes:匹配任意一个字符
username:jam*:匹配任意多个字符
username:j*s:匹配任意多个字符
GET /u/_search?q=j*s
GET /u/_search?q=username:j*s
GET /u/_search?q=j?mes
query DSL 的写法
post /u/_search
{
"query": {
"wildcard": {
"username.keyword":"j*s"
}
}
}
正则表达式查询
这个需要对正则表达的掌握能力,举两个例子
GET /u/_search?q=username:/j.{1,}/
GET /u/_search?q=username:/[张李].*亭{1,}.*/
query DSL 的写法
post /u/_search
{
"query": {
"regexp": {
"username":"j.*"
}
}
}
模糊匹配查询
GET /u/_search?q=job:basketball~1 //查询job中与basketball相差1个char值的文档,例如basketball 、basketball mvp
近似度查询
GET /u/_search?q=job:“basketball mvp“~1 //匹配到basketball mvp 前后中间可以允许插入1个词 basketball xxxx mvp\basketball mvp xxxx\xxxx basketball mvp都会匹配到,
Request Body Search
query DSL
字段查询
1、全文匹配
对text类型的字段查询时,会先分词
-
match:
分词后词语没有先后顺序,随意组合
select * from u where job like '%basketball%' or job like '%mvp%'
post /u/_search
{
"profile":true,//查看es查询的过程
"query":{
"match":{
"job":"basketball mvp"//job字段查询即包含basketball 或者包含mvp的文档
}
}
}
select * from u where job like '%basketball%' and job like '%mvp%'
post /u/_search
{
"query": {
"match":{
"job":{
"query":"basketball mvp",//job必须同时包含basketball和mvp
"operator":"and"//默认是or
}
}
}
}
post /u/_search
{
"query": {
"match":{
"job":{
"query":"basketball mvp act",
"minimum_should_match":"2"//搜索出job字段中至少包含关键词其中两个才会匹配上
}
}
}
}
- match_all:
select * from u
post /u/_search
{
"query": {
"match_all": {}
}
}
-
match_phrase:
分词后词语存在先后顺序- match_phrase还是分词后去搜的
- 目标文档需要包含分词后的所有词
- 目标文档还要保持这些词的相对顺序和文档中的一致
post /u/_search
{
"query": {
"match_phrase":{
"job":"basketball mvp"//查询包含basketball mvp的文档,
//与term不同的是term是从索引的单词找,match_phrase从文档的单词找
}
}
}
post /u/_search
{
"query": {
"match_phrase":{
"job":{
"query":"basketball act",
"slop":"1"//允许文档出现和搜索条件相差一个距离的文档
// 例如这里会匹配到 basketball xxxx act/basketball act xxxx/xxxx basketball act
}
}
}
}
-
query_string:
指定字段查询,类似q
post /u/_search
{
"query": {
"query_string":{
"default_field":"job",//匹配job字段中即有basketball又有act的文档
"query":"basketball AND act"
}
}
}
post /u/_search
{
"query": {
"query_string":{
"fields":["job","username"],//只匹配job和username字段
"query":"james act"
}
}
}
2、精确查询
-
term、terms:将查询语句作为一个单词查询,不做分词处理。
term和match_phrase有点像,但是又有很大的区别,term性能比match_phrase好很多倍
select * from u where job="basketball mvp"
post /u/_search
{
"query": {
"term":{
"job.keyword":"basketball mvp"//这里在倒排索引中匹配basketball mvp
//最终的结果是匹配不到任何文档,因为索引中都是一个个被切分了的单词不存在“basketball mvp”
}
}
}
select * from u where job in ('basketball', 'mvp')
post /u/_search
{
"query": {
"terms":{
"job.keyword":["basketball","mvp"]
}
}
}
3、范围、排序、分页
- range: 范围查询 lt:小于、lte:小于等于、gt:大于、gte大于等于
select * from u where age between 30 and 40;
post /u/_search
{
"query" : {
"range" : {
"age" : {
"gte" : 30,
"lte" : 40
}
}
}
}
- sort,from,size:
POST /test/product/_search
{
"query":{
"match": {
"productName":"苹果小米华为"
}
},
"sort":{"price":"desc"},
"from":2,
"size":3
}
scroll:深分页,对于上面介绍的浅分页,如果请求的页数较少(假设每页20个docs), Elasticsearch不会有什么问题,但是如果页数较大时,比如请求第1000页,Elasticsearch不得不取出第1页到第1000页的所有docs,再去除第1页到第999页的docs,得到第1000页的docs。解决的方式就是使用scroll,scroll就是维护了当前索引段的一份快照信息--缓存
初始化的时候就像是普通的search一样 其中的scroll=3m代表当前查询的数据缓存3分钟
POST test/_search?scroll=3m
{
"query":{
"match_all": {}
}
}
在遍历时候,拿到上一次遍历中的_scroll_id,然后带scroll参数,3m代表重置过期时间
POST /_search/scroll
{
"scroll" : "3m",
"scroll_id" : "FGluY2x1ZGVfY29udGV4dF91dWlkDXF1ZXJ5QW5kRmV0Y2gBFjlxTWE1RTZwUmdpd3FZSkpsYm5kYXcAAAAAAAA-vxZBRWtUWDAyalM5YTZvbmx0YTJxQjZR"
}
相关性得分(_score)
依据:
- 词频:单词在该文档出现的频率越高,相关性越高
- 文档长度:文档越短,相关性越高
- 包含单词文档数量:文档包含某个单词的数量越少,相关性越高
复合查询
constant_score:
将其查询结果的文档得分都设置为1或者boost的值,这个是在当我们不关心检索词频率TF(Term Frequency)对搜索结果排序的影响时使用
POST /test/product/_search
{
"query":{
"constant_score": {
"filter": {
"match":{
"productName":"华为"
}
},
"boost": 1
}
}
}
post /u/_search
{
"query": {
"constant_score":{
"filter":{
"match":{
"job":{
"query":"basketball mvp",
"operator":"and"
}
}
}
}
}
}
bool:
- filter:使用filter查询时会有缓存,所以其效率很高,如果不使用相关性得分的排排序,推荐使用filter
post /u/_search
{
"query": {
"bool": {
"filter": {
"term": {
"job.keyword": "basketball"
}
}
}
}
}
post /u/_search
{
"query": {
"bool": {
"filter": [
{
"term": {
"job": "basketball"
}
},
{
"match": {
"job": "act"
}
}
]
}
}
}
- must:
post /u/_search
//查询文档中的job字段必须同时包含basketball和act
{
"query": {
"bool": {
"must": [
{
"term": {
"job": "basketball"
}
},
{
"match": {
"job": "act"
}
}
]
}
}
}
- must_not:
post /u/_search
//查询文档中的job字段必须同时包含basketball和act,而要求必须不能有mvp
{
"query": {
"bool": {
"must": [
{
"term": {
"job": "basketball"
}
},
{
"match": {
"job": "act"
}
}
],
"must_not": [
{
"match": {
"job": "mvp"
}
}
]
}
}
}
- should:默认至少满足一个条件,可以通过minimum_should_match控制满足个数
post /u/_search
//查询满足其中之一的查询即可,也就是job中包含basketball或者act或者mvp其中一个就能查询出来
{
"query": {
"bool": {
"should": [
{
"term": {
"job": "basketball"
}
},
{
"match": {
"job": "act"
}
},
{
"match_phrase": {
"job": "basketball mvp"
}
}
]
}
}
}
post /u/_search
{
"query": {
"bool": {
"should": [
{
"term": {
"job": "basketball"
}
},
{
"match": {
"job": "act"
}
},
{
"match_phrase": {
"job": "basketball mvp"
}
}
],
"minimum_should_match":3//设置了满足条件为3
//查询结果同时满足其中三个也就job中包含basketball并且包含act和mvp就能查询出来都
}
}
}
- should与must\must_not:查询时必须满足或者必须不满足must/must_not,可以不满足should,如果可以满足should也一并查出
post /u/_search
{
"query": {
"bool": {
"should": [
{
"match": {
"job": "act"
}
}
],
"must": [
{
"match":{
"job":"mvp"
}
}
]
}
}
}
- source:只希望返回个别字段即可
//只希望查询时返回username字段
GET /u/_search?_source=username
Count API
获取符合查询条件的文档数量
查询语句和上面的一样只是把URL修改了
post /u/_count
{
"query": {
"bool": {
"should": [
{
"match": {
"job": "act"
}
}
],
"must": [
{
"match":{
"job":"mvp"
}
}
]
}
}
}
排序、分页滚动
sort
-
数字类型排序遍历
单字段排序
post /u/_search
{
"query": {
"bool": {
"should": [
{
"match": {
"job": "act"
}
}
],
"must": [
{
"match": {
"job": "mvp"
}
}
]
}
},
"sort": {
"age": "desc"//按照年龄倒叙排序
}
}
多字段排序
post /u/_search
{
"query": {
"match": {
"job": "basketball"
}
},
"sort": [
{
"age": "desc"
},
{
"_score": "desc"
}
]
}
-
字符串类型排序
按照字符串类型排序时不能排序text类型的,只能排序keyword的类型,所以在排序text类型时可以使用text.keyword的方式或者使用设置fielddata的属性,例如如下面的查询
post /u/_search
//text.keyword的方式
{
"query": {
"match": {
"job": "basketball"
}
},
"sort": {
"username.keyword":"asc"
}
}
设置fielddata为true
post /u/_mapping
{
"properties":{
"job":{
"type":"text",
"fielddata": true
}
}
}
然后直接进行text排序
POST /u/_search
{
"query": {
"match": {
"job": "basketball"
}
},
"sort": {
"job":"asc"
}
}
-
分页
from:开始位置 es默认不超过过
size:获取的总数
es默认不支持深分页换种说法就是es不支持大容量的分页,默认from+size不能超过10000
post /u/_search
{
"from":9996,
"size":4//from或者size再设置大一点就会报错
}
-
Scroll 分页滚动、遍历
利用Scroll可以改变分页查询中不能做深分页的缺点,Scroll查询方式的原理就是通过每次查询后,返回一个scroll_id。根据这个scroll_id 进行下一页的查询。可以把这个scroll_id理解为通常关系型数据库中的游标。scroll查询的数据是当前索引段的一份快照信息–缓存,所以scroll没有实时性
//创建一个scroll设置,并且设置存活时间是两分钟,也就是快照存活2分钟
GET /u/_search?scroll=2m
{
"query": {"match_all": {}},
"size": 1//设置每次滚动取1条数据,结果会返回
}
image.png
拿着scroll_id可以进行滚动
//滚动scroll
POST /_search?scroll
{
"scroll_id": "DXF1ZXJ5QW5kRmV0Y2gBAAAAAAAAANAWb1IxVjQ0cFBUMy1jb1dFZUdRbDNKdw==",
"scroll":"5m"
}
-
Search After分页滚动、遍历
利用Search After可以解决Scroll不能实时滚动的缺点,但是Search After只能往下滚动,只支持向下滚动,不支持向上滚动,并且创建Search After必须要给文档排序
post /u/_search
{
"size":1,
"sort":{
"age":"desc"
}
}
image.png
将返回结果的sort中的值设置到滚动语句中,上图是56,设置search_after为56
GET /u/_search
{
"size":1,
"search_after":[56],
"sort":{
"age":"desc"
}
}
总结分页的使用场景
Aggregation 聚合查询
多用于统计使用的,举个例子
例如统计从事各类职业的人数有多少
GET /u/_search
{
"size": 0,
"aggs": {
"tj": {
"terms": {
"field": "job.keyword"
}
}
}
}
返回结果
"aggregations": {
"tj": {
"doc_count_error_upper_bound": 0,
"sum_other_doc_count": 2,
"buckets": [
{
"key": "act",
"doc_count": 2
},
{
"key": "act av",
"doc_count": 2
},
{
"key": "basketball",
"doc_count": 2
},
{
"key": "basketball mvp",
"doc_count": 2
},
{
"key": "basketball mvp act",
"doc_count": 2
},
{
"key": "basketball act",
"doc_count": 1
},
{
"key": "cartoonman hai",
"doc_count": 1
},
{
"key": "cartoonman hot",
"doc_count": 1
},
{
"key": "cartoonman qun",
"doc_count": 1
},
{
"key": "cartoonman seven",
"doc_count": 1
}
]
}
}
聚合查询做的就是类似上面的事情,聚合查询分为4大类分别为:
- Bucket:分析统计类型,类似数据库的group by
- Metric:指标类型,统计如最大值,最小值,平均值等
- Pipeline:管道分析类型,基于上一级分析的结果进行再次分析
- Matrix:矩阵分析类型,是es的多维度分析
Bucket
bucket的作用类似数据库的分组,把不同的分析结果装到几个桶里
- terms:按照词语分桶,分桶字段如果是keyword不会进行分词,直接按照整个单词分桶,如果是text则会先分词在分桶,不过使用text进行聚合需要设置fielddata为true
GET /u/_search
{
"aggs": {
"aggs_bulk": {
"terms": {
"field": "job.keyword",
"size":3//指定返回的个数
}
}
}
}
返回结果
"aggregations": {
"aggs_bulk": {
"doc_count_error_upper_bound": 0,
"sum_other_doc_count": 11,
"buckets": [
{
"key": "act",
"doc_count": 2
},
{
"key": "act av",
"doc_count": 2
},
{
"key": "basketball",
"doc_count": 2
}
]
}
}
-
range
范围聚合,统计数据指定范围的数量
GET /u/_search
{
"aggs": {
"aggs_range": {
"range": {
"field": "age",
"ranges": [
{
"to": 36//统计年龄小于36的人数
},
{
"from": 35,
"to": 41
}//统计年龄35.0-41.0之间的人数
]
}
}
}
}
返回结果
"aggregations": {
"aggs_range": {
"buckets": [
{
"key": "*-36.0",
"to": 36.0,
"doc_count": 9
},
{
"key": "35.0-41.0",
"from": 35.0,
"to": 41.0,
"doc_count": 7
}
]
}
}
-
date range
指定日期范围的分桶
GET /u/_search
{
"aggs": {
"aggs_range": {
"date_range": {
"field": "brith",
"format":"yyyy-MM-dd HH:mm:ss",
"ranges": [
{
"from":"1990-01-01 00:00:00",
"to": "2000-12-12 12:00:00"
}
]
}
}
}
}
返回结果
"aggregations": {
"aggs_range": {
"buckets": [
{
"key": "1990-01-01 00:00:00-2000-12-12 12:00:00",
"from": 6.31152E11,
"from_as_string": "1990-01-01 00:00:00",
"to": 9.766224E11,
"to_as_string": "2000-12-12 12:00:00",
"doc_count": 8
}
]
}
}
-
histogram
统计固定间隔的数据数量。类似一个树形柱状图
GET /u/_search
{
"aggs": {
"aggs_his": {
"histogram": {
"field": "age",
"interval":5,//统计年龄间隔5岁的各个阶段的人数
"extended_bounds": //查询范围
{
"min":30,
"max": 56
}
}
}
}
}
返回结果
"aggregations": {
"aggs_his": {
"buckets": [
{
"key": 25.0,
"doc_count": 2
},
{
"key": 30.0,
"doc_count": 6
},
{
"key": 35.0,
"doc_count": 7
},
{
"key": 40.0,
"doc_count": 1
},
{
"key": 45.0,
"doc_count": 0
},
{
"key": 50.0,
"doc_count": 0
},
{
"key": 55.0,
"doc_count": 1
}
]
}
}
-
date histogram
用于日期的histogram
GET /u/_search
{
"aggs": {
"aggs_his": {
"date_histogram": {
"field": "brith",
"interval":"year",//出生日期以每一年为间隔
"format": "yyyy-MM-dd HH:mm:ss"
}
}
}
}
输出结果
"aggregations": {
"aggs_his": {
"buckets": [
{
"key_as_string": "1954-01-01 00:00:00",
"key": -504921600000,
"doc_count": 1
},
{
"key_as_string": "1959-01-01 00:00:00",
"key": -347155200000,
"doc_count": 0
},
{
"key_as_string": "1967-01-01 00:00:00",
"key": -94694400000,
"doc_count": 0
},
{
"key_as_string": "1968-01-01 00:00:00",
"key": -63158400000,
"doc_count": 0
},
{
"key_as_string": "1969-01-01 00:00:00",
"key": -31536000000,
"doc_count": 0
},
{
"key_as_string": "1970-01-01 00:00:00",
"key": 0,
"doc_count": 0
},
{
"key_as_string": "1971-01-01 00:00:00",
"key": 31536000000,
"doc_count": 0
},
{
"key_as_string": "1972-01-01 00:00:00",
"key": 63072000000,
"doc_count": 0
},
{
"key_as_string": "1973-01-01 00:00:00",
"key": 94694400000,
"doc_count": 0
},
{
"key_as_string": "1974-01-01 00:00:00",
"key": 126230400000,
"doc_count": 0
},
{
"key_as_string": "1975-01-01 00:00:00",
"key": 157766400000,
"doc_count": 1
},
{
"key_as_string": "1976-01-01 00:00:00",
"key": 189302400000,
"doc_count": 0
},
{
"key_as_string": "1977-01-01 00:00:00",
"key": 220924800000,
"doc_count": 0
},
{
"key_as_string": "1982-01-01 00:00:00",
"key": 378691200000,
"doc_count": 1
},
{
"key_as_string": "1983-01-01 00:00:00",
"key": 410227200000,
"doc_count": 1
},
{
"key_as_string": "1984-01-01 00:00:00",
"key": 441763200000,
"doc_count": 0
},
.................
Metric
- 单值分析
分析只输出一个结果
- min、max、avg、sum
//查询age的最小值
GET /u/_search
{
"size": 0,
"aggs": {
"aggs_name": {
"min": {
"field": "age"
}
}
}
}
//查询age的最大值
GET /u/_search
{
"size": 0,
"aggs": {
"aggs_name": {
"max": {
"field": "age"
}
}
}
}
//查询age的总值
GET /u/_search
{
"size": 0,
"aggs": {
"aggs_name": {
"sum": {
"field": "age"
}
}
}
}
//查询age的平均值
GET /u/_search
{
"size": 0,
"aggs": {
"aggs_name": {
"avg": {
"field": "age"
}
}
}
}
可以将他们组合在一起
GET /u/_search
{
"size": 0,
"aggs": {
"aggs_avg": {
"avg": {
"field": "age"
}
},
"aggs_sum": {
"sum": {
"field": "age"
}
},
"aggs_max": {
"max": {
"field": "age"
}
},
"aggs_min": {
"min": {
"field": "age"
}
}
}
}
- cardinality:作用类似数据库的distinct,统计不同的数值个数
GET /u/_search
{
"size": 0,
"aggs": {
"aggs_cardinality": {
"cardinality": {
"field": "job.keyword"//统计工作字段不同的值有多少个
}
}
}
}
- 多值分析
分析可以输出多个结果
- stats、extended_stats、percentiles:
GET /u/_search
{
"size": 0,
"aggs": {
"aggs_stats": {
"stats": {
"field": "age"
}
}
}
}
输出的结果
"aggregations": {
"aggs_count": {
"count": 17,
"min": 25.0,
"max": 56.0,
"avg": 34.64705882352941,
"sum": 589.0
}
}
计算方差、标准差等使用extended_stats
GET /u/_search
{
"size": 0,
"aggs": {
"aggs_estats": {
"extended_stats": {
"field": "age"
}
}
}
}
输出结果
"aggregations": {
"aggs_estats": {
"count": 17,
"min": 25.0,
"max": 56.0,
"avg": 34.64705882352941,
"sum": 589.0,
"sum_of_squares": 21175.0,
"variance": 45.16955017301028,
"std_deviation": 6.720829574763094,
"std_deviation_bounds": {
"upper": 48.0887179730556,
"lower": 21.205399674003225
}
}
}
使用percentiles统计范围,例如统计数据中百分之多少的人是在哪个年龄段
GET /u/_search
{
"size": 0,
"aggs": {
"aggs_percentile": {
"percentiles": {
"field": "age"
}
}
}
}
返回结果
"aggregations": {
"aggs_percentile": {
"values": {
"1.0": 24.999999999999996,//24岁年龄段的有百分之1
"5.0": 25.0,//25岁年龄段的有百分之5
"25.0": 31.0,//31岁年龄段的有百分之25
"50.0": 35.0,//35岁年龄段的有百分之50
"75.0": 36.0,//36岁年龄段的有百分之75
"95.0": 50.74999999999998,//51岁年龄段的有百分之95
"99.0": 56.0//56岁年龄段的有百分之99
}
}
}
我们可以指定某个百分数范围内对应的数值是哪些
GET /u/_search
{
"size": 0,
"aggs": {
"aggs_percentile": {
"percentiles": {
"field": "age",
"percents":[50,75]
}
}
}
}
返回结果
"aggregations": {
"aggs_percentile": {
"values": {
"50.0": 35.0,
"75.0": 36.0
}
}
}
我们可以指定某个数值范围内是在数据中是处于哪个百分位
GET /u/_search
{
"size": 0,
"aggs": {
"aggs_percentile": {
"percentile_ranks": {
"field": "age",
"values":[56]//查询数据中在56岁范围内的有百分之多少
}
}
}
}
输出结果
"aggregations": {
"aggs_percentile": {
"values": {
"56.0": 100.0//百分之100
}
}
}
我们希望能从分组统计中同时获取到详情可以使用top_hits
GET /u/_search
{
"size": 0,
"aggs": {
"tj": {
"terms": {
"field": "job.keyword"
},
"aggs": {
"my_top": {
"top_hits": {
"size": 10,
"sort": [
{
"age": {
"order": "desc"
}
}
]
}
}
}
}
}
}
-
桶中桶 bucket+Metric的组合拳
在分组后,对每个组在进行统计
GET /u/_search
//统计在职业分组后,再统计每组职业的年龄段占比多少
{
"aggs": {
"aggs_term": {
"terms": {
"field": "job.keyword",
"size": 10
},
"aggs": {
"aggs_percentile": {
"percentiles": {
"field": "age"
}
}
}
}
}
}
返回结果
"aggregations": {
"aggs_term": {
"doc_count_error_upper_bound": 0,
"sum_other_doc_count": 2,
"buckets": [
{
"key": "act",
"doc_count": 2,
"aggs_percentile": {
"values": {
"1.0": 36.0,
"5.0": 36.0,
"25.0": 36.0,
"50.0": 38.5,
"75.0": 41.0,
"95.0": 41.0,
"99.0": 41.0
}
}
},
{
"key": "act av",
"doc_count": 2,
"aggs_percentile": {
"values": {
"1.0": 25.0,
"5.0": 25.0,
"25.0": 25.0,
"50.0": 30.0,
"75.0": 35.0,
"95.0": 35.0,
"99.0": 35.0
}
}
},
{
"key": "basketball",
"doc_count": 2,
"aggs_percentile": {
"values": {
"1.0": 30.999999999999996,
"5.0": 31.0,
"25.0": 31.0,
"50.0": 31.5,
"75.0": 32.0,
"95.0": 32.0,
"99.0": 32.0
}
}
},
{
"key": "basketball mvp",
"doc_count": 2,
"aggs_percentile": {
"values": {
"1.0": 29.999999999999996,
"5.0": 30.0,
"25.0": 30.0,
"50.0": 31.0,
"75.0": 32.0,
"95.0": 32.0,
"99.0": 32.0
}
}
},
{
"key": "basketball mvp act",
"doc_count": 2,
"aggs_percentile": {
"values": {
"1.0": 30.999999999999996,
"5.0": 31.0,
"25.0": 31.0,
"50.0": 43.5,
"75.0": 56.0,
"95.0": 56.0,
"99.0": 56.0
}
}
},
{
"key": "basketball act",
"doc_count": 1,
"aggs_percentile": {
"values": {
"1.0": 33.0,
"5.0": 33.0,
"25.0": 33.0,
"50.0": 33.0,
"75.0": 33.0,
"95.0": 33.0,
"99.0": 33.0
}
}
},
{
"key": "cartoonman hai",
"doc_count": 1,
"aggs_percentile": {
"values": {
"1.0": 36.0,
"5.0": 36.0,
"25.0": 36.0,
"50.0": 36.0,
"75.0": 36.0,
"95.0": 36.0,
"99.0": 36.0
}
}
},
{
"key": "cartoonman hot",
"doc_count": 1,
"aggs_percentile": {
"values": {
"1.0": 36.0,
"5.0": 36.0,
"25.0": 36.0,
"50.0": 36.0,
"75.0": 36.0,
"95.0": 36.0,
"99.0": 36.0
}
}
},
{
"key": "cartoonman qun",
"doc_count": 1,
"aggs_percentile": {
"values": {
"1.0": 36.0,
"5.0": 36.0,
"25.0": 36.0,
"50.0": 36.0,
"75.0": 36.0,
"95.0": 36.0,
"99.0": 36.0
}
}
},
{
"key": "cartoonman seven",
"doc_count": 1,
"aggs_percentile": {
"values": {
"1.0": 36.0,
"5.0": 36.0,
"25.0": 36.0,
"50.0": 36.0,
"75.0": 36.0,
"95.0": 36.0,
"99.0": 36.0
}
}
}
]
}
-
Pipeline管道聚合分析
针对上层分析的结果进行再次分析,其分析的结果会输出到上层的结果中,根据输出的位置不同分为两类:
-
内嵌到上层的结果中
min、max、avg、stats、percentiles
min_bucket\max_bucket\avg_bucket\stats_bucket\percentiles_bucket
用法差不多,举一个例子,
//计算每一类职业的平均收入,然后找出平均收入最低的职业
GET /u/_search
{
"aggs": {
"aggs_term": {
"terms": {
"field": "job.keyword"
},
"aggs": {
"aggs_avg": {
"avg": {
"field": "income"
}
}
}
},
"min_pipeline": {//给pipeline起名字,pipeline与aggs的子项aggs_term同级,导致后面的buckets_path要写成aggs_term>aggs_avg
//分析谁就与谁同级
"min_bucket": {//min_bucket是关键词,如果是算最大值换成max_bucket,平均值换成avg_bucket、同时获取多个指标则用stats_bucket
"buckets_path": "aggs_term>aggs_avg"//aggs的所属结构
}
}
}
}
输出结果
"min_pipeline": {
"value": 1000.0,//平均收入最低是1000
"keys": [//这几个职业都是收入最低的
"cartoonman hai",
"cartoonman hot",
"cartoonman qun",
"cartoonman seven"
]
}
-
结果与上层的结果同级
-
聚合分析的范围
aggs与query关键字同级,在query执行后,aggs针对query执行的结果进行分析,也就是说query查出来结果就是aggs的分析范围
也可以在aggs内部使用filter关键词限定范围
GET /u/_search
{
"aggs": {
"aggs_filter": {
"filter": {//利用filter限定aggs范围
"match": {
"job":"basketball"
}
},
"aggs":{
"job_aggs":{
"terms":{
"field":"job.keyword"
}
}
}
}
}
}
还要一种限定范围的方法是post_filter在aggs分析之后在进行筛选
{
"aggs": {
"job_aggs": {
"terms": {
"field": "job.keyword"
}
}
},
"post_filter":{
"match":{
"job.keyword":"mvp"
}
}
}
聚合排序sort
GET /u/_search
{
"aggs": {
"aggs_term": {
"terms": {
"field": "job.keyword",
"order":{
"aggs_avg":"desc"//以子聚合分析的结果排序
}
},
"aggs": {
"aggs_avg": {
"avg": {
"field": "income"
}
}
}
}
}
}
返回结果
"buckets": [
{
"key": "soccerball mvp",
"doc_count": 1,
"aggs_avg": {
"value": 5.0E8
}
},
{
"key": "basketball mvp",
"doc_count": 2,
"aggs_avg": {
"value": 1.3E8
}
},
{
"key": "basketball",
"doc_count": 2,
"aggs_avg": {
"value": 8.5E7
}
},
{
"key": "act",
"doc_count": 2,
"aggs_avg": {
"value": 1.01E7
}
},
{
"key": "basketball mvp act",
"doc_count": 2,
"aggs_avg": {
"value": 750000.0
}
},
{
"key": "basketball act",
"doc_count": 1,
"aggs_avg": {
"value": 600000.0
}
},
{
"key": "act av",
"doc_count": 2,
"aggs_avg": {
"value": 40000.0
}
},
{
"key": "great",
"doc_count": 1,
"aggs_avg": {
"value": 8000.0
}
},
.........................
以外层的key或者聚合分析数排序
GET /u/_search
{
"aggs": {
"aggs_term": {
"terms": {
"field": "job.keyword",
"order": [
{
"_key": "desc"//聚合结果的key排序
},
{
"_count": "desc"//聚合数量排序
}
]
},
"aggs": {
"aggs_avg": {
"avg": {
"field": "income"
}
}
}
}
}
}
返回结果
"buckets": [
{
"key": "soccerball mvp",
"doc_count": 1,
"aggs_avg": {
"value": 5.0E8
}
},
{
"key": "great",
"doc_count": 1,
"aggs_avg": {
"value": 8000.0
}
},
{
"key": "cartoonman seven",
"doc_count": 1,
"aggs_avg": {
"value": 1000.0
}
},
{
"key": "cartoonman qun",
"doc_count": 1,
"aggs_avg": {
"value": 1000.0
}
},
{
"key": "cartoonman hot",
"doc_count": 1,
"aggs_avg": {
"value": 1000.0
}
},
{
"key": "cartoonman hai",
"doc_count": 1,
"aggs_avg": {
"value": 1000.0
}
},
..........................
网友评论