【问题标题】:How to lock a file on different application levels?如何在不同的应用程序级别锁定文件?
【发布时间】:2024-01-10 17:26:02
【问题描述】:

以下是场景:我有一个在 servlet 容器内运行的多线程 Java Web 应用程序。 该应用程序在 servlet 容器内多次部署。有多个 servlet 容器 在不同的服务器上运行。

也许这张图说明了:

server1
+- servlet container
   +- application1
   |  +- thread1
   |  +- thread2
   +- application2
      +- thread1
      +- thread2
server2
+- servlet container
   +- application1
   |  +- thread1
   |  +- thread2
   +- application2
      +- thread1
      +- thread2

网络共享目录中有一个文件,所有这些线程都可以访问。他们确实经常访问该文件。 大多数情况下,文件仅由这些线程读取。但有时是写出来的。

我需要一个故障安全解决方案来同步所有这些线程,以保证数据一致性。


无法正常工作的解决方案

  1. 使用 java.nio.channels.FileLock
    我能够使用 FileLock 类同步来自不同服务器的线程。但这不适用于线程 在同一个进程(servlet 容器)内,因为文件锁在进程范围内可用。

  2. 使用单独的文件进行同步
    我可以创建一个单独的文件,表明一个进程正在读取或写入文件。这个解决方案 适用于所有线程,但有几个缺点:

    • 性能。创建、删除和检查文件是相当慢的操作。使用一个同步文件的低权重实现将阻止文件的并行读取。
    • 在需要手动清理的 JVM 崩溃后,同步文件将保留。
    • 我们在删除网络文件系统上的文件时遇到了奇怪的问题。
  3. 使用消息传递
    我们可以实现一个消息系统,线程将使用它来协调文件访问。但这对于这个问题来说似乎太复杂了。再说一遍:性能会很差。

有什么想法吗?

【问题讨论】:

    标签: java multithreading file synchronization locking


    【解决方案1】:

    除了显而易见的解决方案之外,您已经列举了可能的解决方案:删除对该文件的依赖

    线程是否有另一种方法来获取数据而不是从文件中读取数据?如何设置某种进程来负责协调对该信息的访问,而不是让所有线程读取文件。

    【讨论】:

    • 好点凯文。进程是某种数据库服务器,文件是数据库源。当数据库源更新时,进程必须重新加载文件。这些过程彼此不知道,为了简单起见,我想保留它。
    【解决方案2】:

    A.听起来是时候建立数据库了:-)。 与其共享文件,不如将数据存储在数据库中。

    B.或者 - 分层:

    1. 使用标准同步锁锁定进程中的线程。
    2. 使用基于文件的锁定类型的事物锁定进程间/机器 - 例如。创建一个目录来保存锁。

    在 1 内嵌套 2。

    还有清理问题。

    C.或者某种写入新文件/重命名策略以便读者不需要锁定可能?

    【讨论】:

      【解决方案3】:

      最简单的解决方案是创建另一个进程(Web 服务或任何对您来说最简单的方法)。只有这个进程读取/写入文件并监听其他服务的读取/写入请求。

      虽然这似乎比直接使用网络共享要慢,但情况并非如此:使用网络共享意味着使用内置于您的操作系统中的客户端/服务器(这正是这样做的:发送读取/向提供共享的服务器写入请求)。

      由于您的服务针对任务进行了优化(而不是一般的“服务文件”服务),它甚至可能更快。

      【讨论】:

        【解决方案4】:

        如果您只需要很少写入文件,那么以临时名称写入文件然后使用重命名使其对读者“可见”如何?

        不过,这只适用于 Unix 文件系统。在 Windows 上,您需要处理某些进程打开文件(用于读取)的情况。在这种情况下,重命名将失败。重命名成功后再试。

        我建议彻底测试一下,因为您可能会遇到拥塞:读取请求太多,写入器任务很长时间无法替换文件。

        如果是这种情况,请让读取器检查临时文件并等待下一次读取,直到文件消失。

        【讨论】:

        • 这是一个很好的观点。我们已经使用临时文件进行锁定(一种文件信号量)。但是您的方法的好处是阅读速度不会减慢。并且将剩余临时文件的问题减少到删除和重命名之间的少量时间。
        【解决方案5】:

        使用 java.nio.channels.FileLock 和 ReadWriteLock。

        如果我是你,我会从所有业务代码中隐藏 File、FileChannel 和所有 FileOutputStream。替换为我自己的简单适配器类,例如 DAO。

        例如

        abstract class MyWriter{
            private FileChannel file;
            public void writeSomething(byte[] b){
                // get VM scope write lock here
                // get file lock here
                // do write
                // release file lock
                // release readwritelock lock
            }
        }
        

        【讨论】:

        • 不适用于一个进程内的线程同步。
        【解决方案6】:

        您能否使用Semaphore 来控制应用程序中的一次访问?

        引用 API“信号量通常用于限制可以访问某些(物理或逻辑)资源的线程数”

        虽然 API 可能仍然是特定于容器的,但分布式信号量的概念应该是可以实现的,可能使用 JGroups

        在 Google 上粗略搜索“分布式 Java 信号量”出现 Jukebox,看起来它可以解决上述问题

        【讨论】: