目录

1.简介

1.1.文档(document)

文档元数据(document metadata)

 1.2.索引

使用自己的ID

自增ID

1.3.检索文档

1.4.检查文档是否存在

1.5.更新文档

1.6.创建一个新文档

1.7.删除文档

1.8.处理冲突

乐观并发控制(Optimistic concurrency control)

使用外部版本控制系统

1.9.文档局部更新

使用脚本局部更新

更新可能不存在的文档

更新和冲突

1.10.检索多个文档(multi-get/mget  API)

1.11.更新时的批量操作

不要重复

最佳的 bulk  请求大小


1.简介

对象(object)是一种语言相关,记录在内存中的的数据结构

Elasticsearch是一个分布式的文档(document)存储引擎

在Elasticsearch中,每一个字段的数据都是默认被索引的

1.1.文档(document)

特指最顶层结构或者根对象(root object)序列化成的JSON数据(以唯一ID标识并存储于Elasticsearch中)

文档元数据(document metadata)

节点说明
_index文档存储的地方
_type文档代表的对象的类
_id文档的唯一标识

 

 

 

 

 

【详细】

_index

索引(index)类似于关系型数据库里的“数据库”——它是存储和索引关联数据的地方。
提示:
事实上,数据被存储和索引在分片(shards)中,索引只是一个把一个或多个分片分组在一起的逻辑空间。
对于我们的程序而言,文档存储在索引(index)中。
剩下的细节由Elasticsearch处理。

索引名:必须是全部小写,不能以下划线开头,不能包含逗号。


_type

每个对象都属于一个类(class),这个类定义了属性或与对象关联的数据。
在Elasticsearch中,使用相同类型(type)的文档表示相同的“事物”。
每个类型(type)都有自己的映射(mapping)或者结构定义,就像传统数据库表中的列一样。
所有类型下的文档被存储在同一个索引下,类型的映射(mapping)会告诉Elasticsearch不同的文档如何被索引。 

类型名:大写或小写,不能包含下划线或逗号。


_id

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

 1.2.索引

文档通过 index  API被索引——使数据可以被存储和搜索。

文档通过其 _index  、 _type  、 _id  唯一确定。

 

使用自己的ID


PUT /{index}/{type}/{id}

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

自增ID

POST /website/blog/
{
    "title": "My second blog entry",
    "text": "Still trying this out...",
    "date": "2019/01/01"
}

自动生成的ID有22个字符长,URL-safe, Base64-encoded string universally unique identifiers, 或者叫 UUIDs。

 

1.3.检索文档

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": "2019/01/01"
    }
}

【注意】
在任意的查询字符串中增加 pretty  参数,类似于上面的例子。
会让Elasticsearch美化输出(pretty-print)JSON响应以便更加容易阅读。
_source 字段不会被美化,它的样子与我们输入的一致。

【举例】

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

HTTP/1.1 404 Not Found
Content-Type: application/json; charset=UTF-8
Content-Length: 83
{
    "_index" : "website",
    "_type" : "blog",
    "_id" : "124",
    "found" : false
}

 

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

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

 

③  GET /website/blog/123/_source

{
    "title": "My first blog entry",
    "text": "Just trying this out...",
    "date": "2019/01/01"
}

 

1.4.检查文档是否存在

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

【返回存在】

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

【返回不存在】

HTTP/1.1 404 Not Found
Content-Type: text/plain; charset=UTF-8
Content-Length: 0

 

1.5.更新文档

文档在Elasticsearch中是不可变的——我们不能修改他们。

在内部,Elasticsearch已经标记旧文档为删除并添加了一个完整的新文档。旧版本文档不会立即消失,但你也不能去访问它。

Elasticsearch会在你继续索引更多数据时清理被删除的文档。

【过程】

  1.     从旧文档中检索JSON
  2.     修改它
  3.     删除旧文档
  4.     索引新文档

【举例】

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

