ES-基础

1.基础接口--初步了解ES

//查询文档总数
curl -XGET 'http://localhost:9200/_count?pretty' -d '
{
    "query": {
        "match_all": {}
    }
}
'
//新增索引+一个文档
PUT /megacorp_employee/_doc/1
{
    "first_name" : "John",
    "last_name" :  "Smith",
    "age" :        25,
    "about" :      "I love to go rock climbing",
    "interests": [ "sports", "music" ]
}

//新增文档,如果Id存在则报错
PUT /megacorp_employee/_doc/1?op_type=create
 {
      "first_name" :  "longyan",
      "last_name" :   "zhanglong",
      "age" :         27,
      "about" :       "I love to reading",
      "interests":  [ "study", "sport" ]
  }

PUT /megacorp_employee/_create/5
 {
      "first_name" :  "aaaa",
      "last_name" :   "bbbbbb",
      "age" :         100,
      "about" :       "I love to fucking",
      "interests":  [ "study", "sport" ]
  }

//部分修改文档或新增字段
POST /megacorp_employee/_update/1
{
   "doc" : {
      "tags" : [ "testing" ],
      "views": 0,
      "first_name":"fdsfdsafd"
   }
}

//使用脚本更新文档
POST /megacorp_employee/_update/1
{
   "script" : "ctx._source.views+=1"
}

//使用脚本更新数组
POST /megacorp_employee/_update/1
{
   "script" : {
     "source": "ctx._source.tags.add(params.new_tag)",
      "params" : {
      "new_tag" : "search"
    }
   }
}

//使用脚本判断更新
POST test/_update/1
{
  "script": {
    "source": "if (ctx._source.tags.contains(params.tag)) { ctx._source.tags.remove(ctx._source.tags.indexOf(params.tag)) }",
    "lang": "painless",
    "params": {
      "tag": "blue"
    }
  }
}

//使用脚本添加字段
POST test/_update/1
{
  "script" : "ctx._source.new_field = 'value_of_new_field'"
}

//使用脚本删除字段
POST test/_update/1
{
  "script" : "ctx._source.remove('new_field')"
}

//使用脚本判断是否删除文档
POST test/_update/1
{
  "script": {
    "source": "if (ctx._source.tags.contains(params.tag)) { ctx.op = 'delete' } else { ctx.op = 'none' }",
    "lang": "painless",
    "params": {
      "tag": "green"
    }
  }
}

//如果文档存在则新增,不存在则执行脚本
POST test/_update/1
{
  "script": {
    "source": "ctx._source.counter += params.count",
    "lang": "painless",
    "params": {
      "count": 4
    }
  },
  "upsert": {
    "counter": 1
  }
}

//更新时遇到冲突继续
POST my-index-000001/_update_by_query?conflicts=proceed

//重试3次并立即刷新
POST /megacorp_employee/_update/1?retry_on_conflict=3&refresh=true
{
   "script" : {
     "source": "ctx._source.tags.add(params.new_tag)",
      "params" : {
      "new_tag" : "search"
    }
   }
}


//删除文档
DELETE /megacorp_employee/_doc/5

//检查是否_id存在带有0的文档
HEAD /megacorp_employee/_doc/1

//获取单个索引
GET /megacorp_employee/_doc/1

//返回文档的一部分
GET /megacorp_employee/_doc/1?_source=first_name,last_name

//获取文档指定的一部分
GET /megacorp_employee/_doc/1?_source_includes=age,first_name&

//如果你只想得到 _source 字段,不需要任何元数据,你能使用 _source 端点
GET /megacorp_employee/_source/1

//获取多条数据  默认返回10条数据
GET /megacorp_employee/_search

//根据字段last_name检索数据
GET /megacorp_employee/_search?q=last_name:Smith

//根据字段last_name检索数据
GET /megacorp_employee/_search
{
  "query":{
    "match":{
      "last_name":"Smith"
    }
  }
}

//过滤器 filter  同样搜索姓氏为 Smith 的员工,但这次我们只需要年龄大于 30 的
GET /megacorp_employee/_search
{
  "query":{
    "bool":{
      "must":{
        "match":{
          "last_name":"Smith"
        }
      },
      "filter":{
        "range":{
          "age":{"gt":30}
        }
      }
    }
  }
}

