match_phrase
https://www.elastic.co/guide/cn/elasticsearch/guide/current/phrase-matching.html
GET /my_index/my_type/_search
{
"query": {
"match_phrase": {
"title": "quick brown fox"
}
}
}
match_phrase会进行分词匹配,有专门的分词器,决定了输入的待匹配内容会被拆成多少个词,假设上面的会拆成quick、brown、fox三个词,那么能被match_phrase匹配上的结果,必须保证下面几个特点:
1、一定包含这三个词
2、而且三个词的相对位置和输入一样,匹配的记录等价于sql的%quick brown fox%模糊搜索一样
如果想要对被匹配的记录先进性处理后再判断是否命中,例如先计算数据的md5再来匹配我的输入的情况,只有在分词阶段才可以,所以必须要保证要有分词索引
term精确匹配
最为常用的 term 查询, 可以用它处理数字(numbers)、布尔值(Booleans)、日期(dates)以及文本(text)。
{
"term" : {
"price" : 20
}
}
text字段
字段类型为text的,es都会进行分词,而具体的分词器是什么需要自己去查询,analyzer指的就是分词器。查询es表的结构的方法为:
GET /cluster/index/_mappings
因为term是精确匹配,对于text字段,他匹配的其实就是分词的那个词,所以往往不能按照预期匹配到结果。
例如一个记录该字段值为你好,被分成了你、好两个词,那么term匹配“你好”是找不到这条记录的,除非分词的时候,有“你好”这一个完整的记录,才能被查询到,所以一般推荐使用match_phrase
https://www.cnblogs.com/Neeo/articles/10578482.html#match%E7%B3%BB%E5%88%97%E4%B9%8Bmatch_all%E6%9F%A5%E8%AF%A2%E5%85%A8%E9%83%A8
所以term查询对于非text类型的数据很好用
https://www.elastic.co/guide/cn/elasticsearch/guide/current/_finding_exact_values.html
https://blog.51cto.com/u_15127573/3288609
获取集群索引信息
GET /cluster/_cat/indices 获取集群上所有的索引
GET /cluster/_cat/indices/{business_id}* 获取指定业务的索引
GET /cluster/_cat/aliases 获取集群的索引别名
_msearch
_msearch指的是es的并发查询api,总体的格式为:
curl url -d '{"index":"xxxx"}\n{"query":{"match_phrase":{"field":"val"}}}\n{"index":"xxxx"}\n{"query":{"match_phrase":{"field":"val"}}}\n'
就是body是一个index+查询体的对,每一对这个对应了一次_search,而且要注意index和query的内容后一定要加换行符,尤其是最后的换行符不能忘了,否则最后一次的查询不会执行,返回的结果和我们的请求的顺序是一致的。
相当于执行了多个_search,然后把每次的结果放到数组里返回,顺序和请求顺序一致。
布尔查询
https://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl-bool-query.html
主要有should、must、must_not三种布尔关系,分别对应的sql where子句:
should => where fieldname in (a,b,c)
must => where fieldname = a
must_not => where fieldname != a
POST _search
{
"query": {
"bool" : {
"must" : {
"term" : { "user.id" : "kimchy" }
},
"filter": {
"term" : { "tags" : "production" }
},
"must_not" : {
"range" : {
"age" : { "gte" : 10, "lte" : 20 }
}
},
"should" : [
{ "term" : { "tags" : "env1" } },
{ "term" : { "tags" : "deployed" } }
],
"minimum_should_match" : 1,
"boost" : 1.0
}
}
}
es在查询的时候,会给每个文档进行计分,分数越高的文档,表示该记录匹配程度越高;如果是简单的size 1 查询,计分对性能的影响影响不大,但如果需要返回很多结果,那么这个计分的占用就不能忽略了,这个时候可以使用filter来过滤,filter过滤也可以达到布尔查询的作用,但是filter不会计算得分,因此性能会更快。
filter内部查询体和平时的match_phrase、term一样:
GET /_search
{
"query": {
"bool": {
"should": [
{ "match": { "name.first": { "query": "shay", "_name": "first" } } },
{ "match": { "name.last": { "query": "banon", "_name": "last" } } }
],
"filter": {
"terms": {
"name.last": [ "banon", "kimchy" ],
"_name": "test"
}
}
}
}
}
集群压力过大,导致读写失败
https://cloud.tencent.com/document/product/845/56274
[2019-03-01 10:09:58][ERROR]rspItemError: {"reason":"rejected execution of org.elasticsearch.transport.TransportService$7@5436e129 on EsThreadPoolExecutor[bulk, queue capacity = 1024, org.elasticsearch.common.util.concurrent.EsThreadPoolExecutor@6bd77359[Running, pool size = 12, active threads = 12, queued tasks = 2390, completed tasks = 20018208656]]","type":"es_rejected_execution_exception"}
这是因为集群并发太高,导致请求做不过来,es会将做不过来的请求直接丢弃,错误返回,服务降级,以使集群先扛过这批请求高峰
ignore_unavailable=true
使用这个参数,es返回结果的时候,不论是否出现了问题,例如查询失败、集群压力过高等问题,都会按照统一的格式返回,只是错误或者不可用的时候,hits.hits为空数组而已。没有标志位来判断到底有没有执行出错。
如果不使用这个参数,则可以根据回包中的status字段判断是否正常查询,200为正常,其他为不正常。
python elastictsearch_dsl
https://elasticsearch-dsl.readthedocs.io/en/latest/search_dsl.html
Query combination
定位到文档的这个章节,可以知道如何使用Q函数来组装es基本查询体。

