Elasticsearch学习笔记 一

本文版权归博客园和做者吴双本人共同全部 转载和爬虫请注明原文地址 www.cnblogs.com/tdws。html

本文参考和学习资料 《ES权威指南》node

当年在学校的随笔 Lucene概念入门:  http://www.cnblogs.com/tdws/p/4263425.htmlweb

一.基本概念

存储数据到ES中的行为叫作索引,每一个索引能够包含多个类型,这些不一样的类型存储着多个文档,每一个文档有多个属性。数据库

索引 index/indexes至关于传统关系数据库中的数据库,是存储关系型文档的地方。json

索引在ES作动词的时候,索引一个文档就是存储一个文档到索引(名词)中,以便被检阅和查询到。相似于insert。数组

默认下,一个文档中的每一个属性都是被索引的。没被索引的属性是不能搜索到的。网络

二.ES集群 主分片 副分片 健康状态

垂直的硬件扩容是有极限的,真正的扩容能力来自于水平扩容(为集群内增长更多节点),集群是由一个或者多个拥有相同 cluster.name 配置的节点组成, 它们共同承担数据和负载的压力。当有节点加入集群中或者从集群中移除节点时,集群将会从新平均分布全部的数据。并发

主节点负责管理集群范围内全部的变动,增长删除索引和增长删除节点等。主节点不涉及文档级别的变动和搜索等操做。任何一个节点均可接受请求,任何一个节点都知道任意文档所处位置。app

curl -XGET 'localhost:9200/_cluster/health?pretty' 获取集群健康状态负载均衡

status 字段指示着当前集群在整体上是否工做正常。它的三种颜色含义以下:

green 全部的主分片和副本分片都正常运行。

yellow 全部的主分片都正常运行,但不是全部的副本分片都正常运行。

red 有主分片没能正常运行。

分片是一个底层的工做单元,是数据的容器,一个分片是一个Lucene实例。它自己就是一个完整的搜索引擎,到本文最后的分页,相信你能更加理解。应用不会与分片交互。一个分片可使主分片或者副本分片。副本分片只是主分片的一个拷贝。副本分片用于从灾难中保护数据并未搜索读取操做提供服务,注意只是读操做。

默认状况下建立一个索引,会被分配5个分片。固然咱们也能够指定分片数量还有副本数量,好比:

curl -XPUT 'localhost:9200/blogs?pretty' -H 'Content-Type: application/json' -d'
{
"settings" : {
"number_of_shards" : 3,
"number_of_replicas" : 1
}
}

假设咱们目前集群中只有一个节点,那么刚才三个分片的存储是这样的。即便咱们设置了1个副本分片也没用,由于相同数据的主分片和副本分片在一个节点上没有任何意义。

若是此时,咱们为集群增长一个节点,主分片和副本分片是下图这样的。副本分片得以建立,并分配在新节点上。如今咱们的容灾能力就能够容许失去一台节点的数据,数据读取服务节点就是两个。

接下来,集群中再加入节点,状况以下图所示。为了提升更强的服务能力和容灾能力,均匀分布了主分片和副本分片,如今咱们三个node,失去任意一台,都能保障数据和服务。

最后,关闭node1,使集群失去MASTER, 而集群必须拥有一个主节点来保证正常工做,因此发生的第一件事情就是选举一个新的主节点: Node 2 。在失去Master的同时,也失去了主分片 1 和 2 ,而且在缺失主分片的时候索引也不能正常工做。 若是此时来检查集群的情况,咱们看到的状态将会为 red :不是全部主分片都在正常工做。幸运的是,在其它节点上存在着这两个主分片的完整副本, 因此新的主节点当即将这些分片在 Node 2 和 Node 3 上对应的副本分片提高为主分片, 此时集群的状态将会为 yellow 。 这个提高主分片的过程是瞬间发生的,如同按下一个开关通常。

若是如今恢复node1,若是 Node 1 依然拥有着以前的分片,它将尝试去重用它们,同时仅从主分片复制发生了修改的数据文件

三.基本的操做

一个文档的 _index 、 _type 和 _id 惟一标识一个文档。

索引仅仅是一个逻辑上的命名空间,这个命名空间由一个或者多个分片组合在一块儿。对于应用程序而言,无需关注分片,只须要知道一个文档位于一个索引内。

索引index名称必须小写,不能如下划线开头,不能有逗号。类型type名称能够大写或者小写,可是不能如下划线或者句号开头,不该该包含逗号而且长度在265字符及之内。

