有没有一种处理互联网上大型数据集的设计模式?

14
我正在寻找一种处理互联网上大型数据集并定期更新这些对象的设计模式。我正在开发一个应用程序,将在UI中同时显示数千条记录。此外,这些对象上的各种属性非常短暂,并且需要在客户端上更新,以使用户了解系统中这些记录的变化状态。我有一些想法来解决这个问题,但是想知道是否有一种设计模式(或多种模式)可以处理这种情况。
限制条件:
1.此客户端是使用Silverlight编写的。
2.对象本身不是很大(约15个值类型和字符串属性),但查询所有数据很昂贵。这15个属性包含来自各种来源的数据;没有聪明的连接语句或索引可以加速查询。我考虑仅在初始加载时填充部分属性,然后在用户缩放到给定对象组的情况下填充更昂贵的详细信息。类似于Google地图,但它显示的是对象而不是街道和建筑物。
3.我将能够限制正在更新的成千上万个对象的部分。但是,我需要用户能够从允许粒度更新的上下文“缩小”到显示所有数千个对象的上下文。当对象离开足够的缩放上下文时,更新将再次被禁用。
如何解决这个问题的想法?就像我提到的那样,我已经考虑了一些想法,但是到目前为止,没有任何东西让我对此项目的成功有信心。

我认为最困难的部分可以归结为两个方面,可能需要两种不同的模式/实践/策略:

  1. 通过互联网加载大量记录(约5k)。
  2. 通过互联网保持这些对象的子集(约500)更新。

对于其他所有内容,都可以使用几种设计模式。

编辑2:

感谢提供有关Silverlight中各种“推”实现的链接。 我发誓套接字已经从Silverlight中删除,但在下面的答案中找到了基于Silverlight 3的参考。 对我来说,这真的不是一个很大的问题,而且我没有花太多时间研究它,因此我正在编辑原始文本。 无论更新是通过轮询还是通过推送传输,一般的设计问题仍然存在。 很高兴知道我有选择的余地。

编辑3:有关推送技术的后续

正如我所怀疑的那样,Silverlight WCF双工实现类似于comet-like push。 这不会扩展,并且有许多关于它在现实世界中不起作用的文章。

Silverlight中的套接字实现有几个缺陷。 由于Web服务器可能位于任何给定的客户端防火墙后面,不允许非标准端口,并且Silverlight套接字无法连接到80、443等端口,因此看起来它将在我们的场景中没有用处。

我仍在考虑以有限的方式使用WCF双工方法,但轮询似乎是答案。

编辑4:找到解决我的问题一半的模式

我发现了一个名为此模式(PDF)的模式,它展示了如何使用迭代器模式从服务器检索数据页面并将其呈现为简单的迭代器。在 .Net 领域中,我想这将被实现为 IEnumerable(样本代码是基于 Java 和 Oracle SQL)。特别让我感兴趣的是异步页面预取功能,即在客户端缓冲结果集。由于 5k 个对象无法一次性放到屏幕上,所以我可以使用一种策略来不获取全部内容,但又能隐藏该实现细节以避免影响 UI。应用程序将检索核心对象存储在数据库中,然后需要其他查找才能完全填充这些对象。这种方法似乎是快速将某些数据传输到客户端的好方法。

我现在正在考虑使用此模式 + 某种代理对象模式来监听结果集的增量,并相应地更新对象。这里有几种可行的策略。我可以预先加载所有数据,然后发送更改的增量(这可能需要子系统中的一些额外代码来提供更改通知)。这可能是我的第一个尝试。我还在寻找其他方案。谢谢至今提供的所有想法。


非常有趣的问题!我肯定很想有机会参与这样的工作。我可以问一下你正在构建什么吗? - andyp
这是我们尚未发布的专有系统(显然),我已经模糊了一些细节。总的来说,它是一种监视管理大量“设备”应用程序中正在发生的情况的方法(当前最大安装约为5k)。此工具将允许管理员查看整个“设备网络”,并发现问题,然后在有问题的区域采取行动。很抱歉我不能更具体地说明。当我们发布时,我可以向您展示它! - Jason Jackson
8个回答

2

代理设计模式是一种有助于将数据从一个地方传输到另一个地方的模式。 代理设计模式将使您能够像处理本地对象一样处理远程对象。


