016.Elasticsearch文档管理操作
1.1 创建测试用index
curl -X PUT "node01:9200/nba"
curl -X PUT "node01:9200/nba" -H 'Content-Type:application/json' -d'
{
"mappings": {
"_doc": {
"properties": {
"jerse_no": {
"type": "keyword"
},
"name": {
"type": "text"
},
"play_year": {
"type": "keyword"
},
"position": {
"type": "keyword"
},
"team_name": {
"type": "text"
},
"country": {
"type": "keyword"
}
}
}
}
}
'
1.2 自动创建索引
当向一个不存在的index中添加document时,可以自动创建索引,也可以根据传入的数据自动创建mapping,ES也会自动对这些文档进行倒排索引
# 查看ES集群配置
curl -X GET "node01:9200/_cluster/settings"
{
"persistent":{},
"transient":{}
}
# 设置自动创建index功能
curl -X PUT "node01:9200/_cluster/settings" -H 'Content-Type:application/json' -d'
{
"persistent": {
"action.auto_create_index": "false"
}
}
'
{ "acknowledged":true,
"persistent":{
"action":{
"auto_create_index":"false"
}
},
"transient":{}
}
当把action.auto_create_index设置成false后,就不可以向不存在的index插入数据了
1.2 添加document
# 手动指定id
curl -X PUT "node01:9200/nba/_doc/1" -H 'Content-Type:application/json' -d'
{
"name":"哈登",
"team_name":"火箭",
"position":"得分后卫",
"play_year":"10",
"jerse_no":"13",
"country": "美国"
}
'
# 也可以让ES自动生成id,不指定id要使用POST请求
curl -X POST "node01:9200/nba/_doc" -H 'Content-Type:application/json' -d'
{
"name":"库里",
"team_name":"勇士",
"position":"控球后卫",
"play_year":"10",
"jerse_no":"13",
"country": "美国"
}
'
# 可以手动指定操作类型
# 指定操作类型时,必须手动设置id
# 当不指定操作时,假如原来已经有id=2的document,那么执行以下操作就会把原来的覆盖掉
# 而指定操作为"create",那么当id=2的document已经存在时,就会报错
curl -X POST "node01:9200/nba/_doc/2?op_type=create" -H 'Content-Type:application/json' -d'
{
"name":"姚明",
"team_name":"火箭",
"position":"中锋",
"play_year":"9",
"jerse_no":"11",
"country": "中国"
}
'
自动设置id与手动设置id的比较:
- 手动设置id
一般来说,从其他外部系统导入数据到es时,会采取这种方式,使用外部系统中已有数据的唯一标识,作为document的id,例如数据从MySQL导入到ES中,就可以直接使用MySQL表中自己的id
- 自动生成id
自动生成的id,长度为20个字符,URL安全,base64编码,使用GUID算法可以保证在并行生成id时也不会发生冲突,在直接往ES中写数据的时候,可以使用这种方式
1.3 查看文档
查看单个文档
curl -X GET "node01:9200/nba/_doc/1"
{
"_index":"nba",
"_type":"_doc",
"_id":"1",
"_version":1,
"_seq_no":0,
"_primary_term":1,
"found":true,
"_source": {
"name":"哈登",
"team_name":"火箭",
"position":"得分后卫",
"play_year":"10",
"jerse_no":"13",
"country": "美国"
}
}
文档元数据解析:
- _index:此文档属于哪个index
- _type:此文档属于哪个type
- _id:此文档的id
- _version:此文档的版本号,ES基于此版本进行并发控制
- _source:此文档的数据内容
指定返回结果的字段:
curl -X GET "node01:9200/nba/_doc/1?_source=name,country"
{
"_index":"nba",
"_type":"_doc",
"_id":"1",
"_version":1,
"_seq_no":0,
"_primary_term":1,
"found":true,
"_source": {
"name":"哈登",
"country": "美国"
}
}
查看多个文档
curl -X POST "node01:9200/_mget" -H 'Content-Type:application/json' -d'
{
"docs": [
{
"_index": "nba",
"_type": "_doc",
"_id": "1"
},
{
"_index": "nba",
"_type": "_doc",
"_id": "2"
}
]
}
'
curl -X POST "node01:9200/nba/_mget" -H 'Content-Type:application/json' -d'
{
"docs": [
{
"_type": "_doc",
"_id": "1"
},
{
"_type": "_doc",
"_id": "2"
}
]
}
'
curl -X POST "node01:9200/nba/_doc/_mget" -H 'Content-Type:application/json' -d'
{
"docs": [
{
"_id": "1"
},
{
"_id": "2"
}
]
}
'
curl -X POST "node01:9200/nba/_doc/_mget" -H 'Content-Type:application/json' -d'
{
"ids": ["1", "2"]
}
'
说明:
mget是很重要的,查询的时候,如果一次性要查询多条数据的话,那么一定要用mget,尽可能减少网络开销次数,这样可以大幅提升查询性能
1.4 修改文档
# 方法一:直接覆盖原文档
# 但是要列出所有的field,即使这个field不修改
# 否则新的文档就不包含你没有写的那个field了
# 当然,如果就是要删除这个字段,就不用写了
curl -X PUT "node01:9200/nba/_doc/1" -H 'Content-Type:application/json' -d'
{
"name":"大胡子",
"team_name":"火箭",
"position":"得分后卫",
"play_year":"9",
"jerse_no":"13",
"country": "美国"
}
'
# 方法二:修改指定的字段
curl -X POST "node01:9200/nba/_doc/1/_update" -H 'Content-Type:application/json' -d'
{
"doc": {
"name":"登哥"
}
}
'
# ES7.x
curl -X POST "node01:9200/nba/_update/1" -H 'Content-Type:application/json' -d'
{
"doc": {
"name":"登哥"
}
}
'
# 增加一个字段
curl -X POST "node01:9200/nba/_doc/1/_update" -H 'Content-Type:application/json' -d'
{
"script": "ctx._source.age=18"
}
'
# ES7.x
curl -X POST "node01:9200/nba/_update/1" -H 'Content-Type:application/json' -d'
{
"script": "ctx._source.age=18"
}
'
# 删除一个字段
curl -X POST "node01:9200/nba/_doc/1/_update" -H 'Content-Type:application/json' -d'
{
"script": "ctx._source.remove(\"age\")"
}
'
# ES7.x
curl -X POST "node01:9200/nba/_update/1" -H 'Content-Type:application/json' -d'
{
"script": "ctx._source.remove(\"age\")"
}
'
# 根据参数值,更新字段,要求文档存在并且修改的field存在
curl -X POST "node01:9200/nba/_doc/1/_update" -H 'Content-Type:application/json' -d'
{
"script": "ctx._source.age+=params.age",
"params": {
"age": 4
}
}
'
# 根据参数值,更新字段,如果文档不存在,新创建一个文档,并且将upsert中的数据插入到该文档中
curl -X POST "node01:9200/nba/_doc/3/_update" -H 'Content-Type:application/json' -d'
{
"script": {
"source": "ctx._source.age+=params.age",
"params": {
"age": 4
}
},
"upsert": {
"age": 10
}
}
'
说明:
修改一个文档的某个字段,在ES的底层,其实也是全量替换,将原来的文档标记为delete状态,新插入一条数据,根据客户端传入的字段加上原数据的其他字段组成了一条新的文档,只不过,这些操作都在shard内部去做了,相比于让用户执行全量替换的操作,优化了网络传输开销,减少了查询和修改的时间开销,提升了性能。
这是Elasticsearch的一个重要特性,即数据是不可变的,更新和删除都是插入一条新的数据同时把旧数据标记为"delete"状态,这样就带来了以下好处:
- 不需要锁,提升并发能力,避免锁的问题
- 数据不变,一直保存在os cache中,只要cache内存足够
- 数据可以压缩,节省CPU和IO开销
1.5 删除文档
curl -X DELETE "node01:9200/nba/_doc/3"
1.6 批量增删改查
语法:
POST /_bulk
{"delete": {"_index": "index_name", "_type": "type_name", "_id": 1}}
{"create": {"_index": "index_name", "_type": "type_name", "_id": 4}}
{"doc": {"field": "value"}}
{"create": {"_index": "index_name", "_type": "type_name", "_id": 5}}
{"doc": {"field1": "value1", "field2": "value2"}}
{"update": {"_index": "index_name", "_type": "type_name", "_id": 1, "some_properties": "properties_value"}}
{"doc": {"field": "value"}}
# 例如:
POST /_bulk
{"delete": {"_index": "user", "_type": "_doc", "_id": 5}}
{"create": {"_index": "user", "_type": "_doc", "_id": 6}}
{"doc": {"uid": "1006", "uname": "Nancy"}}
{"index": {"_index": "shop", "_type": "product", "_id": 5}}
{"doc": {"name": "Gaolujie Toothpaste"}}
{"update": {"_index": "user", "_type": "_doc", "_id": 1, "retry_on_conflict": 3}}
{"doc": {"name": "Nancy"}}
bulk api对json的语法有严格的要求,每个json串不能换行,只能放一行,同时一个json串和一个json串之间,必须换行,除"delete"操作外,每个操作要两个json串,语法如下:
{"action": {"metadata"}}
{"data"}
# action包括:
delete:删除一个文档,只要1个json串就可以了
create:PUT /index/type/id/_create,强制创建
index:普通的put操作,可以是创建文档,也可以是全量替换文档
update:更新操作
任意一个操作失败,不会影响其他操作,但是在返回结果里,会告诉你哪个操作失败了及其错误信息
批量操作会将所有请求加载到内存中,一次请求过多的话,性能反而会下降,一般从5000-1W条数据开始测试,以得到一个最佳性能,同时,一般来说,将一次请求的大小在5-15MB之间
为什么批量操作对请求json字符串的要求这么严格呢?
bulk中的每个操作都可能要转发到不同的node的shard去执行,如果采用比较良好的json数组格式,允许任意的换行,整个可读性非常棒,读起来很爽,es拿到那种标准格式的json串以后,要按照下述流程去进行处理:
- 将json数组解析为JSONArray对象,整个数据在内存中出现两份,一份数据是json文本,一份数据是JSONArray对象
- 解析json数组里的每个json,对每个请求中的document进行路由
- 为路由到同一个shard上的多个请求,创建一个请求数组
- 将这个请求数组序列化
- 将序列化后的请求数组发送到对应的节点上去
这样就耗费更多内存,造成更多的jvm gc开销,导致性能下降,而使用这种严格的json格式之后:
- 不用解析json字符串,不用将其转换为json对象,不会出现内存中的相同数据的拷贝,直接按照换行符切割json
- 对每两个一组的json,读取其请求信息,进行document路由
- 直接将json发送到对应的node上去
这样,减少了内存开销,减少了解析成本,提高了性能