@Myles Gray - あなたのソリューションにはいくつか問題があります。
まずマイナーな問題:!
1)キューループの各反復の後、あなたは(あなたが期待し、よりその以降で)あなたが現在作業しているオリジナルのキューマイナスラインとしてキューを再作成します。キューを再作成した後、ログに追加します。それはうまくいくでしょうが、非常に非効率的で、ログを大規模で不安定にする可能性があります。 10,000行のキューがあるとします。キューを処理するまでには、ログに49,994,999のキュー行を含めて、99,989,998のキュー行が書き込まれます。それは実際にあなたの仕事をしなくても、処理するのに長い時間がかかります。
2)現在のIDと一致しないすべての行を保存して、FINDSTRを使用してキューを再作成します。しかし、あなたの現在のIDと一致すると、後続の行も削除されます。それは問題ではないかもしれません。しかし、あなたは部分文字列のマッチングを行っています。あなたのFINDSTRは、その中のどこにでもあなたの現在のIDを含む後続の行を削除します。あなたのIDがどのように見えるか分かりません。しかし、あなたの現在のIDが123ならば、以下のIDのすべてが誤って取り除かれます - 31236、12365など。これは潜在的に致命的な問題です。私は、FORループが既にキューをバッファしているので、それは気にしないので、潜在的だと言います - 新しい作業がlate.txtファイルに追加されたのでループを中止しない限り、実際にはそれらの欠けているIDをスキップします!これは、FINDSTRに/ Xオプションを追加することで修正できます。少なくとも真の複製をスキップするだけです。
ここで重要な問題は、1つのプロセスだけが任意の種類の書き込み(または削除)操作でファイルを開くことができるということです。
3)FOR/Fループはファイルに書き込みませんが、ファイルが別のプロセスによってアクティブに書き込まれている場合、失敗するように設計されています。したがって、別のプロセスが追加されている間にFORループがキューを読み込もうとすると、キュー処理スクリプトは失敗します。 busy.txtファイルのチェックがありますが、busy.txtファイルが作成される前にキュー作成者がすでに書き込みを開始している可能性があります。特に多くの行が追加されている場合は、書き込み操作に時間がかかることがあります。行が書き込まれている間にキュープロセッサが起動し、衝突や障害が発生する可能性があります。
4)キュープロセッサがlate.txtをキューに追加し、late.txtを削除します。しかし、キューライターがlate.txtに追加の行を追加する可能性のある、追加と削除の間には時間があります。この遅れて到着した行は処理されずに削除されます!
5)ライターは、キュープロセッサによって削除されている間に、late.txtに書き込もうとする可能性があります。書き込みは失敗し、再度キューが失われます。
6)キューライターがキューに追加されている間にキューがlate.txtを削除しようとする可能性があります。削除は失敗し、次にキュープロセッサがqueue.txtにlate.txtを追加したときに、キューに重複してしまいます。
要約すると、並行性の問題は、キュー内での作業の欠落と、キュー内での作業の重複の両方につながります。ファイルを同時に変更する複数のプロセスがある場合は、イベントをシリアライズするための何らかのロック機構を確立する必要があります。
既にSqlServerデータベースを使用しています。最も論理的なことは、キューをファイルシステムからデータベースに移動することです。リレーショナルデータベースは、同時性を扱うために根本から構築されています。
ロック戦略を採用している限り、Windowsバッチ内のファイルとしてキューを使用することは難しくありません。キュープロセッサとキューライタの両方が同じロック戦略に従っていることを確認する必要があります。
以下はファイルベースのソリューションです。私はあなたが1つのキュープロセッサと複数のキューライタしか持っていないと仮定します。追加作業により、ファイルキューソリューションを複数のキュープロセッサをサポートするように変更することができます。しかし、複数のキュープロセッサはおそらく、my first answerの最後に記述したフォルダベースのキューを使用して実装する方が簡単でしょう。
キューの作成者にqueue.txtまたはlateのどちらかを書き込ませる代わりに、既存のキューの名前を変更してキューの処理を完了させる方が、キューの作成者は常にqueue.txtに書き込む方が簡単です。
このソリューションは、現在のステータスをstatus.txtファイルに書き込みます。コマンドウィンドウからTYPE STATUS.TXT
を発行すると、キュープロセッサの状態を監視できます。
データ内で!
が原因で破損しないようにするために、拡張拡張トグルを行います。 !
が表示されないことがわかっている場合は、SETLOCAL EnableDelayedExpansionを一番上に移動して、切り替えを断つことができます。
その他の最適化 - 各文のファイルを開いたり閉じたりするのではなく、文のグループに対して出力を1回だけリダイレクトする方が高速です。
このコードは完全にテストされていないので、簡単にバグがある可能性があります。しかし、そのコンセプトは健全です。うまくいけば、アイデアを得ることができます。
queueProcessor.bat
@echo off
setlocal disableDelayedExpansion
cd "%UserProfile%\Desktop\Scripting\"
:rerun
::Safely get a copy of the current queue, exit if none or error
call :getQueue || exit /b
::Get the number of lines in the queue to be used in status updates
for /f %%n in ('find /v "" ^<inProcess.txt') do set /a "record=0, recordCount=%%n"
::Main processing loop
for /f "delims=" %%a in (inProcess.txt) do (
rem :: Update the status. Need delayed expansion to access the current record number.
rem :: Need to toggle delayed expansion in case your data contains !
setlocal enableDelayedExpansion
set /a "record+=1"
> status.txt echo processing !record! out of %recordCount%
endlocal
rem :: Create SQL command
> reset.sql (
echo USE dbname
echo EXEC dbo.sp_ResetSubscription @ClientName = '%%a'
echo EXEC dbo.sp_RunClientSnapshot @ClientName = '%%a'
)
rem :: Log this action and execute the SQL command
>> log.txt (
echo #################### %date% - %time% ####################################################
echo Reinitialising '%%a'
sqlcmd -i "reset.sql"
echo.
echo ####################################################################################################
echo.
)
)
::Clean up
delete inProcess.txt
delete status.txt
::Look for more work
goto :rerun
:getQueue
2>nul (
>queue.lock (
if not exist queue.txt exit /b 1
if exist inProcess.txt (
echo ERROR: Only one queue processor allowed at a time
exit /b 2
)
rename queue.txt inProcess.txt
)
)||goto :getQueue
exit /b 0
queueWriter.bat
::Whatever your code is
::At some point you want to append a VALUE to the queue in a safe way
call :appendQueue VALUE
::continue on until done
exit /b
:appendQueue
2>nul (
>queue.lock (
>>queue.txt echo %*
)
)||goto :appendQueue
ロックコードの説明:
:retry
::First redirect any error messages that occur within the outer block to nul
2>nul (
rem ::Next redirect all stdout within the inner block to queue.lock
rem ::No output will actually go there. But the file will be created
rem ::and this process will have a lock on the file until the inner
rem ::block completes. Any other process that tries to write to this
rem ::file will fail. If a different process already has queue.lock
rem ::locked, then this process will fail to get the lock and the inner
rem ::block will not execute. Any error message will go to nul.
>queue.lock (
rem ::you can now safely manipulate your queue because you have an
rem ::exclusive lock.
>>queue.txt echo data
rem ::If some command within the inner block can fail, then you must
rem ::clear the error at the end of the inner block. Otherwise this
rem ::routine can get stuck in an endless loop. You might want to
rem ::add this to my code - it clears any error.
verify >nul
) && (
rem ::I've never done this before, but if the inner block succeeded,
rem ::then I think you can attempt to delete queue.lock at this point.
rem ::If the del succeeds then you know that no process has a lock
rem ::at this point. This could be useful if you are trying to monitor
rem ::the processes. If the del fails then that means some other process
rem ::has already grabbed the lock. You need to clear the error at
rem ::this point to prevent the endless loop
del queue.lock || verify >nul
)
) || goto :retry
:: If the inner block failed to get the lock, then the conditional GOTO
:: activates and it loops back to try again. It continues to loop until
:: the lock succeeds. Note - the :retry label must be above the outer-
:: most block.
あなたはユニークなプロセスIDをお持ちの場合、あなたはそれを書くことができます内部ブロック内のqueue.lock。次に、別のウィンドウからqueue.lockと入力して、現在どのプロセスがロックを持っているか(または最近持っているか)調べることができます。これは、プロセスがハングアップした場合にのみ問題になるはずです。
うわー、私は素晴らしい仕事をすることができ、あなたはこれのための恩恵を受けることができます、それは信じられないほどの仕事です、私はこれを介して作業し、物事があなたのソリューションを使用して行く方法を参照してくださいしかし、明日あなたのために+100! –
@MylesGray - クール。賞金は予期せぬ甘いボーナスです - 私の答えが最も多くの投票を取ると仮定します:)コードは、1980年代にキューベースの仕事をしていたので、それほど難しくありませんでした。しかし説明には時間がかかります。 * Note * - 最初に投稿してからqueueWriter(条件付きのgoto)の最後にバグ修正を追加しました。 – dbenham
私はそれを早期に終了して、とにかくそれを授与することができます - それは私が待っているものです(私ができるようになるまでには24時間かかります)。フォルダベースのソリューションでは、私は単にinProcess.txt、queue.txtなどをフォルダと入れ替えることを前提としていますか? '2> nul(> queue.lock'がどういうふうに動作するか説明できますか? –