获取索引的设置
curl -XGET "host/cluster/index/_settings"
查看表字段结构
curl 'host/cluster/index/_mapping' -XGET
terms多值匹配查询
https://doc.codingdict.com/elasticsearch/267/
筛选所拥有的字段能够匹配提供的多个项(not analyzed)中任意一个的文档。
GET /tweets/_search
{
"query" : {
"terms" : {
"user" : {
"index" : "users",
"type" : "user",
"id" : "2",
"path" : "followers"
}
}
}
}
collapse折叠(实现去重查询)
collapse不可以和scroll一起使用,并且collapse的去重只能根据一个字段来去重:
https://www.elastic.co/guide/en/elasticsearch/reference/current/collapse-search-results.html
GET my-index-000001/_search
{
"query": {
"match": {
"message": "GET /search"
}
},
"collapse": {
"field": "user.id"
},
"sort": [
{
"http.response.bytes": {
"order": "desc"
}
}
],
"from": 0
}

collapse的逻辑其实是在查询条件之后的,等价于
select distinct(field) from table where xxxx;
要注意上面请求es参数中的from,这个是执行完查询(query+collapse)之后才做偏移的,所以,如果collapse查询得到了5条数据,但是from=10,那么最终查询结果返回空(因为偏移5之后就已经没有数据了)。
标准分词下的标点符号问题
在开发过程中,发现“明天,你好”会被match_phrase : {"key":"明天你好"}的query匹配到。
这是因为该字段是标准分词,有可能会把一些标点符号给忽略掉,所以“明天,你好”存在es的分词就是“明天你好”,进而被匹配到。