[返回]

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

 

1.6.创建一个新文档

①  自动生成id

  POST /website/blog/

② 使用 op_type  查询参数

  PUT /website/blog/123?op_type=create

③ 在URL后加 /_create 做为端点

  PUT /website/blog/123/_create

【异常返回】

{
    "error" : "DocumentAlreadyExistsException[[website][4] [blog][123]: document already exists]",
    "status" : 409
}

【成功返回】

{
   ...
    "status" : 201
}

 

1.7.删除文档

删除一个文档不会立即从磁盘上移除,只是被标记成已删除。

Elasticsearch将会在你之后添加更多索引的时候才会在后台进行删除内容的清理。

【举例】

DELETE /website/blog/123

[文档被找到,返回]

200 OK  状态码

{
    "found" : true,
    "_index" : "website",
    "_type" : "blog",
    "_id" : "123",
    "_version" : 3
}

[文档未找到,返回]

404 Not Found  状态码

{
    "found" : false,
    "_index" : "website",
    "_type" : "blog",
    "_id" : "123",
    "_version" : 4
}

1.8.处理冲突

当使用 index  API更新文档的时候,我们读取原始文档,做修改,然后将整个文档(wholedocument)一次性重新索引。

最近的索引请求会生效——Elasticsearch中只存储最后被索引的任何文档。

如果其他人同时也修改了这个文档,他们的修改将会丢失。

 

在数据库中,有两种通用的方法确保在并发更新时修改不丢失:

    悲观并发控制(Pessimistic concurrency control) 未采用

         这在关系型数据库中被广泛的使用,假设冲突的更改经常发生,为了解决冲突我们把访问区块化。

        典型的例子是在读一行数据前锁定这行,然后确保只有加锁的那个线程可以修改这行数据。

    乐观并发控制(Optimistic concurrency control) 被Elasticsearch采用

        假设冲突不经常发生,也不区块化访问,然而,如果在读写过程中数据发生了变化,更新操作将失败。

        这时候由程序决定在失败后如何解决冲突。实际情况中,可以重新尝试更新,刷新数据(重新读取)或者直接反馈给用户。

 

乐观并发控制(Optimistic concurrency control)

当文档被创建、更新或删除,文档的新版本会被复制到集群的其它节点。

Elasticsearch即是同步的又是异步的,意思是这些复制请求都是平行发送的,并无序(out of sequence)的到达目的地。

Elasticsearch使用这个 _version  保证所有修改都被正确排序。当一个旧版本出现在新版本之后,它会被简单的忽略。

利用 _version  的这一优点确保数据不会因为修改冲突而丢失。

可以指定文档的 version  来做想要的更改。如果那个版本号不是现在的,我们的请求就失败了。

【举例】

①②③④⑤⑥⑦⑧⑨⑩

① PUT /website/blog/1/_create
{
    "title": "My first blog entry",
    "text": "Just trying this out..."
}

[返回] _version: 1

 

② GET /website/blog/1

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

 

③ 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
    "created": false
}

[更新失败返回]

重新运行相同的索引请求,依旧指定 version=1 ,更新失败

{
    "error" : "VersionConflictEngineException[[website][2] [blog][1]:version conflict, current [2], provided [1]]",
    "status" : 409
}

使用外部版本控制系统

使用一些其他的数据库做为主数据库,然后使用Elasticsearch搜索数据,所有主数据库发生变化,就要将其拷贝到Elasticsearch中。

版本号必须是整数,大于零小于 9.2e+18  ——Java中的正的 long。

如果请求成功,外部版本号就会被存储到 _version  中。

【举例】

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.9.文档局部更新

文档是不可变的——它们不能被更改,只能被替换

 update  API处理相同的检索-修改-重建索引流程

合并到现有文档中——对象合并在一起,存在的标量字段被覆盖,新字段被添加。

【举例】

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

[请求成功]

