@ Myles Grey - Ваше решение имеет некоторые проблемы.
Сначала незначительные проблемы:
1) После каждой итерации цикла очереди вы воссоздаете очередь как исходную очередь за вычетомлиния, над которой вы сейчас работаете (надеюсь, подробнее об этом позже).После воссоздания очереди вы добавляете ее в свой журнал.Это сработает, но кажется очень неэффективным и может сделать журнал массивным и непривлекательным.Предположим, у вас есть очередь с 10 000 строк.К тому времени, как вы обработали свою очередь, вы записали в свой журнал 99 989 998 строк очереди, включая 49 994 999 строк очереди!Это займет много времени для обработки, даже без фактического выполнения вашей работы.
2) Вы воссоздаете очередь, используя FINDSTR, сохраняя все строки, которые не соответствуют вашему текущему идентификатору.Но это также удалит последующие строки, если они совпадут с вашим текущим идентификатором.Это не может быть проблемой.Но вы делаете совпадение подстроки.Ваш FINDSTR также удалит последующие строки, которые содержат ваш текущий идентификатор в любом месте внутри него.Я понятия не имею, как выглядят ваши идентификаторы.Но если ваш текущий идентификатор равен 123, то все следующие идентификаторы будут ошибочно удалены - 31236, 12365 и т. Д. Это является потенциально разрушающей проблемой.Я говорю, что это возможно, потому что цикл FOR уже буферизовал очередь, поэтому ему все равно - если вы не прервете цикл, потому что новая работа была добавлена в файл late.txt - тогда вы фактически пропустите эти недостающие идентификаторы!Это можно исправить, добавив параметр / X в FINDSTR.По крайней мере, тогда вы будете пропускать только истинные дубликаты.
Теперь основные проблемы - все это связано с тем фактом, что только у одного процесса может быть открыт файл для любой операции записи (или удаления).
3) Хотя цикл FOR / F не записывает данные в файл, он предназначен для сбоя, если файл активно записывается другим процессом.Поэтому, если ваш цикл FOR пытается прочитать очередь, пока к ней добавляется другой процесс, ваш сценарий обработки очереди завершится неудачно.У вас есть проверка файла busy.txt, но ваш писатель очереди, возможно, уже начал писать до того, как был создан файл busy.txt.Операция записи может занять некоторое время, особенно если добавляется много строк.Во время написания строк ваш обработчик очереди может запуститься, и тогда у вас возникнет коллизия и сбой.
4) Обработчик очереди добавляет в свою очередь файл late.txt, а затем удаляет late.txt.Но между добавлением и удалением существует момент времени, когда средство записи очереди может добавить дополнительную строку к late.txt.Эта поздняя поступающая строка будет удалена без обработки!
5) Другая возможность - автор может попытаться записать в файл late.txt, пока он находится в процессе удаления процессором очереди.Запись не удастся, и снова в вашей очереди будет отсутствовать работа.
6) Еще одна возможность - ваша очередь может попытаться удалить late.txt, пока к ней добавляется средство записи очереди.Удаление не удастся, и вы получите дубликаты в своей очереди в следующий раз, когда обработчик очереди добавит late.txt к queue.txt.
В итоге проблемы параллелизма могут привести к отсутствию работы в вашей очереди., а также дублируй работу в своей очереди.Когда у вас есть несколько процессов, вносящих изменения в файл одновременно, вы ДОЛЖНЫ установить какой-то механизм блокировки для сериализации событий.
Вы уже используете базу данных SqlServer.Самое логичное, что нужно сделать - это переместить свою очередь из файловой системы в базу данных.Реляционные базы данных создаются с нуля, чтобы иметь дело с параллелизмом.
При этом не сложно использовать файл в качестве очереди в пакете Windows, если вы используете стратегию блокировки.Вы должны убедиться, что и ваш обработчик очереди, и ваши авторы очередей следуют одинаковой стратегии блокировки.
Ниже приведено файловое решение.Я собираюсь предположить, что у вас есть только один обработчик очереди и, возможно, несколько авторов очереди.С дополнительной работой вы можете адаптировать решение очереди файлов для поддержки нескольких процессоров очереди.Но несколько процессоров очереди, вероятно, проще реализовать, используя очередь на основе папок, которую я описал в конце моего первого ответа .
Вместо того, чтобы пишущие очереди записывали в файл queue.txt илив конце.txt проще обработать процессором очереди переименование существующей очереди и обработать ее до конца, в то время как средства записи очереди всегда записывают в queue.txt.
Это решение записывает текущий статус в файл status.txt.файл.Вы можете отслеживать состояние обработчика очереди, введя TYPE STATUS.TXT
из командного окна.
Я выполняю несколько отложенных расширений, чтобы защитить от повреждения из-за !
в ваших данных.Если вы знаете, что !
никогда не появится, то вы можете просто переместить SETLOCAL EnableDelayedExpansion наверх и отказаться от переключения.
Еще одна оптимизация - быстрее перенаправить вывод только один раз для группы оператороввместо того, чтобы открывать и закрывать файл для каждого оператора.
Этот код полностью не проверен, поэтому могут быть некоторые глупые ошибки.Но концепции здоровы.Надеюсь, вы поняли.
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.
Если у вас есть уникальный идентификатор процесса, вы можете записать его в queue.lock во внутреннем блоке.Затем вы можете ввести queue.lock из другого окна, чтобы узнать, какой процесс в данный момент (или совсем недавно) имел блокировку.Это должно быть проблемой, только если какой-то процесс зависает.