https://www.elastic.co/guide/cn/elasticsearch/guide/current/standard-tokenizer.html
es的一些基本概念
segment file: 众所周知,Elasticsearch 存储的基本单元是 shard , ES 中一个 Index 可能分为多个 shard , 事实上每个 shard 都是一个 Lucence 的 Index ,并且每个 Lucence Index 由多个 Segment 组成, 每个 Segment 事实上是一些倒排索引的集合, 每次创建一个新的 Document , 都会归属于一个新的 Segment , 而不会去修改原来的 Segment ; 且每次的文档删除操作,会仅仅标记 Segment 中该文档为删除状态, 而不会真正的立马物理删除, 所以说 ES 的 index 可以理解为一个抽象的概念。es 每秒都会生成一个 segment 文件,当文件过多时 es 会自动进行 segment merge(合并文件),合并时会同时将已经标注删除的文档物理删除;
commit:为了数据安全, 每次的索引变更都最好要立刻刷盘, 所以 Commit 操作意味着将 Segment 合并,并写入磁盘。保证内存数据尽量不丢。刷盘是很重的 IO 操作, 所以为了机器性能和近实时搜索, 并不会刷盘那么及时。
commit point: 记录当前所有可用的 segment ,每个 commit point 都会维护一个 .del 文件( es 删除数据本质是不属于物理删除),当 es 做删改操作时首先会在 .del 文件中声明某个 document 已经被删除,文件内记录了在某个 segment 内某个文档已经被删除,当查询请求过来时在 segment 中被删除的文件是能够查出来的,但是当返回结果时会根据 commit point 维护的那个 .del 文件把已经删除的文档过滤掉;
translog: translog 提供所有还没有被刷到磁盘的操作的一个持久化纪录。当 Elasticsearch 启动的时候, 它会从磁盘中使用最后一个提交点去恢复已知的段,并且会重放 translog 中所有在最后一次提交后发生的变更操作。为了防止 elasticsearch 宕机造成数据丢失保证可靠存储,es 会将每次的操作同时写到 translog 日志中。新文档被索引意味着文档会被首先写入内存 buffer ,操作会被写入 translog 文件。每个 shard 都对应一个 translog 文件;translog 会每隔 5 秒异步执行或者在每一个请求完成之后执行一次 fsync 操作,将 translog 从缓存刷入磁盘,这个操作比较耗时,如果对数据一致性要求不是跟高时建议将索引改为 async ,如果节点宕机时会有 5 秒数据丢失;
refresh:写入和打开一个新 segment 的轻量的过程,es 接收数据请求时先存入内存中,默认每隔一秒会从内存 buffer 中将数据写入 filesystem cache 中的一个 segment,内存 buffer 被清空,这个时候索引变成了可被搜索的,这个过程叫做 refresh;
fsync:fsync 是一个 Unix 系统调用函数, 用来将内存 buffer 中的数据存储到文件系统. 这里作了优化, 是指将 filesystem cache 中的所有 segment 刷新到磁盘的操作;
flush:es 默认每隔 30 分钟或者操作数据量达到 512mb ,会将内存 buffer 的数据全都写入新的 segment 中,内存 buffer 被清空,一个 commit point 被写入磁盘,并将 filesystem cache 中的数据通过 fsync 刷入磁盘,同时清空 translog 日志文件,这个过程叫做 flush;
近实时搜索:提交(Commit)一个新的 segment 到磁盘需要一个 fsync 来确保 segment 被物理性地写入磁盘,这样在断电的时候就不会丢失数据。 但是 fsync 操作代价很大; 如果每次索引一个文档都去执行一次的话会造成很大的性能问题。我们需要的是一个更轻量的方式来使一个文档可被搜索,这意味着 fsync 要从整个过程中被移除。在 es 和磁盘之间是 filesystem cache 。 像之前描述的一样, 在内存缓冲区中的文档会被写入到一个新的段中。 但是这里新段会被先写入到文件系统缓存,这一步代价会比较低,稍后再被刷新(flush)到磁盘,这一步代价比较高。不过只要文件已经在 filesystem cache 中, 就可以像其它文件一样被打开和读取了。Lucene 允许新 segment 被写入和打开,使其包含的文档在未进行一次完整提交时便对搜索可见。 这种方式比进行一次提交代价要小得多,并且在不影响性能的前提下可以被频繁地执行。而 es 中底层搜索的此种方式基于 refresh ,refresh 的默认值是 1s ,所以搜索不是实时的,而是近实时。
https://elasticsearch.cn/question/2941
https://www.elastic.co/guide/en/elasticsearch/reference/6.4/consistent-scoring.html
https://cloud.tencent.com/developer/article/1445643
https://blog.csdn.net/weixin_43378396/article/details/105569647
ES的查询阶段
https://www.elastic.co/guide/cn/elasticsearch/guide/current/_query_phase.html
收到用户请求的节点,成为协调节点,会将请求广播到该索引下所有分片所在的节点,每个分片进行查询,并将结果返回给协调节点,协调节点组合返回给客户端。
使用的问题和解决
上一秒查得到,下一秒查不到,数据不一致
背景:
服务先根据查询条件查了一遍数据,假设有10条数据,此时将其中的某几条数据勾选出来,查询条件改为terms: [_id,...]
发现第二次查不到数据,或者数据查少了(例如勾选5条,第二次只查到3条)
1、一开始以为是索引refresh的问题,但是如果第一次能查到,说明索引已经refresh好了,不应该出现第二次查不到的情况。
- 关于refresh的,有几篇文章可以后面回顾:
https://www.elastic.co/guide/cn/elasticsearch/guide/current/translog.html
https://cloud.tencent.com/developer/article/1823390
https://blog.csdn.net/hugo_lei/article/details/106519069
https://www.elastic.co/guide/cn/elasticsearch/guide/current/near-real-time.html
简单来说,es是一个近实时查询系统,近实时的原因是因为写入的数据并不是立刻可以被查到的,es的存储结构是这样的:
内存buffer、文件系统缓存、磁盘
写入的数据会首先落在内存buffer,时间过了一个refresh_interval后(不是写入时间开始算,是es自己的时间),会将内存buffer的数据写入到一个segment上,这个segment其实就是lucene的索引文件,这时候segment已经可以被打开了,文档可以被用户搜索得到,默认es集群配置是1s的refresh_interval,所以称es为近实时搜索。
为了持久化存储数据,segment的数据最终会fsync到磁盘,这很吃io性能,基本上都设置为async的形式进行,fsync也有一个时间间隔。
es还有一个translog,也会最终将数据落到磁盘,类似于mysql的binlog,记录了写操作,集群可以从translog中恢复数据,保持数据一致性。
2、随后认为是es数据落磁盘的过程中,不可搜索,实际上并不是(如果是的话,就太蠢了)
3、查询google的过程中,发现很多人也遇到过类似的问题:https://www.elastic.co/guide/en/elasticsearch/reference/6.4/consistent-scoring.html#_scores_are_not_reproducible
es的索引数据是以分片(shard)的形式存储的,类似于数据库分表,但es为了做容灾,一般都会配置副本分片,所以es的分片有两类:主分片、副本分片,副本分片是可读的,也即查询请求可以落到副本分片上,所以副本分片多的话也可以提高集群的查询性能,但过多的副本分片会导致机器成本的上升。
主分片和副本分片不会部署在同一个节点,来保证容灾切换。
我们先来了解一下es数据写入的逻辑:
https://www.elastic.co/guide/en/elasticsearch/reference/master/docs-replication.html

