软件架构简介

软件架构和软件设计是同一主题的两个方面。两者都是关于如何构建软件以执行其任务。术语“软件架构”通常指软件系统的较大结构,而“软件设计”通常指较小的结构。

架构和设计之间的确切界限很难说,因为系统的架构也会影响其设计。较大结构的设计会影响较小结构的设计。

软件架构的定义如何与“分布式系统”一词相符?
软件架构提供了各种分布式算法可以运行的基本结构。是的,这两个术语之间存在一定的重叠,但是各种不同的分布式算法可以在相同的底层架构之上运行。

软件架构还受到整个系统的硬件架构(软件+硬件)的影响。根据您使用的硬件,您可能需要不同的架构(以及设计)。或者,您可以根据您的架构选择不同的硬件。

架构有许多不同类型,但某些架构模式比其他模式更常见。以下是常见软件架构模式的列表:

  • 单一进程。
  • 客户端/服务器(2 个进程协作)。
  • 3 层系统(链中协作的 3 个流程)。
  • N 层系统(N 个流程在链中协作)。
  • 面向服务的体系结构(许多进程相互交互)。
  • 对等架构(许多进程在没有中央服务器的情况下进行交互)。
  • 混合架构 - 上述架构的组合。


进程通信渠道
进程通常具有三种可以相互通信的介质。这些都是:

  • 网络
  • 磁盘
  • 管道

进程可以通过计算机网络相互通信。通过这种介质,进程可以与运行在同一计算机上的进程通信,或者与运行在不同计算机上的进程通信,前提是运行进程的两台计算机与计算机网络连接。

在同一台计算机上运行的进程还可以通过计算机的硬盘(或其他磁盘,如 USB 磁盘等)相互通信。进程 A 可以将文件写入磁盘,由进程 B 处理。进程 B 也可以将回复发送回写入磁盘的文件中,然后进程 A 读取该文件。

进程还可以通过网络存储进行通信,网络存储本质上是连接到计算机网络的硬盘。这样,进程还可以通过网络和磁盘通信的组合与不同计算机上运行的进程进行通信。

根据进程运行的操作系统,在同一台机器上运行的进程也可以通过管道相互通信。管道是操作系统为进程提供的通信通道。通信的方式类似于网络通信,但交换的消息保存在计算机内部的 RAM 中。管道可以比网络通信更快,因为当通信进程在同一台计算机上运行时,可以消除大量网络协议开销。

进程还可以通过 RAM 磁盘进行通信,RAM 磁盘是分配在计算机 RAM 中的虚拟硬盘。RAM 磁盘对于进程来说看起来像磁盘,但比磁盘快得多,因为数据仅存储在 RAM 中。

进程通信模式
进程可以通过以下任一方式相互通信:

  • 同步模式。
  • 异步模式。

当进程A与进程B同步通信时,意味着进程A向进程B发送消息并等待B回复。进程 A 在收到进程 B 的回复之前不会执行任何操作。

当两个进程异步通信时,进程会互相发送消息,而不等待对方回复。进程 A 可能会向进程 B 发送消息,然后继续进行其他一些工作。在某个时刻,进程 B 将消息发送回进程 A,进程 A 在进程 A 有时间处理该消息时处理该消息。

同步和异步通信具有不同的优点和用例。您可以使用异步通信来实现同步通信,也可以使用同步通信来实现异步通信。

单进程架构
由单个运行进程组成的软件系统被称为具有单一进程架构。或者简单地说它是一个单一的过程。您也可以将其称为 1 层架构。单进程应用程序通常也称为独立应用程序。

单进程应用程序的常见示例有:

  • 命令行程序。
  • 没有网络通信的桌面应用程序。
  • 无需网络通信的移动应用程序。

在当今世界,越来越多的应用程序以一种或另一种方式开放进行通信。命令行应用程序也许能够处理另一个命令行应用程序的输出。桌面应用程序可以通过互联网进行自我更新,并向远程错误数据库报告错误。移动应用程序可以通过意图(Android)等与安装在同一手机上的其他移动应用程序进行通信。

