Elasticsearch
什么是Elasticsearch
Elasticsearch 是分布式、高性能、高可用、可伸缩的搜索和分析系统。
这里搜索的含义指的是就是在任何场景下,找寻你想要的信息,这个时候,会输入一段你要搜索的关键字,然后就期望找到这个关键字相关的全部信息。
为什么不用传统数据库做搜索
数据库也具备查询数据的能力,但是不太适合做全文检索。但是目前能想到的有2点不符合我的要求:
- 每条记录的指定字段的文本,可能会很长,比如说“商品描述”字段的长度,有长达数千个,甚至数万个字符,这个时候,每次都要对每条记录的所有文本进行扫描,懒判断说,你包不包含我指定的这个关键词(比如说"机构")
- 不能将搜索词拆分开来,尽可能去搜索更多的符合你的期望的结果,比如输入"生化飞机",就搜索不出来"生化危机"和"战斗飞机"。
什么是全文检索和lucene
- 全文检索是基于倒排索引进行搜索,对全文进行分词得到一张表,表的key可以看做是分词得到的词语,value看做是每条记录的编号。
- lucene,就是一个jar包,里面包含了封装好的各种建立倒排索引,以及进行搜索的代码,包括各种算法。我们就用java开发的时候,引入lucene jar,然后基于lucene的api进行去进行开发就可以了。用lucene,我们就可以去将已有的数据建立索引,lucene会在本地磁盘上面,给我们组织索引的数据结构。
什么是Elasticsearch
lucene可以做搜索,但是lucene是单机版的,如果想要做分布式来满足大数据的要求,那我们就需要在每台机器上去弄一个lucene,并且要去实现一台机器宕机后系统还能正常运转,前端过来的请求到底发送到哪个lucene机器建立的索引上也是我们要考虑的问题,还有很多分布式的问题需要我们考虑。Elasticsearch就是基于lucene做的封装,帮我们实现了我们想到的和没有想到的很多问题。
Elasticsearch功能
- 分布式的搜索引擎和数据分析引擎
- 搜索:网站的站内搜索,IT系统的检索
- 数据分析:电商网站,最近7天牙膏这种商品销量排名前10的商家有哪些;新闻网站,最近1个月访问量排名前3的新闻版块是哪些
- 全文检索,结构化检索,数据分析
- 全文检索:我想搜索商品名称包含牙膏的商品,select * from products where product_name like "%牙膏%"
- 结构化检索:我想搜索商品分类为日化用品的商品都有哪些,select * from products where category_id='日化用品'
- 数据分析:我们分析每一个商品分类下有多少个商品,select category_id,count(*) from products group by category_id
- 对海量数据进行近实时的处理
- 分布式:ES自动可以将海量数据分散到多台服务器上去存储和检索
- 近实时:在秒级别对数据进行搜索和分析
Elasticsearch核心概念
- Near Realtime(NRT):近实时。可以从2个层面来理解:从写入数据到数据可以被搜索到有一个小延迟(大概1秒);基于es执行搜索和分析可以达到秒级
- Cluster(集群):包含多个节点,每个节点属于哪个集群是通过一个配置(集群名称,默认是elasticsearch)来决定的,对于中小型应用来说,刚开始一个集群就一个节点很正常
- Node(节点):集群中的一个节点,节点也有一个名称(默认是随机分配的),节点名称很重要(在执行运维管理操作的时候),节点默认会去加入一个名称为“elasticsearch”的集群,如果直接启动一堆节点,那么它们会自动组成一个elasticsearch集群,当然一个节点也可以组成一个elasticsearch集群
- Document&field:文档是es中的最小数据单元,一个document可以是一条客户数据,一条商品分类数据,一条订单数据,通常用JSON数据结构表示。一个document里面有多个field,每个field就是一个数据字段。
- Index:索引,包含一堆有相似含义的文档数据,比如可以有一个客户索引,商品分类索引,订单索引,索引有一个名称。比如说建立一个product index,商品索引,里面可能就存放了所有的商品数据。
- type:类型,每个索引里都可以有一个或多个type,type是index中的一个逻辑数据分类,一个type下的document,都有相同的field,比如博客系统,有一个索引,可以定义用户数据type,博客数据type,评论数据type。商品index可以分为鞋类type, 服装类type等。
- shard:单台机器无法存储大量数据,es可以将一个索引中的数据切分为多个shard,分布在多台服务器上存储。有了shard就可以横向扩展,存储更多数据,让搜索和分析等操作分布到多台服务器上去执行,提升吞吐量和性能。每个shard都是一个lucene index。一台机器可以放多个shard.
- replica:任何一个服务器随时可能故障或宕机,此时shard可能就会丢失,因此可以为每个shard创建多个replica副本。replica可以在shard故障时提供备用服务,保证数据不丢失,多个replica还可以提升搜索操作的吞吐量和性能。primary shard(建立索引时一次设置,不能修改,默认5个),replica shard(随时修改数量,默认1个),默认每个索引10个shard,5个primary shard,5个replica shard,最小的高可用配置,是2台服务器。
Elasticsearch 的数据格式
elasticsearch是面向文档的,这里理解和关系型数据库的区别。对象数据存储到数据库中,只能拆解开来,变为扁平的通过外键关联多张表,每次查询的时候还得还原回对象格式。ES是面向文档的,文档中存储的数据结构,与面向对象的数据结构是一样的,基于这种文档数据结构,es可以提供复杂的索引,全文检索,分析聚合等功能,这需要我们转换一下思维。
集群管理
- 查看集群健康状态:GET /_cat/health?v
- green:每个索引的primary shard和replica shard都是active状态
- yellow:每个索引的primary shard都是active状态的,但是部分replica shard不是active状态,处于不可用的状态
- red:不是所有索引的primary shard都是active状态的,部分索引有数据丢失了
- 快速查看集群已有索引:GET /_cat/indices?v
- 创建索引:PUT /test_index
- 删除索引:DELETE /test_index
Elasticsearch架构
Elasticsearch对复杂分布式机制的透明隐藏特性
Elasticsearch是一套分布式的系统,分布式是为了应对大数据量隐藏了复杂的分布式机制。
shard负载均衡:举例,假设现在有3个节点,总共有25个shard要分配到3个节点上去,es会自动进行均匀分配,以保持每个节点的均衡的读写负载请求
shard副本,请求路由,集群扩容,shard重分配。
垂直扩容与水平扩容
-
垂直扩容:采购更强大的服务器,成本非常高昂,而且会有瓶颈,假设世界上最强大的服务器容量就是10T,但是当你的总数据量达到5000T的时候,你要采购多少台最强大的服务器啊
-
水平扩容:业界经常采用的方案,采购越来越多的普通服务器,性能比较一般,但是很多普通服务器组织在一起,就能构成强大的计算和存储能力
水平扩容
(1)primary&replica自动负载均衡,6个shard,3 primary,3 replica
(2)每个node有更少的shard,IO/CPU/Memory资源给每个shard分配更多,每个shard性能更好
(3)扩容的极限,6个shard(3 primary,3 replica),最多扩容到6台机器,每个shard可以占用单台服务器的所有资源,性能最好
(4)超出扩容极限,动态修改replica数量,9个shard(3primary,6 replica),扩容到9台机器,比3台机器时,拥有3倍的读吞吐量
(5)3台机器下,9个shard(3 primary,6 replica),资源更少,但是容错性更好
master节点
- 创建或删除索引
- 增加或删除节点
节点平等的分布式架构
- 节点对等,每个节点都能接收所有的请求
- 自动请求路由
- 响应收集
master选举,replica容错,数据恢复
(1)9 shard,3 node
(2)master node宕机,自动master选举,red
(3)replica容错:新master将replica提升为primary shard,yellow
(4)重启宕机node,master copy replica到该node,使用原有的shard并同步宕机后的修改,green
shard&replica机制
(1)index包含多个shard
(2)每个shard都是一个最小工作单元,承载部分数据,lucene实例,完整的建立索引和处理请求的能力
(3)增减节点时,shard会自动在nodes中负载均衡
(4)primary shard和replica shard,每个document肯定只存在于某一个primary shard以及其对应的replica shard中,不可能存在于多个primary shard
(5)replica shard是primary shard的副本,负责容错,以及承担读请求负载
(6)primary shard的数量在创建索引的时候就固定了,replica shard的数量可以随时修改
(7)primary shard的默认数量是5,replica默认是1,默认有10个shard,5个primary shard,5个replica shard
(8)primary shard不能和自己的replica shard放在同一个节点上(否则节点宕机,primary shard和副本都丢失,起不到容错的作用),但是可以和其他primary shard的replica shard放在同一个节点上
Elasticsearch元数据
_index:
- 类似的数据放在一个索引,非类似的数据放不同索引:product index(包含了所有的商品),sales index(包含了所有的商品销售数据),inventory index(包含了所有库存相关的数据)。如果你把比如product,sales,human resource(employee),全都放在一个大的index里面,比如说company index,不合适的。
- index中包含了很多类似的document:类似是什么意思,其实指的就是说,这些document的fields很大一部分是相同的,你说你放了3个document,每个document的fields都完全不一样,这就不是类似了,就不太适合放到一个index里面去了。
为什么类似的数据放在一个_index
如果product和sales的数据放在一个index里面,那么比如有3个shard放了这些数据,如果前端对于product的请求很耗时,那么就会影响sales的请求。放在不同的index, 就会放在不同的shard, 这样不同种类的数据就不会互相影响。
_type
一个索引通常会划分为多个type,逻辑上对index中有些许不同的几类数据进行分类:因为一批相同的数据,可能有很多相同的fields,但是还是可能会有一些轻微的不同,可能会有少数fields是不一样的,举个例子,就比如说,商品,可能划分为电子商品,生鲜商品,日化商品,等等.
type,是一个index中用来区分类似的数据的,类似的数据,但是可能有不同的fields,而且有不同的属性来控制索引建立、分词器
field的value,在底层的lucene中建立索引的时候,全部是opaque bytes类型,不区分类型的
lucene是没有type的概念的,在document中,实际上将type作为一个document的field来存储,即_type,es通过_type来进行type的过滤和筛选
一个index中的多个type,实际上是放在一起存储的,因此一个index下,不能有多个type重名,而类型或者其他设置不同的,因为那样是无法处理的.
{
"name": "geli kongtiao",
"price": 1999.0,
"service_period": "one year"
}
{
"name": "aozhou dalongxia",
"price": 199.0,
"eat_period": "one week"
}
表明上看存储是这样的:
{
"ecommerce": {
"mappings": {
"product": {
"properties": {
"desc": {
"type": "text",
"fields": {
"keyword": {
"type": "keyword",
"ignore_above": 256
}
}
},
"name": {
"type": "text",
"fields": {
"keyword": {
"type": "keyword",
"ignore_above": 256
}
},
"fielddata": true
},
"price": {
"type": "long"
},
"producer": {
"type": "text",
"fields": {
"keyword": {
"type": "keyword",
"ignore_above": 256
}
}
},
"query": {
"properties": {
"match_all": {
"type": "object"
}
}
},
"tags": {
"type": "text",
"fields": {
"keyword": {
"type": "keyword",
"ignore_above": 256
}
},
"fielddata": true
}
}
},
"xx": {
"properties": {
"name": {
"type": "text",
"fields": {
"keyword": {
"type": "keyword",
"ignore_above": 256
}
},
"fielddata": true
}
}
}
}
}
}
在底层的存储是这样子的:
"ecommerce": {
"mappings": {
"_type": {
"type": "string",
"index": "not_analyzed"
},
"name": {
"type": "string"
}
"price": {
"type": "double"
}
"service_period": {
"type": "string"
}
"eat_period": {
"type": "string"
}
}
}
}
{
"_type": "elactronic_goods",
"name": "geli kongtiao",
"price": 1999.0,
"service_period": "one year",
"eat_period": ""
}
{
"_type": "fresh_goods",
"name": "aozhou dalongxia",
"price": 199.0,
"service_period": "",
"eat_period": "one week"
}
_id
代表document的唯一标识,与index和type一起,可以唯一标识和定位一个document
DSL
DSL:Domain Specified Language,特定领域的语言.
validate-query API
用于校验和提示查询语法。
GET /company/employee/_validate/query?explain
{
"query": {
"constant_score": {
"filter": {
"range": {
"age": {
"gte": 30
}
}
}
}
}
}
explain用于解释不合法的原因。
简单查询
查询所有:
match_all 里不能加字段
GET /ecommerce/product/_search
{
"query":{
"match_all":{}
}
}
match、match_phase、term和query_string查询
-
term query会去倒排索引中寻找确切的term,它并不知道分词器的存在,就是说查询的时候,是查询字段分词结果中是否有term指定的字样。这种查询适合keyword 、numeric、date.
-
match query会把要查询的词语分词,然后把分词得到的结果去倒排索引里进行查询
-
match_phase: 会对输入做分词,但是需要结果中也包含所有的分词,而且顺序要求一样。以"hello world"为例,要求结果中必须包含hello和world,而且还要求他们是连着的,顺序也是固定的,hello that word不满足,world hello也不满足条件。
-
query_string:和match类似,但是match需要指定字段名,query_string是在所有字段中搜索,范围更广泛。
GET /text/product2/_search { "query": { "query_string": { "query": "I hello" } } }
查询名称包含yagao的商品,同时按照价格降序排序:
GET /ecommerce/product/_search
{
"query": {
"match": {
"name": "yagao"
}
},
"sort":[
{"price": {"order": "desc"}}
],
"_source": ["name", "price"]
}
分页查询商品,总共3条商品,假设每页就显示1条商品,现在显示第2页,所以就查出来第2个商品:
GET /ecommerce/product/_search
{
"query": {
"match": {
"name": "yagao"
}
},
"from": 1,
"size": 1
}
range过滤允许我们按照指定范围查找一批数据:
GET /ecommerce/product/_search
{
"query": {
"range": {
"price": {
"gte": 10,
"lte": 20
}
}
}
}
minimum_should_match代表了最小匹配精度,如果设置minimum_should_match=1,那么should语句中至少需要有一个条件满足,查询语句
多条件查询
搜索商品名称包含yagao,而且售价大于25元的商品
GET /ecommerce/product/_search
{
"query": {
"bool": {
"must": [
{"match": {
"name": "yagao"
}},
{"range": {
"price": {
"gte": 24
}
}}
]
}
}
}
scoll 批量查询
如果一次性要查出来比如10万条数据,那么性能会很差,此时一般会采取用scoll滚动查询,一批一批的查,直到所有数据都查询完处理完
使用scoll滚动搜索,可以先搜索一批数据,然后下次再搜索一批数据,以此类推,直到搜索出全部的数据来
scoll搜索会在第一次搜索的时候,保存一个当时的视图快照,之后只会基于该旧的视图快照提供数据搜索,如果这个期间数据变更,是不会让用户看到的
采用基于_doc进行排序的方式,性能较高
每次发送scroll请求,我们还需要指定一个scoll参数,指定一个时间窗口,每次搜索请求只要在这个时间窗口内能完成就可以了
GET /test_index/test_type/_search?scroll=1m
{
"query": {
"match_all": {}
},
"sort": [ "_doc" ],
"size": 3
}
"_scroll_id": "DnF1ZXJ5VGhlbkZldGNoBQAAAAAAACxeFjRvbnNUWVZaVGpHdklqOV9zcFd6MncAAAAAAAAsYBY0b25zVFlWWlRqR3ZJajlfc3BXejJ3AAAAAAAALF8WNG9uc1RZVlpUakd2SWo5X3NwV3oydwAAAAAAACxhFjRvbnNUWVZaVGpHdklqOV9zcFd6MncAAAAAAAAsYhY0b25zVFlWWlRqR3ZJajlfc3BXejJ3",
"took": 5,
"timed_out": false,
"_shards": {
"total": 5,
"successful": 5,
"failed": 0
},
"hits": {
"total": 10,
"max_score": null,
"hits": [
{
"_index": "test_index",
"_type": "test_type",
"_id": "8",
"_score": null,
"_source": {
"test_field": "test client 2"
},
"sort": [
0
]
},
{
"_index": "test_index",
"_type": "test_type",
"_id": "6",
"_score": null,
"_source": {
"test_field": "tes test"
},
"sort": [
0
]
},
{
"_index": "test_index",
"_type": "test_type",
"_id": "AVp4RN0bhjxldOOnBxaE",
"_score": null,
"_source": {
"test_content": "my test"
},
"sort": [
0
]
}
]
}
}
获得的结果会有一个scoll_id,下一次再发送scoll请求的时候,必须带上这个scoll_id
GET /_search/scroll
{
"scroll": "1m",
"scroll_id" : "DnF1ZXJ5VGhlbkZldGNoBQAAAAAAACxeFjRvbnNUWVZaVGpHdklqOV9zcFd6MncAAAAAAAAsYBY0b25zVFlWWlRqR3ZJajlfc3BXejJ3AAAAAAAALF8WNG9uc1RZVlpUakd2SWo5X3NwV3oydwAAAAAAACxhFjRvbnNUWVZaVGpHdklqOV9zcFd6MncAAAAAAAAsYhY0b25zVFlWWlRqR3ZJajlfc3BXejJ3"
}
scroll,看起来挺像分页的,但是其实使用场景不一样。分页主要是用来一页一页搜索,给用户看的;scoll主要是用来一批一批检索数据,让系统进行处理的.
filter
{
"query" : {
"filtered" : {
"query" : {
"term" : { "name" : "joe" }
},
"filter" : {
"term" : { "year" : 1981 }
}
}
}
}
GET /company/employee/_search
{
"query": {
"bool": {
"must": [
{
"match": {
"join_date": "2016-01-01"
}
}
],
"filter": {
"range": {
"age": {
"gte": 30
}
}
}
}
}
}
一般来说,如果你是在进行搜索,需要将最匹配搜索条件的数据先返回,那么用query;如果你只是要根据一些条件筛选出一部分数据,不关注其排序,那么用filter. 除非是你的这些搜索条件,你希望越符合这些搜索条件的document越排在前面返回,那么这些搜索条件要放在query中;如果你不希望一些搜索条件来影响你的document排序,那么就放在filter中即可.
filter 不能单独放在query的下一层,可以放置在bool里,要是想单独使用可以在外层套用一个constant_score.
GET /company/employee/_search
{
"query": {
"constant_score": {
"filter": {
"range": {
"age": {
"gte": 30
}
}
}
}
}
}
多种query搜索条件
match、match_all、multi_match
GET /company/employee/_search
{
"query": {
"multi_match": {
"query": "2016",
"fields": ["_all"]
}
}
}
range query
GET /company/employee/_search
{
"query": {
"range": {
"age": {
"gte": 30
}
}
}
}
term query 和 terms query
GET /_search
{
"query": { "terms": { "tag": [ "search", "full_text", "nosql" ] }}
}
排序
如果对一个string field进行排序,结果往往不准确,因为分词后是多个单词,elasticsearch拿分词后的哪个单词用于排序对用户而言是不清楚的。
GET /website/article/_search
{
"query": {
"match_all": {}
},
"sort": [
{
"title": {
"order": "desc"
}
}
]
}
得到的结果:
{
"took": 36,
"timed_out": false,
"_shards": {
"total": 5,
"successful": 5,
"skipped": 0,
"failed": 0
},
"hits": {
"total": 3,
"max_score": null,
"hits": [
{
"_index": "website",
"_type": "article",
"_id": "3",
"_score": null,
"_source": {
"post_date": "2017-01-03",
"title": "my third article",
"content": "this is my third article in this website",
"author_id": 11400
},
"sort": [
"third"
]
},
{
"_index": "website",
"_type": "article",
"_id": "2",
"_score": null,
"_source": {
"post_date": "2017-01-02",
"title": "my second article",
"content": "this is my second article in this website",
"author_id": 11400
},
"sort": [
"second"
]
},
{
"_index": "website",
"_type": "article",
"_id": "1",
"_score": null,
"_source": {
"title": "first article",
"content": "this is my second article",
"post_date": "2017-01-01",
"author_id": 110
},
"sort": [
"first"
]
}
]
}
}
通常解决方案是,将一个string field建立两次索引,一个分词,用来进行搜索;一个不分词,用来进行排序.
PUT /website
{
"mappings": {
"article": {
"properties": {
"title": {
"type": "text",
"fields": {
"raw": {
"type": "string",
"index": "not_analyzed"
}
},
"fielddata": true
},
"content": {
"type": "text"
},
"post_date": {
"type": "date"
},
"author_id": {
"type": "long"
}
}
}
}
}
排序:
GET /website/article/_search
{
"query": {
"match_all": {}
},
"sort": [
{
"title.raw": {
"order": "desc"
}
}
]
}
排序的bouncing result问题
bouncing results问题:两个document field值相同,想要进行排序,在不同的shard上这2个document可能排序不同;每次请求轮询打到不同的replica shard上;每次页面上看到的搜索结果的排序都不一样。这就是bouncing result,也就是跳跃的结果。
搜索的时候,是轮询将搜索请求发送到每一个replica shard(primary shard),但是在不同的shard上,可能document的排序不同
解决方案就是将preference设置为一个字符串,比如说user_id,让每个user每次搜索的时候,都使用同一个replica shard去执行,就不会看到bouncing results了
preference: 该参数能够让你控制哪些分片或者节点会用来处理搜索请求。
聚合分析和下钻分析
{
"took": 4,
"timed_out": false,
"_shards": {
"total": 5,
"successful": 5,
"skipped": 0,
"failed": 0
},
"hits": {
"total": 3,
"max_score": 1,
"hits": [
{
"_index": "ecommerce",
"_type": "product",
"_id": "2",
"_score": 1,
"_source": {
"name": "jiajieshi yagao",
"desc": "youxiao fangzhu",
"price": 25,
"producer": "jiajieshi producer",
"tags": [
"fangzhu"
]
}
},
{
"_index": "ecommerce",
"_type": "product",
"_id": "1",
"_score": 1,
"_source": {
"name": "gaolujie yagao",
"desc": "gaoxiao meibai",
"price": 30,
"producer": "gaolujie producer",
"tags": [
"meibai",
"fangzhu"
]
}
},
{
"_index": "ecommerce",
"_type": "product",
"_id": "3",
"_score": 1,
"_source": {
"name": "zhonghua yagao",
"desc": "caoben zhiwu",
"price": 70,
"producer": "zhonghua producer",
"tags": [
"qingxin"
]
}
}
]
}
}
聚合的执行速度很快,并且就像搜索一样几乎是实时的。
聚合的两个主要的概念,分别是 桶 和 指标。
桶(Buckets)
一个桶就是满足特定条件的文档的集合。
-
当聚合开始被执行,每个文档会决定符合哪个桶的条件,如果匹配到,文档将放入相应的桶并接着进行聚合操作,一个文档可以根据条件放入不同的桶里。像是一个员工属于男性桶或者女性桶,日期2014-10-28属于十月桶,也属于2014年桶
-
桶可以被嵌套在其他桶里面:像是北京能放在中国桶裡,而中国桶能放在亚洲桶裡
指标(Metrics)
指标就是对桶内的文档进行统计计算
-
桶能让我们划分文档到多个有意义的集合, 但是最终我们需要的是对这些桶内的文档进行一些指标的计算
-
指标通常是简单的数学运算(像是min、max、avg、sum),而这些是通过当前桶中的文档的值来计算的,利用指标能让你计算像平均薪资、最高出售价格、95%的查询延迟这样的数据
aggs 聚合的模板
-
当query和aggs一起存在时,会先执行query的主查询,主查询query执行完后会搜出一批结果,而这些结果才会被拿去aggs拿去做聚合。
- 另外要注意aggs后面会先接一层自定义的这个聚合的名字,然后才是接上要使用的聚合桶。
- 如果有些情况不在意查询结果是什麽,而只在意aggs的结果,可以把size设为0,如此可以让返回的hits结果集是0,加快返回的速度。
-
一个aggs裡可以有很多个聚合,每个聚合彼此间都是独立的,因此可以一个聚合拿来统计数量、一个聚合拿来分析数据、一个聚合拿来计算标准差...,让一次搜索就可以把想要做的事情一次做完
-
aggs可以嵌套在其他的aggs裡面,而嵌套的桶能作用的文档集范围,是外层的桶所输出的结果集。
GET 127.0.0.1/mytest/doc/_search
{
"query": { ... },
"size": 0,
"aggs": {
"custom_name1": { //aggs后面接著的是一个自定义的name
"桶": { ... } //再来才是接桶
},
"custom_name2": { //一个aggs裡可以有很多聚合
"桶": { ... }
},
"custom_name3": {
"桶": {
.....
},
"aggs": { //aggs可以嵌套在别的aggs裡面
"in_name": { //记得使用aggs需要先自定义一个name
"桶": { ... } //in_name的桶作用的文档是custom_name3的桶的结果
}
}
}
}
}
返回结果:
{
"hits": {
"total": 8,
"max_score": 0,
"hits": [] //因为size设为0,所以没有查询结果返回
},
"aggregations": {
"custom_name1": {
...
},
"custom_name2": {
...
},
"custom_name3": {
... ,
"in_name": {
....
}
}
}
}
聚合中常用的桶 terms、filter、top_hits
terms
terms桶 : 针对某个field的值进行分组,field有几种值就分成几组
- terms桶在进行分组时,会爲此field中的每种值创建一个新的桶
- 要注意此 "terms桶" 和平常用在主查询query中的 "查找terms" 是不同的东西
计算每个tag下的商品数量:
GET /ecommerce/product/_search
{
"size": 0,
"aggs": {
"group_by_tag": {
"terms": {
"field": "tags"
}
}
}
}
返回结果:
{
"took": 8,
"timed_out": false,
"_shards": {
"total": 5,
"successful": 5,
"skipped": 0,
"failed": 0
},
"hits": {
"total": 3,
"max_score": 0,
"hits": []
},
"aggregations": {
"group_by_tag": {
"doc_count_error_upper_bound": 0,
"sum_other_doc_count": 0,
"buckets": [
{
"key": "fangzhu",
"doc_count": 2
},
{
"key": "meibai",
"doc_count": 1
},
{
"key": "qingxin",
"doc_count": 1
}
]
}
}
}
因为tags总共有3种值,fangzhu、meibai、qingxin,所以terms桶为他们产生了3个bucket,并计算了每个bucket中符合的文档有哪些
bucket和bucket间是独立的,也就是说一个文档可以同时符合好几个bucket,像是{"tags": ["fangzhu", "meibai"]}就同时符合了fangzhu和meibai。
对名称中包含yagao的商品,计算每个tag下的商品数量:
GET /ecommerce/product/_search
{
"query": {
"match": {
"name": "yagao"
}
},
"size": 0,
"aggs": {
"group_by_tag": {
"terms": {
"field": "tags"
}
}
}
}
先分组,再算每组的平均值,计算每个tag下的商品的平均价格
GET /ecommerce/product/_search
{
"size": 0,
"aggs": {
"group_by_tag": {
"terms": {
"field": "tags"
},
"aggs": {
"avg_price": { // avg_price计算每个bucket的平均price
"avg": {
"field": "price"
}
}
}
}
}
}
计算每个tag下的商品的平均价格,并且按照平均价格降序排序。
可以在每个bucket里指定order进行排序
GET /ecommerce/product/_search
{
"size": 0,
"aggs": {
"group_by_tag": {
"terms": {
"field": "tags",
"order": {
"avg_price": "desc"
}
},
"aggs": {
"avg_price": {
"avg": {
"field": "price"
}
}
}
}
}
}
按照指定的价格范围区间进行分组,然后在每组内再按照tag进行分组,最后再计算每组的平均价格
GET /ecommerce/product/_search
{
"size": 0,
"aggs": {
"group_by_price":{
"range": {
"field": "price",
"ranges": [
{
"from": 10,
"to": 20
},
{
"from": 20,
"to": 40
},
{
"from": 40,
"to": 100
}
]
},
"aggs": {
"tag": {
"terms": {
"field": "tags"
},
"aggs": {
"avg_price": {
"avg": {"field": "price"}
}
}
}
}
}
}
}
返回结果
{
"took": 10,
"timed_out": false,
"_shards": {
"total": 5,
"successful": 5,
"skipped": 0,
"failed": 0
},
"hits": {
"total": 3,
"max_score": 0,
"hits": []
},
"aggregations": {
"group_by_price": {
"buckets": [
{
"key": "10.0-20.0",
"from": 10,
"to": 20,
"doc_count": 0,
"tag": {
"doc_count_error_upper_bound": 0,
"sum_other_doc_count": 0,
"buckets": []
}
},
{
"key": "20.0-40.0",
"from": 20,
"to": 40,
"doc_count": 2,
"tag": {
"doc_count_error_upper_bound": 0,
"sum_other_doc_count": 0,
"buckets": [
{
"key": "fangzhu",
"doc_count": 2,
"avg_price": {
"value": 27.5
}
},
{
"key": "meibai",
"doc_count": 1,
"avg_price": {
"value": 30
}
}
]
}
},
{
"key": "40.0-100.0",
"from": 40,
"to": 100,
"doc_count": 1,
"tag": {
"doc_count_error_upper_bound": 0,
"sum_other_doc_count": 0,
"buckets": [
{
"key": "qingxin",
"doc_count": 1,
"avg_price": {
"value": 70
}
}
]
}
}
]
}
}
}
filter桶
一个用来过滤的桶, 过滤得到的结果只有一个bucket.
要注意此处的 "filter桶" 和用在主查询query的 "过滤filter" 的用法是一模一样的,都是过滤. 不过差别是 "filter桶" 会自己给创建一个新的桶,而不会像 "过滤filter" 一样依附在query下. 因为filter桶毕竟还是一个聚合桶,因此他可以和别的桶进行嵌套,但他不是依附在别的桶上.
GET /ecommerce/product/_search
{
"size": 0,
"aggs": {
"filter_name": {
"filter": {
"bool": {
"must":[
{"match": {
"name": "yagao"
}}
]
}
}
}
}
}
filter桶和terms桶嵌套使用,先过滤出name为yagao的文档,再对这些文档进行tags分组.
GET /ecommerce/product/_search
{
"size": 0,
"aggs": {
"filter_name": {
"filter": {
"bool": {
"must":[
{"match": {
"name": "yagao"
}}
]
}
},
"aggs": {
"group_by_tags": {
"terms": {
"field": "tags"
}
}
}
}
}
}
top_hits桶
在某个桶底下找出这个桶的前几笔hits,返回的hits格式和主查询query返回的hits格式一模一样.
top_hits桶支持的参数:
-
from、size
-
sort : 设置返回的hits的排序
要注意,假设在主查询query裡已经对数据设置了排序sort,此sort并不会对aggs裡面的数据造成影响,也就是说主查询query查找出来的数据会先丢进aggs而非先经过sort,因此就算主查询设置了sort,也不会影响aggs数据裡的排序.因此如果在top_hits桶裡的返回的hits数据想要排序,需要自己在top_hits桶裡设置sort.如果没有设置sort,默认使用主查询query所查出来的_score排序
-
_source : 设置返回的字段
使用terms桶分组,再使用top_hits桶找出每个group裡面的price最小的前5笔hits.
GET /ecommerce/product/_search
{
"size": 0,
"aggs": {
"ggp": {
"terms": {
"field": "tags"
},
"aggs": {
"hits": {
"top_hits": {
"size": 5,
"sort":[
{"price": "desc"}
]
}
}
}
}
}
}
Elasticsearch的并发冲突
当并发操作ES的线程越多,或者并发请求越多,或者读取一份数据供用户查阅和操作的时间越长,就越可能出现错误。因为这段时间里ES内的数据很可能就已经被修改了,那么我们拿到的就是旧数据,基于旧数据去操作,后面的结果就错了。
悲观锁和乐观锁的并发控制
悲观锁并发控制方案,常见于关系型数据库。就是在各种情况下,都上锁。上锁之后,就只有 -一个线程可以操作这一条数据了,当然,不同的场景下,上的锁不同,行级锁,表级锁,读锁,写锁。
乐观锁并发方案,就是对取到的数据记录一个版本,然后更新的时候比较这个版本和数据库版本是否一致,不一致说明数据已经被改动了,这时就重复页面逻辑,再次比较,直到已有数据版本和数据库一致才进行更新操作。
-
悲观锁的优点是:方便,直接加锁,对应用程序来说,透明,不需要做额外的操作;缺点,并发能力很低,同一-时间只能有一条线程操作数据
-
乐观锁的优点是:并发能力很高,不给数据加锁,大星线程并发操作;缺点,麻烦,每次跟新的时候,都要先比对版本号,然后可能需要重新加载数据,再次修改,再写;这个过程,可能要重复好几次。
Elasticsearch乐观锁并发控制
PUT /test_index/test_type/7?version=2
在请求中带上version参数,只有ES中文档的_version 和 version 相同才可以进行更新。
external version
es提供了一个feature,就是说,你可以不用它提供的内部_version版本号来进行并发控制,可以基于你自己维护的一个版本号来进行并发控制。举个列子,加入你的数据在mysql里也有一份,然后你的应用系统本身就维护了一个版本号,无论是什么自己生成的,程序控制的。这个时候,你进行乐观锁并发控制的时候,可能并不是想要用es内部的_version来进行控制,而是用你自己维护的那个version来进行控制。
es,_version=1,?version=1,才能更新成功
es,_version=1,?version>1&version_type=external,才能成功.且更新后的document的version就变成了设置的version,不像之前的自动加1的操作。
Elasticsearch partial update
全量更新:查询,放界面,用户光修改,可能就 10 分钟,甚至半小时。然后修改完以后,再写回去,可能 es 中的数据早就被人修改了。所以并发冲突的情况就会发生的较多。
其实 es 内部对 partial update 的实际执行,跟传统的全量替换方式,是几乎一样的
- 内部先获取 document
- 将传过来的 field 更新到 document 的 json 中
- 将老的 document 标记为 deleted
- 将修改后的新的 document 创建出来
Partial update 相比于全量替换的优点
- 所有的查询、修改和写回操作,都发生在 es 中的一个 shard 内部,避免了所有的网络数据传输的开销(减少 2 次网络请求),大大提升了性能
- 减少了查询和修改中的时间间隔,可以有效减少并发冲突的情况(但不能完全杜绝,内部也使用乐观锁的并发控制策略)。全量更新的情况下获取到的其他字段的信息可能已经被修改了,partial update的情况下更新的时候会快速获取那些其他字段。
- 内部可能有多个partial update在操作,所以内部更新的时候也是基于内部拿到的version的版本来做并发控制,可以指定失败重试次数retry_on_conflict
POST /test_index/test_type/10/_update?retry_on_conflict=5
{
"doc": {
"test_field2": "updated test2"
}
}
![image-20191202114957873](../../../Library/Application Support/typora-user-images/image-20191202114957873.png)
mget
_mget 语法有3种用法
GET /_mget
{
"docs" : [
{
"_index" : "test_index",
"_type" : "test_type",
"_id" : 1
},
{
"_index" : "test_index",
"_type" : "test_type",
"_id" : 2
}
]
}
GET /test_index/_mget
{
"docs" : [
{
"_type" : "test_type",
"_id" : 1
},
{
"_type" : "test_type",
"_id" : 2
}
]
}
GET /test_index/test_type/_mget
{
"ids": [1, 2]
}
一般来说,在进行查询的时候,如果一次性要查询多条数据的话,那么一定要用batch批量操作的api尽可能减少网络开销次数,可能可以将性能提升数倍,甚至数十倍.
bulk update
格式要求严谨,每个json串不能换行,只能放一行,同时一个json串和一个json串之间,必须有一个换行
{"action": {"metadata"}}
{"data"}
POST /_bulk
{ "delete": { "_index": "test_index", "_type": "test_type", "_id": "3" }}
{ "create": { "_index": "test_index", "_type": "test_type", "_id": "12" }}
{ "test_field": "test12" }
{ "index": { "_index": "test_index", "_type": "test_type", "_id": "2" }}
{ "test_field": "replaced test2" }
{ "update": { "_index": "test_index", "_type": "test_type", "_id": "1", "_retry_on_conflict" : 3} }
{ "doc" : {"test_field2" : "bulk test1"} }
_bulk的操作和mget一样,可以把index和type提到url中。
bulk操作中,任意一个操作失败,是不会影响其他的操作的,但是在返回结果里,会告诉你异常日志。
bulk request会加载到内存里,如果太大的话,性能反而会下降,因此需要反复尝试一个最佳的bulk size。一般从10005000条数据开始,尝试逐渐增加。另外,如果看大小的话,最好是在515MB之间。
document的路由
每次增删改查一个document的时候是怎么把这个document路由到对应的shard的?
我们知道,一个 index 的数据会被分为多片,每片都在一个 shard 中。所以说,一个 document,只能存在于一个 shard 中。
当客户端创建 document 的时候。es 此时就需要决定说,这个 document 是放在这个 index 的哪个 shard 上。这个过程,就称之为 document routing,数据路由。
primary shard一旦指定个数,不允许修改的。但是 replica shard 可以随时修改,这也是由路由算法决定的。比如存储的时候基于3个shard的路由算法存储了documnet,一旦shard数量更改之后,在做查询的时候基于新的shard的数量计算的路由算法就会匹配到别的shard上。
路由算法:shard = hash(routing) % number_of_primary_shards
其中routing没有指定的时候默认就是_id, 也可以自定义。
put /index/type/id?routing=user_id
Elasticsearch增删改查内部原理
增删改内部原理
- 客户端选择任意一个node发送请求过去,这个node就是coordinating node(协调节点, 每个node都能根据路由算法找到document在哪个shard上)。
- coordinating node,对document进行路由,将请求转发给对应的node(有primary shard)
- 实际的node上的primary shard处理请求,然后将数据同步到replica node
- coordinating node,如果发现primary node和所有replica node都搞定之后,就返回响应结果给客户端
对于增删改操作,只能由primary shard进行操作,并同步到replica shard。
对于搜索操作,会打到所有的shard上,对于指定id的搜索会依据路由算法路由到某个shard上。
查询内部原理
- 客户端发送请求到任意一个node,成为coordinate node
- coordinate node对document进行路由,路由之后就知道请求应该发给哪个shard了,此时会使用round-robin随机轮询算法,在primary shard以及其所有replica中随机选择一个,让读请求负载均衡
- 接收请求的node返回document给coordinate node
- coordinate node返回document给客户端
- 特殊情况:document如果还在建立索引过程中,可能只有primary shard有,任何一个replica shard都没有,此时可能会导致无法读取到document,但是document完成索引建立之后,primary shard和replica shard就都有了
round-robin 随机轮询算法: 比如说 coordinate node,接收到了对一个 document 的 4 次查询,就会使用算法,将 2 次查询请求转发给 P1, 将 2 次查询请求转发给 R1, 尽量让 primary shard 和所有的 replica shard 均匀的服务读请求,得到负载均衡的效果
写一致性
我们在发送任何一个增删改操作的时候,比如说put /index/type/id,都可以带上一个consistency参数,指明我们想要的写一致性是什么?
put /index/type/id?consistency=quorum
one:要求我们这个写操作,只要有一个primary shard是active活跃可用的,就可以执行
all:要求我们这个写操作,必须所有的primary shard和replica shard都是活跃的,才可以执行这个写操作
quorum:默认的值,要求所有的shard中,必须是大部分的shard都是活跃的,可用的,才可以执行这个写操作。
quorum机制:写之前必须确保大多数shard都可用,int( (primary + number_of_replicas) / 2 ) + 1,当number_of_replicas>1时才生效(为了兼容单节点单shard的情况)。
quorum不齐全时可以进行wait操作。
等待期间,期望活跃的shard数量可以增加,最后实在不行,就会timeout
我们其实可以在写操作的时候,加一个timeout参数,比如说put /index/type/id?timeout=30,这个就是说自己去设定quorum不齐全的时候,es的timeout时长,可以缩短,也可以增长
search的timeout机制
默认情况下,没有所谓的 timeout,比如说,如果你的捏素特别疼别慢,每个 shard 都要花好几分钟才能 1 询出来所有的数据,那么你的搜索请求也会等待好几分钟之后才会返回.
我们有些搜索应用,对时间是很敏感的,比如说我么的电商网站,你不能说让用户等各 10 分钟,才能等到一次搜索请求的结果,如果那样的话,人家早就走了,不来买东西了。
timeout 机制,指定每个 shard,就只能在 timeout 时间范围内,将搜索到的部分数据(也可能全都搜索到了),直接理解返回给 client 程序,而不是等到所有的数据全都搜索出来以后再返回。
确保说,一次搜索请求可以在用户指定的 timeout 时长内完成。为-些时间敏感的搜索应用提供良好的支持。
GET /_search?timeout=10m
multi-index和multi-type搜索模式
/_search:所有索引,所有type下的所有数据都搜索出来
/index1/_search:指定一个index,搜索其下所有type的数据
/index1,index2/_search:同时搜索两个index下的数据
/*1,*2/_search:按照通配符去匹配多个索引
/index1/type1/_search:搜索一个index下指定的type的数据
/index1/type1,type2/_search:可以搜索一个index下多个type的数据
/index1,index2/type1,type2/_search:搜索多个index下的多个type的数据
/_all/type1,type2/_search:_all,可以代表搜索所有index下的指定type的数据
deep paging
什么叫 deep paging?简单来说,就是搜索的特别深,比如总共有 60000 条数据,每个 shard上分了 20000 条数据。每页是 10 条数据,这个时候,你要搜索到第 1000 页,实际上要拿到的是 10001~10010 大家自己思考一下,是第几条到第几条数啊?
每个 shard,其实都要返回的是最后 10 条数据,这种理解是错误的。
你的请求首先可能是打到一个不包含这个 index 的 shard 的 node 上去,这个 node 就是一个 coordinate node,那么这个 coordinate node 就会将搜索请求转发到 index 的三个 shard 所在的 node 上去。
比如说我们刚才说的情况下,要搜索 60000 条数据中的第 1000 页,实际上每个 shard 都要将内部的 20000 条数据中的第 10001~10010 条数据,拿出来,不是才 10 条,是 10010 条数据。3 个 shard 每个 shard 都返回 10010 条数据给 coordinate node, coordinate node 会收到总共 30030 条数据,然后在这些数据中进行排序,score,相关度分数,然后取到排位最高的前 10 条数据,其实就是我们要的最后的第 1000 页的 10 条数据。
搜索的过深的时候,就需要在 coordinate node。上保存大量的数据,还要进行大量数据的排序,排序之后,再取出对应的那一页。所以这个过程,即耗费网络带宽,耗费内存,还耗费 cpu。所以 deep paging 的性能问题。我们应该尽量避免出现这种 deep paging 操作。
_all metadata的原理和作用
GET /test_index/test_type/_search?q=test
直接可以搜索所有的field,任意一个field包含指定的关键字就可以搜索出来。我们在进行中搜索的时候,难道是对document中的每一个field都进行一次搜索吗?不是的
es中的_all元数据,在建立索引的时候,我们插入一条document,它里面包含了多个field,此时,es会自动将多个field的值,全部用字符串的方式串联起来,变成一个长的字符串,作为_all field的值,同时建立索引
后面如果在搜索的时候,没有对某个field指定搜索,就默认搜索_all field,其中是包含了所有field的值的.
{
"name": "jack",
"age": 26,
"email": "jack@sina.com",
"address": "guamgzhou"
}
"jack 26 jack@sina.com guangzhou",作为这一条document的_all field的值,同时进行分词后建立对应的倒排索引.
精确匹配与全文搜索的对比
1、exact value
2017-01-01,exact value,搜索的时候,必须输入2017-01-01,才能搜索出来
如果你输入一个01,是搜索不出来的
2、full text
(1)缩写 vs. 全程:cn vs. china
(2)格式转化:like liked likes
(3)大小写:Tom vs tom
(4)同义词:like vs love
2017-01-01,2017 01 01,搜索2017,或者01,都可以搜索出来
china,搜索cn,也可以将china搜索出来
likes,搜索like,也可以将likes搜索出来
Tom,搜索tom,也可以将Tom搜索出来
like,搜索love,同义词,也可以将like搜索出来
不是说单纯的只是匹配完整的一个值,而是可以对值进行拆分词语后(分词)进行匹配,也可以通过缩写、时态、大小写、同义词等进行匹配.
分词器
内置分词器
分词器分出的结果再进行倒排索引。
考虑如下字符串: Set the shape to semi-transparent by calling set_trans(5)
standard analyzer:set, the, shape, to, semi, transparent, by, calling, set_trans, 5(默认的是standard)
simple analyzer:set, the, shape, to, semi, transparent, by, calling, set, trans
whitespace analyzer:Set, the, shape, to, semi-transparent, by, calling, set_trans(5)
language analyzer(特定的语言的分词器,比如说,english,英语分词器):set, shape, semi, transpar, call, set_tran, 5。
默认情况下,对于一个query string, es会使用它对应的field建立倒排索引时相同的分词器去进行分词,分词和normalization,只有这样,才能实现正确的搜索.
测试分词效果:
GET /_analyze
{
"analyzer": "standard",
"text": ["Text to analyze", "hello is world"]
}
{
"tokens": [
{
"token": "text",
"start_offset": 0,
"end_offset": 4,
"type": "<ALPHANUM>",
"position": 0
},
{
"token": "to",
"start_offset": 5,
"end_offset": 7,
"type": "<ALPHANUM>",
"position": 1
},
{
"token": "analyze",
"start_offset": 8,
"end_offset": 15,
"type": "<ALPHANUM>",
"position": 2
},
{
"token": "hello",
"start_offset": 16,
"end_offset": 21,
"type": "<ALPHANUM>",
"position": 3
},
{
"token": "is",
"start_offset": 22,
"end_offset": 24,
"type": "<ALPHANUM>",
"position": 4
},
{
"token": "world",
"start_offset": 25,
"end_offset": 30,
"type": "<ALPHANUM>",
"position": 5
}
]
}
(1)往es里面直接插入数据,es会自动建立索引,同时建立type以及对应的mapping
(2)mapping中就自动定义了每个field的数据类型
(3)不同的数据类型(比如说text和date),可能有的是exact value,有的是full text
(4)exact value,在建立倒排索引的时候,分词的时候,是将整个值一起作为一个关键词建立到倒排索引中的;full text,会经历各种各样的处理,分词,normaliztion(时态转换,同义词转换,大小写转换),才会建立到倒排索引中
(5)同时呢,exact value和full text类型的field就决定了,在一个搜索过来的时候,对exact value field或者是full text field进行搜索的行为也是不一样的,会跟建立倒排索引的行为保持一致;比如说exact value搜索的时候,就是直接按照整个值进行匹配,full text query string,也会进行分词和normalization再去倒排索引中去搜索
(6)可以用es的dynamic mapping,让其自动建立mapping,包括自动设置数据类型;也可以提前手动创建index和type的mapping,自己对各个field进行设置,包括数据类型,包括索引行为,包括分词器,等等.
mapping
只能创建index时手动建立mapping,或者新增field mapping,但是不能update field mapping.
索引的3种方式:
- analyzed(分词)
- not_analyzed(不分词)
- no(不支持搜索)
手动指定mapping:
创建mapping:
PUT /website
{
"mappings": {
"article": {
"properties": {
"author_id": {
"type": "long"
},
"title": {
"type": "text",
"analyzer": "english"
},
"content": {
"type": "text"
},
"post_date": {
"type": "date"
},
"publisher_id": {
"type": "text",
"index": "not_analyzed"
}
}
}
}
}
修改mapping
PUT /website/_mapping/article
{
"properties" : {
"new_field" : {
"type" : "string",
"index": "not_analyzed"
}
}
}
测试mapping
GET /website/_analyze
{
"field": "content",
"text": "my-dogs"
}
_mapping复杂数据类型以及object类型数据底层结构
-
multivalue field:{ "tags": [ "tag1", "tag2" ]},数组里的数据类型需要一致,建立索引的方式和string一样。
-
object field
{ "address": { "country": "china", "province": "guangdong", "city": "guangzhou" }, "name": "jack", "age": 27, "join_date": "2017-01-01" }
存储格式:
{ "name": [jack], "age": [27], "join_date": [2017-01-01], "address.country": [china], "address.province": [guangdong], "address.city": [guangzhou] }
{ "authors": [ { "age": 26, "name": "Jack White"}, { "age": 55, "name": "Tom Jones"}, { "age": 39, "name": "Kitty Smith"} ] }
存储格式:
{ "authors.age": [26, 55, 39], "authors.name": [jack, white, tom, jones, kitty, smith] }
score 计算
relevance score算法,简单来说,就是计算出,一个索引中的文本,与搜索文本,他们之间的关联匹配程度
Elasticsearch使用的是 term frequency/inverse document frequency算法,简称为TF/IDF算法
Term frequency:搜索文本中的各个词条在field文本中出现了多少次,出现次数越多,就越相关
搜索请求:hello world
doc1:hello you, and world is very good
doc2:hello, how are you
Inverse document frequency:搜索文本中的各个词条在整个索引的所有文档中出现了多少次,出现的次数越多,就越不相关
搜索请求:hello world
doc1:hello, today is very good
doc2:hi world, how are you
比如说,在index中有1万条document,hello这个单词在所有的document中,一共出现了1000次;world这个单词在所有的document中,一共出现了100次,那么doc2更相关
Field-length norm:field长度,field越长,相关度越弱
搜索请求:hello world
doc1:{ "title": "hello article", "content": "babaaba 1万个单词" }
doc2:{ "title": "my article", "content": "blablabala 1万个单词,hi world" }
hello world在整个index中出现的次数是一样多的
doc1更相关,title field更短
doc values
搜索的时候,要依靠倒排索引;排序的时候,需要依靠正排索引,看到每个document的每个field,然后进行排序,所谓的正排索引,其实就是doc values
在建立索引的时候,一方面会建立倒排索引,以供搜索用;一方面会建立正排索引,也就是doc values,以供排序,聚合,过滤等操作使用
doc values是被保存在磁盘上的,此时如果内存足够,os会自动将其缓存在内存中,性能还是会很高;如果内存不足够,os会将其写入磁盘上
doc1: { "name": "jack", "age": 27 }
doc2: { "name": "tom", "age": 30 }
document name age
doc1 jack 27
doc2 tom 30
query 和 fetch 流程
1、query phase
(1)搜索请求发送到某一个coordinate node,构构建一个priority queue,长度以paging操作from和size为准,默认为10
(2)coordinate node将请求转发到所有shard,每个shard本地搜索,并构建一个本地的priority queue
(3)各个shard将自己的priority queue返回给coordinate node,并构建一个全局的priority queue
2、fetch phbase工作流程
(1)coordinate node构建完priority queue之后,就发送mget请求根据id去所有shard上获取对应的document(query phase拿到的是一对document id)
(2)各个shard将document返回给coordinate node
(3)coordinate node将合并后的document结果返回给client客户端
创建、修改和删除索引
创建索引:
PUT /my_index
{
"settings": {
"number_of_shards": 1,
"number_of_replicas": 0
},
"mappings": {
"my_type": {
"properties": {
"my_field": {
"type": "text"
}
}
}
}
}
修改索引
PUT /my_index/_settings
{
"number_of_replicas": 1
}
删除索引
DELETE /my_index
DELETE /index_one,index_two
DELETE /index_*
DELETE /_all
_source和_all
_source
好处
(1)查询的时候,直接可以拿到完整的document,不需要先拿document id,再发送一次请求拿document
(2)partial update基于_source实现
(3)reindex时,直接基于_source实现,不需要从数据库(或者其他外部存储)查询数据再修改
(4)可以基于_source定制返回field
(5)debug query更容易,因为可以直接看到_source
如果不需要上述好处,可以禁用_source
PUT /my_index/_mapping/my_type2
{
"_source": {"enabled": false}
}
_all
将所有field打包在一起,作为一个_all field,建立索引。没指定任何field进行搜索时,就是使用_all field在搜索。
PUT /my_index/_mapping/my_type3
{
"_all": {"enabled": false}
}
也可以在field级别设置include_in_all field,设置是否要将field的值包含在_all field中
PUT /my_index/_mapping/my_type4
{
"properties": {
"my_field": {
"type": "text",
"include_in_all": false
}
}
}
dynamic策略
true:遇到陌生字段,就进行dynamic mapping
false:遇到陌生字段,就忽略, 用这个字段搜索不出内容
strict:遇到陌生字段,就报错。
PUT /my_index
{
"mappings": {
"my_type": {
"dynamic": "strict",
"properties": {
"title": {
"type": "text"
},
"address": {
"type": "object",
"dynamic": "true"
}
}
}
}
}
mapping如果已经设置成strict,那么传到Elasticsearch的数据要按照规定的格式ceiling可以。
定制dynamic mapping策略
(1)date_detection
默认会按照一定格式识别date,比如yyyy-MM-dd。但是如果某个field先过来一个2017-01-01的值,就会被自动dynamic mapping成date,后面如果再来一个"hello world"之类的值,就会报错。可以手动关闭某个type的date_detection,如果有需要,自己手动指定某个field为date类型。
PUT /my_index/_mapping/my_type
{
"date_detection": false
}
2)定制自己的dynamic mapping template(type level)
PUT /my_index
{
"mappings": {
"my_type": {
"dynamic_templates": [
{ "en": {
"match": "*_en",
"match_mapping_type": "string",
"mapping": {
"type": "string",
"analyzer": "english"
}
}}
]
}}}
PUT /my_index/my_type/1
{
"title": "this is my first article"
}
PUT /my_index/my_type/2
{
"title_en": "this is my first article"
}
title没有匹配到任何的dynamic模板,默认就是standard分词器,不会过滤停用词,is会进入倒排索引,用is来搜索是可以搜索到的.
title_en匹配到了dynamic模板,就是english分词器,会过滤停用词,is这种停用词就会被过滤掉,用is来搜索就搜索不到了.