//检索多个文档
GET /_mget
{
   "docs" : [
      {
         "_index" : "website",
         "_type" :  "blog",
         "_id" :    2
      },
      {
         "_index" : "website",
         "_type" :  "pageviews",
         "_id" :    1,
         "_source": "views"
      }
   ]
}

//检索的数据都在相同的 _index 中(甚至相同的 _type 中),则可以在 URL 中指定默认的 /_index 或者默认的 /_index/_type 
GET /megacorp_employee/_mget
{
   "docs" : [
      { "_id" : 2 },
      {  "_id" :   1 }
   ]
}

//如果所有文档的 _index 和 _type 都是相同的,你可以只传一个 ids 数组,而不是整个 docs 数组
GET /megacorp_employee/_mget
{
   "ids" : [ "2", "1" ]
}

//bulk 批量操作 	请注意 delete 动作不能有请求体,它后面跟着的是另外一个操作
POST _bulk
{ "index" : { "_index" : "megacorp_employee", "_id" : "1" } }
{ "first_name" : "value1" }
{ "delete" : { "_index" : "megacorp_employee", "_id" : "5" } }
{ "create" : { "_index" : "megacorp_employee", "_id" : "3" } }
{ "last_name" : "value3" }
{ "update" : {"_id" : "2", "_index" : "megacorp_employee"} }
{ "doc" : {"first_name" : "value2"} }

//只返回失败的信息
POST /_bulk?filter_path=items.*.error
{ "update": {"_id": "5", "_index": "index1"} }
{ "doc": {"my_field": "baz"} }
{ "update": {"_id": "6", "_index": "index1"} }
{ "doc": {"my_field": "baz"} }
{ "update": {"_id": "7", "_index": "index1"} }
{ "doc": {"my_field": "baz"} }


//全文检索 搜索下所有喜欢攀岩(rock climbing)的员工
//Elasticsearch 默认按照相关性得分排序,即每个文档跟查询的匹配程度。第一个最高得分的结果很明显:John Smith 的 about 属性清楚地写着 “rock climbing” 。

//但为什么 Jane Smith 也作为结果返回了呢?原因是她的 about 属性里提到了 “rock” 。因为只有 “rock” 而没有 “climbing” ,所以她的相关性得分低于 John 的。

//这是一个很好的案例,阐明了 Elasticsearch 如何 在 全文属性上搜索并返回相关性最强的结果。Elasticsearch中的 相关性 概念非常重要,也是完全区别于传统关系型数据库的一个概念,数据库中的一条记录要么匹配要么不匹配
GET /megacorp_employee/_search
{
    "query" : {
        "match" : {
            "about" : "rock climbing"
        }
    }
}

//短语搜索 仅匹配同时包含 “rock” 和 “climbing” 
GET /megacorp_employee/_search
{
    "query" : {
        "match_phrase": {
            "about" : "rock climbing"
        }
    }
}

//高亮检索 许多应用都倾向于在每个搜索结果中 高亮 部分文本片段,以便让用户知道为何该文档符合查询条件
GET /megacorp_employee/_search
{
    "query" : {
        "match_phrase": {
            "about" : "rock climbing"
        }
    },
    "highlight": {
      "fields": {
        "about": {}
      }
    }
}

//分析 持管理者对员工目录做分析。 Elasticsearch 有一个功能叫聚合(aggregations),允许我们基于数据生成一些精细的分析结果
//挖掘出员工中最受欢迎的兴趣爱好
GET /megacorp_employee/_search
{
  "aggs": {
    "all_interests": {
      "terms": {
        "field": "interests"
      }
    }
  }
}

//默认情况会报错:Fielddata is disabled on text fields by default.
//设置fielddata为true
PUT megacorp_employee/_mapping
{
  "properties": {
    "interests": { 
      "type":     "text",
      "fielddata": true
    }
  }
}

//聚合还支持分级汇总 。比如,查询特定兴趣爱好员工的平均年龄:
GET /megacorp_employee/_search
{
  "aggs": {
    "all_interests": {
      "terms": {
        "field": "interests"
      }
      , "aggs": {
        "avg_age": {
          "avg": {
            "field": "age"
          }
        }
      }
    }
  }
}


//集群健康 Elasticsearch 的集群监控信息中包含了许多的统计数据,其中最为重要的一项就是 集群健康 , 它在 status 字段中展示为 green 、 yellow 或者 red 。
//green:所有的主分片和副本分片都正常运行。
//yellow:所有的主分片都正常运行,但不是所有的副本分片都正常运行。
//red:有主分片没能正常运行。
GET /_cluster/health

