1

サーバーとクライアントがあります。

クライアントがリクエストを送信します。a-1リクエストには、 、a-2b-1などの特定のキーが関連付けられていb-4ます。

同じキーに対する 2 つのリクエストが同時に来ると、同じデータ構造が変更されているため、競合エラーが発生します。

同じキーの 2 つの要求を一度に送信しないようにクライアントを適応させることができます。ただし、このシステムが複数のクライアントでも動作することを望みます。クライアントがサーバーに送信するものを調整するのはばかげているようです。代わりに、特定のキーが既に変更されている場合、同じキーを持つ他のリクエストが完了するまで、サーバーが特定のキーのリクエストを単純にブロックするようにしたいと思います。

この目的のために、ロック システムを作成しました。サーバー上の関数の開始時に、次のことを行います。

key = ...
print "Acquiring %s lock..." % (key,)
KEY_LOCKS[key].acquire()
print "%s lock acquired." % (key,)
def after_commit_hook(success):
    KEY_LOCKS[key].release()
    print "(after %s commit): Released %s lock" % (('failed', 'successful')[success], key)
transaction.get().addAfterCommitHook(after_commit_hook)

ここKEY_LOCKSで、キーをthreading.Locks にマッピングする dict です。その後、永続データ構造を変更するコードに従います。

私が想定しているのは、すでに処理されているキーのリクエストが来た場合、ロックを取得するときにブロックされるということです。以前のリクエストがすでにコミットされている(競合エラーが発生していない) 場合にのみ、新しいリクエストが再開されます。ロックが取得されるまで、要求は競合することはありません。

ほとんどのリクエストは正常に機能します。

Acquiring a-b lock...
a-b lock acquired.
(after successful commit): Released a-b lock
Acquiring a-c lock...
a-c lock acquired.
(after successful commit): Released a-c lock

ただし、ロックが機能しているように見えても、同じキーが送信された場合にはまだ問題があります。

Acquiring q-q lock...
q-q lock acquired.
Acquiring q-q lock...
(after successful commit): Released q-q lock
q-q lock acquired.
(after failed commit): Released q-q lock
repoze.retry retrying, count = 1
Traceback (most recent call last):
...
ConflictError: database conflict error (oid 0x13009b, class persistent.list.PersistentList)

そして、リクエストが再試行されます。q-q lockは、コミットが成功した後にのみ取得されたことに注意してください。

何を与える?このシステムが競合エラーを防げないのはなぜですか? 私の仮定はどこが間違っていますか?


編集:まあ、transaction.get().addAfterCommitHook(after_commit_hook)私が入れた行の前にtransaction.begin()、それは機能します。私の人生では、理由がわかりません。行の前に、transaction.begin()私のコード全体は次のとおりです。

post = request.params
if not post: return Response("No data!")

data = eval(post['data'])
time_parsed = time.time() 
my_app = request.context

これは私の問題を解決しますが、私はまだ知りたいので、それを答えとして入れていません:直前に新しいトランザクションを開始しないと、競合エラーが発生するのはなぜですか?

4

1 に答える 1

3

ZODB は、トランザクションが開始された瞬間から一貫したビューで接続を提供します。つまり、スレッドは、新しいトランザクションが開始されるまで、他のスレッドによって行われたデータベースの変更を認識しないということです! これは、 Multiversion Concurrency Controlと呼ばれるデータベースの基本機能です。

つまり、ロックを使用してアプリケーション スレッドが競合しないようにする場合は、ロックが使用可能になったときに新しいトランザクションを開始する必要もあります。

私の意見では、ロックは避けたいパフォーマンスのボトルネックです。ロックを長く保持するほど、一貫性のないデータ (開始時の状態とデータベースの状態) が発生する可能性が高くなります。

Zope では、代わりに楽観的なアプローチが使用されます。競合エラーが発生すると、トランザクションは中止され、代わりに最初から再試行されます。トランザクションが短くて速い場合は、ほとんどのロックの問題を回避し、代わりに、永続的な変更が変更されて競合が発生するたびに、データベースへの変更を再計算します。

于 2012-03-22T10:39:34.837 に答える