主分片和副本分片之间需要同步数据,客户端请求写入是双写成功后才会返回的,所以如果写入请求回复成功,至少保证了主分片和可用的副本分片都有了这条数据(至少在内存buffer上)。
问题就出现在这里,主、副本分片写入之后,并不能保证是同时refresh的,两者refresh有时间差,所以会出现下面的情况:
1、第一次查询路由到主分片(已refresh),第二次路由到副本分片(未refresh)
2、第一次查询路由到副本分片(已refresh),第二次路由到主分片(未refresh)
这就导致了本问题的出现。
时间差的问题官方也有相关回答,主分片和副本分片的refresh不是同步的:
https://discuss.elastic.co/t/same-query-different-results/60982

https://discuss.elastic.co/t/how-refresh-works-between-primary-and-replica-shards/219181

知道问题之后,那就是保证两次查询都路由到同一类分片就行了,在查询es的参数中,有一个preference的参数,可以指定为下列值:
(1)_primary:只查询主shard,也就是说不管你有多少个副本,
只对主shard进行检索,这种场景可以用在所有副本不可用的时候,强制读取主shard数据。
(2)_primary_first:优先读取主shard,如果主shard无效或者失败,
则会读取其他shard
(3)_replica:只查询replia
(4)_replica_first:优先查询replia,如果replia无效就查询其他的shard。
(5)_local:尽可能在本地执行查询,不跨网络
(6)_prefer_nodes:abc,xyz 在指定的节点id上执行查询
(7)_shards:2,3 查询指定分片上的数据,此外这种写法还可以和前面的用法组合,
如:_shards:2,3|_primary ,查询分片2和3且在主节点上的数据
(8)_only_nodes :限制在特定的node上执行操作
(9)Custom (string) value :使用自定义的值来保证同一个值的数据,
在一个shard里面,感觉有点像routing字段的功能,暂时没太理解如何使用这个功能,因为官网给的例子,只有查询,在索引的时候没有看到设置preference的功能:
GET /_search?preference=xyzabc123
{
"query": {
"match": {
"title": "elasticsearch"
}
}
}
在使用preference参数,指定为_primary_first之后,问题不再复现。
但是这样会影响原有的查询性能,因为副本分片承载用户请求负载的能力给去掉了,所有负载都打到了主分片上,对查询性能有一定影响,但具体影响多少没有确定的百分比,因为查询延时还受集群当前查询压力而定。
text和keyword字段的regexp查询
regexp的查询方式:
https://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl-regexp-query.html