//为索引设置分片
集群的健康状况为 yellow 则表示全部 主 分片都正常运行(集群可以正常服务所有请求),但是 副本 分片没有全部处在正常状态。 实际上,所有3个副本分片都是 unassigned —— 它们都没有被分配到任何节点。 在同一个节点上既保存原始数据又保存副本是没有意义的,因为一旦失去了那个节点,我们也将丢失该节点上的所有副本数据。

当前我们的集群是正常运行的,但是在硬件故障时有丢失数据的风险。
原因是集群中只有一个节点,但是这个节点被用于放置主分片,副分片不可和主分片在同一个节点,所以副分片没有其他节点可以放置。这样就避免了一个节点出现故障,其上所有数据全部丢失,我们可以通过其他节点上的副分片进行恢复数据
PUT /blogs
{
   "settings" : {
      "number_of_shards" : 3,
      "number_of_replicas" : 1
   }
}

//增加两倍于主分片的副分片
PUT /blogs/_settings
{
   "number_of_replicas" : 2
}







2.集群内的原理

一个运行中的 Elasticsearch 实例称为一个节点,而集群是由一个或者多个拥有相同 cluster.name 配置的节点组成, 它们共同承担数据和负载的压力。当有节点加入集群中或者从集群中移除节点时,集群将会重新平均分布所有的数据。

当一个节点被选举成为  节点时, 它将负责管理集群范围内的所有变更,例如增加、删除索引,或者增加、删除节点等。 而主节点并不需要涉及到文档级别的变更和搜索等操作,所以当集群只拥有一个主节点的情况下,即使流量的增加它也不会成为瓶颈。 任何节点都可以成为主节点。我们的示例集群就只有一个节点,所以它同时也成为了主节点。

作为用户,我们可以将请求发送到 集群中的任何节点 ,包括主节点。 每个节点都知道任意文档所处的位置,并且能够将我们的请求直接转发到存储我们所需文档的节点。 无论我们将请求发送到哪个节点,它都能负责从各个包含我们所需文档的节点收集回数据,并将最终结果返回給客户端。 Elasticsearch 对这一切的管理都是透明的。

3.添加索引

我们往 Elasticsearch 添加数据时需要用到 索引 —— 保存相关数据的地方。 索引实际上是指向一个或者多个物理 分片 的 逻辑命名空间 。

一个 分片 是一个底层的 工作单元 ,它仅保存了全部数据中的一部分。 在分片内部机制中,我们将详细介绍分片是如何工作的,而现在我们只需知道一个分片是一个 Lucene 的实例,以及它本身就是一个完整的搜索引擎。 我们的文档被存储和索引到分片内,但是应用程序是直接与索引而不是与分片进行交互。

Elasticsearch 是利用分片将数据分发到集群内各处的。分片是数据的容器,文档保存在分片内,分片又被分配到集群内的各个节点里。 当你的集群规模扩大或者缩小时, Elasticsearch 会自动的在各节点中迁移分片,使得数据仍然均匀分布在集群里。

一个分片可以是 分片或者 副本 分片。 索引内任意一个文档都归属于一个主分片,所以主分片的数目决定着索引能够保存的最大数据量。

技术上来说,一个主分片最大能够存储 Integer.MAX_VALUE - 128 个文档,但是实际最大值还需要参考你的使用场景:包括你使用的硬件, 文档的大小和复杂程度,索引和查询文档的方式以及你期望的响应时长。

一个副本分片只是一个主分片的拷贝。副本分片作为硬件故障时保护数据不丢失的冗余备份,并为搜索和返回文档等读操作提供服务。

在索引建立的时候就已经确定了主分片数,但是副本分片数可以随时修改。

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

集群的健康状况为 yellow 则表示全部 主 分片都正常运行(集群可以正常服务所有请求),但是 副本 分片没有全部处在正常状态。 实际上,所有3个副本分片都是 unassigned —— 它们都没有被分配到任何节点。 在同一个节点上既保存原始数据又保存副本是没有意义的,因为一旦失去了那个节点,我们也将丢失该节点上的所有副本数据。

当前我们的集群是正常运行的,但是在硬件故障时有丢失数据的风险。

原因是集群中只有一个节点,但是这个节点被用于放置主分片,副分片不可和主分片在同一个节点,所以副分片没有其他节点可以放置。这样就避免了一个节点出现故障,其上所有数据全部丢失,我们可以通过其他节点上的副分片进行恢复数据

