zoukankan      html  css  js  c++  java
  • ElasticSearch权威指南学习(文档)

    什么是文档

    1. 在Elasticsearch中,文档(document)这个术语有着特殊含义。它特指最顶层结构或者根对象(root object)序列化成的JSON数据(以唯一ID标识并存储于Elasticsearch中)
    2. 文档元数据
    节点 说明
    _index 文档存储的地方
    _type 文档代表的对象的类
    _id 文档的唯一标识
    1. _index

      • 索引(index)类似于关系型数据库里的“数据库”——它是我们存储和索引关联数据的地方。
    2. _type

      • 在应用中,我们使用对象表示一些“事物”,例如一个用户、一篇博客、一个评论,或者一封邮件。每个对象都属于一个类(class),这个类定义了属性或与对象关联的数据。user类的对象可能包含姓名、性别、年龄和Email地址。
      • 每个类型(type)都有自己的映射(mapping)或者结构定义,就像传统数据库表中的列一样。
      • _type的名字可以是大写或小写,不能包含下划线或逗号。
    3. _id

      • id仅仅是一个字符串,它与_index和_type组合时,就可以在Elasticsearch中唯一标识一个文档。
      • 当创建一个文档,你可以自定义_id,也可以让Elasticsearch帮你自动生成。

    索引一个文档

    1. 使用自己的ID
    PUT /{index}/{type}/{id}
    {
      "field": "value",
      ...
    }
    
    1. 例如我们的索引叫做“website”,类型叫做“blog”,我们选择的ID是“123”
    PUT /website/blog/123
    {
      "title": "My first blog entry",
      "text":  "Just trying this out...",
      "date":  "2014/01/01"
    }
    响应
    {
       "_index":    "website",
       "_type":     "blog",
       "_id":       "123",
       "_version":  1,
       "created":   true
    }
    
    1. 自增ID
    POST /website/blog/
    {
      "title": "My second blog entry",
      "text":  "Still trying this out...",
      "date":  "2014/01/01"
    }
    响应
    {
       "_index":    "website",
       "_type":     "blog",
       "_id":       "wM0OSFhDQXGZAWDf0-drSA",
       "_version":  1,
       "created":   true
    }
    

    自动生成的ID有22个字符长

    检索文档

    1. 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"
      }
    }
    

    pretty
    在任意的查询字符串中增加pretty参数,类似于上面的例子。会让Elasticsearch美化输出(pretty-print)JSON响应以便更加容易阅读

    1. 检索文档的一部分 GET /website/blog/123?_source=title,text
      • _source字段现在只包含我们请求的字段
      {
        "_index" :   "website",
        "_type" :    "blog",
        "_id" :      "123",
        "_version" : 1,
        "exists" :   true,
        "_source" : {
            "title": "My first blog entry" ,
            "text":  "Just trying this out..."
        }
      }
      
    2. 或者你只想得到_source字段而不要其他的元数据,你可以这样请求:
    GET /website/blog/123/_source
    
    {
       "title": "My first blog entry",
       "text":  "Just trying this out...",
       "date":  "2014/01/01"
    }
    

    检查文档是否存在

    1. 只是检查文档是否存在——你对内容完全不感兴趣——使用HEAD方法来代替GET。HEAD请求不会返回响应体,只有HTTP头:
    curl -i -XHEAD http://localhost:9200/website/blog/123
    

    Elasticsearch将会返回200 OK状态如果你的文档存在:

    HTTP/1.1 200 OK
    Content-Type: text/plain; charset=UTF-8
    Content-Length: 0
    

    如果不存在返回404 Not Found:

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

    更新整个文档

    1. 文档在Elasticsearch中是不可变的——我们不能修改他们。如果需要更新已存在的文档,我们可以使用《索引文档》章节提到的index API 重建索引(reindex) 或者替换掉它
    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增加了。

    {
      "_index" :   "website",
      "_type" :    "blog",
      "_id" :      "123",
      "_version" : 2,
      "created":   false
    }
    

    created标识为false因为同索引、同类型下已经存在同ID的文档
    2. 在内部,Elasticsearch已经标记旧文档为删除并添加了一个完整的新文档。旧版本文档不会立即消失,但你也不能去访问它。Elasticsearch会在你继续索引更多数据时清理被删除的文档。

    创建一个新文档

    POST /website/blog/
    { ... }
    
    1. 如果想使用自定义的_id
      第一种方法使用op_type查询参数
    PUT /website/blog/123?op_type=create
    { ... }
    

    第二种方法是在URL后加/_create做为端点:

    PUT /website/blog/123/_create
    { ... }
    
    1. 请求成功响应状态码是201 Created
    2. 如果包含相同的_index、_type和_id的文档已经存在,Elasticsearch将返回409 Conflict响应状态码
    {
      "error" : "DocumentAlreadyExistsException[[website][4] [blog][123]:
                 document already exists]",
      "status" : 409
    }
    

    删除文档

    1. DELETE /website/blog/123
    成功,返回200 OK状态码,注意_version数字已经增加了。
    {
      "found" :    true,
      "_index" :   "website",
      "_type" :    "blog",
      "_id" :      "123",
      "_version" : 3
    }
    失败,得到一个404 Not Found状态码
    {
      "found" :    false,
      "_index" :   "website",
      "_type" :    "blog",
      "_id" :      "123",
      "_version" : 4
    }
    

    处理冲突

    1. 当使用index API更新文档的时候,我们读取原始文档,做修改,然后将整个文档(whole document)一次性重新索引。最近的索引请求会生效——Elasticsearch中只存储最后被索引的任何文档。如果其他人同时也修改了这个文档,他们的修改将会丢失。
    2. 乐观并发控制
      • Elasticsearch使用_version保证所有修改都被正确排序。当一个旧版本出现在新版本之后,它会被简单的忽略。
      • 比如,创建一个新的博文,响应体告诉我们这是一个新建的文档,它的_version是1
      PUT /website/blog/1/_create
      {
        "title": "My first blog entry",
        "text":  "Just trying this out..."
      }
      
      • 当我们通过重新索引文档保存修改时,我们这样指定了version参数,我们只希望文档的_version是1时更新才生效
      PUT /website/blog/1?version=1
      {
        "title": "My first blog entry",
        "text":  "Starting to get the hang of this..."
      }
      
      • 成功
      {
        "_index":   "website",
        "_type":    "blog",
        "_id":      "1",
        "_version": 2 //成功版本+1
        "created":  false
      }
      
      • 失败
      {
        "error" : "VersionConflictEngineException[[website][2] [blog][1]:
                   version conflict, current [2], provided [1]]",
        "status" : 409
      }
      
    3. 使用外部版本控制系统
      • 外部版本号与之前说的内部版本号在处理的时候有些不同。它不再检查_version是否与请求中指定的一致,而是检查是否小于指定的版本。如果请求成功,外部版本号就会被存储到_version中。
      • 创建一个包含外部版本号5的新博客
      PUT /website/blog/2?version=5&version_type=external
      {
        "title": "My first external blog entry",
        "text":  "Starting to get the hang of this..."
      }
      响应
      {
        "_index":   "website",
        "_type":    "blog",
        "_id":      "2",
        "_version": 5,
        "created":  true
      }
      

    文档局部更新

    1. 文档是不可变的——它们不能被更改,只能被替换。update API必须遵循相同的规则。表面看来,我们似乎是局部更新了文档的位置,内部却是像我们之前说的一样简单的使用update API处理相同的检索-修改-重建索引流程,我们也减少了其他进程可能导致冲突的修改。
    2. 为博客添加一个tags字段和一个views字段:
    POST /website/blog/1/_update
    {
       "doc" : {
          "tags" : [ "testing" ],
          "views": 0
       }
    }
    
    1. 使用脚本局部更新

      • Elasticsearch允许你使用脚本实现自己的逻辑。脚本支持非常多的API,例如搜索、排序、聚合和文档更新。脚本可以通过请求的一部分、检索特殊的.scripts索引或者从磁盘加载方式执行。
      • 默认的脚本语言是Groovy,一个快速且功能丰富的脚本语言,语法类似于Javascript。它在一个沙盒(sandbox)中运行,以防止恶意用户毁坏Elasticsearch或攻击服务器。
      • 例如,我们可以使用脚本增加博客的views数量:
      POST /website/blog/1/_update
      {
         "script" : "ctx._source.views+=1"
      }
      
    2. 更新可能不存在的文档

      • 想象我们要在Elasticsearch中存储浏览量计数器。每当有用户访问页面,我们增加这个页面的浏览量。但如果这是个新页面,我们并不确定这个计数器存在与否。当我们试图更新一个不存在的文档,更新将失败。
        在这种情况下,我们可以使用upsert参数定义文档来使其不存在时被创建。
      POST /website/pageviews/1/_update
      {
         "script" : "ctx._source.views+=1",
         "upsert": {
             "views": 1
         }
      }
      

      第一次执行这个请求,upsert值被索引为一个新文档,初始化views字段为1.接下来文档已经存在,所以script被更新代替,增加views数量。

    3. 更新和冲突

      • 对于多用户的局部更新,文档被修改了并不要紧。例如,两个进程都要增加页面浏览量,增加的顺序我们并不关心——如果冲突发生,我们唯一要做的仅仅是重新尝试更新既可。
      • 这些可以通过retry_on_conflict参数设置重试次数来自动完成,这样update操作将会在发生错误前重试——这个值默认为0。
      POST /website/pageviews/1/_update?retry_on_conflict=5 //在错误发生前重试更新5次
      {
         "script" : "ctx._source.views+=1",
         "upsert": {
             "views": 0
         }
      }
      
      • 这适用于像增加计数这种顺序无关的操作

    检索多个文档

    1. 像Elasticsearch一样,检索多个文档依旧非常快。合并多个请求可以避免每个请求单独的网络开销。如果你需要从Elasticsearch中检索多个文档,相对于一个一个的检索,更快的方式是在一个请求中使用multi-get或者mget API。
    POST /_mget
    {
       "docs" : [
          {
             "_index" : "website",
             "_type" :  "blog",
             "_id" :    2
          },
          {
             "_index" : "website",
             "_type" :  "pageviews",
             "_id" :    1,
             "_source": "views"
          }
       ]
    }
    

    响应体也包含一个docs数组,每个文档还包含一个响应,它们按照请求定义的顺序排列。每个这样的响应与单独使用get request响应体相同:

    {
       "docs" : [
          {
             "_index" :   "website",
             "_id" :      "2",
             "_type" :    "blog",
             "found" :    true,
             "_source" : {
                "text" :  "This is a piece of cake...",
                "title" : "My first external blog entry"
             },
             "_version" : 10
          },
          {
             "_index" :   "website",
             "_id" :      "1",
             "_type" :    "pageviews",
             "found" :    true,
             "_version" : 2,
             "_source" : {
                "views" : 2
             }
          }
       ]
    }
    
    1. 如果你想检索的文档在同一个_index中(甚至在同一个_type中),你就可以在URL中定义一个默认的/_index或者/_index/_type。
    POST /website/blog/_mget
    {
       "docs" : [
          { "_id" : 2 },
          { "_type" : "pageviews", "_id" :   1 }
       ]
    }
    
    1. 如果所有文档具有相同_index和_type,你可以通过简单的ids数组来代替完整的docs数组
    POST /website/blog/_mget
    {
       "ids" : [ "2", "1" ]
    }
    

    我们请求的第二个文档并不存在。我们定义了类型为blog,但是ID为1的文档类型为pageviews。这个不存在的文档会在响应体中被告知。

    {
      "docs" : [
        {
          "_index" :   "website",
          "_type" :    "blog",
          "_id" :      "2",
          "_version" : 10,
          "found" :    true,
          "_source" : {
            "title":   "My first external blog entry",
            "text":    "This is a piece of cake..."
          }
        },
        {
          "_index" :   "website",
          "_type" :    "blog",
          "_id" :      "1",
          "found" :    false //没被找到
        }
      ]
    }
    

    事实上第二个文档不存在并不影响第一个文档的检索。每个文档的检索和报告都是独立的

    更新时的批量操作

    1. 就像mget允许我们一次性检索多个文档一样,bulk API允许我们使用单一请求来实现多个文档的create、index、update或delete。这对索引类似于日志活动这样的数据流非常有用,它们可以以成百上千的数据为一个批次按序进行索引。
    2. bulk请求体如下
    { action: { metadata }}
    
    { request body        }
    
    { action: { metadata }}
    
    { request body        }
    
    ...
    

    这种格式类似于用" "符号连接起来的一行一行的JSON文档流(stream)。两个重要的点需要注意:

    - 每行必须以"
    "符号结尾,包括最后一行。这些都是作为每行有效的分离而做的标记。
    - 每一行的数据不能包含未被转义的换行符,它们会干扰分析——这意味着JSON不能被美化打印。
    

    action/metadata这一行定义了文档行为(what action)发生在哪个文档(which document)之上。
    行为(action)必须是以下几种:

    行为 解释
    create 当文档不存在时创建之
    index 创建新文档或替换已有文档
    update 局部更新文档
    delete 删除一个文档
    1. 例如删除请求看起来像这样:
    { "delete": { "_index": "website", "_type": "blog", "_id": "123" }}
    
    1. bulk请求表单是这样的
    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行为(action)没有请求体,它紧接着另一个行为(action)
    • 记得最后一个换行符
    1. Elasticsearch响应包含一个items数组,它罗列了每一个请求的结果,结果的顺序与我们请求的顺序相同:
    {
       "took": 4,
       "errors": false, <1>
       "items": [
          {  "delete": {
                "_index":   "website",
                "_type":    "blog",
                "_id":      "123",
                "_version": 2,
                "status":   200,
                "found":    true
          }},
          {  "create": {
                "_index":   "website",
                "_type":    "blog",
                "_id":      "123",
                "_version": 3,
                "status":   201
          }},
          {  "create": {
                "_index":   "website",
                "_type":    "blog",
                "_id":      "EiwfApScQiiy7TIKFxRCTw",
                "_version": 1,
                "status":   201
          }},
          {  "update": {
                "_index":   "website",
                "_type":    "blog",
                "_id":      "123",
                "_version": 4,
                "status":   200
          }}
       ]
    }}
    

    每个子请求都被独立的执行,所以一个子请求的错误并不影响其它请求。如果任何一个请求失败,顶层的error标记将被设置为true,然后错误的细节将在相应的请求中被报告

    1. 演示错误情况
    POST /_bulk
    { "create": { "_index": "website", "_type": "blog", "_id": "123" }}
    { "title":    "Cannot create - it already exists" }
    { "index":  { "_index": "website", "_type": "blog", "_id": "123" }}
    { "title":    "But we can update it" }
    

    响应中我们将看到create文档123失败了,因为文档已经存在,但是后来的在123上执行的index请求成功了:

    {
       "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 //正确状态
          }}
       ]
    }
    
    1. 多大才算太大?
      • 整个批量请求需要被加载到接受我们请求节点的内存里,所以请求越大,给其它请求可用的内存就越小。有一个最佳的bulk请求大小。超过这个大小,性能不再提升而且可能降低
      • 最佳大小,当然并不是一个固定的数字。它完全取决于你的硬件、你文档的大小和复杂度以及索引和搜索的负载
      • 试着批量索引标准的文档,随着大小的增长,当性能开始降低,说明你每个批次的大小太大了。开始的数量可以在1000~5000个文档之间,如果你的文档非常大,可以使用较小的批次。
      • 通常着眼于你请求批次的物理大小是非常有用的。一千个1kB的文档和一千个1MB的文档大不相同。一个好的批次最好保持在5-15MB大小间

    如果,您认为阅读这篇博客让您有些收获,不妨点击一下右下角的推荐按钮。
    如果,您希望更容易地发现我的新博客,不妨点击一下【关注我】。

    我的写作热情也离不开您的肯定支持,感谢您的阅读,我是【老梁】!

  • 相关阅读:
    Good Bye 2014 B. New Year Permutation(floyd )
    hdu 5147 Sequence II (树状数组 求逆序数)
    POJ 1696 Space Ant (极角排序)
    POJ 2398 Toy Storage (叉积判断点和线段的关系)
    hdu 2897 邂逅明下 (简单巴什博弈)
    poj 1410 Intersection (判断线段与矩形相交 判线段相交)
    HDU 3400 Line belt (三分嵌套)
    Codeforces Round #279 (Div. 2) C. Hacking Cypher (大数取余)
    Codeforces Round #179 (Div. 2) B. Yaroslav and Two Strings (容斥原理)
    hdu 1576 A/B (求逆元)
  • 原文地址:https://www.cnblogs.com/sky-chen/p/9953485.html
Copyright © 2011-2022 走看看