Elasticsearch-同時競合およびソリューション

10310 ワード

1.同時衝突の詳細


電子商取引のシーンの下で、仕事の流れは以下の通りです.
  • 在庫数量
  • を含む商品情報を読み取る.
  • ユーザー注文
  • 商品情報を更新し、在庫数を
  • に減らす.
    マルチスレッド操作であれば、上記の3ステップフローを複数のスレッドが同時に実行する可能性があり、このとき2人が商品データを読み取りに来た場合、2つのスレッドが同時に2人にサービスし、同時に商品在庫データの修正を行う.正しい場合:スレッドAは在庫-1を99件に設定し、スレッドBは99件を読み取り、-1を98件に変更します.A,Bスレッドのいずれも100件を読み出し,A処理が完了したら99件,B処理が完了してから再び99件に変更した場合,結果はエラーとなる.

    2.ソリューション


    2.1悲観ロック


    商品データを読み取ると同時に、この行のデータにロックをかけ、このスレッドがデータを処理した後、ロックを解除し、別のスレッドが処理を開始します.
    悲観的ロック同時制御方式は、様々な場合にロックされる.ロックした後、このデータを操作できるスレッドは1つしかありません.異なるシーンの下で、上のロックは異なり、行レベルロック、表レベルロック、リードロック、ライトロックがあります.

    2.2楽観ロック


    楽観的なロックはロックされず、各スレッドは任意に操作できます.Esの各ドキュメントにはversionフィールドがあり、新規ドキュメントを作成すると1となり、1回の累積を修正し、スレッドA,Bが同時にデータを読み取り、version=1、Aが処理された後に在庫が99となり、esを書き込む際にesのバージョン番号と比較され、いずれも1となると書き込みに成功し、version=2、Bが処理された後も99となり、esに格納する際にesのデータのバージョン番号version=2と比較して明らかに異なり、この場合99で更新することはありません.最新のデータを読み直し、さらに1を減らして98にし、上記の操作を実行して書き込みます.

    2.3 Elasticsearchの楽観ロック


    Elasticsearchのバックグラウンドはいずれもマルチスレッド非同期であり,複数のリクエスト間は乱順であり,後で変更される可能性がある先着,先に変更される後着である.
    Elasticsearchのマルチスレッド非同期同時修正は独自の_バージョン番号は楽観的なロックと同時制御を行います.
    后で改正の先で着いて、改正が终わった后で、先に改正の后で着いて、比较してみますバージョン番号は、等しくなければそのまま捨てて、不要です.これにより、結果は正しい状態に保存されます.
    コードの例:
    PUT /test_index/test_type/3
    {
      "test_field": "test test"
    }
     :
    {
      "_index": "test_index",
      "_type": "test_type",
      "_id": "3",
      "_version": 1,
      "result": "created",
      "_shards": {
        "total": 2,
        "successful": 1,
        "failed": 0
      },
      "created": true
    }
     
    PUT /test_index/test_type/3
    {
      "test_field": "test1 test1"
    }
     
    {
      "_index": "test_index",
      "_type": "test_type",
      "_id": "3",
      "_version": 2,
      "result": "created",
      "_shards": {
        "total": 2,
        "successful": 1,
        "failed": 0
      },
      "created": true
    }
     
    DELETE /test_index/test_type/3
     :
    {
      "_index": "test_index",
      "_type": "test_type",
      "_id": "3",
      "_version": 3,
      "result": "created",
      "_shards": {
        "total": 2,
        "successful": 1,
        "failed": 0
      },
      "created": true
    }
     
    PUT /test_index/test_type/3
    {
      "test_field": "test1 test1"
    }
     
    {
      "_index": "test_index",
      "_type": "test_type",
      "_id": "4",
      "_version": 1,
      "result": "created",
      "_shards": {
        "total": 2,
        "successful": 1,
        "failed": 0
      },
      "created": true
    }
    

    削除操作では、このデータのバージョン番号にも1が加算されます.
    documentを削除した後、バージョン番号などの情報が残っているため、すぐに物理的に削除されたわけではないことを側面から証明することができます.まずdocumentを削除してから、このdocumentを再作成します.実はdelete versionの基礎の上に、version番号を1追加します.

    2.4 esの楽観ロック同時制御例

  • まず新しいデータ
    PUT /test_index/test_type/4
    {
      "test_field": "test"
    }
    
  • を作成します.
  • は2つのクライアントをシミュレートし、同じデータ
    GET /test_index/test_type/4
     
    {
        "_index": "test_index",
        "_type": "test_type",
        "_id": "4",
        "_version": 1,
        "found": true,
        "_source": {
          "test_field": "test"
        }
     }
    
  • を取得した.
  • クライアントの1つは、まずこのデータを更新し、同時にデータのバージョン番号を持ってきて、esのデータのバージョン番号が、クライアントのデータのバージョン番号と同じであることを確認してから、
    PUT test_index/test_type/4?version=1
    {
      "test_field": "client1 changed"
    }
     
    {
        "_index": "test_index",
        "_type": "test_type",
        "_id": "4",
        "_version": 2,
        "result": "updated",
        "_shards": {
          "total": 2,
          "successful": 1,
          "failed": 0
        },
        "created": false
    }
    
  • を修正することができます.
  • もう1つのクライアントは、version=1のデータに基づいて修正を試み、同様にversionバージョン番号を付けて、楽観ロックを行う同時制御
    PUT test_index/test_type/4?version=1
    {
      "test_field": "client2 changed"
    }
     , 
    {
        "error": {
          "root_cause": [
            {
              "type": "version_conflict_engine_exception",
              "reason": "[test_type][4]: version conflict, current version [2] is different than the one provided [1]",
              "index_uuid": "rsiZYqiwSCC2XdR8N2bJow",
              "shard": "2",
              "index": "test_index"
            }
          ],
          "type": "version_conflict_engine_exception",
          "reason": "[test_type][4]: version conflict, current version [2] is different than the one provided [1]",
          "index_uuid": "rsiZYqiwSCC2XdR8N2bJow",
          "shard": "2",
          "index": "test_index"
        },
        "status": 409
    }
    
    楽観ロックは同時問題
  • を阻止することに成功した.
  • 楽観ロックが同時問題を阻止することに成功した後、正しく更新を完了して再びGET要求を行うことを試み、version
    GET /test_index/test_type/4
    {
      "_index": "test_index",
      "_type": "test_type",
      "_id": "4",
      "_version": 2,
      "found": true,
      "_source": {
        "test_field": "client1 changed"
      }
    }
    
    が最新のデータとバージョン番号に基づいて修正を行い、修正後、最新のバージョン番号を持っていくと、このステップは何度も繰り返し実行する必要がある可能性があり、特にマルチスレッドが同じデータを同時更新することが頻繁な場合、
    PUT /test_index/test_type/4?version=2
    {
      "test_field": "client2 changed"
    }
     
    {
      "_index": "test_index",
      "_type": "test_type",
      "_id": "4",
      "_version": 3,
      "result": "updated",
      "_shards": {
        "total": 2,
        "successful": 1,
        "failed": 0
      },
      "created": false
    }
    
  • .

    2.5 external versionに基づく楽観的ロック同時制御


    Esはfeatureを提供しています.つまり、内部を提供しなくてもいいということです.バージョン番号は同時制御を行い、自分でメンテナンスしたバージョン番号に基づいて同時制御を行うことができます.
    ?version=1&version_type=external
    

    version_type=external、唯一の違いは、バージョン、あなたが提供したバージョンとesの中の_だけバージョンがそっくりの場合、修正することができます.違いさえあれば、間違いを報告します.version_type=externalの場合、あなたが提供したversion比esの中の_だけです.バージョンが大きい場合、修正が完了します.
    es,_version=1,?version=1で、成功esを更新できます.version=1,?version>1&version_type=external、成功することができます.例えば?version=2&version_type=external
    コードの例:
  • まずデータ
    PUT test_index/test_type/5
    {
      "test_field": "external test"
    }
     
    {
      "_index": "test_index",
      "_type": "test_type",
      "_id": "5",
      "_version": 1,
      "result": "created",
      "_shards": {
        "total": 2,
        "successful": 1,
        "failed": 0
      },
      "created": true
    }
    
  • を作成する.
  • は、2つのクライアントが同時にこのデータ
    GET /test_index/test_type/5
     
    {
      "_index": "test_index",
      "_type": "test_type",
      "_id": "5",
      "_version": 1,
      "found": true,
      "_source": {
        "test_field": "external test"
      }
    }
    
  • にクエリーすることをシミュレートする.
  • 最初のクライアントが最初に変更すると、クライアントプログラムは、2
    PUT /test_index/test_type/5?version=2&version_type=external
    {
      "test_field": "external client1 changed"
    }
     
    {
      "_index": "test_index",
      "_type": "test_type",
      "_id": "5",
      "_version": 2,
      "result": "updated",
      "_shards": {
        "total": 2,
        "successful": 1,
        "failed": 0
      },
      "created": false
    }
    
  • のように、自分のデータベースでこのデータの最新バージョン番号を取得する.
  • は2番目のクライアントをシミュレートし、同時に自分のデータベースで維持されているバージョン番号を取得し、2であり、version=2に基づいて
    PUT /test_index/test_type/5?version=2&version_type=external
    {
      "test_field": "external client2 changed"
    }
     , 
    {
      "error": {
        "root_cause": [
          {
            "type": "version_conflict_engine_exception",
            "reason": "[test_type][5]: version conflict, current version [2] is higher or equal to the one provided [2]",
            "index_uuid": "rsiZYqiwSCC2XdR8N2bJow",
            "shard": "1",
            "index": "test_index"
          }
        ],
        "type": "version_conflict_engine_exception",
        "reason": "[test_type][5]: version conflict, current version [2] is higher or equal to the one provided [2]",
        "index_uuid": "rsiZYqiwSCC2XdR8N2bJow",
        "shard": "1",
        "index": "test_index"
      },
      "status": 409
    }
    
  • の修正を開始した.
  • 同時制御に成功した後、最新のバージョン番号に基づいて更新
    GET /test_index/test_type/5
     
    {
      "_index": "test_index",
      "_type": "test_type",
      "_id": "5",
      "_version": 2,
      "found": true,
      "_source": {
        "test_field": "external client1 changed"
      }
    }
    PUT /test_index/test_type/5?version=3&version_type=external
    {
      "test_field": "external client2 changed"
    }
     
    {
      "_index": "test_index",
      "_type": "test_type",
      "_id": "5",
      "_version": 3,
      "result": "updated",
      "_shards": {
        "total": 2,
        "successful": 1,
        "failed": 0
      },
      "created": false
    }
  • を再開する.
    転載先:https://juejin.im/post/5c66afb0e51d452a1c6159cc