elasticsearch之五document详解

个人专题目录


1. 文档document详解

1.1 document默认自带字段解析

{
  "_index" : "book",
  "_type" : "_doc",
  "_id" : "1",
  "_version" : 1,
  "_seq_no" : 10,
  "_primary_term" : 1,
  "found" : true,
  "_source" : {
    "name" : "Bootstrap开发教程1",
    "description" : "Bootstrap是由Twitter推出的一个前台页面开发css框架,是一个非常流行的开发框架,此框架集成了多种页面效果。此开发框架包含了大量的CSS、JS程序代码,可以帮助开发者(尤其是不擅长css页面开发的程序人员)轻松的实现一个css,不受浏览器限制的精美界面css效果。",
    "studymodel" : "201002",
    "price" : 38.6,
    "timestamp" : "2019-08-25 19:11:35",
    "pic" : "group1/M00/00/00/wKhlQFs6RCeAY0pHAAJx5ZjNDEM428.jpg",
    "tags" : [
      "bootstrap",
      "开发"
    ]
  }
}

_index

  • 含义:此文档属于哪个索引
  • 原则:类似数据放在一个索引中。数据库中表的定义规则。如图书信息放在book索引中,员工信息放在employee索引中。各个索引存储和搜索时互不影响。
  • 定义规则:英文小写。尽量不要使用特殊字符。order user

_type

  • 含义:类别。book java node
  • 注意:以后的es9将彻底删除此字段,所以当前版本在不断弱化type。不需要关注。见到_type都为doc。

_id

含义:文档的唯一标识。就像表的id主键。结合索引可以标识和定义一个文档。

生成:手动(put /index/_doc/id)、自动

创建索引时,不同数据放到不同索引中

生成文档id的方式

手动生成id

场景:数据从其他系统导入时,本身有唯一主键。如数据库中的图书、员工信息等。使用这种方式,需要考虑是否满足手动指定id的条件。如果数据是从其他数据源中读取并新增到ElasticSearch中的时候,使用手动指定id。如:数据是从Database中读取并新增到ElasticSearch中的,那么使用Database中的PK作为ElasticSearch中的id比较合适。建议,不要把不同表的数据新增到同一个index中,可能有id冲突。

用法:put /index/_doc/id

PUT /test_index/_doc/1
{
  "test_field": "test"
}

自动生成id