PUT /blogs

{

  "settings" : {

     "number_of_shards" : 3,

     "number_of_replicas" : 1

  }

}

4.添加故障转移

当集群中只有一个节点在运行时,意味着会有一个单点故障问题——没有冗余。 幸运的是,我们只需再启动一个节点即可防止数据丢失。

为了测试第二个节点启动后的情况,你可以在同一个目录内,完全依照启动第一个节点的方式来启动一个新节点(参考安装并运行 Elasticsearch)。多个节点可以共享同一个目录。

当你在同一台机器上启动了第二个节点时,只要它和第一个节点有同样的 cluster.name 配置,它就会自动发现集群并加入到其中。 但是在不同机器上启动节点的时候,为了加入到同一集群,你需要配置一个可连接到的单播主机列表。 详细信息请查看最好使用单播代替组播

当第二个节点加入到集群后,3个 副本分片 将会分配到这个节点上——每个主分片对应一个副本分片。 这意味着当集群内任何一个节点出现问题时,我们的数据都完好无损。

所有新近被索引的文档都将会保存在主分片上,然后被并行的复制到对应的副本分片上。这就保证了我们既可以从主分片又可以从副本分片上获得文档。

cluster-health 现在展示的状态为 green ,这表示所有6个分片(包括3个主分片和3个副本分片)都在正常运行。

5.水平扩容

怎样为我们的正在增长中的应用程序按需扩容呢? 当启动了第三个节点,我们的集群将会看起来如Figure 4, “拥有三个节点的集群——为了分散负载而对分片进行重新分配”所示。

Node 1Node 2 上各有一个分片被迁移到了新的 Node 3 节点,现在每个节点上都拥有2个分片,而不是之前的3个。 这表示每个节点的硬件资源(CPU, RAM, I/O)将被更少的分片所共享,每个分片的性能将会得到提升。

分片是一个功能完整的搜索引擎,它拥有使用一个节点上的所有资源的能力。 我们这个拥有6个分片(3个主分片和3个副本分片)的索引可以最大扩容到6个节点,每个节点上存在一个分片,并且每个分片拥有所在节点的全部资源。

主分片的数目在索引创建时就已经确定了下来。实际上,这个数目定义了这个索引能够 存储 的最大数据量。(实际大小取决于你的数据、硬件和使用场景。) 但是,读操作——搜索和返回数据——可以同时被主分片  副本分片所处理,所以当你拥有越多的副本分片时,也将拥有越高的吞吐量。

在运行中的集群上是可以动态调整副本分片数目的,我们可以按需伸缩集群。让我们把副本数从默认的 1 增加到 2 :

当然,如果只是在相同节点数目的集群上增加更多的副本分片并不能提高性能,因为每个分片从节点上获得的资源会变少。 你需要增加更多的硬件资源来提升吞吐量。

Figure 5, “将参数 number_of_replicas 调大到 2”所示, blogs 索引现在拥有9个分片:3个主分片和6个副本分片。 这意味着我们可以将集群扩容到9个节点,每个节点上一个分片。相比原来3个节点时,集群搜索性能可以提升 3 倍。

但是更多的副本分片数提高了数据冗余量:按照上面的节点配置,我们可以在失去2个节点的情况下不丢失任何数据。

6.应对故障

我们之前说过 Elasticsearch 可以应对节点故障,接下来让我们尝试下这个功能。 如果我们关闭第一个节点,这时集群的状态为Figure 6, “关闭了一个节点后的集群”

我们关闭的节点是一个主节点。而集群必须拥有一个主节点来保证正常工作,所以发生的第一件事情就是选举一个新的主节点: Node 2

在我们关闭 Node 1 的同时也失去了主分片 12 ,并且在缺失主分片的时候索引也不能正常工作。 如果此时来检查集群的状况,我们看到的状态将会为 red :不是所有主分片都在正常工作。

幸运的是,在其它节点上存在着这两个主分片的完整副本, 所以新的主节点立即将这些分片在 Node 2Node 3 上对应的副本分片提升为主分片, 此时集群的状态将会为 yellow 。 这个提升主分片的过程是瞬间发生的,如同按下一个开关一般。

