65.9K
CodeProject 正在变化。 阅读更多。
Home

部署、SOA 和可伸缩性

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.55/5 (7投票s)

2015 年 5 月 29 日

CPOL

18分钟阅读

viewsIcon

6733

部署、SOA 和可伸缩性

我最近在论坛上看到一个关于 SOA(面向服务架构)和一个非常“奇怪”的部署策略的问题,其中每个 Web 应用程序都包含一份相同服务的副本,而不是共享该服务。

这个问题让我想到了 SOA 的可伸缩性以及新的部署策略(或者说,是旧的但被重新发现的策略)。事实上,这是两个不同的主题,我可以在完全不同的帖子中讨论它们,但是,既然我同时考虑了它们,所以我也将它们一起发布。

部署

关于如何组织 DLL、服务和依赖项,实际上存在两种“对立”的思路。一种是最大化重用,因此每个文件/库/服务只存在一个副本,并被许多不同的应用程序引用;另一种是隔离事物,将所有必要的文件包含在每个应用程序中,如果许多应用程序需要相同的文件,实际上就会有相同文件的许多副本。

拥有相同文件的许多副本可能看起来是个糟糕的主意,但这种策略正变得越来越受欢迎,而最大的问题是:为什么?

简短的答案是简单性。为了有个概念,以下是一些优点

安装

安装应用程序时,无需验证依赖项是否存在。只需将所有文件放在一个文件夹中即可正常工作。

卸载应用程序时,无需验证共享文件是否被其他应用程序使用(以避免删除它),也无需担心丢失永远保存在共享文件夹中的未使用文件。只需删除包含所有依赖项的应用程序文件夹即可正常工作。

更新应用程序及其依赖项时,无需担心库的新版本是否具有可能影响其他应用程序的破坏性更改。只要正在安装的应用程序能与此特定库版本正常工作即可。其他应用程序将不会使用此特定版本的库。

开发

从“共享”资源(一个可被一个以上应用程序使用的资源,即使它实际上被复制了)的开发人员的角度来看,当不需要保持向后兼容性时,重构库会更容易。当然,我们必须尽量避免过度更改,因为我们不希望人们因为库变化太大而放弃它,但我们可以进行必要的重构,而无需提供“过时”的包装器来保持旧代码正常工作。

从使用“共享”资源(应用程序)的开发人员的角度来看,我们可以确定,即使其他应用程序安装了同一库的不同版本,也不会影响到我们的应用程序,因为我们的应用程序将继续使用其本地库副本,而不是随时可能更改的共享副本。

好吧,我实际上还有一些要点,比如保留库中过时函数的影响,但我认为这足以说明为什么拥有任何依赖项的多个副本可能比使用真正“共享”的副本更好。

然而,这种方法存在两个主要问题

  1. 在许多情况下,我们会将同一个文件复制很多很多次给不同的应用程序。也许文件系统可以检测到这一点并重用相同的内容,但默认情况下,当许多应用程序需要相同的 DLL 或文件时,我们会浪费空间;
  2. 安全修复更难应用。使用共享解决方案,只需“应用修复”,所有使用共享库的应用程序都将获得改进的安全性。使用隔离方法,您可能已经更新了 10 个应用程序,但仍有其他应用程序引用了旧版本的库。实际上,获取修复的时间也增加了:DLL 的发布者发布新版本的 DLL 并不够。使用该 DLL 的每个应用程序都必须由该应用程序的发布者更新,而许多情况下发布者是不同的。

然而,第二点又变成了库/共享资源开发人员的优势。如果安全修复导致了破坏性更改(这是很常见的情况,因为大多数安全修复只是阻止某个操作发生或在函数调用中添加额外的参数以提供安全信息),那么测试和修复其应用程序就变成了应用程序发布者的责任。如果应用程序在新版本的 DLL 中无法正常工作,那么由他们自行决定是让应用程序保持脆弱,还是如果应用程序在使用新版本 DLL 后崩溃,则归咎于他们。

如果资源是真正共享的,那么共享资源编写者将有责任保证没有任何内容损坏。这通常会使安全修复变得复杂,因为可以通过额外参数完成的事情可能需要使用一些(线程)静态数据或其他类型的解决方法来保持现有 API 不受影响。

