共享资源的最佳读写优化方法

6
我的一个需求是管理共享资源(更像是日志,包括读和写操作),在应用程序中的不同进程(因此也有多个线程)之间。数据还应该在系统重启后持久化,因此它应该是一个物理文件/数据库。
共享资源是一些具有键值信息的数据。(因此,可以对此共享资源进行的可能操作是添加新的键值信息,更新/删除现有的键值信息。)
因此,我考虑使用XML文件来物理存储信息,示例内容将如下所示:
<Root>
   <Key1>Value</Key1>
   <Key2>Value</Key2>
   <Key3>Value</Key3>
</Root>

进行读取和操作的界面将如下所示:

    public interface IDataHandler
    {
       IDictionary<string,string> GetData();
       void SetData(string key,string value);
    }

我可以假设数据不会超过500 MB,因此决定使用xml,如果数据增长了,我会将其移动到数据库中。同时,与读操作相比,数据的写入量更大。
与上述情况相关的一些查询/设计考虑如下: 在一个xml文件中处理500 MB的数据是否可行? 假设文件为xml,现在如何考虑性能问题? 我正在考虑使用缓存(.Net中的MemoryCache类)将数据作为字典缓存,这将使读取操作达到更好的性能。是否可以在内存中缓存500 MB的数据,或者是否有其他选项?
现在,如果我使用上述缓存机制,在写操作期间应该发生什么:
每次写操作时,是否应该将字典内容再次转换为xml进行写入?

将整个字典转换为XML?还是有办法只更新数据被修改/添加的XML文件的部分内容?或者其他处理此情况的方法?

我是否应该通过将写操作放入队列并在后台线程中读取队列并启用实际的写操作来提高性能,以便实际编写数据的人不会因为写入文件而受到影响?

为了处理多线程场景,计划使用带有全局名称的互斥体,是否有更好的方法?

我确定,我正在做出一些假设,并试图从中构建,如果我的某些假设是错误的,则大部分设计概念都会改变。因此,完全新的解决方案也是欢迎的(以性能为主要标准)。谢谢。


这听起来非常复杂且容易出错。为什么不使用数据库呢?数据库可以解决持久性、一致性、崩溃一致性、备份和高可用性等问题。 - usr
我会将数据库作为备选方案,但是与其直接考虑数据库,我也想了解其他选项的优缺点,因此开始使用XML/文本文件。如果您认为基于文件的方法容易出错,请详细解释一下,至少对我来说这将是一个新的学习机会。 - srsyogesh
@usr:忘了提到,要开始的话 - XML 大小不会超过 1MB 左右,但可能会在未来几年内逐渐增长。我不想在最初阶段投资于数据库解决方案,但必须保持我的设计开放,以支持未来的发展。您认为我做出了错误的决定或假设吗? - srsyogesh
你必须对我提到的所有问题都有一个答案。这需要时间,并且构建过程容易出错。例如,如果在编写新数据库版本时进程死亡(出现错误、蓝屏、断电),那么你现在只有一半的数据库。其余部分已经丢失了。虽然你可以安全地使其正常工作,但为什么要承担这个负担呢?你还谈到了线程和队列。你正在涉及危险领域。这对于学习项目来说是可以的,但对于商业项目来说,这是错误的解决方案。选择一个易于正确实现并通过构造保证安全的方案。 - usr
有没有特殊的原因使用XML文件? - Kuba Wyrostek
我推荐这篇文章,幸运的是,它特别涉及XML文件作为数据存储。http://www.joelonsoftware.com/articles/fog0000000319.html - Kuba Wyrostek
7个回答

3
由于您说“写操作比读操作多”,我猜测数据增长得更快,因此我的建议是从数据库设计开始。这不需要像MSSQL或MYSQL这样的完整功能数据库,您可以从SQL-Lite或MSSQL-Compact开始。这将为您的应用程序提供大数据处理能力,使其具备未来的扩展性。
将不经常更改的重读数据(例如配置)存储在RAM中是一种高效的方式。我的建议是使用一些缓存管理器,如MemoryCache或Enterprise Library Caching Block,这会节省您大量实现“线程安全”的数据访问和头疼 :) 的时间,而无需编写自己的代码。
public interface IDataHandler
{
   IDictionary<string,string> GetData();
   void SetData(string key,string value);
}

public class MyDataHandler : IDataHandler
{
   public IDictionary<string,string> GetData()
   {
       return CacheManager.GetData("ConfigcacheKey") as IDictionary<string,string>;
   }

   public void SetData(string key,string value)
   {
       var data = GetData() ?? new Dictionary<string,string();
       if(data.ContainsKey(key)) data[key] = value;
       else data.Add(key,value);

       CacheManager.Add("ConfigcacheKey", data);

       // HERE write an async method to save the key,value in database or XML file
   }
}

如果您使用XML,则不需要每次将字典转换为XML。在XmlDocument / XDocument对象中加载XML文档,使用XPath查找要更新值或添加新元素的元素,并保存文档。
从性能角度来看,除非您执行一些疯狂的逻辑或处理非常大(我指非常大)的GB数据,否则建议您使用已经可用的经过战斗测试的组件(如数据库、CacheManagers),这些组件会将线程安全操作抽象出来,以便快速完成应用程序。

2