当你建立一个新文档的时候,要么主动给id,要么让ES自动生成。因此,确保建立一个新文档的最简单办法是,使用索引请求的 POST 形式让 Elasticsearch 自动生成惟一 _id,好比:

POST /website/blog/ { ... }

如下两种方式为等效操做:

PUT /website/blog/123?op_type=create { ... }
PUT /website/blog/123/_create { ... }

在获取文档的时候,_source字段是用于筛选咱们只想要的目标字段,好比

GET /website/blog/123?_source=title,text

若是想要获得http响应头部,能够经过传递 -i 好比

curl -i -XGET http://localhost:9200/website/blog/124?pretty

若是只想获得source字段,不想获得任何其余元数据,使用方式以下:

GET /website/blog/123/_source

若是查询文档是否存在,使用Http HEAD,好比:

curl -i -XHEAD http://localhost:9200/website/blog/123  //存在则返回200 OK,不存在则是404Not Found

在 Elasticsearch 中文档是 不可改变 的,不能修改它们。 相反,若是想要更新现有的文档,须要 重建索引或者进行替换, 咱们可使用相同的 index API 进行实现。和使用建立时PUT是同样的,ES内部都作了。部分更新,也是如此。使用DELETE删除文档,删除文档不会当即将文档从磁盘中删除,只是将文档标记为已删除状态,随着你不断的索引更多的数据,Elasticsearch 将会在后台清理标记为已删除的文档。

四.并发冲突的解决

 

假设上图操做的是库存数量stock_count ,web_1 对 stock_count 所作的更改已经丢失,由于 web_2 不知道它的 stock_count 的拷贝已通过期。变动越频繁,读数据和更新数据的间隙越长,也就越可能丢失变动。在数据库领域中,有悲观和乐观两种方法一般被用来确保并发更新时变动不会丢失:

悲观并发控制

悲观并发控制通常锁住当前被操做的资源。

乐观并发控制

Elasticsearch 中使用的这种方法假定冲突是不可能发生的,而且不会阻塞正在尝试的操做。 然而,若是源数据在读写当中被修改,更新将会失败。应用程序接下来将决定该如何解决冲突。 例如,能够重试更新、使用新的数据、或者将相关状况报告给用户。Elasticsearch 是分布式的,当建立和更新文档的时候,若是你是多个node,而且还有replication备份数据,这中间就会有时间差存在,而且还会存在检索-修改-重建索引间隔。或者说并发更新发生的时候,所获取并在处理中的数据,可能已经被其余请求更新掉,这就致使数据的过时,结果就是覆盖更新,过时数据覆盖新数据。在ES中 咱们解决此问题的方式就是利用更新中指定_version版本号来确保相互冲突的更新不会丢失。全部文档的更新或删除 API,均可以接受 version 参数,好比:

PUT /website/blog/1?version=1

 过指定了version,若是请求到达ES后,该条数据version已经不是1了,这时就会更新失败并返回409 Conflict响应码。以后应该怎么处理,就取决于你的场景了,正如上面所说的能够重试更新、使用新的数据、或者将相关状况报告给用户。另一种方式是使用外部版本号,而非ES内置自增的版本号。外部的版本号源于你本身的系统控制,好比更新一条ES文档,指定使用外部版本号为5:

PUT /website/blog/2?version=5&version_type=external

为了并发控制,下一次你的请求给到了version为10若是你的请求version小于等于上面的5则会更新失败,像前面提到的409同样,只有新version大于ES文档已有version,更新才会成功。

PUT /website/blog/2?version=10&version_type=external

ES文档是不可变的,即便是在部分更新状况下。好比:

POST /website/blog/1/_update { "doc" : { "tags" : [ "testing" ], "views": 0 } }   //其将会覆盖现有字段,增长新字段。被称为Update API

部分更新的时候,若是被更新的文档还不存在,这时应该使用upsert参数

POST /website/pageviews/1/_update { "script" : "ctx._source.views+=1", "upsert": { "views": 1 } }

若是你某个场景中常常会出现所更新文档不存在的状况下,那么使用它是明智之选,该方式会在文档存在的时候,直接执行更新脚本将值应用,不存在的时候,则执行upsert.

ES update API还提供了retry_on_conflict参数,指示了返回失败以前,要重试多少次。

POST /website/pageviews/1/_update?retry_on_conflict=5 