curl 'your_index/_search?pretty' -X GET -H 'Content-Type: application/json' -d '{"_source":[],"query":{"bool":{"must": [{"term": {"is_valid":"1"}},{"range":{"create_time": {"gte":"1654012800","lte":"1654703999"}}},{"regexp": {"file_name":".rex."}}]}},"sort": [{"create_time":{"order":"desc"}}],"size":100,"from":0}'
上面的dsl表示查询file_name符合正则规则类似于mysql的'%rex%'
但是对于text和keyword字段,这里需要特别注意,由于text字段会分词索引,会对英文进行转小写后分词,所以如果我们如果录入的是REX,但是我查的时候用的REX就查不出来了, 因为底层存的是rex的分词;
相反,keyword不会分词,存的内容就是落的内容,也即REX,所以我们用REX去搜索是能够查到的;
修改字段类型
目前没查到可以直接修改字段类型的,都需要重建索引。。
https://blog.csdn.net/napoay/article/details/52012249
https://blog.csdn.net/apple9005/article/details/90415558
字段类型
https://blog.csdn.net/ZYC88888/article/details/83059040
更新字段类型
这里对已经存在的索引,进行的字段更新只是指字段新增或者对已有的字段,更新为multi-fields
https://www.elastic.co/guide/en/elasticsearch/reference/current/indices-put-mapping.html



如果将已有的字段类型改成别的类型,原有的数据将会失效,需要重新reindex构建,可以使用reindex的api:https://www.elastic.co/guide/en/elasticsearch/reference/current/docs-reindex.html
fields关键字
https://www.elastic.co/guide/en/elasticsearch/reference/current/multi-fields.html
这个关键字的作用时,实现同一个字段的multi-field,举个例子,例如字段A原本是text类型,可以使用这个关键字,在字段A落存的时候同时生成别的字段类型的数据,例如生成keyword的存储,这样我们可以用分词去搜到这条记录,也可以使用keyword的全词搜索;
以下面的为例

