Elasticsearch笔记

本文详细介绍了Elasticsearch的索引概念,包括其在分布式环境下的运作机制,如分片、副本、主节点行为,以及如何通过水平扩容提升性能。同时,文章探讨了文档的元数据、索引操作、更新和删除文档的方法,和处理冲突的策略。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

索引
索引是指向一个或者多个物理分片的逻辑命名空间, 索引这个词在 Elasticsearch 语境中有多种含义:

  • 名词:一个 索引类似于传统关系数据库中的一个 数据库 ,是一个存储关系型文档的地方。
  • 动词:索引一个文档就是存储一个文档到一个 索引(名词)中以便被检索和查询。这类似于 SQL 语句中的 INSERT 关键词,除了文档已存在时,新文档会替换旧文档情况之外。
  • 倒排索引:关系型数据库通过增加一个 索引比如一个 B树(B-tree)索引到指定的列上,以便提升数据检索速度。Elasticsearch 和 Lucene 使用了一个叫做倒排索引的结构来达到相同的目的。默认,一个文档中的每一个属性都是被索引的(有一个倒排索引)和可搜索的。一个没有倒排索引的属性是不能被搜索到的。

分布式特性
Elasticsearch 尽可能地屏蔽了分布式系统的复杂性。这里列举了一些在后台自动执行的操作:

  • 分配文档到不同的容器 或 分片 中,文档可以储存在一个或多个节点中
  • 按集群节点来均衡分配这些分片,从而对索引和搜索过程进行负载均衡
  • 集群扩容时无缝整合新节点,重新分配分片以便从离群节点恢复
  • 复制每个分片以支持数据冗余,从而防止硬件故障导致的数据丢失
  • 将集群中任一节点的请求路由到存有相关数据的节点

集群原理
ElasticSearch 的主旨是随时可用和按需扩容。 而扩容可以通过购买性能更强大( 垂直扩容 ,或 纵向扩容 ) 或者数量更多的服务器( 水平扩容 ,或 横向扩容 )来实现。ElastiSearch天生就是 分布式的 ,它知道如何通过管理多节点来提高扩容性和可用性。
一个运行中的 Elasticsearch 实例称为一个节点,而集群是由一个或者多个拥有相同 cluster.name 配置的节点组成, 它们共同承担数据和负载的压力。当有节点加入集群中或者从集群中移除节点时,集群将会重新平均分布所有的数据。
主节点行为:
它将负责管理集群范围内的所有变更,例如增加、删除索引,或者增加、删除节点等。 而主节点并不需要涉及到文档级别的变更和搜索等操作,所以当集群只拥有一个主节点的情况下,即使流量的增加它也不会成为瓶颈。 任何节点都可以成为主节点。
作为用户,我们可以将请求发送到 集群中的任何节点 ,包括主节点。 每个节点都知道任意文档所处的位置,并且能够将我们的请求直接转发到存储我们所需文档的节点。 无论我们将请求发送到哪个节点,它都能负责从各个包含我们所需文档的节点收集回数据,并将最终结果返回給客户端。
主分片和副本分片

  • 一个分片是一个底层的工作单元 ,它仅保存了全部数据中的一部分。分片是一个 Lucene 的实例,以及它本身就是一个完整的搜索引擎。
  • Elasticsearch 利用分片将数据分发到集群内各处。分片是数据的容器,文档保存在分片内,分片又被分配到集群内的各个节点里。 当你的集群规模扩大或者缩小时, Elasticsearch 会自动的在各节点中迁移分片,使得数据仍然均匀分布在集群里。
  • 索引内任意一个文档都归属于一个主分片。
  • 一个副本分片只是一个主分片的拷贝。副本分片作为硬件故障时保护数据不丢失的冗余备份,并为搜索和返回文档等读操作提供服务。
  • 在索引建立的时候就已经确定了主分片数,但是副本分片数可以随时修改。