计算机架构
计算机的体系结构影响软件体系结构。事实上,由于计算机是软件架构的一部分,因此计算机架构也是软件架构的一部分。您的软件架构位于计算机架构之上。

CPU 执行指令。RAM 存储指令和指令所处理的数据。磁盘驱动器可以在系统重新启动之间永久存储数据,而 RAM 通常会在计算机重新启动时被删除。NIC 将计算机连接到网络,以便它可以与其他计算机进行通信。总线将 CPU 与 RAM、磁盘驱动器和 NIC 连接起来,以便它们可以交换数据和指令。

中央处理器
CPU(中央处理单元)是执行所有指令的部件。CPU 的速度越快,计算机执行指令的速度就越快。

有些 CPU 有多个核心。每个核心的功能就像一个单独的 CPU,可以独立执行指令,而不管其他核心在做什么。如果您的计算机 CPU 有多个内核,则在设计软件架构时应该考虑如何利用所有内核。

CPU 通常具有有限数量的高速缓存,这是只有 CPU 可以访问的内存,并且比普通 RAM 快得多。

内存
RAM(随机存取存储器)可以存储 CPU 执行的指令以及这些指令正在处理的数据。计算机的内存越多,它可以存储的数据和指令就越多。当计算机关闭或重新启动时,RAM 通常会被清除。它通常不是永久存储(尽管永久 RAM 实际上存在)。

当缓存通常驻留在磁盘上的数据或通过网络(使用 NIC)从远程系统读取的 数据 时,计算机中拥有大量内存非常有用。

内存的速度决定了在其中读取和写入数据的速度。越快越好。

磁盘驱动器
磁盘驱动器可以像 RAM 一样存储数据,但与 RAM 不同的是,即使计算机关闭,数据也会保存。磁盘驱动器的访问速度通常比 RAM 慢得多,因此如果您需要处理大量数据,最好将这些数据保存在 RAM 中。

磁盘驱动器的速度决定了从中读取和写入数据的速度。越快越好。磁盘的速度由两个数字组成:搜索时间和传输时间。搜索时间表示磁盘搜索到磁盘上某个位置的速度。传输时间表明磁盘位于正确位置后传输数据的速度。

有些磁盘具有一定的读取高速缓存 RAM,以加快从磁盘读取数据的速度。当从磁盘请求数据块时,磁盘会将更大的数据块读入缓存,希望下一个请求的数据块位于磁盘缓存内存中存储的数据内。

某些类型的硬盘的工作方式更像内存。其中包括 SSD(固态硬盘)。由于 SSD 的工作方式类似于内存,因此搜索时间非常短。每个存储单元都可以直接寻址。如果您的软件要从磁盘上的不同位置进行许多小型读取,那么这非常有用。SSD 的传输时间通常也高于普通硬盘。

网卡
NIC(网络接口卡)将计算机连接到网络。这使得计算机能够与其他计算机进行通信,例如通过互联网。NIC 的速度决定了计算机与其他计算机通信的速度。当然,其他计算机中的网卡速度以及它们之间的网络设备也很重要。

Bus总线
总线将 CPU 连接到 RAM、磁盘驱动器和 NIC。美国的速度影响组件交换数据和指令的速度。当然,组件本身的速度也会影响这一点。

其他设备
这里省略了键盘、鼠标、显示器、USB 设备、声卡和显卡等设备。这些设备通常不会对您的软件架构产生太大影响(除非您正在做电脑游戏或类似的事情)。

客户端-服务器架构
客户端/服务器架构也称为2层架构。客户端正在与代表客户端执行某些服务的服务器进行通信。

客户端/服务器通信的常见示例是:

  • 桌面应用程序到数据库服务器的通信
  • 浏览器到 Web 服务器的通信。
  • 移动设备到服务器的通信。
  • FTP 客户端到 FTP 服务器的通信。

将客户端/服务器应用程序的所有业务逻辑嵌入客户端应用程序有一些缺点。首先,当两个桌面应用程序尝试同时更新数据库时,它会导致潜在的竞争条件(并行化问题)。如果两个应用程序同时读取一条记录、更新它并再次保存它,则更新后的记录将保存在数据库中的哪个版本?