当然,我不是在讨论那些我们无法拥有给定库/资源的单独副本的情况。我只是在试图展示在可能的情况下,使用副本而不是共享文件的优点。

(Web) 服务和 SOA

到目前为止,我主要关注库而不是服务。在许多情况下,我们可以说拥有一个库或一个服务可以达到相同的结果,除了在性能和异常隔离方面存在一些差异。也就是说,就像我们可以将一个库复制到许多不同的应用程序中一样,我们可以将一个服务复制到许多不同的(Web)应用程序中。

然而,正如我刚才所说,在某些情况下,我们确实无法让同一服务的多个实例并行运行,所以这实际上成为了创建服务的理由。也许我们想在两个或多个应用程序之间共享数据库。也许我们只是想在内存中缓存数据,而内存中有多个副本会导致过高的内存消耗,并且如果其中一个实例更新了数据而其他实例不知道该更改,则可能导致不一致。当然,每种情况可能都有替代解决方案,但我想说的是,某些服务必须共享,而拥有多个副本根本行不通。

那么,我们如何确定一个服务是否可以共享呢?

在我看来,任何可以按应用程序复制的服务都应该首先作为一个库存在。也许它必须被公开为一个服务,因为网页需要访问它的功能,但是,如果 Web 应用程序本身(在服务器上运行)需要该功能,最好避免调用服务的开销,并在本地调用它(我稍后会详细讨论这一点)。

只有当服务不能按应用程序复制时,它才应该作为一个真正的服务存在。但是,在这种情况下,已经知道它必须被许多应用程序共享而不是被复制。

那不是 SOA

我知道,说我们应该尽可能使用库与 SOA 相悖……或者至少与一些人对 SOA 的理解相悖。

SOA 的一些解释认为一切都应该是一个独立的服务。忘记不同的类。任何两个不相关的機能都必须由不同的 Web 服务完成。

也就是说,您的应用程序需要获取日期和时间?创建一个服务来完成这个任务。

您想保存文件?创建一个服务来完成这个任务。

保存文件时,必须获取日期和时间?不要让保存文件的服务自己获取日期和时间。它必须调用那个只负责获取日期和时间的另一个服务。

您想验证访问权限?嗯,不同的服务必须为此服务。

将一切作为不同服务的优点

在开始这个话题之前,请记住,我不认为将一切都作为不同的服务是个好主意,并且我更倾向于推荐使用库而不是服务。尽管如此,我将阐述一些支持该观点的人所使用的观点。

第一个优点是隔离(至少当你将每个服务放在自己的进程中时)。这个单一的优点实际上可以分解成许多小的优点,例如

  • 如果在某个服务中存在内存泄漏,只有持有该服务的进程才会开始使用越来越多的内存。这实际上使得识别问题源头变得容易得多。如果一个库被用作应用程序的一部分,那么包含一切的单一进程将是源头,而没有关于泄漏的真正责任者的线索;
  • 同样,如果使用了不安全的代码并发生了内存损坏,只有损坏内存的服务才会出现问题。所有其他服务(和进程)将继续正常工作。没有服务 2 因服务 1 损坏其内存而崩溃的风险;
  • 统计数据?嗯,它们也将按服务进行。这将更容易了解哪些服务消耗更多的 CPU、更多的内存、过于频繁地读写磁盘,从而帮助识别哪些需要优化或需要更好的硬件来运行;
  • 重置。考虑到某个东西真的崩溃了需要重启,嗯,只有崩溃的服务需要重启。所有其他服务都可以继续正常运行;
  • 嗯,可能还有很多,但我认为这足以说明这一点。

使用一切作为服务的第二个和第三个优点是可伸缩性和工作负载分布。由于每个服务都可以在不同的进程甚至不同的计算机上运行,如果发现服务器利用率已达到最大值,则可以获取另一台计算机,至少通过将一些服务放在一台计算机上,其余服务放在新计算机上来分摊负载。考虑到而不是单个应用程序有 10 个服务的情况,这意味着我们可以使用多达 10 台不同的计算机来分摊工作负载,而且这还没有考虑到服务器场(其中 2 台或多台服务器实际运行相同的服务)。

可伸缩性和分布问题

我无法对隔离的好处说任何坏话。它确实非常有效。