我认为这个通用模式可能有效,但我遇到的问题有点更具体。不过,这给了我一个想法,即在服务器上将所有对象缓存起来并在那里更新它们,然后在客户端轮询时为代理提供增量。 - Jason Jackson
更明确地说,在这种情况下,代理自然会断开连接,因此需要保持一些状态。我不确定更新和初始加载的最佳模式是什么。我已经与同事讨论了这个问题,一个想法是使用存储库/工厂模式来提供对象,并且这些对象将充当代理,使其自身数据无效并在某个常规周期内回调到存储库(因此回调到服务器)。 - Jason Jackson

2

提出两种解决方案:

1)在传输之前压缩您的集合,并在传输后进行解压缩。

2)使用Flyweight +代理模式。


我打错字了 - 应用程序将加载约5000(5k)个对象,而不是5000k个对象。我已经将这么多对象加载到内存中,Silverlight可以很好地处理它。我正在考虑使用代理模式的某个版本,但在这里使用享元模式并没有什么好处。 - Jason Jackson
我最终确实使用了一种享元模式,通过使用Bean而不是将复杂对象图提供给Silverlight。 - Jason Jackson

2
我想知道是否可以在一开始就减少发送到客户端屏幕的数据量?因为你无法一次看到5000个数据点。如果需要滚动查找重要内容,考虑从一开始就过滤掉非重要内容。考虑使用一些UI设计(仪表盘和仪表类型),使用户只看到问题区域。然后他们可以深入了解并根据需要采取行动。
我知道你不能透露细节,我做了很多假设,并且这不是对你问题的直接技术答案 - 但重新思考必要的数据源可能有助于将您推向更高效的后端和前端方向。

UI需要是一种“Google Maps”界面,因此我们肯定需要显示所有数据点。你说得很对,我正在考虑最后加载屏幕外数据或延迟加载它。 - Jason Jackson

1

我在这里发现一篇文章,似乎解释了如何在Silverlight 2中创建sockets。Silverlight 2 and System.Net.Sockets.Socket

我并没有深入阅读它(现在已经有点晚了),但是它似乎可以用于你的情况。我看到的主要限制是,你的Silverlight应用程序只能连接到从服务器下载它的位置。

这里有一个关于Channel 9的教程Silverlight using socket

希望这会有所帮助。


我对Silverlight中的套接字进行了相当多的研究。有一些严格限制阻止我使用它们。其中一个限制是它们将工作在非常小的端口范围内,这似乎很愚蠢。 - Jason Jackson

1

总的来说,我认为回答你的问题并没有一个或多个设计模式可以真正解决你的问题。相反,这是一个相当大规模的应用程序,需要大量的规划和设计工作。

在设计时,我认为你会遇到一些可能在小范围内帮助你的DPs,但这个巨大的东西应该如何工作的细节更多的是一个普遍(而且有趣)的设计问题。

也许稍微澄清一下你的问题可能会帮助人们给出关于系统整体设计的建议。此外,一旦你付出了一些设计/努力来想出一个高层次的设计,你可以要求批评/建议。对于某人来说,完全回答StackOverflow问题是很难的。 :)


1

如果我理解正确,这里实际上有两个问题:

  1. 系统状态由来自多个数据源的数据表示。因此,查询状态的结果是昂贵的。
  2. 描述系统状态的数据量很大。因此,查询描述状态的所有数据也是昂贵的。

解决这些问题的标准模式是引入中间层并使用增量更新状态。例如:

  1. 显然,您不希望Silverlight客户端直接与后端系统通信。这不仅可能不可行,而且非常低效,因为每个客户端都可以向同一数据源询问其状态。为了避免这种情况,标准解决方案是引入一个中间层,聚合来自所有后端数据源的数据,并为客户端提供公共接口。结果是后端数据源将只在需要时轮询(可以在中间层中针对每个数据源进行配置),并且客户端不必处理那些后端数据源的具体细节。此外,您还可以根据客户端最常用的查询实现中间层中的数据索引。

  2. 假设每条记录都有ID,则客户端应仅请求自上次更新以来的增量。其中一种模式是使用时间戳。例如,当客户端初始化时,它会请求系统的状态,中间层会发送该状态和时间戳。当客户端需要更新某些记录时,它会在请求中提供ID和上次更新的时间戳。因此,中间层将仅发送自该上次时间戳以来的更改,并且仅针对请求的ID。如果对象有15个属性,但自上次时间戳以来仅更改了其中3个属性,则更新将仅包含这3个属性的值。

关于推送与轮询 - 推送并不总是最佳解决方案。这实际上取决于客户端需要更新的频率和客户端/中间层之间的流量之间的权衡。例如,如果状态更改频繁但稀少(例如仅影响几个属性),并且没有立即更新客户端状态的要求,则客户端可能更喜欢累积更改而不是接收每个单独的更新,因此轮询将是更可取的选择。