自动生成的ID特点:长度为20的字符串;URL安全(经过base64编码的);GUID生成策略,支持分布式高并发(在分布式系统中,并发生成ID也不会有重复可能,参考https://baike.baidu.com/item/GUID/3352285?fr=aladdin)。适合用于手工录入的数据。数据没有一个数据源,且未经过任何的管理和存储。这种数据,是没有唯一标识,如果使用手工指定id的方式,容易出现id冲突,导致数据丢失。

用法:POST /index/_doc

POST /test_index/_doc
{
  "test_field": "test1"
}

返回:

{
  "_index" : "test_index",
  "_type" : "_doc",
  "_id" : "x29LOm0BPsY0gSJFYZAl",
  "_version" : 1,
  "result" : "created",
  "_shards" : {
    "total" : 2,
    "successful" : 1,
    "failed" : 0
  },
  "_seq_no" : 0,
  "_primary_term" : 1
}

自动id特点:

长度为20个字符,URL安全,base64编码,GUID,分布式生成不冲突

_source 字段

含义:就是查询的document中的field值。也就是document的json字符串。此元数据可以定义显示结果(field)。

GET /book/_doc/1

定制返回字段

就像sql不要select *,而要select name,price from book …一样。

GET /book/_doc/1?__source_includes=name,price

{
  "_index" : "book",
  "_type" : "_doc",
  "_id" : "1",
  "_version" : 1,
  "_seq_no" : 10,
  "_primary_term" : 1,
  "found" : true,
  "_source" : {
    "price" : 38.6,
    "name" : "Bootstrap开发教程1"
  }
}

_version元数据

代表的是document的版本。在ElasticSearch中,为document定义了版本信息,document数据每次变化,代表一次版本的变更。版本变更可以避免数据并发冲突,同时提高ElasticSearch的搜索效率。

第一次创建Document时,_version版本号为1,默认情况下,后续每次对Document执行修改或删除操作都会对_version数据自增1。

删除Document也会_version自增1。

当使用PUT命令再次增加同id的Document,_version会继续之前的版本继续自增。

1.2 使用脚本更新

es可以内置脚本执行复杂操作。例如painless脚本。

注意:groovy脚本在es6以后就不支持了。原因是耗内存,不安全远程注入漏洞。

内置脚本

需求1:修改文档6的num字段,+1。

插入数据

PUT /test_index/_doc/6
{
  "num": 0,
  "tags": []
}

执行脚本操作

POST /test_index/_doc/6/_update
{
   "script" : "ctx._source.num+=1"
}

查询数据

GET /test_index/_doc/6

返回

{
  "_index" : "test_index",
  "_type" : "_doc",
  "_id" : "6",
  "_version" : 2,
  "_seq_no" : 23,
  "_primary_term" : 1,
  "found" : true,
  "_source" : {
    "num" : 1,
    "tags" : [ ]
  }
}

需求2:搜索所有文档,将num字段乘以2输出

插入数据

PUT /test_index/_doc/7
{
  "num": 5
}

查询

GET /test_index/_search
{
  "script_fields": {
    "my_doubled_field": {
      "script": {
       "lang": "expression",
        "source": "doc['num'] * multiplier",
        "params": {
          "multiplier": 2
        }
      }
    }
  }
}

返回

{
        "_index" : "test_index",
        "_type" : "_doc",
        "_id" : "7",
        "_score" : 1.0,
        "fields" : {
          "my_doubled_field" : [
            10.0
          ]
        }
      }

外部脚本

Painless是内置支持的。脚本内容可以通过多种途径传给 es,包括 rest 接口,或者放到 config/scripts目录等,默认开启。

注意:脚本性能低下,且容易发生注入。

官方文档:https://www.elastic.co/guide/en/elasticsearch/reference/current/modules-scripting-using.html

1.3 es内部基于_version乐观锁控制

如同秒杀,多线程情况下,es同样会出现并发冲突问题。

为控制并发问题,我们通常采用锁机制。分为悲观锁和乐观锁两种机制。

悲观锁:很悲观,所有情况都上锁。此时只有一个线程可以操作数据。具体例子为数据库中的行级锁、表级锁、读锁、写锁等。

特点:优点是方便,直接加锁,对程序透明。缺点是效率低。

乐观锁:很乐观,对数据本身不加锁。提交数据时,通过一种机制验证是否存在冲突,如es中通过版本号验证。

特点:优点是并发能力高。缺点是操作繁琐,在提交数据时,可能反复重试多次。

基于_version的版本控制

es对于文档的增删改都是基于版本号。

1新增多次文档:

PUT /test_index/_doc/3
{
  "test_field": "test"
}

返回版本号递增

2删除此文档

DELETE /test_index/_doc/3

返回

DELETE /test_index/_doc/3
{
  "_index" : "test_index",
  "_type" : "_doc",
  "_id" : "2",
  "_version" : 6,
  "result" : "deleted",
  "_shards" : {
    "total" : 2,
    "successful" : 1,
    "failed" : 0
  },
  "_seq_no" : 7,
  "_primary_term" : 1
}

3再新增

PUT /test_index/_doc/3
{
  "test_field": "test"
}

可以看到版本号依然递增,验证延迟删除策略。

如果删除一条数据立马删除的话,所有分片和副本都要立马删除,对es集群压力太大。

es内部并发控制

es内部主从同步时,是多线程异步。乐观锁机制。

1.4 客户端程序基于_version并发操作流程

java python客户端更新的机制。

新建文档

PUT /test_index/_doc/5
{
  "test_field": "itcast"
}

返回:

{
  "_index" : "test_index",
  "_type" : "_doc",
  "_id" : "3",
  "_version" : 1,
  "result" : "created",
  "_shards" : {
    "total" : 2,
    "successful" : 1,
    "failed" : 0
  },
  "_seq_no" : 8,
  "_primary_term" : 1
}

客户端1修改。带版本号1。

首先获取数据的当前版本号

GET /test_index/_doc/5

更新文档

PUT /test_index/_doc/5?version=1
{
  "test_field": "itcast1"
}
PUT /test_index/_doc/5?if_seq_no=21&if_primary_term=1
{
  "test_field": "itcast1"
}

客户端2并发修改。带版本号1。

PUT /test_index/_doc/5?version=1
{
  "test_field": "itcast2"
}
PUT /test_index/_doc/5?if_seq_no=21&if_primary_term=1
{
  "test_field": "itcast1"
}

报错。

客户端2重新查询。得到最新版本为2。seq_no=22

GET /test_index/_doc/4

客户端2并发修改。带版本号2。

PUT /test_index/_doc/4?version=2
{
  "test_field": "itcast2"
}
es7
PUT /test_index/_doc/5?if_seq_no=22&if_primary_term=1
{
  "test_field": "itcast2"
}

修改成功。

1.5 手动控制版本号 external version

背景:已有数据是在数据库中,有自己手动维护的版本号的情况下,可以使用external version控制。hbase。

要求:修改时external version要大于当前文档的_version

对比:基于_version时,修改的文档version等于当前文档的版本号。

使用?version=1&version_type=external

新建文档

PUT /test_index/_doc/4
{
  "test_field": "itcast"
}

更新文档:

客户端1修改文档

PUT /test_index/_doc/4?version=2&version_type=external
{
  "test_field": "itcast1"
}

客户端2同时修改

PUT /test_index/_doc/4?version=2&version_type=external
{
  "test_field": "itcast2"
}

返回:

{
  "error": {
    "root_cause": [
      {
        "type": "version_conflict_engine_exception",
        "reason": "[4]: version conflict, current version [2] is higher or equal to the one provided [2]",
        "index_uuid": "-rqYZ2EcSPqL6pu8Gi35jw",
        "shard": "1",
        "index": "test_index"
      }
    ],
    "type": "version_conflict_engine_exception",
    "reason": "[4]: version conflict, current version [2] is higher or equal to the one provided [2]",
    "index_uuid": "-rqYZ2EcSPqL6pu8Gi35jw",
    "shard": "1",
    "index": "test_index"
  },
  "status": 409
}

客户端2重新查询数据

GET /test_index/_doc/4

客户端2重新修改数据

PUT /test_index/_doc/4?version=3&version_type=external
{
  "test_field": "itcast2"
}

1.6 更新时 retry_on_conflict 参数

retry_on_conflict

指定重试次数

POST /test_index/_doc/5/_update?retry_on_conflict=3
{
  "doc": {
    "test_field": "itcast1"
  }
}

与 _version结合使用

POST /test_index/_doc/5/_update?retry_on_conflict=3&version=22&version_type=external
{
  "doc": {
    "test_field": "itcast1"
  }
}

1.7 批量查询 mget

单条查询 GET /test_index/_doc/1,如果查询多个id的文档一条一条查询,网络开销太大。

mget 批量查询:

GET /_mget
{
   "docs" : [
      {
         "_index" : "test_index",
         "_type" :  "_doc",
         "_id" :    1
      },
      {
         "_index" : "test_index",
         "_type" :  "_doc",
         "_id" :    7
      }
   ]
}

返回:

{
  "docs" : [
    {
      "_index" : "test_index",
      "_type" : "_doc",
      "_id" : "2",
      "_version" : 6,
      "_seq_no" : 12,
      "_primary_term" : 1,
      "found" : true,
      "_source" : {
        "test_field" : "test12333123321321"
      }
    },
    {
      "_index" : "test_index",
      "_type" : "_doc",
      "_id" : "3",
      "_version" : 6,
      "_seq_no" : 18,
      "_primary_term" : 1,
      "found" : true,
      "_source" : {
        "test_field" : "test3213"
      }
    }
  ]
}

提示去掉type

GET /_mget
{
   "docs" : [
      {
         "_index" : "test_index",
         "_id" :    2
      },
      {
         "_index" : "test_index",
         "_id" :    3
      }
   ]
}

同一索引下批量查询:

GET /test_index/_mget
{
   "docs" : [
      {
         "_id" :    2
      },
      {
         "_id" :    3
      }
   ]
}

第三种写法:搜索写法

post /test_index/_doc/_search
{
    "query": {
        "ids" : {
            "values" : ["1", "7"]
        }
    }
}

1.8 批量增删改 bulk

Bulk 操作解释将文档的增删改查一些列操作,通过一次请求全都做完。减少网络传输次数。

语法:

POST /_bulk
{"action": {"metadata"}}
{"data"}

如下操作,删除5,新增14,修改2。

POST /_bulk
{ "delete": { "_index": "test_index",  "_id": "5" }} 
{ "create": { "_index": "test_index",  "_id": "14" }}
{ "test_field": "test14" }
{ "update": { "_index": "test_index",  "_id": "2"} }
{ "doc" : {"test_field" : "bulk test"} }

总结:

  1. 功能:
  • delete:删除一个文档,只要1个json串就可以了
  • create:相当于强制创建 PUT /index/type/id/_create
  • index:普通的put操作,可以是创建文档,也可以是全量替换文档
  • update:执行的是局部更新partial update操作
  1. 格式:每个json不能换行。相邻json必须换行。

  2. 隔离:每个操作互不影响。操作失败的行会返回其失败信息。

  3. 实际用法:bulk请求一次不要太大,否则一下积压到内存中,性能会下降。所以,一次请求几千个操作、大小在几M正好。

  4. bulk size最佳大小

bulk request会加载到内存里,如果太大的话,性能反而会下降,因此需要反复尝试一个最佳的bulk size。一般从10005000条数据开始,尝试逐渐增加。另外,如果看大小的话,最好是在515MB之间。

你可能感兴趣的:(elasticsearch之五document详解)