我认为有两种可能的解决方法:

  • 使用数据库。在我看来,这是首选的方法,因为这正是数据库的设计目的:多个应用程序并发读写访问。
  • 使用“服务”应用程序来管理资源,并可被其他应用程序访问(管道、套接字、共享内存等)。

需要记住的关键点:

  1. GlobalMutex在多台机器上不起作用(XML文件可能位于网络共享上。如果不能将其排除为“不支持”,则不应使用Mutex)。
  2. “锁定文件”可能会泄漏锁定(例如,如果创建锁定文件的进程被杀死,则文件可能仍留在磁盘上)。
  3. 如果一个文件被多个进程重复更新,则XML格式非常糟糕(例如,如果每次访问都需要“加载-更新-写入”,则性能非常差)。

1
关于性能 - 当XML文件大小超过100MB时,速度非常慢。我的需求是在磁盘上读写数据(约1GB),读写操作可以并行进行。例如,数据来自1个线程,正在将其写入文件,并且另一个/相同的应用程序可以要求数据以显示在图表/其他UI上。我们转向二进制读写器,进行了性能分析,与XML相比,二进制读写器速度非常快(适用于更大的文件大小)。
现在我们已经转向HDF5,并正在处理20GB数据文件,同时进行读写操作。
具有全局名称的互斥锁应该可以工作,我们使用了相同的方法。

你在这种情况下是否使用了缓存?另外,你是如何处理仅更新部分数据或是与从/向文件中读取整个数据有关的问题的?你是否有性能分析的结果?如果你能更新你的答案并加上代码片段将会非常有帮助。 - srsyogesh

1
我会从一个单一的、轻量级的管理进程开始,该进程负责访问数据文件。其他进程通过管理员与之通信(即通过IDataHandler接口在此情况下通过.NET Remoting),并且永远不会直接操作文件。这样做不仅可以抽象出与多访问相关的问题,而且还可以获得一些功能:
  • 轻量级、简单的进程更可靠,并且在任何“使用者”进程失败的情况下不会损坏您的数据
  • 您只需维护一个代码来处理可靠性、锁定、共享等事项。
  • 每当您决定将XML切换到其他内容时,只需更改一个地方即可更改技术

你提到的功能我已经考虑过了,即实现接口的类名(作为单一职责原则的一部分)看起来像XmlDataHandler。我对这个设计感到满意,但不确定如何处理与xml加载、更新和写入操作相关的性能和可靠性问题以及相关的最佳实践。如果您也能就这些问题提供一些见解,那将非常有帮助。谢谢! - srsyogesh
许多专家已经建议使用抽象问题的数据库,而我正在考虑使用XML。但是,如果我在使用XML时得到了一些具体的答案,即解决问题的方法,那将是非常有帮助的。因为如果没有其他方法,我可以转向数据库,否则就要想办法实现需要用XML文件本身来完成所有事情。 - srsyogesh

1

数据库,毫无疑问。

如果您不想创建另一个服务器,只需在网络驱动器上使用SQLCE共享文件(只要您不需要超过256个并发连接)。

没有庞大的数据库支持,但是您可以获得强类型数据和使用数据库带来的所有其他好处,例如索引、哈希、行版本等。

即使别无选择,也要避免每次查找(或更新、删除或添加记录(如果您需要唯一键))时对整个文件进行线性扫描。

您实际上正在编写哈希表,将键映射到值。不要使用元组数组的数据存储等效物。使用真正的永久存储。

如果可以使用XML文件(如果能够很好地使用),则仅有的优点是人类可读性和可编辑性(如果这是一个优点...... SSMS难以使用吗)?

缺点:

1) 线性扫描所有查询 2) 应用程序级别没有安全或密码访问...任何人都可以编辑此XML文件。 SQLCE可以加密和密码锁定。 3) 未经类型化的数据。 4) 冗长的格式(严肃地说,JSON会更好,更快,更小,更有类型,并且易于阅读)。 5) SQL> XPath / XSLT 6) 如果您的数据需求增长,您具有内置的约束和键。

我想不出比SQLCE实例更高效、开销更小的解决方案。


1
首先,你必须忘记在高性能系统中使用XML。我建议使用JSON。它是轻量级的,并且许多高性能需求的应用程序(尽管不是所有数据)都使用JSON存储其数据。
最好尝试使用NOSQL基于文档的数据库,而不是关系型数据库,因为它们专门设计用于高性能系统,其中一些可以保存原始JSON格式数据。我建议选择MongoDB(具有C#驱动程序并支持LINQ)。还有许多其他基于文档的NOSQL数据库。但我没有使用过它们。
对于并发,您可以使用其中一个并发集合,特别是ConcurrentDictionary,这样您就不必担心同步问题。

谢谢提到ConcurrentDictionary。 - srsyogesh

1

基于这个Stackoverflow答案的设计原则来构建你的解决方案:

如何有效地异步记录?

正如你在其中一个考虑中提到的那样,以上解决方案涉及线程和排队。

此外,你可以使用BinaryFormatter来获得更好的性能,而不是将数据序列化为XML。


嗯,我还没有决定写作部分。这就是我不确定最好的方法是什么的地方。我心中有几个选项,可以使用XmlDocument / XDocument / Serialization来进行写作。正如你建议的那样,也会添加一个新项目 :) 我会检查这个的。谢谢。 - srsyogesh
请写下您对此的看法,或接受上面的答案以通知我们其他人。 - John Jesus
当然没问题,期待其他专家的意见或建议。 - srsyogesh

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接