为什么我们集群状态是 yellow 而不是 green 呢? 虽然我们拥有所有的三个主分片,但是同时设置了每个主分片需要对应2份副本分片,而此时只存在一份副本分片。 所以集群不能为 green 的状态,不过我们不必过于担心:如果我们同样关闭了 Node 2 ,我们的程序 依然 可以保持在不丢任何数据的情况下运行,因为 Node 3 为每一个分片都保留着一份副本。

如果我们重新启动 Node 1 ,集群可以将缺失的副本分片再次进行分配,那么集群的状态也将如Figure 5, “将参数 number_of_replicas 调大到 2”所示。 如果 Node 1 依然拥有着之前的分片,它将尝试去重用它们,同时仅从主分片复制发生了修改的数据文件。

到目前为止,你应该对分片如何使得 Elasticsearch 进行水平扩容以及数据保障等知识有了一定了解。 接下来我们将讲述关于分片生命周期的更多细节。

7.数据输入和输出

Elastcisearch 是分布式的 文档 存储。它能存储和检索复杂的数据结构—序列化成为JSON文档—以 实时 的方式。 换句话说,一旦一个文档被存储在 Elasticsearch 中,它就是可以被集群中的任意节点检索到。

当然,我们不仅要存储数据,我们一定还需要查询它,成批且快速的查询它们。 尽管现存的 NoSQL 解决方案允许我们以文档的形式存储对象,但是他们仍旧需要我们思考如何查询我们的数据,以及确定哪些字段需要被索引以加快数据检索。

在 Elasticsearch 中, 每个字段的所有数据 都是 默认被索引的 。 即每个字段都有为了快速检索设置的专用倒排索引。而且,不像其他多数的数据库,它能在 同一个查询中 使用所有这些倒排索引,并以惊人的速度返回结果。

在本章中,我们展示了用来创建,检索,更新和删除文档的 API。就目前而言,我们不关心文档中的数据或者怎样查询它们。 所有我们关心的就是在 Elasticsearch 中怎样安全的存储文档,以及如何将文档再次返回。

a.文档元数据

一个文档不仅仅包含它的数据 ,也包含 元数据 —— 有关 文档的信息。 三个必须的元数据元素如下:

_index文档在哪存放_type文档表示的对象类别_id文档唯一标识

自动生成的Id:自动生成的 ID 是 URL-safe、 基于 Base64 编码且长度为20个字符的 GUID 字符串。 这些 GUID 字符串由可修改的 FlakeID 模式生成,这种模式允许多个节点并行生成唯一 ID ,且互相之间的冲突概率几乎为零。

b.取回一个文档

GET 请求的响应体包括 {"found": true} ,这证实了文档已经被找到。 如果我们请求一个不存在的文档,我们仍旧会得到一个 JSON 响应体,但是 found 将会是 false 。 此外, HTTP 响应码将会是 404 Not Found ,而不是 200 OK 

c.修改文档

修改文档操作:es内部执行顺序:先取出文档,然后生成json,然后修改json 删除原有文档,新增文档,原有文档不会被真正删除,仅仅是做了删除标记,真正的删除由es内部控制

d.处理冲突

乐观并发控制

Elasticsearch 是分布式的。当文档创建、更新或删除时, 新版本的文档必须复制到集群中的其他节点。Elasticsearch 也是异步和并发的,这意味着这些复制请求被并行发送,并且到达目的地时也许 顺序是乱的 。 Elasticsearch 需要一种方法确保文档的旧版本不会覆盖新的版本。

当我们之前讨论 indexGETdelete 请求时,我们指出每个文档都有一个 _version (版本)号,当文档被修改时版本号递增。 Elasticsearch 使用这个 _version 号来确保变更以正确顺序得到执行。如果旧版本的文档在新版本之后到达,它可以被简单的忽略。

我们可以利用 _version 号来确保 应用中相互冲突的变更不会导致数据丢失。我们通过指定想要修改文档的 version 号来达到这个目的。 如果该版本不是当前版本号,我们的请求将会失败。

ES冲突的时候会返回 409 Conflict HTTP 响应码

e.文档的部分更新

我们也介绍过文档是不可变的:他们不能被修改,只能被替换。 update API 必须遵循同样的规则。 从外部来看,我们在一个文档的某个位置进行部分更新。然而在内部, update API 简单使用与之前描述相同的 检索-修改-重建索引 的处理过程。 区别在于这个过程发生在分片内部,这样就避免了多次请求的网络开销。通过减少检索和重建索引步骤之间的时间,我们也减少了其他进程的变更带来冲突的可能性。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值