水平扩容
在包含一个空节点的集群内创建名为blogs的索引。 索引在默认情况下会被分配5个主分片, 但是我们将分配3个主分片和一份副本(每个主分片拥有一个副本分片):

在这里插入图片描述
集群的健康状况为yellow,它表示全部主分片都正常运行(集群可以正常服务所有请求),但是副本分片没有全部处在正常状态。 实际上,所有3个副本分片都是 unassigned,它们都没有被分配到任何节点。 在同一个节点上既保存原始数据又保存副本是没有意义的,因为一旦失去了那个节点,我们也将丢失该节点上的所有副本数据。
在同一台机器上启动第二个节点,只要它和第一个节点有同样的 cluster.name 配置,它就会自动发现集群并加入到其中:
在这里插入图片描述
当第二个节点加入到集群后,3个 副本分片 将会分配到这个节点上——每个主分片对应一个副本分片。 这意味着当集群内任何一个节点出现问题时,我们的数据都完好无损。
然后,启动第三个节点:
在这里插入图片描述
Node1和 Node2上各有一个分片被迁移到了新的 Node 3 节点,现在每个节点上都拥有2个分片。 每个节点的硬件资源(CPU, RAM, I/O)将被更少的分片所共享,每个分片的性能将会得到提升。
这个拥有6个分片(3个主分片和3个副本分片)的索引可以最大扩容到6个节点,每个节点上存在一个分片,并且每个分片拥有所在节点的全部资源。
在运行中的集群上是可以动态调整副本分片数目的,我们可以按需伸缩集群。让我们把副本数从默认的1增加到 2 :

PUT /blogs/_settings
{
   "number_of_replicas" : 2
}

在这里插入图片描述
blogs索引现在拥有9个分片:3个主分片和6个副本分片。 这意味着我们可以将集群扩容到9个节点,每个节点上一个分片。相比原来3个节点时,集群搜索性能可以提升3倍。
应对故障
当我们关闭主节点Node1,集群必须拥有一个主节点来保证正常工作,所以发生的第一件事情就是选举一个新的主节点Node 2。
在这里插入图片描述

因为其它节点上存在着这两个主分片的完整副本, 所以新的主节点立即将这些分片在 Node 2 和 Node 3 上对应的副本分片提升为主分片, 此时集群的状态将会为 yellow 。
如果我们重新启动 Node1,集群可以将缺失的副本分片再次进行分配,如果Node1依然拥有着之前的分片,它将尝试去重用它们,同时仅从主分片复制发生了修改的数据文件。
文档的元数据
_index
文档在哪存放,在 Elasticsearch中数据被存储和索引在分片中,而一个索引仅仅是逻辑上的命名空间, 这个命名空间由一个或者多个分片组合在一起。
_type
文档表示的对象类别
_id
ID 是一个字符串,当它和 _index以及 _type组合就可以唯一确定 Elasticsearch 中的一个文档。
索引(动词)文档
1.使用自定义ID

PUT /{index}/{type}/{id}
{
  "field": "value",
  ...
}

在 Elasticsearch 中每个文档都有一个版本号。当每次对文档进行修改时(包括删除), _version 的值会递增。
2.自动生成ID
取回一个文档

GET /website/blog/123?pretty
{
  "_index" :   "website",
  "_type" :    "blog",
  "_id" :      "123",
  "_version" : 1,
  "found" :    true,
  "_source" :  {
      "title": "My first blog entry",
      "text":  "Just trying this out...",
      "date":  "2014/01/01"
  }
}

只返回一个文档的一部分:

GET /website/blog/123?_source=title,text
{
  "_index" :   "website",
  "_type" :    "blog",
  "_id" :      "123",
  "_version" : 1,
  "found" :   true,
  "_source" : {
      "title": "My first blog entry" ,
      "text":  "Just trying this out..."
  }
}

或者,如果你只想得到 _source 字段,不需要任何元数据,你能使用 _source 端点:

