个人专题目录
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"} }
总结:
- 功能:
- delete:删除一个文档,只要1个json串就可以了
- create:相当于强制创建 PUT /index/type/id/_create
- index:普通的put操作,可以是创建文档,也可以是全量替换文档
- update:执行的是局部更新partial update操作
格式:每个json不能换行。相邻json必须换行。
隔离:每个操作互不影响。操作失败的行会返回其失败信息。
实际用法:bulk请求一次不要太大,否则一下积压到内存中,性能会下降。所以,一次请求几千个操作、大小在几M正好。
bulk size最佳大小
bulk request会加载到内存里,如果太大的话,性能反而会下降,因此需要反复尝试一个最佳的bulk size。一般从10005000条数据开始,尝试逐渐增加。另外,如果看大小的话,最好是在515MB之间。