我同意在中间层跟踪增量对于该应用程序的性能至关重要。我们确实有一个类似于您在第1点中描述的中间层,但它大多是无状态的。 - Jason Jackson

0

我认为您可能遗漏了一些内容:自 Silverlight 3 开始,可以将数据推送到客户端。 这里有一篇文章可能对此有所帮助。


我曾经考虑过那个方法,但它并不是真正的推送(尽管它可能有效),也没有真正解决我的设计问题。服务器系统在查询子系统之前不知道更新已准备好,这意味着服务器端仍然需要轮询。我可能会在服务器上缓存结果以加快速度...感谢提供链接。 - Jason Jackson
一个后续:我更深入地研究了那个轮询双工类。它看起来有实际的可扩展性问题。 - Jason Jackson

0

我给了一些好的答案赞,但是通过对后端数据进行一些更改并使用新的方法从Silverlight检索数据,我提出了一个解决方案。以下是我们正在采取的措施:

  1. 我正在使用bean来表示大数据图。这样可以减少很多传输XML。我只关心数据的一个子集,尽管它是一个相当重要的子集。通过将数据展开成一个bean,我认为我已经将序列化对象的大小削减到原始对象图的20-25%。
  2. 现在后端的几乎所有数据都有一个字段来记录最后修改时间。我已经能够获取所有大数据的最后修改时间。虽然有一些数据没有这个字段,但是查询性能和数据聚合的真正问题都得到了解决。作为其他人的一般解决方案,看起来这在许多DBMS中实现起来相当简单。
  3. 我正在编写新的API来检索在提供的DateTime之后更新的数据。这使我能够仅查询来自后端系统的新对象和更改对象(这是Web服务调用这些API,Silverlight调用Web服务)。
  4. 在Web服务中聚合更改并检测数据图的某个部分是否已更改。为了简单起见,如果有任何更改,我只发送整个数据图。这实际上是最难解决的部分。数据图的一部分可能具有新的更新时间,但是图的核心对象尚未更新。最终,我不得不编写API来查找子对象的更改,然后编写API来基于这些子对象查找根对象(如果它们已更改)。可以返回带有未自上次轮询以来未更新的根对象(实际上是数据图的大部分)的对象图。Web服务逻辑正在查询少量更改,因此即使单个查询不便宜,它们也可能仅运行几次每个轮询。即使在我们产品的非常大的安装中,这个查询循环每个轮询周期只会运行10或20次(请参见下面关于我的轮询解决方案的内容)。虽然我们的系统非常动态,但在30秒内并没有那么多变化。处理所有这些的Web服务调用对初始加载调用和轮询调用做出相同的反应。它所关心的只是检索比给定时间更新的数据。
  5. 我编写了一个从ObservableCollection继承的集合,它处理查询和轮询。使用此集合的客户端代码提供一个查询数据的委托。日期是异步返回的,并且按页面返回。我还没有确定页面大小。它会一直重新查询页面,直到服务器返回小于最大页面大小的页面。该集合还提供了有关如何确定集合中最新对象的最新日期的信息。它会定期轮询更新,这些更新比集合中最新项目更新。实际上,这个“最新日期”实际上是一个包含原始对象图的各个部分的几个日期的对象。如果从服务器返回了一个已存在于集合中的项目,则使用返回的数据更新集合中的项目。我之所以这样做,而不是插入新项目并删除旧项目,是因为它在更多的数据绑定情况下起作用。

这种模式可以改进。我可以仅向Silverlight发送更改的增量。我仍然可以尝试使用某种推送技术。但是,这个解决方案为我提供了一个Web服务调用,可以返回各种情况的数据。轮询也非常简单,只有一个东西在进行所有数据检索。没有太多的移动部件。通过相同的机制,它处理对象状态的更改,既在初始数据加载期间,也在轮询期间。这似乎也很好扩展。初始调用似乎是最昂贵的,随后的调用运行得越来越快。我认为这是因为留在后端的数据随着每次传递而变得越来越小。

我对此实现还有一个问题,我已经在这里发布

感谢所有建议。虽然我没有听从所有建议,但一些想法直接帮助了我或引导我思考如何以不同的方式使其工作。


请问您能否澄清/解释一下“beans”是什么意思? - Buzz
Bean是指一种持久类,旨在供UI使用。标准的业务/模型类可能对UI使用不方便,因此将其值填充到bean中,并由UI使用。在我的情况下,服务器上的业务层有各种具有相当丰富对象图的模型对象。我将它们转换为bean以供Silverlight使用。 - Jason Jackson

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