胖客户端应用程序的另一个问题是客户端应用程序必须安装在每台客户端计算机上。过去必须手动完成,但现在存在可以在台式计算机上安装应用程序的自动安装系统。这样就可以集中管理应用程序(包括更新)。

2 层胖客户端应用程序的缺点促使软件开发人员转向 3 层和 n 层架构。

N层架构
N 层架构意味着将系统分为 N 层,其中 N 是 1 及以上的数字。1 层架构与单进程架构相同。2 层架构与客户端/服务器架构等相同。
三层架构是一种非常常见的架构。3 层架构通常分为表示层或 GUI 层、应用程序逻辑层和数据层。

表示层或 GUI 层包含应用程序的用户界面。表示层是“哑的”,这意味着它不做出任何应用程序决策。它只是将用户的操作转发到应用程序逻辑层。如果用户需要输入信息,这也在表示层中完成。

应用程序逻辑层做出所有应用程序决策。这就是“业务逻辑”所在的位置。应用程序逻辑知道什么是可能的,什么是允许的等等。应用程序逻辑在数据层中读取和存储数据。

数据层存储应用程序中使用的数据。数据层通常可以安全地存储数据、执行事务、快速搜索大量数据等。

Web 应用程序是三层应用程序的一个非常常见的示例。表示层由 HTML、CSS 和 JavaScript 组成,应用程序逻辑层以 Java Servlet、JSP、ASP.NET、PHP、Ruby、Python 等形式运行在 Web 服务器上,数据层由一些数据库组成。种类(mysql、postgresql、noSQL 数据库等)。

丰富的互联网应用程序 (RIA)
在第一代 Web 应用程序中,大量 HTML 以及部分 CSS 和 JavaScript 是由在 Web 服务器上运行的脚本生成的。当浏览器请求 Web 服务器上的某个页面时,Web 服务器上会执行一个脚本,为该页面生成 HTML、CSS 和 JavaScript。

如今,世界正在转向富互联网应用程序 (RIA)。RIA 也使用 3 层架构,但所有 HTML、CSS 和 JavaScript 都是在浏览器中生成的。浏览器必须下载一次初始的 HTML、CS 和 JavaScript 文件,但此后 RIA 客户端仅与 Web 服务器交换数据。不会来回发送 HTML、CSS 或 JavaScript(除非它们是数据的一部分,例如包含 HTML 代码的文章)。

N 层架构的目的是使应用程序的不同层彼此隔离。GUI客户端不知道服务器内部如何工作,服务器也不知道数据库服务器内部如何工作等。他们只是通过标准接口进行通信。

Web 应用程序尤其具有另一个优势。如果您对 GUI 客户端或服务器上运行的应用程序逻辑进行更新,则所有客户端在下次访问应用程序时都会获得最新更新。浏览器下载更新后的客户端,更新后的客户端访问更新后的服务器。

