Elasticsearch由浅入深(六)批量操作:mget批量查询、bulk批量增删改、路由原理、增删改内部原理、document查询内部原理、bulk api的奇特json格式
mget批量查询
- 批量查询的好处
就是一条一条的查询,比如说要查询100条数据,那么就要发送100次网络请求,这个开销还是很大的
如果进行批量查询的话,查询100条数据,就只要发送1次网络请求,网络请求的性能开销缩减100倍 - mget批量查询的语法
GET _mget
{
"docs":[
{
"_index":"test_index",
"_type":"test_type",
"_id":
},
{
"_index":"test_index",
"_type":"test_type",
"_id":
}
]
}{
"took": ,
"timed_out": false,
"_shards": {
"total": ,
"successful": ,
"failed":
},
"hits": {
"total": ,
"max_score": ,
"hits": [
{
"_index": "test_index",
"_type": "test_type",
"_id": "AWypxxLYFCl_S-ox4wvd",
"_score": ,
"_source": {
"test_content": "my test"
}
},
{
"_index": "test_index",
"_type": "test_type",
"_id": "",
"_score": ,
"_source": {
"test_field": "test client 2"
}
},
{
"_index": "test_index",
"_type": "test_type",
"_id": "",
"_score": ,
"_source": {
"test_field1": "test1",
"test_field2": "updated test2"
}
},
{
"_index": "test_index",
"_type": "test_type",
"_id": "",
"_score": ,
"_source": {
"test_content": "my test"
}
},
{
"_index": "test_index",
"_type": "test_type",
"_id": "",
"_score": ,
"_source": {
"test_field": "test test"
}
},
{
"_index": "test_index",
"_type": "test_type",
"_id": "",
"_score": ,
"_source": {
"test_field": "test test"
}
},
{
"_index": "test_index",
"_type": "test_type",
"_id": "",
"_score": ,
"_source": {
"test_field1": "test field1",
"test_field2": "test field2"
}
},
{
"_index": "test_index",
"_type": "test_type",
"_id": "",
"_score": ,
"_source": {
"test_field": "test client 1"
}
},
{
"_index": "test_index",
"_type": "test_type",
"_id": "",
"_score": ,
"_source": {
"num": ,
"tags": []
}
}
]
}
} 如果查询的document是一个index下的不同type的话
GET /test_index/_mget
{
"docs" : [
{
"_type" : "test_type",
"_id" :
},
{
"_type" : "test_type",
"_id" :
}
]
}- 如果查询的数据都在同一个index下的同一个type下,最简单了
GET /test_index/test_type/_mget
{
"ids": [, ]
}可以说mget是很重要的,一般来说,在进行查询的时候,如果一次性要查询多条数据的话,那么一定要用batch批量操作的api
尽可能减少网络开销次数,可能可以将性能提升数倍,甚至数十倍,非常非常之重要
bulk批量增删改
bulk语法
POST /_bulk
{ "delete": { "_index": "test_index", "_type": "test_type", "_id": "" }}
{ "create": { "_index": "test_index", "_type": "test_type", "_id": "" }}
{ "test_field": "test12" }
{ "index": { "_index": "test_index", "_type": "test_type", "_id": "" }}
{ "test_field": "replaced test2" }
{ "update": { "_index": "test_index", "_type": "test_type", "_id": "", "_retry_on_conflict" : } }
{ "doc" : {"test_field2" : "bulk test1"} }
有哪些类型的操作可以执行呢?
- delete:删除一个文档,只要1个json串就可以了
- create:PUT /index/type/id/_create,强制创建
- index:普通的put操作,可以是创建文档,也可以是全量替换文档
- update:执行的partial update操作
bulk api对json的语法,有严格的要求,每个json串不能换行,只能放一行,同时一个json串和一个json串之间,必须有一个换行
示例:
POST _bulk
{"delete":{"_index":"test_index","_type":"test_type","_id":}}
{"create":{"_index":"test_index","_type":"test_type","_id":}}
{"test_field":"test3"}
{"create":{"_index":"test_index","_type":"test_type","_id":}}
{"test_field":"test2"}
{"index":{"_index":"test_index","_type":"test_type","_id":}}
{"test_field":"test4"}
{"index":{"_index":"test_index","_type":"test_type","_id":}}
{"test_field":"replaces test2"}
{"update":{"_index":"test_index","_type":"test_type","_id":}}
{"doc":{"test_field2":"partial updated test1"}}
{
"took": ,
"errors": true,
"items": [
{
"delete": {
"found": true,
"_index": "test_index",
"_type": "test_type",
"_id": "",
"_version": ,
"result": "deleted",
"_shards": {
"total": ,
"successful": ,
"failed":
},
"status":
}
},
{
"create": {
"_index": "test_index",
"_type": "test_type",
"_id": "",
"_version": ,
"result": "created",
"_shards": {
"total": ,
"successful": ,
"failed":
},
"created": true,
"status":
}
},
{
"create": {
"_index": "test_index",
"_type": "test_type",
"_id": "",
"status": ,
"error": {
"type": "version_conflict_engine_exception",
"reason": "[test_type][2]: version conflict, document already exists (current version [17])",
"index_uuid": "d7GOSxVnTNKYuI8x7cZfkA",
"shard": "",
"index": "test_index"
}
}
},
{
"index": {
"_index": "test_index",
"_type": "test_type",
"_id": "",
"_version": ,
"result": "updated",
"_shards": {
"total": ,
"successful": ,
"failed":
},
"created": false,
"status":
}
},
{
"index": {
"_index": "test_index",
"_type": "test_type",
"_id": "",
"_version": ,
"result": "updated",
"_shards": {
"total": ,
"successful": ,
"failed":
},
"created": false,
"status":
}
},
{
"update": {
"_index": "test_index",
"_type": "test_type",
"_id": "",
"_version": ,
"result": "updated",
"_shards": {
"total": ,
"successful": ,
"failed":
},
"status":
}
}
]
}
bulk操作中,任意一个操作失败,是不会影响其他的操作的,但是在返回结果里,会告诉你异常日志
BulkIndex语法:(指定Index)
POST /test_index/_bulk
{ "delete": { "_type": "test_type", "_id": "" }}
{ "create": { "_type": "test_type", "_id": "" }}
{ "test_field": "test12" }
{ "index": { "_type": "test_type" }}
{ "test_field": "auto-generate id test" }
{ "index": { "_type": "test_type", "_id": "" }}
{ "test_field": "replaced test2" }
{ "update": { "_type": "test_type", "_id": "", "_retry_on_conflict" : } }
{ "doc" : {"test_field2" : "bulk test1"} }
BulkIndex语法:(指定Index、type)
POST /test_index/test_type/_bulk
{ "delete": { "_id": "" }}
{ "create": { "_id": "" }}
{ "test_field": "test12" }
{ "index": { }}
{ "test_field": "auto-generate id test" }
{ "index": { "_id": "" }}
{ "test_field": "replaced test2" }
{ "update": { "_id": "", "_retry_on_conflict" : } }
{ "doc" : {"test_field2" : "bulk test1"} }
bulk size最佳大小:bulk request会加载到内存里,如果太大的话,性能反而会下降,因此需要反复尝试一个最佳的bulk size。一般从1000~5000条数据开始,尝试逐渐增加。另外,如果看大小的话,最好是在5~15MB之间。
Elasticsearch document数据路由原理
我们知道,一个index的数据会被分成多个分片shard,所以说一个document只能存在与一个shard中。
当客户端创建document的时候,elasticsearch此时就需要决定这个document是放在这个index的哪个分片shard中,这个过程就称之为document routing,即数据路由。
document数据路由算法
算法;shard = hash(routing) % number_of_primary_shards
举个例子,假设一个index有5个primary shard(p0,p1,p2,p3,p4)。每次对index的一个document进行增删改查的时候,都会带过来一个routing number,默认就是这个documentd的_id(可能是手动指定,也可以是自动生成),routing=_id。
假设_id=1,那么就会将routing=1这个routing值传入一个hash函数中,产生一个routing值的hash值,假设hash(routing)=21,然后将hash函数产生的值对这个index的primary shard的数量求余数,21 % 5 = 1
也就决定了这个document就放在p1上。
注意:此这个计算过程就可以看出,决定一个document在哪个shard上,最重要的值就是routing值,默认是_id,也可以手动指定,相同的routing值,每次过来,从hash函数中生成的hash值一定是相同的。无论最后计算的hash值是多少,对number_of_primary_shards求余数,结果一定在0~number_of_primary_shards之间。
决定一个document在哪个shard上,最重要的一个值就是routing值,默认是_id,也可以手动指定,相同的routing值,每次过来,从hash函数中,产出的hash值一定是相同的
_id or custom routing value
默认的routing就是_id
也可以在发送请求的时候,手动指定一个routing value,比如说put /index/type/id?routing=user_id
手动指定routing value是很有用的,可以保证说,某一类document一定被路由到一个shard上去,那么在后续进行应用级别的负载均衡,以及提升批量读取的性能的时候,是很有帮助的
routing实战
- 测试一下默认的routing
//先插入数据
PUT /test_index/test_doc/
{
"test_field": "test10 routing _id"
}
//获取数据不带routing参数
GET /test_index/test_doc/
{
"_index" : "test_index",
"_type" : "_doc",
"_id" : "",
"_version" : ,
"_seq_no" : ,
"_primary_term" : ,
"found" : true,
"_source" : {
"test_field" : "test10 routing _id"
}
}
//获取数据带routing参数 参数值为_id
GET /test_index/test_doc/?routing=
{
"_index" : "test_index",
"_type" : "_doc",
"_id" : "",
"_version" : ,
"_seq_no" : ,
"_primary_term" : ,
"found" : true,
"_source" : {
"test_field" : "test10 routing _id"
}
} - 测试带上routing值
//先插入数据
PUT /test_index/test_doc/?routing=
{
"test_field": "test routing not _id"
}
//获取数据不带routing参数
GET /test_index/test_doc/
{
"_index" : "test_index",
"_type" : "_doc",
"_id" : "",
"found" : false
}
//获取数据带routing参数 参数值为自定义的值
GET /test_index/test_doc/?routing=
{
"_index" : "test_index",
"_type" : "_doc",
"_id" : "",
"_version" : ,
"_seq_no" : ,
"_primary_term" : ,
"_routing" : "",
"found" : true,
"_source" : {
"test_field" : "test routing not _id"
}
}
主分片数量不可变
通过上面的分析,特别是路由算法,我们不难知道,在我们最开始创建索引的时候,确定了primary shard的数量,之后根据路由算法,每个document就被路由到了指定的shard上面,之后的各种操作路由规则都是一样的。试想一下,如果我们改变了primary shard的数量,那么路由算法取余的时候值可能就跟之前的不一样了,就会被路由到其它的shard上面去,就会导致数据混乱,本该已经存在的document,可能通过查询根本查不到,某种程度上说这也会造成数据的丢失。
document增删改内部原理,写一致性机制
document增删改内部原理
- 对于客户端首先会选择一个节点node发送请求过去,这个节点node就是协调节点coordinating node
- 协调节点coordinating node会对docuemnt数据进行路由,将请求转发给对应的node(含有primary shard)
- 实际上node的primary shard会处理请求,然后将数据同步到对应的含有replica shard的node
- 协调节点coordinating node如果发现含有primary shard的node和所有的含有replica shard的node都搞定之后,就会返回响应结果给客户端
下面手工画图展示一下上面的过程:
假设我们有2个节点,5个primary shard replica=1
- 客户端发送增删改请求给协调节点node2
- 协调节点node2将请求路由到含有primary shard的node1
- node1处理请求,并同步数据到对应的含有replica shard的node2
- 协调节点node2如果发现含有primary shard的node1以及所有含有replica shard的node2都搞定了,就会返回响应结果给客户端
写一致性机制(已经被移除,替换为wait_for_active_shards)
consistency
默认情况下,主分片需要法定数量或大部分的分片副本(其中分片副本可以是主分片或副本分片)在尝试写入操作之前可用。这是为了防止将数据写入网络分区的 “wrong side”。法定人数定义如下:
int((primary + number_of_replicas)/ 2)+ 1
允许的值consistency是one(仅主要分片),all (主要和所有副本),或默认quorum或大多数分片副本。
请注意,它number_of_replicas是索引设置中指定的副本数,而不是当前活动的副本数。如果您已指定索引应具有三个副本,则仲裁将如下所示:
int( (primary + 3 replicas) / 2 ) + 1 = 4
但是,如果仅启动两个节点,则将没有足够的活动分片副本来满足仲裁,并且您将无法索引或删除任何文档。
timeout
如果可用的分片副本不足,会发生什么?Elasticsearch等待,希望会出现更多的分片。默认情况下,它将等待最多1分钟。如果需要,可以使用timeout参数让它更快地中止:100是100毫秒,30s是30秒。
注意
默认情况下,新索引具有一个副本,这意味着应该需要两个活动分片副本以满足需要的quorum。但是,这些默认设置会阻止我们对单节点群集执行任何有用的操作。为避免此问题,仅当number_of_replicas大于1时才强制要求仲裁。
移除consistency这个参数之后,用wait_for_active_shards这个参数替代了。
原因就是,consistency检查是在Put之前做的。然而,虽然检查的时候,shard满足quorum,但是真正从primary shard写到replica之前,仍会出现shard挂掉,但Update Api会返回succeed。因此,这个检查并不能保证replica成功写入,甚至这个primary shard是否能成功写入也未必能保证。
为了提高对系统写入的弹性,使用wait_for_active_shards,可以将索引操作配置为在继续操作之前等待一定数量的活动分片副本。如果必需数量的活动分片副本不可用,则写入操作必须等待并重试,直到必需的分片副本已启动或发生超时。默认情况下,写入操作仅等待主分片在继续(即wait_for_active_shards=1)之前处于活动状态。
注意,此设置大大降低了写入操作不写入所需数量的分片副本的可能性,但它并未完全消除这种可能性,因为此检查在写入操作开始之前发生。一旦写入操作正在进行,复制在任何数量的分片副本上仍然可能失败,但仍然可以在主分片上成功。在_shards写操作的响应部分揭示了其复制成功/失败碎片的份数。
Elasticsearch document查询内部原理
查询请求打过来Elasticsearch内部做了什么?
- 客户端发送请求到任意一个node,这个node就成为了协调节点coordinating node
- 协调节点coordinating node会对document进行路由,将请求转发到包含该document的对应的node上面去,此时会使用round-robin随机轮询算法,在primary shard以及所有的replica shard中随机选择一个,让打过来的读请求实现负载均衡
- 接收请求的node会返回document给协调节点coordinating node
- 协调节点将document数据返回给客户端
对于读取请求,协调节点将在每个请求上选择不同的分片副本以平衡负载; 它循环遍历所有碎片副本。
在索引文档时,文档可能已经存在于主分片上但尚未复制到副本分片。在这种情况下,副本可能会报告文档不存在,而主副本可能会成功返回文档。索引请求将成功返回给用户后,该文档将在主分片和所有副本分片上可用。
最后简单描述一下随机轮询算法:
举个例子,比如一个协调节点coordinating接受到一个document的4次请求,就会使用随机轮询算法,循环遍历所有shard,将4次请求均匀的打在所有shard上面,比如有4个shard,就会每个shard各一个请求。
bulk api的奇特json格式与底层性能优化关系
为什么bulk要采用这种奇特的json格式?
由于bulk中的每个操作都可能要转发到不同的node的shard去执行,假设我们不用这种奇特的json格式,采用比较良好的json数组格式,允许任意的换行,整个可读性非常棒,读起来很爽。但是ES拿到这种标准格式的json串之后,要按照下述流程去进行执行处理。
格式如下:
[{
"action": {
},
"data": {
}
}]
- 将json数组解析为JSONArray对象,这个时候,整个数据,就会在内存中出现一份一摸一样的拷贝,一份数据是json文本,一份数据是JSONArray对象
- 解析json数组里面的每个json,对每个请求中的document进行路由
- 为路由到同一个shard上的多个请求,创建一个请求数组
- 将这个请求数组序列化
- 将序列化后的请求数组发送到对应的节点上去
不难看出这样就会耗费更多的内存,更多的jvm gc开销。
假设一个场景,对于bulk size的大小一般建议在几千条,大小在10MB左右,所以说,可怕的事情来了。假设说现在100个bulk请求发送到了一个节点上去,然后每个请求是10MB,100个请求就是1000MB=1G,然后每个请求的json都copy一份JSONArray对象,此时内存中的占用就会翻倍,就会占用2GB的内存,甚至还不止,因为弄成JSONArray对象之后,还可能会多弄一些其它的数据结构,2GB+的内存占用。
占用更多的内存可能就会积压其它请求的内存使用量,比如说最重要的搜索请求,分析请求等等。此时就可能会导致其它请求的性能急速下降,另外的话,占用内存更多,就会导致java虚拟机的垃圾回收次数更多,更加频繁,每次要回收的垃圾对象更多,耗费的时间更多,导致ES的java虚拟机停止工作线程的时间更多。
而使用这个奇特格式的json
{"action": {"meta"}}\n
{"data"}\n
{"action": {"meta"}}\n
{"data"}\n
- 不用将其转换为json对象,不会出现内存中的相同数据的拷贝,直接按照换行符切割json
- 对每两个一组的json,读取meta,进行document路由
- 直接将对应的json发送到node上去
和标准格式的json相比,最大的优势在于不需要将json数组解析为一个JSONArray对象,形成一份大数据的拷贝,浪费内存空间,尽可能的保证性能。
示例:
PUT _bulk
{"index": {"_index": "test","_type":"test_doc", "_id": ""}}
{"field1": "value1", "field2": "value2"}
{"index": {"_index": "test","_type":"test_doc", "_id": ""}}
{"field1": "value1 id2", "field2": "value2 id2"}
{"delete": {"_index": "test","_type":"test_doc", "_id": ""}}
{"create": {"_index": "test","_type":"test_doc", "_id": ""}}
{"field1": "value3"}
{"update": {"_index": "test", "_type":"test_doc","_id": ""}}
{"doc": {"field2": "value2"}}
{
"took": ,
"errors": false,
"items": [
{
"index": {
"_index": "test",
"_type": "test_doc",
"_id": "",
"_version": ,
"result": "created",
"_shards": {
"total": ,
"successful": ,
"failed":
},
"created": true,
"status":
}
},
{
"index": {
"_index": "test",
"_type": "test_doc",
"_id": "",
"_version": ,
"result": "created",
"_shards": {
"total": ,
"successful": ,
"failed":
},
"created": true,
"status":
}
},
{
"delete": {
"found": true,
"_index": "test",
"_type": "test_doc",
"_id": "",
"_version": ,
"result": "deleted",
"_shards": {
"total": ,
"successful": ,
"failed":
},
"status":
}
},
{
"create": {
"_index": "test",
"_type": "test_doc",
"_id": "",
"_version": ,
"result": "created",
"_shards": {
"total": ,
"successful": ,
"failed":
},
"created": true,
"status":
}
},
{
"update": {
"_index": "test",
"_type": "test_doc",
"_id": "",
"_version": ,
"result": "noop",
"_shards": {
"total": ,
"successful": ,
"failed":
},
"status":
}
}
]
}
Elasticsearch由浅入深(六)批量操作:mget批量查询、bulk批量增删改、路由原理、增删改内部原理、document查询内部原理、bulk api的奇特json格式的更多相关文章
- ElasticSearch基础之批量操作(mget+mbulk)
在前面的演示中,我们都是基于一次http查询,每次查询都要建立http的三次握手请求,这样比较耗费性能!因此ES给我们提供了基本的批量查询功能,例如如下的查询,注意里面的index是可以任意指明的 ...
- Elasticsearch —— bulk批量导入数据
在使用Elasticsearch的时候,一定会遇到这种场景--希望批量的导入数据,而不是一条一条的手动导入.那么此时,就一定会需要bulk命令! 更多内容参考我整理的Elk教程 bulk批量导入 批量 ...
- StackExchange.Redis加载Lua脚本进行模糊查询的批量删除和修改
前言 使用StackExchange.Redis没有直接相关的方法进行模糊查询的批量删除和修改操作,虽然可以通过Scan相关的方法进行模糊查询,例如:HashScan("hashkey&qu ...
- php-elasticsearch bulk批量插入数据
1.单条插入 <?php include '../vendor/Elasticsearch/autoload.php'; $a['hosts'] = array( #需要用户名时 http:// ...
- Elasticsearch由浅入深(十)搜索引擎:相关度评分 TF&IDF算法、doc value正排索引、解密query、fetch phrase原理、Bouncing Results问题、基于scoll技术滚动搜索大量数据
相关度评分 TF&IDF算法 Elasticsearch的相关度评分(relevance score)算法采用的是term frequency/inverse document frequen ...
- MongoDB与传统数据库的使用区别——批量插入与批量查询
我在百X知道上回答问题时经常遇到类似与这样的问题:MongoDB有没有像MySQL一样的ODBC驱动?MongoDB能不能像MySQL一样获取字段名称或类型. 我的回答是:不行,因为MongoDB不是 ...
- mysql根据查询结果批量更新多条数据(插入或更新)
mysql根据查询结果批量更新多条数据(插入或更新) 1.1 前言 mysql根据查询结果执行批量更新或插入时经常会遇到1093的错误问题.基本上批量插入或新增都会涉及到子查询,mysql是建议不要对 ...
- oracle 在xml中批量插入,批量修改及多组条件查询
最近公司用ibatis开发项目,本来可以用存储过程处理批量插入,批量修改及多组条件查询:但由于使用模块相对较小,暂时就在xml中配置,以前没有在xml做过类似处理,有必要记录一下:好了,代码如下: & ...
- Sql批量添加,批量查询,批量删除,批量修改。mybatis都有对应标签
Sql批量添加,批量查询,批量删除,批量修改.mybatis都有对应标签
随机推荐
- WPF Button IsMouseOver Background
https://stackoverflow.com/questions/17259280/how-do-you-change-background-for-a-button-mouseover-in- ...
- C#中的一些对话框问题处理
1. 对于打开文件对话框处理 #region 打开文件对话框 string StrPath; OpenFileDialog Flag = new OpenFileDialog(); Flag.Mult ...
- Mysql 报错:#1067 - Invalid default value for 'update_time
由于 字段UPDATE_TIME 的字段类型是 timestamp ,默认值是:'0000-00-00 00:00:00' 即:`UPDATE_TIME` timestamp NOT NULL DEF ...
- Java生鲜电商平台-供应链模块的设计与架构
Java生鲜电商平台-供应链模块的设计与架构 说明:Java开源生鲜电商平台中供应链模块属于卖家的行为,也就是卖家如何管理他们自己的供应商,包括结算方式,压款方式,结算周期等等,超出了我这个B2B平台 ...
- Python分页
# -*-coding:utf-8-*- # Author:Ds from django.utils.safestring import mark_safe from django.http.requ ...
- 【设计模式】Singleton
前言 Singleton设计模式,确保全局只存在一个该类的实例.将构造器声明为private,防止调用(虽然还是可以使用反射来调用).声明一个静态的类实例在类中,声明一个公共的获取实例的方法.这篇博文 ...
- vue中nextTick的理解
A. vue 中的 nextTick 是什么? 1.首先需要清楚,nextTick是一个函数:这个函数的作用,简单理解就是下一次渲染后才执行 nextTick 函数中的操作: 2.在下一次 DOM 更 ...
- bat脚本里面if else if的写法
曾经困扰了很久的bat脚本,如果里面包含多种条件判断,就必须要试用if,else if,else的写法了.尝试了很久,终于找到规律: 第一种写法:最简单,就是写一行. @echo off rem 写一 ...
- java引用的强制转型
在java的面向对象的特性里,父类的引用可以指向子类的实例对象.但是,如果一个引用b(b本身指向了一个对象)想赋值给引用a,b不是a的类型且不是a的子类类型,那么就需要强制转换,并有失败的可能性,这个 ...
- xshell 远程登陆CentOS7 免密登陆
首先说一下大体的思路: 1. 以密码登陆CentOS系统 2. 配置ssh 3. xshell 生成秘钥 4. 进行免密登陆 软件.设备: xshell(下载地址(免费版),也可以自行百度下载) Ce ...