美文网首页
ELASSTICSEARCH DATA

ELASSTICSEARCH DATA

作者: 转身一世铅华尽 | 来源:发表于2020-06-30 11:30 被阅读0次

    Elasticsearch数据的存储格式

    Elastcisearch 是分布式的 文档 存储。它能存储和检索复杂的数据结构—​序列化成为JSON文档—​以 实时 的方式。 换句话说,一旦一个文档被存储在 Elasticsearch 中,它就是可以被集群中的任意节点检索到
    在 Elasticsearch 中, 每个字段的所有数据 都是 默认被索引的 。 即每个字段都有为了快速检索设置的专用倒排索引。而且,不像其他多数的数据库,它能在 同一个查询中 使用所有这些倒排索引,并以惊人的速度返回结果

    Elasticsearch 文档

    在 Elasticsearch 中,术语 文档 有着特定的含义。它是指最顶层或者根对象, 这个根对象被序列化成 JSON 并存储到 Elasticsearch 中,指定了唯一 ID
    这个JSON中的字段的名字可以是任何合法的字符串,但 不可以 包含英文句号(.),ES中的每一个文档就是一个JSON对象,其中包含了我们定义的数据字段,其中可以每个字段可以存储任意类型的值。

    元数据

    一个文档不光是包括文档本身的数据,同时也包含了元数据(元数据就是有关文档本身的信息)
    三个元数据必须的元素如下:

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

    _index

    这个表示文档存储对应的索引,一个索引对应多个分片,文档则存储在索引对应的分片中,通过分片计算公式可以计算出文档应该存入那个分片中,公式为:

    shard = hash(routing) % number_of_primary_shards
    //就是索引的ID取hash然后对主分片数量取余
    

    实际上,在 Elasticsearch 中,我们的数据是被存储和索引在 分片 中,而一个索引仅仅是逻辑上的命名空间, 这个命名空间由一个或者多个分片组合在一起。 然而,这是一个内部细节,我们的应用程序根本不应该关心分片,对于应用程序而言,只需知道文档位于一个 索引 内。 Elasticsearch 会处理所有的细节

    _type

    数据可能在索引中只是松散的组合在一起,但是通常明确定义一些数据中的子分区是很有用的。 例如,所有的产品都放在一个索引中,但是你有许多不同的产品类别,比如 "electronics" 、 "kitchen" 和 "lawn-care"。
    这些文档共享一种相同的(或非常相似)的模式:他们有一个标题、描述、产品代码和价格。他们只是正好属于“产品”下的一些子类。
    Elasticsearch 公开了一个称为 types (类型)的特性,它允许您在索引中对数据进行逻辑分区。不同 types 的文档可能有不同的字段,但最好能够非常相似。 详情参考 类型和映射 中更多的讨论关于 types 的一些应用和限制。
    一个 _type 命名可以是大写或者小写,但是不能以下划线或者句号开头,不应该包含逗号, 并且长度限制为256个字符.

    _id

    ID 是一个字符串,当它和 _index 以及 _type 组合就可以唯一确定 Elasticsearch 中的一个文档。 当你创建一个新的文档,要么提供自己的 _id ,要么让 Elasticsearch 帮你生成,如果你是通过ELK将数据存入ES时,在logstash中指定document_id字段为你文档数据中的ID字段就可以自行指定ID了

    文档索引

    通过使用 index API ,文档可以被 索引 —— 存储和使文档可被搜索。 但是首先,我们要确定文档的位置。正如我们刚刚讨论的,一个文档的 _index 、 _type 和 _id 唯一标识一个文档。 我们可以提供自定义的 _id 值,或者让 index API 自动生成
    我们可以直接通过命令:

    PUT /{INDEX}/{TYPE}/{ID}
    {
      //SOURCE
    }
    

    指定索引名、类型、id插入一个文档
    插入成功后的响应是如下的格式

    {
    "_index": {INDEX}
    "_type": {TYPE}
    "_id": {ID}
    "_version": 1
    "created" : true
    }
    

    其中有一个_version,这也是元数据中的一个元素,标识着这个文档的版本,每次对这个文档做修改时,包括删除,都会递增,这样可以标识文档的修改,保证文档的修改部分不会影响未修改部分
    如果你未指定ID,则ES会自行随机生成一个ID,生成规则如下:
    自动生成的 ID 是 URL-safe、 基于 Base64 编码且长度为20个字符的 GUID 字符串。 这些 GUID 字符串由可修改的 FlakeID 模式生成,这种模式允许多个节点并行生成唯一 ID ,且互相之间的冲突概率几乎为零。

    ES文档的查询(数据的查询)

    我们可以通过一下语句进行数据的查询

    GET /{INDEX}/{TYPE}/{ID}?pretty
    

    的到的响应如下:

    {
    "_index": {INDEX}
    "_type": {TYPE}
    "_id": {ID}
    "_version": 1
    "_source":{//元数据字段
                    //我们存入ES文档时的原数据
                    }
    }
    

    在请求的查询串参数中加上 pretty 参数,正如前面的例子中看到的,这将会调用 Elasticsearch 的 pretty-print 功能,该功能 使得 JSON 响应体更加可读。但是, _source 字段不能被格式化打印出来。相反,我们得到的 _source 字段中的 JSON 串,刚好是和我们传给它的一样
    GET 请求的响应体包括 {"found": true} ,这证实了文档已经被找到。 如果我们请求一个不存在的文档,我们仍旧会得到一个 JSON 响应体,但是 found 将会是 false 。 此外, HTTP 响应码将会是 404 Not Found ,而不是 200 OK
    我们可以通过传递 -i 参数给 curl 命令,该参数能够显示响应的头部

    curl -i XGET http://ip:port/{INDEX}/{TYPE}/{ID}?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 请求会返回整个文档,这个文档正如存储在 _source 字段中的一样。但是也许你只对其中的 title 字段感兴趣。单个字段能用 _source 参数请求得到,多个字段也能使用逗号分隔的列表来指定

    GET /{INDEX}/{TYPE}/{ID}?_source=title,text
    

    这样返回的文档只会返回你指定的字段,如tittle和text

    检查文档是否存在

    如果只想检查一个文档是否存在--根本不想关心内容—​那么用 HEAD 方法来代替 GET 方法。 HEAD 请求没有返回体,只返回一个 HTTP 请求报头

    curl -i -XHEAD http://localhost:9200/{INDEX}/{TYPE}/{ID}
    

    如果ES中存在该文档则返回

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

    如果ES中不存在该文档则返回

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

    文档的更新操作

    ES的文档一旦建立则不可更改,虽然ES有uopdate这种API,看似是对ES中的文档进行了更新操作,但是实际上ES中进行了先对旧文档的JSON数据提取出来,然后进行修改,然后将旧文档删除,在相同的索引相同的位置存入一个新文档
    唯一的区别在于, update API 仅仅通过一个客户端请求来实现这些步骤,而不需要单独的 get 和 POST 请求对index进行操作

    创建一个新文档

    ES中的文档的唯一标识是由几个元数据的元素组成的,由_index,_type,_id等三个元素标识了一个文档的唯一性,如果我们需要指定id插入一个文档的话,如果是通过API自动插入的话,API会先检索一下ES中是否存在这个id对应的文档,如果存在则进行覆盖,如果不存在则插入新的,但是如果通过命令进行插入的话,我们可以通过以下命令进行校验

    PUT /{INDEX}/{TYPE}/{ID}?op_type=create
    { ... }
    //或者
    
    
    PUT /{INDEX}/{TYPE}/{ID}/_create
    { ... }
    

    通过这样的方式进行插入会对ID进行index,type和id组成的唯一标识进行校验,如果存在的话,则会返回一个409的报错信息,如下所示:

    {
       "error": {
          "root_cause": [
             {
                "type": "document_already_exists_exception",
                "reason": "[{TYPE}][{ID}]: document already exists",
                "shard": "0",
                "index": "website"
             }
          ],
          "type": "document_already_exists_exception",
          "reason": "[{TYPE}][{ID}]: document already exists",
          "shard": "0",
          "index": "website"
       },
       "status": 409
    }
    

    如果ES中不存在这个文档,则会进行向其中插入新文档,返回201,也就是成功的状态码和http响应

    文档删除

    删除文档的语法和我们所知道的规则相同,只是使用 DELETE 方法:

    DELETE /{INDEX}/{TYPE}/{ID}
    

    如果ES中存在改文档,则会给该文档打上一个删除标记,然后返回成功的状态码,如果不存在该文档,则会返回一个404,结果如下:

    {//200
      "found" :    true,
      "_index" :   "{INDEX}",
      "_type" :    "{TYPE}",
      "_id" :      "{ID}",
      "_version" : 3
    }
    //404
    {
      "found" :    false,
      "_index" :   "website",
      "_type" :    "blog",
      "_id" :      "123",
      "_version" : 4
    }
    

    无论该文档在ES中是否存在,都会对version进行一个自增操作。
    而已经打上删除标记的删除文档不会立即将文档从磁盘中删除,只是将文档标记为已删除状态。随着你不断的索引更多的数据,Elasticsearch 将会在后台清理标记为已删除的文档

    ES处理冲突

    当我们对数据进行操作时,如果出现了同时两个人对数据进行操作的话,那么只有最近的一次操作会起作用,而对于其他的操作则会被覆盖,这样就出现了数据处理冲突,我们可以通过并发控制来控制这些操作,处理冲突

    悲观控制并发

    这种方法广泛运用于关系型数据库中,这种方法控制的前提是假定所有资源都有可能被并发访问,都有可能会发生冲突,所以在,访问之前就给他锁住,阻塞其余访问这个资源的线程以防止冲突,确保只有加锁的对象能够对资源进行访问(synchronized就是一个典型的悲观锁)

    乐观控制并发

    ES中假定冲突是不会发生的并不会阻塞正在尝试的操作,但是如果源数据在读写中被修改,则会更新失败。之后会尝试去解决这个操作失败的问题,也就是解决冲突。 例如,可以重试更新、使用新的数据、或者将相关情况报告给用户。
    Elasticsearch 是分布式的。当文档创建、更新或删除时, 新版本的文档必须复制到集群中的其他节点。Elasticsearch 也是异步和并发的,这意味着这些复制请求被并行发送,并且到达目的地时也许 顺序是乱的 。 Elasticsearch 需要一种方法确保文档的旧版本不会覆盖新的版本。
    当我们之前讨论 index , GET 和 delete 请求时,我们指出每个文档都有一个 _version (版本)号,当文档被修改时版本号递增。 Elasticsearch 使用这个 _version 号来确保变更以正确顺序得到执行。如果旧版本的文档在新版本之后到达,它可以被简单的忽略。
    我们可以利用 _version 号来确保 应用中相互冲突的变更不会导致数据丢失。我们通过指定想要修改文档的 version 号来达到这个目的。 如果该版本不是当前版本号,我们的请求将会失败。

    更新和冲突

    在本节的介绍中,我们说明 检索 和 重建索引 步骤的间隔越小,变更冲突的机会越小。 但是它并不能完全消除冲突的可能性。 还是有可能在 update 设法重新索引之前,来自另一进程的请求修改了文档。
    为了避免数据丢失, update API 在 检索 步骤时检索得到文档当前的 _version 号,并传递版本号到 重建索引 步骤的 index 请求。 如果另一个进程修改了处于检索和重新索引步骤之间的文档,那么 _version 号将不匹配,更新请求将会失败。
    对于部分更新的很多使用场景,文档已经被改变也没有关系。 例如,如果两个进程都对页面访问量计数器进行递增操作,它们发生的先后顺序其实不太重要; 如果冲突发生了,我们唯一需要做的就是尝试再次更新。
    这可以通过设置参数 retry_on_conflict 来自动完成, 这个参数规定了失败之前 update 应该重试的次数,它的默认值为 0 。
    在增量操作无关顺序的场景,例如递增计数器等这个方法十分有效,但是在其他情况下变更的顺序 非常重要的。 类似 index APIupdate API 默认采用 最终写入生效 的方案,但它也接受一个 version 参数来允许你使用 optimistic concurrency control 指定想要更新文档的版本。

    取回多个文档

    Elasticsearch 的速度已经很快了,但甚至能更快。 将多个请求合并成一个,避免单独处理每个请求花费的网络延时和开销。 如果你需要从 Elasticsearch 检索很多文档,那么使用 multi-get 或者 mget API 来将这些检索请求放在一个请求中,将比逐个文档请求更快地检索到全部文档。
    例如:

    GET /_mget
    {
       "docs" : [
          {
             "_index" : "index",
             "_type" :  "type",
             "_id" :    id
          },
          {
               "_index" : "index",
             "_type" :  "type",
             "_id" :    id
             "_source": "字段名"
          }
       ]
    }
    

    可以指定字段名进行查询

    代价比较小的批量操作

    和mget一样,我们同样有一次性提交大量操作的API(bulk),bulk API 允许在单个步骤中进行多次 create 、 index 、 update 或 delete 请求。 如果你需要索引一个数据流比如日志事件,它可以排队和索引数百或数千批次。
    bulk 与其他的请求体格式稍有不同,如下所示

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

    这种格式类似一个有效的单行 JSON 文档 流 ,它通过换行符(\n)连接到一起。注意两个要点:
    每行一定要以换行符(\n)结尾, 包括最后一行 。这些换行符被用作一个标记,可以有效分隔行。
    这些行不能包含未转义的换行符,因为他们将会对解析造成干扰。这意味着这个 JSON 不 能使用 pretty 参数打印。
    ction/metadata 行指定 哪一个文档什么操作
    action 必须是以下选项之一:
    create: 如果文档不存在,那么就创建它。详情请见 创建新文档
    index: 创建一个新文档或者替换一个现有的文档。详情请见 索引文档更新整个文档
    update: 部分更新一个文档。详情请见 文档的部分更新
    delete: 删除一个文档。详情请见 删除文档
    metadata 应该指定被索引、创建、更新或者删除的文档的 _index_type_id

    request body 行由文档的 _source 本身组成—​文档包含的字段和值。它是 index 和 create 操作所必需的,这是有道理的:你必须提供文档以索引。
    它也是 update 操作所必需的,并且应该包含你传递给 update API 的相同请求体: doc 、 upsert 、 script 等等。 删除操作不需要 request body 行。
    如下列操作:

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

    上述操作,1号操作,delete操作后面不需要跟详细的操作语句,因为delete本身就是直接进行文档删除的。
    2号操作执行的是create操作,后续接的title语句就是操作建立的详细字段,如果这个这个指定条件的文档已经存在了,则会返回 一个文档已经存在的错误信息
    3号操作是建立或者覆盖一个文档,如果没有直接指定ID的话,那么就是新建一个文档,会自动随机生成一个新的ID
    4号操作执行的是更新操作,后续接的doc就是操作的字段。
    bulk操作d的每个子请求都是独立执行,因此某个子请求的失败不会对其他子请求的成功与否造成影响。 如果其中任何子请求失败,最顶层的 error 标志被设置为 true ,并且在相应的请求报告出错误明细
    这也意味着 bulk 请求不是原子的: 不能用它来实现事务控制。每个请求是单独处理的,因此一个请求的成功或失败不会影响其他的请求
    如果你是使用bulk对相同的索引(_index)和类型(_type)下的文档进行操作的话,则不需要重复指定这两个元数据,只需要指定ID就行了。

    批量请求的大小

    整个批量请求都需要由接收到请求的节点加载到内存中,因此该请求越大,其他请求所能获得的内存就越少。 批量请求的大小有一个最佳值,大于这个值,性能将不再提升,甚至会下降。 但是最佳值不是一个固定的值。它完全取决于硬件、文档的大小和复杂度、索引和搜索的负载的整体情况。
    幸运的是,很容易找到这个 最佳点 :通过批量索引典型文档,并不断增加批量大小进行尝试。 当性能开始下降,那么你的批量大小就太大了。一个好的办法是开始时将 1,000 到 5,000 个文档作为一个批次, 如果你的文档非常大,那么就减少批量的文档个数。
    密切关注你的批量请求的物理大小往往非常有用,一千个 1KB 的文档是完全不同于一千个 1MB 文档所占的物理大小。 一个好的批量大小在开始处理后所占用的物理大小约为 5-15 MB。

    相关文章

      网友评论

          本文标题:ELASSTICSEARCH DATA

          本文链接:https://www.haomeiwen.com/subject/jyklxktx.html