GET /website/blog/123/_source
{
   "title": "My first blog entry",
   "text":  "Just trying this out...",
   "date":  "2014/01/01"
}

检查文档是否存在

curl -i -XHEAD http://localhost:9200/website/blog/123

如果文档存在, Elasticsearch 将返回一个200ok的状态码,否则返回404 Not Found。
更新文档
在Elasticsearch中,文档数据是不为修改的,但是可以通过覆盖的方式进行更新。

PUT /website/blog/123
{
  "title": "My first blog entry",
  "text":  "I am starting to get the hang of this...",
  "date":  "2014/01/02"
}

在响应体中,Elasticsearch 已经增加了 _version 字段值;在内部,Elasticsearch 已将旧文档标记为已删除,并增加一个全新的文档。
尽管不能再对旧版本的文档进行访问,但它并不会立即消失。当继续索引更多的数据,Elasticsearch 会在后台清理这些已删除文档。
局部更新:
1.从旧文档构建 JSON
2.更改该 JSON
3.删除旧文档
4.索引一个新文档
创建新文档
当我们索引一个文档,怎么确认我们正在创建一个完全新的文档,而不是覆盖现有的呢?
确保创建一个新文档的最简单办法是,使用索引请求的 POST 形式让 Elasticsearch 自动生成唯一 _id 。
然而,如果已经有自己的 _id ,那么我们必须告诉 Elasticsearch ,只有在相同的 _index、 _type 和 _id 不存在时才接受我们的索引请求。这里有两种方式:
第一种方法使用 op_type 查询-字符串参数:

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

第二种方法是在 URL 末端使用 /_create :

PUT /website/blog/123/_create
{ ... }

删除文档

DELETE /website/blog/123

字段 _version会增加(即使文档不存在)
处理冲突
当我们使用 index API 更新文档 ,可以一次性读取原始文档,做我们的修改,然后重新索引 整个文档 。 最近的索引请求将获胜:无论最后哪一个文档被索引,都将被唯一存储在 Elasticsearch 中。如果其他人同时更改这个文档,他们的更改将丢失。
有一天,管理层决定做一次促销。突然地,我们一秒要卖好几个商品。 假设有两个 web 程序并行运行,每一个都同时处理所有商品的销售。
在这里插入图片描述
web_1 对 stock_count 所做的更改已经丢失,因为 web_2 不知道它的 stock_count 的拷贝已经过期。 结果我们会认为有超过商品的实际数量的库存,因为卖给顾客的库存商品并不存在,我们将让他们非常失望。
在数据库领域中,有两种方法通常被用来确保并发更新时变更不会丢失:

  • 悲观并发控制:这种方法被关系型数据库广泛使用,它假定有变更冲突可能发生,因此阻塞访问资源以防止冲突。 一个典型的例子是读取一行数据之前先将其锁住,确保只有放置锁的线程能够对这行数据进行修改。
  • 乐观并发控制:Elasticsearch 中使用的这种方法假定冲突是不可能发生的,并且不会阻塞正在尝试的操作。 然而,如果源数据在读写当中被修改,更新将会失败。
    应用程序接下来将决定该如何解决冲突。
    例如,可以重试更新、使用新的数据、或者将相关情况报告给用户。

乐观并发控制
Elasticsearch 是分布式的。当文档创建、更新或删除时, 新版本的文档必须复制到集群中的其他节点。Elasticsearch 也是异步和并发的,这意味着这些复制请求被并行发送,并且到达目的地时也许顺序是乱的 。 Elasticsearch 需要一种方法确保文档的旧版本不会覆盖新的版本:当 index有GET 和 delete 请求时,我们指出每个文档都有一个 _version (版本)号,当文档被修改时版本号递增。 Elasticsearch 使用这个 _version 号来确保变更以正确顺序得到执行。如果旧版本的文档在新版本之后到达,它可以被简单的忽略。
我们可以通过指定想要修改文档的 version 号来达到这个目的。 如果该版本不是当前版本号,我们的请求将会失败。