zhiling_num是一个text字段,他会分词索引,有些查询方式不一定能查到数据,例如regexp,text字段会把英文全部转小写后分词,所以regexp输入大写是搜不到数据;
而使用zhiling_num.keyword字段就可以正确使用regexp,注意这里zhiling_num.keyword是一个完整的字段名,我们可以还可以取别的名字,例如zhiling_num.my_new_field_name。
就相当于,我们写入zhiling_num的字段值时,es会给我们同时按照keyword字段类型落一份数据到zhiling_num.keyword字段下,并且其字段为keyword,这样我们就能通过不同搜索方式来匹配到这条数据。
动态字段
es默认是支持动态新增字段的,也即我们不需要提前定义索引的字段类型,如果有新的字段插入,es会对类型进行推测,并自动创建对应的新字段,这一切和dynamic参数有关系
https://www.elastic.co/guide/cn/elasticsearch/guide/current/dynamic-mapping.html

推测的方式受dynamic template和dynamic field mappings影响
https://www.elastic.co/guide/en/elasticsearch/reference/current/dynamic-mapping.html
https://www.elastic.co/guide/en/elasticsearch/reference/current/dynamic-field-mapping.html
dynamic除了可以是true、false、strict以外,还可以是runtime,runtime和true的情况很像,但是有一定差别,具体可以看上面的链接介绍
https://www.elastic.co/guide/en/elasticsearch/reference/current/dynamic-templates.html
上面的链接是动态模块的介绍,动态模版的作用和dynamic类似,只是提供了更灵活的动态创建字段的能力:

支持匹配类型、正则匹配字段名、正则匹配路径名(例如match和unmatch,正向匹配和反向匹配,前者表示匹配的就使用某个规则,后者表示排除匹配的到情况)

上面表示如果新的字段类型是string,并且字段名以long_开头,就映射为long字段类型,并且排除字段名为_text结尾的,如果是这个结尾的就匹配不上这个规则。

path_match是针对object对象的,例如上面的规则表示name对象的任意一个key都符合条件,This example copies the values of any fields in the name object to the top-level full_name field, except for the middle field。
例如将name.first的值John写到full_name字段下;
虽然下面这种情况都可以命中这条规则,但是会报错,因为title对象不是一个字符串:

更多的信息看官方的链接文档。
template创建和更新
https://discuss.elastic.co/t/how-do-i-update-a-index-template/91455
A put request should work to update template, note template is applied at index creation so wont affect existing indices
https://www.elastic.co/guide/en/elasticsearch/reference/current/indices-templates.html
template只是数据插入es时,索引的字段类型的创建,不会确切影响字段类型,相当于是一个索引字段类型的创建指引。
所以可以更新template但是不会去影响索引已有的数据,一般是在创建索引后,随即创建template,这样后续数据插入的时候就可以知道该映射生成什么类型的字段了。
nested字段类型
https://www.elastic.co/guide/en/elasticsearch/reference/current/nested.html
是一种object的字段类型,类似与object(json),但是又有不同之处,主要是针对数组字段。
allows arrays of objects to be indexed in a way that they can be queried independently of each other
很难文字说明,看官方文档的示例,简单来说就是一个含有数组字段的obj,如果是object类型的字段,es只会把数组的元素合并到一起展平为一个字段,这样在查询时就有种类似值是否在这个数组里的判断,就会出现误判,可以理解成两个数组的取值的任意排列组合只要满足你的query条件,就都会返回这条数据,这往往和你预期不符合;而nested字段则不会,他会给每个数组元素都进行一个隐式的索引,这样查询数组内的字段值就不会出现错乱。
https://blog.csdn.net/laoyang360/article/details/82950393
https://opster.com/guides/elasticsearch/data-architecture/elasticsearch-nested-field-object-field/
object字段的mapping
https://www.elastic.co/guide/en/elasticsearch/reference/current/object.html

创建、新增、删除mapping
https://blog.csdn.net/huang_550/article/details/80619750
删除文档
curl "es_host/your_doc_type/doc_id" -XDELETE
your_doc_type是我们写入es的文档类型,查询es的时候会同时返回type,就是这个的值;
doc_id是文档的主键,查询的时候返回的_id就是这个的值