update API和前面index API所提到的乐观并发控制同样,也支持制定version参数来控制冲突。

ES提供了mget API (multi-get),能够减小网络传输时间等,好比:

GET /_mget { "docs" : [ { "_index" : "website", "_type" : "blog", "_id" : 2 }, { "_index" : "website", "_type" : "pageviews", "_id" : 1, "_source": "views" } ] }

能够看到这段代码中index和type 实在docs数组的item项中。所获得的响应数组中,每一个item和使用单个get拿到的结果是同样的。

若是想检索的数据都在相同的 _index 中(甚至相同的 _type 中),则能够在 URL 中指定默认的 /_index或者默认的 /_index/_type 。

你仍然能够经过单独请求覆盖这些值:

GET /website/blog/_mget { "docs" : [ { "_id" : 2 }, { "_type" : "pageviews", "_id" : 1 } ] }

事实上,若是全部文档的 _index 和 _type 都是相同的,你能够只传一个 ids 数组,而不是整个 docs 数组:

GET /website/blog/_mget { "ids" : [ "2", "1" ] }

若是数组中某个寻找目标未找到结果,则响应数组中,其余值正常,未找到的found字段则为false "found" : false,并不妨碍其余文档。因此在应用中,应该检查的不是200仍是404,而是found字段标记。

既然提供了mget, 同时bulk API也提供了单个步骤进行屡次create index, update和delete请求。

POST /_bulk {
"delete": { "_index": "website", "_type": "blog", "_id": "123" }}
{ "create": { "_index": "website", "_type": "blog", "_id": "123" }} { "title": "My first blog post" }
{ "index": { "_index": "website", "_type": "blog" }} { "title": "My second blog post" }
{ "update": { "_index": "website", "_type": "blog", "_id": "123", "_retry_on_conflict" : 3} } { "doc" : {"title" : "My updated blog post"} }

delete是没有请求体的,而create,update 请求体是必须的 如上所示。若是其中哪一个操做失败了,则会在响应中的一个对应的item中给出相应的error. 而成功的则给出200.好比

{ "took": 3, "errors": true, "items": [ 
{ "create": { "_index": "website", "_type": "blog", "_id": "123", "status": 409, "error": "DocumentAlreadyExistsException [[website][4] [blog][123]: document already exists]" }},
{ "index": { "_index": "website", "_type": "blog", "_id": "123", "_version": 5, "status": 200 }} ] }

bulk 不是原子性的,不能用它来实现事务控制。每一个请求是单独处理的,所以一个请求的成功或失败不会影响其余的请求。为何是下面这种格式的写法,为何是每一个请求单独处理,这是由于不一样的文档数据,他们主分片可能不一样,每一个文档可能被分配给不一样节点,因此ES这种设计的方式是最明智的。若是使用json数组,看起来简便了,实则致使须要ES解析大量数据,占用更多内存,让JVM来花大量时间进行回收。相反ES现有的方式,则能够直接将原始请求转发到正确的分片上,用最小的内存处理。

也许你正在批量索引日志数据到相同的 index 和 type 中。 但为每个文档指定相同的元数据是一种浪费。相反,能够像 mget API 同样,在 bulk 请求的 URL 中接收默认的 /_index 或者 /_index/_type ,好比:

POST /website/_bulk { "index": { "_type": "log" }} { "event": "User logged in" }
POST /website/log/_bulk { "index": {}} { "event": "User logged in" } { "index": { "_type": "blog" }} { "title": "Overriding the default type" }

五.分布式文档存储

 当索引一个文档的时候,文档将被存储到ES的一个主分片当中。ES索引文档到分片听从公式:shard = hash(routing) % number_of_primary_shards 。

routing是一个可变值,默认你为文档_id。 如上公式hash出的结果是  0 到 number_of_primary_shards-1 之间的余数,未来咱们寻找数据所在分片,就是这样找到的。因此在建立索引的时候就要肯定好主分片数量,而且永远不改变primary_shards ,由于一旦改变了,以前hash路由的值将所有无效,文档也就不能被正确找到。固然若是你想扩容,能够有其余的奇技淫巧,后面将会提到。

好比咱们有三个节点,两个主分片,每一个主分片有两个副本。咱们依然能够将请求指向任意节点,每一个节点都知道任一文档的位置,并转发请求到目标节点。为了扩展负载,更好的作法是轮询全部节点,以单节点免压力过大。我想,ES知道任一文档的位置,经过上一段给出的公式便可。

