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

  

posted on 2024-05-13 17:55  花阴偷移  阅读(535)  评论(0编辑  收藏  举报

导航