#java #hibernate #synchronization #locks
#java #спящий режим #синхронизация #блокировки
Вопрос:
У меня следующий сценарий. Есть 2 приложения, которые совместно используют базу данных. Оба эти приложения могут использоваться для изменения базовой базы данных. Например, Клиент 1 может быть изменен в обеих системах. Я хочу убедиться, что когда кто-то выполняет действие, скажем, с клиентом 1 в приложении 1, мне нужна постоянная блокировка для этой блокировки, чтобы никто из приложения 2 не мог выполнять какие-либо действия с тем же клиентом. Даже если какое-либо из этих приложений выйдет из строя, оно все равно должно удерживать блокировку. Каков будет правильный подход для решения такой проблемы?
Комментарии:
1. Вы уверены, что это то, что вы хотите? Это, по сути, означало бы, что блокировка не снимается, если приложение не возвращается снова. В контексте, например, k8s, модули обычно не имеют идентификатора, таким образом, «новый» модуль не получает блокировку «старого» модуля.
2. @Turing85 да, мне нужно такое поведение. Это необходимо для самостоятельного предоставления. Приложение 1 (с его отдельной базой данных) будет доступно конечному потребителю, а приложение 2 является внутренним приложением. Когда клиент изменяет какую-либо конфигурацию в приложении 1, это приводит к приложению 1, где значения из базы данных приложения 1 будут скопированы в базу данных приложения 2. Но в то же время нам нужно убедиться, что база данных приложения 2 не обновляется.
Ответ №1:
Как намекает комментарий @Turing85, это чрезвычайно опасная территория: если кто-то отключит кабель питания, ваше приложение не будет запущено и не сможет быть запущено снова. постоянно. По крайней мере, до тех пор, пока кто-нибудь не войдет и вручную не решит проблему. Это редко то, что вы хотите.
Нормальное решение — выполнить блокировку на уровне базы данных: если это модель «один файл — база данных», такая как H2 или SQLite, то пусть механизм DB заблокирует файл для записи и использует блокировку файла на уровне ОС в качестве вашего механизма блокировки. Это имеет значительное преимущество в том, что если приложение A выпадает из эфира по какой-либо причине (нехватка питания, жесткий сбой, кто знает), блокировка снимается.
Если БД является отдельным запущенным процессом (psql, mysql, mssql и т.д.), У них есть функции блокировки, Которые вы можете использовать.
Если ни один из этих параметров не доступен, вы можете использовать его вручную: вы можете создавать файлы с новым file API, которые гарантированно будут атомарными / уникальными:
int pid = 0; // see below
Path p = Paths.get("/absolute/path/to/agreed/upon/lockfile/location/lockfile.pid");
Files.write(p, String.valueOf(pid), StandardOpenOption.CREATE_NEW);
CREATE_NEW
Опция open запрашивает java для обеспечения атомарности: либо [A] файл не существовал ранее, существует сейчас, и именно этот процесс его создал, либо [B] это приведет к сбою.
Нет [C] этот процесс создал его, но другой процесс, к несчастью, делал то же самое в то же время и также создал его, и один из этих процессов теперь перезаписывает усилия другого — это то, что означает и гарантирует CREATE_NEW: что этого не произойдет. (против CREATE
опции, которая перезапишет то, что там есть, и не дает никаких гарантий атомарности).
Теперь вы можете использовать файловую систему в качестве глобальной уникальной блокировки: чтобы получить блокировку, вы создаете этот файл. Если вы можете, отлично. Вы получили это. Если вы не можете, тогда вам придется подождать (вам нужно будет использовать watcher API или цикл, если вы хотите получить его как можно скорее, не лучший вариант, это очень дорогая операция по сравнению с блокировкой в процессе!) — чтобы снять блокировку, просто удалите файл.
Чтобы защититься от жесткого сбоя, оставляющего файл там, застрявшим, навсегда, предотвращая повторный запуск вашего приложения, это может помочь зарегистрировать ‘pid’ (идентификатор процесса) внутри него. Это дает вам некоторую отладку, если вы исправляете проблемы вручную, и вы можете использовать для автоматической проверки (‘эй, ОС, есть ли еще процесс, запущенный с идентификатором 123981? Нет? Хорошо, тогда, должно быть, произошел жесткий сбой и файл блокировки остался на месте!’). К сожалению, работа с pid в java запутана, поскольку java более или менее разработана с учетом того, что вы не должны слишком полагаться на базовую ОС, и java на самом деле не предполагает, что «идентификаторы процессов» — это то, что делает базовая ОС. погуглите, как его получить, вы МОЖЕТЕ это сделать.
Что подводит нас к конечной точке, вашему очевидному страху перед непоследовательностью: в конце концов, вы на самом деле, кажется, желаете явно безумную идею, что вы хотите, чтобы приложение было навсегда отключено при жестком сбое (сбой процесса и блокировка явно не отменяется). Я предполагаю, что вы хотите этого, потому что боитесь, что база данных остается в несогласованном состоянии, и вы не хотите, чтобы что-либо когда-либо касалось ее снова, пока вы вручную не посмотрите на это.
Ооокей, ну, проблема с файлом блокировки заключается именно в том, как вы это получаете. Однако это довольно враждебно к пользователю и не требуется: вы можете проектировать базы данных и потоки процессов (используя транзакции, таблицы только для добавления и системы журналов) так, чтобы они всегда без проблем выдерживали жесткие сбои.
Например, рассмотрим файловые системы. В вашем старом, окрашенном в цвета сепии прошлом, когда вы спотыкались о свой шнур питания, при загрузке вы получали неприятную вещь, когда система выполняла «полную проверку диска», и вполне могла обнаружить кучу ошибок.
Но в современных системах это уже не так. Весь день отключайте эту карту питания. Вы не получите поврежденные файлы (если только процессы не спроектированы плохо, и в этом случае повреждение является ошибкой приложения, а не файловой системы), и никаких обширных проверок диска не требуется.
Это работает в основном с помощью концепции, известной как «ведение журнала».
Допустим, вы хотите заменить файл с надписью «Привет, мир!» на текст «Прощай сейчас!». Вы могли бы просто начать записывать байты. Допустим, вы переходите к «Goodbying, World!», а затем кто-то отключается по кабелю.
Теперь вы под струей. Данные противоречивы, и кто знает, что происходило.
Но представьте себе другую систему:
Ведение журнала
Система сначала создает файл с именем ‘.jobrecord’, записывает в нем: Я собираюсь открыть этот файл и перезаписать данные в начале с ‘Goodbye, now!’.
Затем он действительно выполняет это.
Затем запись задания удаляется атомарным способом (например, путем обновления одного байта, чтобы отметить: «готово»).
Теперь при загрузке система может проверить, есть ли этот файл, и если он есть, проверить, действительно ли задание было выполнено, или завершить его, если это необходимо. Вуаля, теперь у вас никогда не будет несогласованной системы.
Вы тоже можете написать такие инструменты.
Альтернатива: только для добавления
Другой способ отката заключается в том, что данные добавляются только когда-либо и имеют маркер достоверности. Итак, вы никогда не перезаписываете какие-либо файлы, вы только создаете новые и «поворачиваете их на место». Например, вместо записи поверх файла вы создаете новый файл с именем ‘target.new’, копируете данные, затем перезаписываете начало на «Goodbye, now!», а затем атомарно переименовываете файл поверх исходного ‘target’, гарантируя таким образом, что оригинал никогда не пострадает, и в один момент времени «view» целевого файла является старым, а в другой момент атомарного продолжения — новым, причем время никогда не меняется. между ними это находится на полпути между двумя точками.
Аналогичная концепция в базах данных заключается в том, чтобы никогда не ОБНОВЛЯТЬ, только вставлять, имея атомарно увеличивающийся счетчик и зная, что «текущее состояние» всегда является строкой с наибольшим номером счетчика.
Суть в том, что существуют способы построения надежных систем, которые никогда не приводят к несогласованности данных, если только внешняя сила не повредит вашим хранилищам данных.
Комментарии:
1. Я думаю, что я не смог должным образом объяснить проблему. У вас есть 2 приложения. Приложение 1 и приложение 2. Приложение 1 напрямую доступно конечному потребителю, в то время как приложение 2 предназначено для сотрудников службы поддержки. По сути, приложение 1 является урезанной версией приложения 2. Теперь приложения 1 и 2 имеют свои отдельные базы данных. Во время t1 клиент вносит некоторые изменения в некоторый объект O1, и он будет сохранен в базе данных приложения 1. Теперь, в некоторый момент времени t2, приложение 2 перенесет эти данные в базу данных приложения. Я хочу убедиться, что в промежутке времени t2-t1 никто не сможет изменить объект O1.
2. в этот период времени t2-t1 любое приложение может выйти из строя, но, тем не менее, блокировка должна сохраняться.