PUT /website/blog/1?version=1 
{
  "title": "My first blog entry",
  "text":  "Starting to get the hang of this..."
}

如果此请求成功,响应体会告诉我们 _version已经递增到 2,否则返回409 Conflict HTTP 响应码,和一个带有错误信息的响应体。
所有文档的更新或删除 API,都可以接受 version 参数
通过外部系统使用版本控制:
es提供了一个feature,就是说,你可以不用它提供的内部_version版本号来进行并发控制,可以基于你自己维护的一个版本号来进行并发控制。举个列子,加入你的数据在mysql里也有一份,然后你的应用系统本身就维护了一个版本号,无论是什么自己生成的,程序控制的。这个时候,你进行乐观锁并发控制的时候,可能并不是想要用es内部的_version来进行控制,而是用你自己维护的那个version来进行控制。

?version=1
?version=1&version_type=external

version_type=external,唯一的区别在于,_version,只有当你提供的version与es中的_version一模一样的时候,才可以进行修改,只要不一样,就报错;当version_type=external的时候,只有当你提供的version比es中的_version大的时候,才能完成修改。
文档的部分更新
文档是不可变的:不能被修改,只能被替换。
update API 必须遵循同样的规则。
从外部来看,我们在一个文档的某个位置进行部分更新。
然而在内部, update API 简单使用与之前描述相同的检索-修改-重建索引的处理过程。
区别在于这个过程发生在分片内部,这样就避免了多次请求的网络开销。
通过减少检索和重建索引步骤之间的时间,我们也减少了其他进程的变更带来冲突的可能性。

POST /website/blog/1/_update
{
   "doc" : {
      "tags" : [ "testing" ],
      "views": 0
   }
}

更新的文档并不存在?
假设我们需要在 Elasticsearch 中存储一个页面访问量计数器。
每当有用户浏览网页,我们对该页面的计数器进行累加。但是,如果它是一个新网页,我们不能确定计数器已经存在。 如果我们尝试更新一个不存在的文档,那么更新操作将会失败。
我们可以使用 upsert 参数,指定如果文档不存在就应该先创建它。

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

更新和冲突
在本节的介绍中,我们说明 检索 和 重建索引 步骤的间隔越小,变更冲突的机会越小。 但是它并不能完全消除冲突的可能性。 还是有可能在 update 设法重新索引之前,来自另一进程的请求修改了文档。

为了避免数据丢失, update API 在 检索 步骤时检索得到文档当前的 _version 号,并传递版本号到 重建索引 步骤的 index 请求。 如果另一个进程修改了处于检索和重新索引步骤之间的文档,那么 _version 号将不匹配,更新请求将会失败。
对于部分更新的很多使用场景,文档已经被改变也没有关系,
如果两个进程都对页面访问量计数器进行递增操作,它们发生的先后顺序其实不太重要; 如果冲突发生了,我们唯一需要做的就是尝试再次更新。
这可以通过设置参数 retry_on_conflict 来自动完成, 这个参数规定了失败之前 update 应该重试的次数,它的默认值为 0 。
取回多个文档
mget API 要求有一个 docs 数组作为参数,每个元素包含需要检索文档的元数据, 包括 _index 、_type 和 _id 。
如果你想检索一个或者多个特定的字段,那么你可以通过 _source 参数来指定这些字段的名字:

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

如果想检索的数据都在相同的 _index 中(甚至相同的 _type 中),则可以在 URL 中指定默认的 /_index 或者默认的 /_index/_type 。
仍然可以通过单独请求覆盖这些值:

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

如果所有文档的 _index 和 _type 都是相同的,你可以只传一个 ids 数组

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

批量操作

{ action: { metadata }}\n
{ request body        }\n
{ action: { metadata }}\n
{ request body        }\n
...

bulk 请求不是原子的: 不能用它来实现事务控制。
每个请求是单独处理的,因此一个请求的成功或失败不会影响其他的请求。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值