面向服务的架构(SOA
面向服务的架构(SOA)是一种架构,其中一个(或多个)应用程序的应用程序逻辑被分割成可以单独调用的单独服务。如果需要,服务也可以相互调用。

事件驱动架构(EDA
事件驱动架构是一种架构风格,系统中的组件发出事件并对事件做出反应。当某个事件发生时,组件 A 不会直接调用组件 B,组件 A 只是发出一个事件。组件 A 对于哪些组件监听其事件一无所知。

事件驱动架构既可以在单个流程内部使用,也可以在流程之间使用。例如,GUI 框架通常大量使用事件。此外,正如我关于并发模型的教程中所解释的,装配线并发模型(又名反应式非阻塞并发模型) 使用事件驱动的体系结构。

事件驱动架构是一种架构风格,其中系统的传入请求被收集到一个或多个中央事件队列中。事件从事件队列转发到要处理事件的后端服务。

事件驱动架构有时也称为消息驱动架构或 流处理架构。事件可以被视为消息流 - 因此有另外两个名称。流处理架构也被称为lambda 架构。无论如何,我将继续使用事件驱动架构这个名称。

  • 进程间的事件驱动架构
  • 事件队列
  • 事件日志
  • 事件收集器
  • 回复队列
  • 读取与写入事件
  • 事件日志重播挑战
    • 处理动态值
    • 与外部系统的协调
  • 事件日志重播解决方案
    • 重播模式
    • 多步事件队列

点对点 (P2P) 架构
点对点(P2P 架构)是一种由多个平等的对等点协作为彼此提供服务的架构,没有中央服务器。所有对等点既是彼此的客户端,又是彼此的服务器。

可扩展架构(Scalable
可扩展架构是一种可以扩展以满足增加的工作负载的架构。换句话说,如果工作负载突然超出了您现有的软件+硬件组合的能力,您可以扩展系统(软件+硬件)来满足增加的工作负载。

扩展系统有两种主要方法:垂直扩展和水平扩展。

  • 垂直扩展意味着您可以通过将软件部署在比当前部署的计算机容量更高的计算机上来扩展系统。新计算机可能比当前计算机具有更快的CPU、更多的内存、更快且更大的硬盘、更快的内存总线等。
  • 水平扩展意味着您可以通过添加更多部署了软件的计算机来扩展系统。添加的计算机通常具有与系统当前运行的计算机大致相同的容量,或者您在购买时以相同的价格获得的任何容量(随着时间的推移,计算机往往会以相同的价格变得更强大)。

架构可扩展性要求:任务并行化
任务的并行化可以在几个级别上完成:

  • 将单独的任务分配到单独的计算机上。
  • 将单独的任务分配到同一台计算机上的单独 CPU 上。
  • 将单独的任务分配到同一 CPU 上的单独线程上。

将单独的任务分配给单独的计算机通常称为“负载平衡”。负载平衡将在单独的文本中更详细地介绍。
在同一台计算机上执行多个不同的应用程序(可能使用相同的 CPU 或使用不同的 CPU)被称为“多任务”。多任务处理通常由操作系统完成,因此软件开发人员不需要过多考虑这一点。您需要考虑的是如何将您的应用程序分解为更小的、独立但协作的进程,如果需要,这些进程也可以分布到不同的 CPU 甚至计算机上。

负载均衡
最常见的负载均衡方案是:

  • 均匀任务分配方案
  • 加权任务分配方案
  • 粘性会话方案
  • 均匀大小任务队列分配方案
  • 自治队列方案

1、均匀任务分配方案
均匀任务分配方案意味着任务在集群中的服务器之间均匀分配。因此该方案非常简单。这使得它更容易实施。均匀任务分配方案也称为“循环”,这意味着服务器以循环方式接收工作(均匀分布)。

2、基于 DNS 的负载平衡
基于 DNS 的负载平衡是一种简单的方案,您可以将 DNS 配置为当不同的计算机请求您的域名的 IP 地址时,将不同的 IP 地址返回给它们。这实现了类似于均匀任务分配方案的效果,不同之处在于大多数计算机缓存 IP 地址,从而不断返回到相同的 IP 地址,直到进行新的 DNS 查找。
虽然基于 DNS 的负载平衡是可能的,但这并不是在多台计算机之间可靠地分配流量的最佳方式。您最好使用专用的负载平衡软件或硬件。

3、加权任务分配方案
加权任务分配负载平衡方案使用权重将传入任务分配到集群中的服务器上。这意味着您可以指定服务器相对于其他服务器应接收的任务权重(比率)。如果集群中的服务器并非全部具有相同的容量,这非常有用。
例如,如果三台服务器中的一台只有另外两台服务器的 2/3 容量,则可以使用权重3, 3, 2。这意味着每接收 8 个任务,第一个服务器应接收 3 个任务,第二个服务器应接收 3 个任务,最后一个服务器仅应接收 2 个任务。这样,与集群中其他服务器相比,容量为 2/3 的服务器仅接收 2/3 的任务。

4、粘性会话方案
前面的两个负载平衡方案基于这样的假设:任何传入任务都可以独立于先前执行的任务进行处理。但情况可能并非总是如此。
想象一下,如果集群中的服务器保留某种会话状态,例如 Java Web 应用程序(或 PHP 或 ASP)中的会话对象。如果任务(HTTP 请求)到达服务器 1,并导致向会话状态写入一些值,那么如果来自同一用户的后续请求发送到服务器 2 或服务器 3,会发生什么情况?那么该会话值可能会丢失,因为它存储在服务器 1 的内存中。

此问题的解决方案称为粘性会话负载平衡。属于同一会话(例如同一用户)的所有任务(例如HTTP 请求)都被发送到同一服务器。这样,后续任务(请求)可能需要的任何存储的会话值都可用。

通过粘性会话负载平衡,分配到服务器的不是任务,而是任务会话。这当然会导致工作负载的分布更加不可预测,因为一些会话将包含很少的任务,而其他会话将包含许多任务。

另一种解决方案是完全避免使用会话变量,或者将会话变量存储在数据库或缓存服务器中,可供集群中的所有服务器访问。如果可能的话,我更喜欢完全避免会话变量,但您可能有充分的理由使用会话变量。

避免会话变量的一种方法是使用RIA GUI + 架构 ,该架构可以在 RIA 客户端内存中(而不是在 Web 服务器内存中)包含任何所需的会话范围变量。

5、均匀大小任务队列分配方案
均匀大小的任务队列分配方案与加权任务分配方案类似,但有所不同。负载均衡器不会盲目地将任务分配到集群中的服务器上,而是为每个服务器保留一个任务队列。任务队列包含每个服务器当前正在处理或等待处理的所有请求。

当服务器完成任务时,例如完成向客户端发送回 HTTP 响应时,该任务将从该服务器的任务队列中删除。

均匀任务排队分配方案的工作原理是确保每个服务器队列同时具有相同数量的正在进行的任务。具有较高容量的服务器将比具有较低容量的服务器更快地完成任务。因此,容量较高的服务器的任务队列将更快地清空,从而更快地为新任务提供空间。

正如您可以想象的那样,这种负载平衡方案隐式地考虑了处理每个任务所需的工作量 以及每个服务器的容量。新任务将发送到排队任务最少的服务器。任务完成后将从队列中清空,这意味着处理任务所花费的时间会自动影响队列的大小。由于任务完成的速度取决于服务器容量,因此服务器容量也会自动考虑在内。如果某个服务器暂时超载,其任务队列大小将变得大于集群中其他服务器的任务队列。因此,在超载的服务器完成队列工作之前,不会有新的任务分配给它。

负载均衡器必须使用此方案进行更多的计算。它必须跟踪任务队列,并且必须跟踪任务何时完成,以便可以将其从相应的任务队列中删除。

6、自治队列方案
自治队列负载均衡方案,所有传入的任务都存储在一个任务队列中。服务器集群中的服务器连接到该队列并获取它们可以处理的任务数量。

在这个方案中没有真正的负载均衡器。每个服务器都会承受它能够处理的负载。只有任务队列和服务器。如果一台服务器脱离集群,其任务将在任务队列中保持未处理状态,并由其他服务器稍后处理。因此,每个服务器独立于其他服务器和任务队列运行。负载均衡器不需要知道哪些服务器是集群的一部分等。任务队列不需要知道服务器。每个服务器只需要了解任务队列即可。

自治队列负载平衡还隐式地考虑了每个服务器的工作负载和容量。服务器仅从队列中获取任务,然后就有能力处理它们。

与均匀队列大小分布相比,自治队列有一点缺点。想要任务的服务器需要首先连接到队列,然后下载任务,然后提供响应。这是 2 到 3 次网络往返(取决于是否需要发回响应)。

均匀队列大小分配方案可以减少一次网络往返。负载均衡器向服务器发送请求,服务器发回响应(如果需要)。这只是 1 到 2 次网络往返。

缓存
在实现缓存时,您需要考虑以下三个问题:

  • 填充缓存
  • 保持缓存和远程系统同步
  • 管理缓存大小