ElasticSearch 数据并发冲突处理
一.概述
Elasticsearch使用文档版本来控制文档的并发更新,并用于解决冲突。Elasticsearch从写入到检索的时间间隔是由刷新频率refresh_interval设定的,该值可以更新,但默认最快是1s,也就是这1秒之内如果同一条数据(文档的新版本必须复制到群集中的其他节点。 Elasticsearch 也是异步和并发的),有多次更新或删除操作,则可能会引起文档版本冲突。
每一个文档数据都有一个_version,代表文档的版本号。当我们在Elasticsearch中创建一个新文档时,它会为该文档分配一个_version:1。当我们后续对该文档进行任何操作(如更新、索引或删除)时,_version都会增加1,如下所示:
{ "_index" : "my_index_1006", "_type" : "_doc", "_id" : "2", "_version" : 3, "_seq_no" : 6, "_primary_term" : 1, "found" : true, "_source" : { "name" : "B", "sales" : 20, "visitors" : 20 } }
这里_version值为3,代表更新该文档有二次,Elasticsearch使用_version来鉴别文档是否已更改。
当频繁更新文档时,有时会出现http 409状态,version_conflict_engine_exception异常,这就是版本冲突。Elasticsearch是不允许旧版本文档覆盖新版本文档数据。
Elasticsearch是采集乐观锁并发控制,Elasticsearch乐观锁实质上没有给数据加锁,而是基于文档版本实现的,每次更新或删除数据的时候都需要对比版本号。
二.解决和避免文档版本冲突
2.1 使用external对版本号控制
Elasticsearch提供了多种版本控制策略,其中之一便是利用external来对版本号进行外部控制。这种机制允许在Elasticsearch外部(如数据库)维护版本号的值,以实现对索引操作的精细控制。要启用此功能,需要将version_type设置为external。
在external模式下,Elasticsearch相当于将版本控制的责任委托给了外部数据库或其他第三方库,它们负责生成和管理文档的版本号。这种方式可以看作一种放权操作,它让数据源对其数据在Elasticsearch中的版本有更多的控制权。
例如mysql数据要同步到es中, 可以在mysql中创建一个字段来管理es版本号,如取值为:修改数据的毫秒的时间戳或者数字值递增。
当使用external版本类型时,Elasticsearch在处理索引请求时会对比传入文档的版本号和已存储文档的版本号。如果传入的版本号大于已存储的,就表明这是一个新版本的文档,Elasticsearch则会执行索引操作并更新文档的版本号。然而,如果传入的版本号小于或等于已有版本号,这就意味着可能存在并发修改的问题,Elasticsearch会认为发生了版本冲突,索引操作将被拒绝,出现version_conflict_engine_exception异常和http 409状态。
使用时间戳做为版本号示例如下:
PUT my_index_1006/_doc/3?version=1234567891235&version_type=external { "sales":50, "visitors":10, "name":"A" }
利用external控制版本号的优势在于,它能确保Elasticsearch中的数据始终是最新的,因为任何过期的修改都会因版本冲突而被拒绝。此外,借助external版本控制,可以更有效地解决并发修改带来的版本冲突问题,从而保证数据的一致性和完整性。
2.2使用if_seq_no和if_primary_term避免版本冲突
Elasticsearch提供了一种更精细的版本控制机制——通过if_seq_no和if_primary_term参数来对文档的版本进行唯一标识,从而避免并发操作引起的冲突。
在执行索引写入操作时,Elasticsearch会检查传入的if_seq_no和if_primary_term是否与最后一次修改文档的序列号(seq no)和主要项(primary term)匹配。这两个参数共同提供了一种强大的控制机制,确保只有当所提供的版本信息与文档的最新状态一致时,才会执行写入操作。
如果if_seq_no和if_primary_term的值与当前文档状态不匹配,Elasticsearch会认为发生了版本冲突。在这种情况下,系统会返回一条“VersionConflictException”的报错信息,同时伴随一个409的HTTP状态码,表明请求的操作由于版本冲突而未能成功执行。
更新或删除前,先用get根据文档id,获取到该文档的if_seq_no和if_primary_term值,如下所示:
#发送请求 get my_index_1006/_doc/3
#返回结果获取到_seq_no和_primary_term { "_index" : "my_index_1006", "_type" : "_doc", "_id" : "3", "_version" : 1234567891235, "_seq_no" : 16, "_primary_term" : 1, "found" : true, "_source" : { "sales" : 50, "visitors" : 10, "name" : "A" } }
执行更新操作
PUT my_index_1006/_doc/3?if_seq_no=16&if_primary_term=1 { "sales":70, "visitors":10, "name":"A" }
2.3批量更新或删除使用proceed忽略冲突
在_update_by_query和_delete_by_query命令请求中,加入conflicts=proceed,实际上是告诉进程忽略冲突并继续更新其他文档。经过该操作,代码运行不会报409错误了,但依然会有版本冲突。不过,在某些企业级场景下,该操作是有效的。
示例如下所示:
post /hqbuy_stock/_update_by_query?conflicts=proceed { "query": { "term": { "Id": { "value": "541607807996132" } } }, "script": { "source": "ctx._source['PartNo']='1NT01L-7942';" } } #返回结果中,version_conflicts代表冲突数量 { "took" : 7461, "timed_out" : false, "total" : 1, "updated" : 1, "deleted" : 0, "batches" : 1, "version_conflicts" : 0, "noops" : 0, "retries" : { "bulk" : 0, "search" : 0 }, "throttled_millis" : 0, "requests_per_second" : -1.0, "throttled_until_millis" : 0, "failures" : [ ] }
考参文献: 官方乐观并发控制文档
一本书讲透Elasticsearch