向node1发送存储数据请求,路由到node3主分片上,复制给node1和node2,最后返回结果给node1

 

向node1 发送更新数据请求,路由到node3主分片,更改_source,从新索引数据,成功后复制到node1和node2。若是是部分更新的时候,通知副本节点更新的时候,不是转发部分更新的内容,而是转发完整文档的新版本。

 

六.搜索

上面,咱们能够简单的把ES看成NOSQL风格的分布式文档存储系统。咱们能够将文档扔到ES里,而后根据ID检索。但其真正的强大之处并不在于此,而是从无规律的数据找出有意义的信息,从大数据到大信息。

搜索(search) 能够作到:

1.在相似于 gender 或者 age 这样的字段 上使用结构化查询,join_date 这样的字段上使用排序,就像SQL的结构化查询同样。

2.全文检索,找出全部匹配关键字的文档并按照相关性(relevance) 排序后返回结果。

3.以上两者兼而有之。

下面分析一则查询:

GET /_search (curl -XGET 'localhost:9200/_search?pretty')
{ "hits" : { "total" : 14, "hits" : 
[ { "_index": "us", "_type": "tweet", "_id": "7", "_score": 1,
"_source": { "date": "2014-09-17", "name": "John Smith", "tweet": "The Query DSL is really powerful and flexible", "user_id": 2 } },
... 9 RESULTS REMOVED ... ], "max_score" : 1 },
"took" : 4,
"_shards" : { "failed" : 0, "successful" : 10, "total" : 10 }, "timed_out" : false }

 

解释一下查询结果:

hits是返回结果最重要的部分,其中total表明匹配总数,每一个文档都有元数据_index库,_type类型,_id编号,_source值字段,_score表明相关性,文档按照相关性倒序返回。

_took字段表明搜索花了多少ms .

_shards告诉咱们参与分片的总数和成功与失败多少个。正常状况下不会出现失败,若是遇到灾难故障好比同一分片原始数据和副本都丢失了,那么对这个分片将没有可用副本作响应,ES则会报告有失败的分片,但正确返回剩余分片结果。

time_out指示了是否查询超时,在查询的时候,你也能够设置超时时间 GET /_search?timeout=10ms 。关于超时须要特别注意的是,超时并非中止执行查询,而是告知正在协调节点,在所设置的时间内,返回目前已查询收集到的的结果。在后台,即便结果已经返回给咱们了,可是查询仍然在执行。为何说是返回已收集到的文档,是由于在集群内搜索的时候,协调节点转发请求到其余节点的主分片或者副分片上,聚集结果。

不记得什么是协调节点了吗?Elasticsearch集群中做为客户端接入的节点叫协调节点。协调节点会将客户端请求路由到集群中合适的分片上。对于读请求来讲,协调节点每次会选择不一样的分片处理请求,以实现负载均衡。

一些在一个或多个特殊的索引而且在一个或者多个特殊的类型中进行搜索查询的示范:
/_search 在全部的索引中搜索全部的类型
/gb/_search 在 gb 索引中搜索全部的类型
/gb,us/_search 在 gb 和 us 索引中搜索全部的文档
/g*,u*/_search 在任何以 g 或者 u 开头的索引中搜索全部的类型
/gb/user/_search 在 gb 索引中搜索 user 类型
/gb,us/user,tweet/_search 在 gb 和 us 索引中搜索 user 和 tweet 类型
/_all/user,tweet/_search 在全部的索引中搜索 user 和 tweet 类型

分页搜索,若是每页展现 5 条结果,能够用下面方式请求获得 1 到 3 页的结果:

GET /_search?size=5 GET /_search?size=5&from=5 GET /_search?size=5&from=10

ES返回分页数据的思路是 : 好比在一个有 5 个主分片的索引中搜索。 当咱们请求结果的第一页(结果从 1 到 10 ),每个分片产生前 10 的结果,而且返回给 协调节点 ,协调节点对 50 个结果排序获得所有结果的前 10 个。

这也说明在深度分页中,所将付出的代价,好比获取第1000页,那么每一个节点查询出10010条,最后到协调节点中,从50050条抛弃50040条取10条. 补充一句 为何每一个分片能够作咱们想要的查询,想要的数量和结果?是由于每一个分片都是一个Lucene实例,是一个完整的搜索引擎

相关文章
相关标签/搜索