6

这是场景:我有一个在 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. 使用消息传递
    我们可以实现一个消息传递系统,线程将使用它来协调文件访问。但这对于这个问题来说似乎太复杂了。再说一遍:性能会很差。

有什么想法吗?

4

6 回答 6

2

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

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

于 2009-01-27T16:56:40.537 回答
2

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

B. 或者 - 分层:

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

将 2 嵌套在 1 内。

还是有清理问题。

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

于 2009-01-27T16:57:39.473 回答
2

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

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

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

于 2009-01-28T07:57:43.587 回答
1

使用带有 ReadWriteLock 的 java.nio.channels.FileLock。

如果我是你,我会从所有业务代码中隐藏 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
    }
}
于 2009-01-27T17:00:00.593 回答
1

您能否在应用程序中使用信号量来控制访问?

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

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

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

于 2009-01-27T17:45:52.700 回答
1

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

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

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

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

于 2009-01-28T07:59:57.763 回答