深入浅出Zookeeper(五):BadVersionExceptionはいったいどういうことなのか


本文は
ポタリポタリ本 :
https://www.jianshu.com/u/204...
バージョン#バージョン#
日付
コメント
1.0
2020.4.19
文章の先発
前言
最近では開発時にたまにzkがBadVersionExceptionと報告されているのが観測され,検索による楽観的ロックに関する問題であることがわかり,すぐに問題が解決した.しかし、単体アプリケーションでも分散システムでも、実行中にデータの排他性を保証するメカニズムが必要です.次にzkがこのメカニズムをどのように実現したかを見てみましょう.
ノードのプロパティ
ソースコードを分析する前に、zkノードの3つのバージョンのプロパティを理解する必要があります.
  • version:現在のデータノードデータコンテンツのバージョン番号
  • cversion:現在のデータサブノードのバージョン番号
  • aversion:現在のデータノードACL変更バージョン番号
  • これらのプロパティはStatPersistedというクラスで見つけました.
    関連する属性が変更されると、バージョン番号は+1になります.作成したばかりのノードで、バージョン番号は0で、このノードが0回更新されたことを示します.
    ソース分析
    一般的にsetDataを呼び出すと、コードはこう書きます.
    //Curator  
    //      。  , -1              
    client.setData().withVersion(version).forPath(path, payload);
    //       
    client.setData().forPath(path, payload);

    zookeeperのclientコードは非常に簡単です.
        /**
         * The asynchronous version of setData.
         *
         * @see #setData(String, byte[], int)
         */
        public void setData(final String path, byte data[], int version,
                StatCallback cb, Object ctx)
        {
            final String clientPath = path;
            PathUtils.validatePath(clientPath);
    
            final String serverPath = prependChroot(clientPath);
    
            RequestHeader h = new RequestHeader();
            h.setType(ZooDefs.OpCode.setData);
            SetDataRequest request = new SetDataRequest();
            request.setPath(serverPath);
            request.setData(data);
            request.setVersion(version);
            SetDataResponse response = new SetDataResponse();
            cnxn.queuePacket(h, new ReplyHeader(), request, response, cb,
                    clientPath, serverPath, ctx, null);
        }

    その後versionという属性はリクエストにシーケンス化され、サービス側に送信されます.
    サービス側のコードを見てください.異常な名前から、コードPrepRequestProcessor.pRequest2Txnのコードを簡単に見つけることができます.
           case OpCode.setData:
                    zks.sessionTracker.checkSession(request.sessionId, request.getOwner());
                    SetDataRequest setDataRequest = (SetDataRequest)record;
                    if(deserialize)
                        ByteBufferInputStream.byteBuffer2Record(request.request, setDataRequest);
                    path = setDataRequest.getPath();
                    validatePath(path, request.sessionId);
                    nodeRecord = getRecordForPath(path);
                    checkACL(zks, nodeRecord.acl, ZooDefs.Perms.WRITE, request.authInfo);
                    int newVersion = checkAndIncVersion(nodeRecord.stat.getVersion(), setDataRequest.getVersion(), path);
                    request.setTxn(new SetDataTxn(path, setDataRequest.getData(), newVersion));
                    nodeRecord = nodeRecord.duplicate(request.getHdr().getZxid());
                    nodeRecord.stat.setVersion(newVersion);
                    addChangeRecord(nodeRecord);
                    break;
    checkAndIncVersionの論理を見てみましょう.
        private static int checkAndIncVersion(int currentVersion, int expectedVersion, String path)
                throws KeeperException.BadVersionException {
            if (expectedVersion != -1 && expectedVersion != currentVersion) {
                throw new KeeperException.BadVersionException(path);
            }
            return currentVersion + 1;
        }

    コードの分かりやすさ:zkからノードのバージョンを取り出し、比較(クライアントによって-1に設定されていない)が必要な場合は、ノードの現在のバージョンと比較します.
    例外が投げ出されていない場合、このバージョン番号は+1され、更新はキューにコミットされ、最後にzkのメモリデータベースに更新されます.
    明らかに、これはCAS技術の実現である.では、なぜCASに基づいてロックを実現するのでしょうか.その前に、楽観的なロックと悲観的なロックの適用シーンを振り返る必要があります.
  • 悲観ロック:データ更新の競争が激しいシーンに適しています.独占性と排他性が強いからだ.
  • 楽観ロック:データの同時競合が少なく、トランザクションの競合が少ないシーンに適用されます.排他的にロックを実現するのではなく、一般的な実装は私たちが言及したCASです.

  • zkは一般的に 、DNS 、 に使用され、これはデータの同時競合が少ないことを意味し、トランザクションも実際にはleaderサーバによってシリアル処理されることを知っています.明らかに、これは楽観的なロックの使用シーンに合致するため、zkは「重い」悲観的なロックを採用して分布データの原子的な操作を実現していない.
    小結
    本論文では,zkのデータ排他的機構の実現が楽観的ロックであることを知った.このように設計された理由は、zkが通常、シーンデータを使用して競合する場合が少なく(もちろん、競合を激しくすることができますが、全体的にプロセスを見ると時間がかかります)、トランザクション操作はシリアルで実行されます.