我认为 SOA 的问题(至少当一切都是不同的服务时)是可伸缩性和工作负载的不必要分布。我知道,这看起来我自相矛盾,因为我刚说第二个好处是可伸缩性,但那是因为我只是在阐述许多人的想法。而这正是我实际上不同意的观点。

同一台计算机上两个进程之间的通信比调用同一进程中的函数慢。更糟糕的是,两台计算机之间的通信速度要慢得多。因此,当每个服务确实做了很多工作时,通信成本可能不是问题。与花费时间做所有工作相比,花费一些时间与另一台计算机通信然后可以做其他事情要好。但是,当通信是为了小动作时,我们可能在通信上花费的时间更多(甚至消耗更多内存),而不是在本地完成工作。

我使用了将日期和时间作为服务的一个例子。如果我们真的需要确保所有访问都具有毫秒级的精确日期和时间(或保证我们永远不会得到完全相同的值,例如),也许我们真的需要一个单一的服务来处理。但在大多数情况下,这并不是我们需要的。所以,有些人会说,这就是服务的额外好处所在:每个服务器都可以托管相同的日期和时间服务,因此它们可以进行本地通信而不是远程通信,并且任何在服务器之外运行的应用程序都可以与任何服务器通信以获取日期和时间。也就是说,我们可以有一个日期和时间服务组成的服务器场。

然而,在我看来,我们可以做得更好。如果每台服务器都可以托管日期和时间服务,这意味着运行在这些服务器上的其他所有服务都可以直接获取日期和时间(也许使用共享或复制的库),并完全避免通信开销。请注意,直接获取日期和时间比简单地发送消息(即使是同一台计算机)来请求日期要快得多。事实上,当服务在同一台计算机上运行时,该计算机仍然会花费其 CPU 时间来完成工作,这意味着计算机要么只花时间获取日期,要么创建通信消息并也花时间获取日期。因此,在这种情况下,将一切作为服务并不能提高可伸缩性。实际上,它会起反作用,使用更多资源来完成相同的工作,使操作变慢,并且可伸缩性更差

类比?

如果您喜欢类比,我可以这样说,与其看自己的手表来知道时间,不如问问您身边的同事,然后等着他看他的手表然后告诉你时间(就像本地通信),或者您可以打开您的电子邮件应用程序,给某人发一封电子邮件询问时间。在此期间,您可以做其他事情,但需要时间的那个操作会暂停,直到您收到一封返回的邮件告诉您时间。

那么,这些选项中哪一个更具可伸缩性?哪一个选项更好地“分配了工作”?

也许您可以说问您的同事时间更好地分配了工作,但它实际上增加了您的工作,并且给您的同事带来了一些工作。作为人类,如果我们想聊天,我们可能会这样做。计算机没有这种需求。

真实案例?

您可能会认为我谈论一个获取日期和时间的服务有点小题大做。然而,我可以说是最常见的情况之一。也许没有真正的服务,而是使用数据库服务器来获取正确的日期。也许有一个真正的服务。也许情况更糟,有一个获取日期和时间的服务,而这个服务又查询数据库以获取日期和时间。

想想看。考虑到事物的封装方式以及记录通常在屏幕上显示的方式,当创建一个新记录时,会向服务发出请求以获取日期和时间(第一次通信)。然后该服务从数据库获取日期和时间(第二次通信)。现在我们只有内存中的一个空记录,其中填入了日期和时间。然后,记录被编辑并保存,进行第三次通信。

导入一个大的文本文件,屏幕上没有任何数据显示。它可以非常容易地为所有记录填充相同的日期和时间,或者直接执行查询,但为了重用代码,会创建一个记录实例。也就是说,读取一行文本,创建一个记录(进行两次通信以获取日期和时间)。然后,填充记录并保存,进行第三次通信。最终,对于每个记录,它只需要一次数据库访问来插入,但实际上有 3 次通信,其中两次是到数据库的。

您认为这会如何扩展?

如果应用程序可以直接获取日期和时间(使用本地函数),那么我们每插入一条记录只需要一次通信,而不是三次,这样事情会不会更具可伸缩性?

我甚至不在这里讨论批量插入。只有一次数据库访问,而且获取日期和时间没有任何通信,数据库在达到限制之前可能会支持“双倍”的插入,并且与过度 SOA 方法相比,网络的使用量将只有三分之一。