{
    "_index" : "website",
    "_id" : "1",
    "_type" : "blog",
    "_version" : 3
}

使用脚本局部更新

使用Groovy脚本
这时候当API不能满足要求时,Elasticsearch允许你使用脚本实现自己的逻辑。
脚本支持非常多的API,例如搜索、排序、聚合和文档更新。
脚本可以通过请求的一部分、检索特殊的 .scripts  索引或者从磁盘加载方式执行。
默认的脚本语言是Groovy,一个快速且功能丰富的脚本语言,语法类似于Javascript。
它在一个沙盒(sandbox)中运行,以防止恶意用户毁坏Elasticsearch或攻击服务器。

【举例】

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

添加参数

POST /website/blog/1/_update
{
    "script" : "ctx._source.tags+=new_tag",
    "params" : {
        "new_tag" : "search"
    }
}

通过设置 ctx.op  为 delete  可以根据内容删除文档

POST /website/blog/1/_update
{
    "script" : "ctx.op = ctx._source.views == count ? 'delete' : 'none' ",
    "params" : {
        "count": 1
    }
}

更新可能不存在的文档

使用 upsert  参数定义文档来使其不存在时被创建

【举例】

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

更新和冲突

① 对于多用户的局部更新,文档被修改了并不要紧。

例如,两个进程都要增加页面浏览量,增加的顺序并不关心——如果冲突发生,我们唯一要做的仅仅是重新尝试更新既可。

可以通过 retry_on_conflict  参数设置重试次数来自动完成,这样 update  操作将会在发生错误前重试——这个值默认为 0。

【举例】顺序不重要

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

顺序重要

使用“保留最后更新(last-write-wins)”的 update  API

 

1.10.检索多个文档(multi-get/mget  API)

mget  API参数是一个 docs  数组,数组的每个节点定义一个文档的 _index  、 _type  、 _id  元数据。

如果你只想检索一个或几个确定的字段,也可以定义一个 _source  参数

【举例】

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
        }
    }]
}

如果检索的文档在同一个 _index  中(甚至在同一个 _type  中),可以在URL中定义一个默认的 /_index  或者 /_index/_type

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

如果所有文档具有相同 _index  和 _type  ,可以通过简单的 ids  数组来代替完整的 docs  数组

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

注意:
尽管前面提到有一个文档没有被找到,但HTTP请求状态码还是 200 。
事实上,就算所有文档都找不到,请求也还是返回 200,原因是 mget 请求本身成功了。
如果想知道每个文档是否都成功了,你需要检查 found 标志。

 

1.11.更新时的批量操作

bulk  API允许使用单一请求来实现多个文档的 create  、 index  、 update  或 delete  

它们可以以成百上千的数据为一个批次按序进行索引

bulk  请求不是原子操作——不能实现事务。每个请求操作时分开的,每个请求的成功与否不干扰其它操作

bulk  请求体

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

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

【解释】

action/metadata 定义了文档行为(what action)发生在哪个文档(which document)之上

行为(action)
行为 解释
create当文档不存在时创建之
index创建新文档或替换已有文档
update局部更新文档
delete删除一个文档

 

 

 

 

 

 

在索引、创建、更新或删除时必须指定文档的 _index  、 _type  、 _id  这些元数据(metadata)

【举例】

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" : "5"}}
{ "doc" : {"title" : "My updated blog post"} }

[请求成功]

{
    "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
        }

    }
]
}}

【注意】

 delete  行为(action)没有请求体,它紧接着另一个行为(action);

记得最后一个换行符

 

不要重复

POST /website/log/_bulk
{ "index": {}}                                         // 没有覆盖时它会使用URL中的值作为默认值
{ "event": "User logged in" }
{ "index": { "_type": "blog" }}
{ "title": "Overriding the default type" }

 

最佳的 bulk  请求大小

一个好的批次最好保持在5-15MB大小间

Logo

开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!

更多推荐