首页 > 解决方案 > 需要一种即使应用程序重新启动也不会丢失的锁定机制(持久锁)?

问题描述

我有以下情况。有 2 个应用程序共享一个数据库。这两个应用程序都可用于更改底层数据库。例如,可以从两个系统修改客户 1。我想确保当有人在应用程序 1 中对客户 1 执行操作时,我需要为该锁设置一个持久锁,以便应用程序 2 中的任何人都不能对同一客户执行任何操作。即使这些应用程序中的任何一个出现故障,它仍应保持锁定。解决此类问题的正确方法是什么?

标签: javahibernatesynchronizationlocks

解决方案


正如@Turing85 的评论所暗示的那样,这是一个极其危险的领域:如果有人绊倒电源线,您的应用程序将停止运行并且无法再次启动。永久。至少,直到有人进入并手动解决问题。这很少是你想要的。

正常的解决方案是在DB层面做加锁:如果是‘单文件就是数据库’的模型,比如H2或者SQLite,那么让DB引擎加锁文件写,对OS层面的文件进行处理锁充当您的门控机制。这具有相当大的优势,即如果应用程序 A 因任何原因(电力短缺、硬崩溃,谁知道)而从空中掉下来,锁将被放弃。

如果数据库是一个单独的运行进程(psql、mysql、mssql 等),那么它们具有您可以使用的锁定功能。

如果这些选项都不可用,您可以手动处理它:您可以使用保证原子/唯一的新文件 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);

open 选项要求 java 确保原子性:CREATE_NEW[A] 文件以前不存在,现在存在,并且是这个进程创建的,或者 [B] 这将抛出。

没有 [C] 这个进程创建了它,但不幸的是另一个进程在同一时间做同样的事情并创建了它,其中一个进程现在覆盖了另一个进程的努力 - 这就是 CREATE_NEW 的含义和保证:这不会发生。(与CREATE将覆盖那里的内容并且不保证原子性的选项相比)。

您现在可以将文件系统用作全局唯一锁:要获取锁,您需要创建该文件。如果可以的话,太好了。你说对了。如果不能,则必须等待(如果您想尽快获取它,则需要使用观察程序 API 或循环,这不是一个好选择,与-进程锁定!) - 要放弃锁定,只需删除文件。

为了防止硬崩溃将文件留在那里,永久卡住,阻止您的应用程序再次运行,它可以帮助在其中注册“pid”(进程 ID)。如果您手动修复问题,这将为您提供一些调试,并且您可以使用自动检查('嘿,操作系统,是否还有一个进程以 id 123981 运行?没有?好吧,那么它一定是硬崩溃并离开了将文件锁定到位!')。不幸的是,在 java 中使用 pids 很复杂,因为 java 或多或少是围绕着你不应该过多依赖底层操作系统的概念设计的,而且 java 并不真正假设“进程 ID”是底层操作系统的东西做。google一下如何获取,你可以这样做。

这让我们明白了最后一点,你对不一致的明显恐惧:毕竟,你实际上似乎希望在发生硬崩溃时永久禁用应用程序(进程崩溃并且锁不是明确放弃)。我假设您想要这个,因为您担心数据库处于不一致的状态,并且您不希望任何东西再次触摸它,直到您手动查看它。

好吧,锁文件业务正是您获得它的方式。但是,这对用户是相当不利的,并且不需要:您可以设计数据库和流程(使用事务、仅附加表和日志系统),以便它们始终能够干净地在硬崩溃中幸免于难。

例如,考虑文件系统。在过去,当你被电源线绊倒时,你会遇到一个令人讨厌的事情,系统会进行“全盘检查”,而且很可能会发现一堆错误。

但在现代系统上,情况已不再如此。整天被那张电源卡绊倒。您不会收到损坏的文件(除非流程设计不当,在这种情况下损坏是应用程序的故障,而不是文件系统的故障),并且不需要进行大量的磁盘检查。

这主要通过称为“日志”的概念起作用。

比如说,你想替换一个“Hello, World!”的文件。与文字“再见!”。你可以开始写字节。假设您到达“Goodb,World!” 然后有人被电缆绊倒。

你现在被水洗了。数据不一致,谁知道发生了什么。

但是想象一个不同的系统:

日记

系统首先创建一个名为“.jobrecord”的文件,在其中写道:我要打开这个文件,并在开始时用“再见,现在!”覆盖数据。

然后,它实际上会继续并做到这一点。

然后,它以原子方式删除作业记录(例如,通过更新单个字节来标记:“完成”)。

现在,在启动时,系统可以检查该文件是否存在,如果存在,则检查工作是否实际完成,或者在需要时完成它。瞧,现在你永远不会有一个不一致的系统。

你也可以编写这样的工具。

替代方案:仅附加

另一种滚动方式是只添加数据,并且具有有效性标记。因此,您永远不会覆盖任何文件,您只会创建新文件,然后将它们“旋转到位”。例如,不是覆盖文件,而是创建一个名为“target.new”的新文件,复制数据,然后用“再见,现在!”覆盖开头,然后以原子方式将文件重命名为原始“目标” ',从而保证原始文件永远不会受到损害,并且在某个时刻,目标文件的“视图”是旧的,而在另一个原子后续时刻,是新的,中间没有时间是两点之间的一半。

数据库中的一个类似概念是从不更新,只插入,具有原子增加的计数器,并且知道“当前状态”始终是具有最高计数器编号的行。

关键是:有一些方法可以构建强大的系统,除非外力破坏了您的数据存储,否则这些系统永远不会导致数据不一致。


推荐阅读