服务器场

使用 SOA 时最大的可伸缩性提升之一是使用服务器场。实际上,两台或多台计算机(在某些情况下,甚至数千台计算机)并行运行相同的服务。这不仅提高了性能,还提高了服务的可用性,因为一台服务器可能关闭或重启,而其他服务器可以继续处理请求。

实际上,不一定需要使用 SOA 架构才能使用服务器场。您可以创建一个 Web 应用程序,并使用两台或多台计算机来托管它,完全忽略任何 Web 服务。然而,考虑到互联网的演变,大多数 Web 应用程序现在被分为 HTML、JavaScript 和由 JavaScript 访问的 Web 服务,所以我会继续谈论服务。

正如我之前所说,有些服务被创建是因为我们根本无法让它们并行运行。在这种情况下,服务器场将无济于事(并且试图这样做可能会导致各种错误)。那么,通常会发生什么?

通常,有一个计算机用作数据库服务器,整个服务器场连接到这台计算机。如果我们考虑 Web 服务所做的工作是数据库内部工作量的 10 倍的情况,这意味着一个高达 10 台计算机的服务器场可以由一台数据库服务器服务。将 11 台或更多计算机加入服务器场将无济于事(并且可能会迫使数据库在内存中保留过多的活动连接,从而降低第 10 台之后的每台计算机的服务器场优势)。

这里的问题是:我们如何衡量服务所做的工作与数据库所做的工作之间的比例?此外,我们还有问题,例如

  • 存储过程的使用:据说使用存储过程比使用常规数据库查询更好,因为它们在数据库内部进行了优化/准备,并避免了将过多的数据传输到客户端。然而,这样做意味着数据库要做更多的工作,服务要做的工作更少……而服务是受益于服务器场的。
  • 数据缓存:许多开发人员仍然不了解通信的成本,也不知道如何正确地缓存数据。如果服务器场上的每台计算机都有缓存的数据副本,当一台计算机更新数据时,服务器场上的所有其他缓存仍将拥有旧数据。在某些情况下,这是可以接受的,但在许多情况下则不行。与服务器场上的其他计算机通信以告知发生了更改将完全破坏缓存的优势。因此,“常见”的解决方案是缓存会 ping 数据库以查看记录是否有更改(大多数人认为这是一个小请求),如果有,则执行第二个查询来获取数据(实际返回数据的查询)。如果没有,则使用缓存中已有的数据。嗯,这里的错误是,ping 数据库以查看是否有更改所花费的时间几乎与直接从数据库获取数据所花费的时间相同。发送查询、定位记录和发送应答消息所花费的时间比应答的大小要多。也就是说,当缓存是最新的时,这种策略几乎没有带来任何好处,但当缓存不是最新的时,会使到数据库的通信量加倍;
  • 内存锁过时:当我们处理单个进程和多个线程时,我们可以使用锁来执行所谓的“原子”操作。当两台或多台计算机可能正在做相同的工作/处理相同共享数据时,我们无法使用内存锁。这意味着这些情况要么不受保护(最常见的情况,因为本地锁使单元测试通过,而没有提供真正的保护),要么锁必须在某个公共位置管理……通常是数据库,从而进一步增加了数据库的负载。

好吧,我并不是说没有办法让 SOA 奏效。有……而且证明就是我们有很多大型网站在运行。然而,仅仅创建一个服务器场并将服务放在那里并不能解决问题。有些服务无法并行运行。而数据库通常是其中之一。

我并不是说数据库不能从服务器场中受益。但通常会有限制。要么我们有可能在请求发送到错误的服务器时获取旧数据,要么我们必须强制来自某个区域的请求始终由同一台计算机提供服务(如果大多数请求来自同一区域,这将减少服务器场的优势),以及许多类似的问题。

最后,在因为“它可伸缩”而使用 SOA 之前,请考虑该特定服务是否真的可伸缩。创建新服务和使用 SOA 并不能免费带来可伸缩性。

暂时结束

嗯……这篇帖子的结束。也许我会在另一篇帖子里继续讨论 SOA 的问题。

如果这篇帖子在谈论库和服务的部署时失去了焦点,然后又谈论了 SOA 的可伸缩性问题,我感到抱歉……但是,嗯,这就是我脑子里所想的。

 

© . All rights reserved.