15

同样,是否有应该避免的设计模式?

4

8 回答 8

10

我假设您正在编写一个服务器类型的应用程序(让我们暂时离开 Web 应用程序 - 有一些现成的解决方案可以提供帮助,所以让我们看看“我已经编写了这种很棒的新型服务器",但我希望它是 HA 问题)。

在服务器实现中,来自客户端的请求通常(以某种形式)转换为某种事件或命令类型模式,然后在一个或多个队列上执行。

因此,第一个问题 - 需要以能够在集群中生存的方式存储事件/命令(即,当新节点接管为主节点时,它会查看下一个需要执行并开始的命令)。

让我们从单线程服务器 impl 开始(最简单的 - 概念仍然适用于多线程,但它有自己的一组问题0。处理命令时需要某种事务处理。

另一个问题是管理副作用以及如何处理当前命令的失败?在可能的情况下,以事务的方式处理副作用,以便它们全有或全无。IE。如果命令更改了状态变量,但在执行中途崩溃,那么能够返回到“先前的”状态是很棒的。这允许新的主节点恢复崩溃的命令并重新运行该命令。另一个好方法是将副作用分解成更小的任务,这些任务可以再次在任何节点上运行。IE。存储主要请求开始和结束任务,有很多小任务处理每个任务只有一个副作用。

这也引入了其他会影响您的设计的问题。这些状态变量不一定是数据库更新。它们可以是共享状态(比如内部组件的有限状态机),也需要分布在集群中。因此,管理更改的模式使得主代码必须看到它需要的一致版本的状态,然后在整个集群中提交该状态。使用某种形式的不可变(至少来自进行更新的主线程)数据存储很有用。IE。所有更新都有效地在必须通过某种中介或外观的新副本上完成,这些中介或外观仅在跨集群更新后使用更新更新内存中的本地副本(或跨集群的最小成员数量以实现数据一致性)。

其中一些问题也存在于 master worker 系统中。

还需要良好的错误管理,因为状态更新可能出错的事情数量增加(因为您现在涉及网络)。

我经常使用状态模式。对于您想要发送请求/响应的副作用,而不是一行更新,并使用特定于对话的 fsm 来跟踪进度。

另一个问题是端点的表示。IE。连接到主节点的客户端需要能够重新连接到新的主节点,然后监听结果?还是您只是取消所有待处理的结果并让客户重新提交?如果您允许处理待处理的请求,则需要一种识别端点(客户端)的好方法(即查找中的某种客户端 ID)。

还需要清理代码等(即不希望等待客户端重新连接的数据永远等待)。

使用了很多队列。因此,很多人会使用一些消息总线(jms 对 java 来说)以事务方式推送事件。

兵马俑(再次用于 java)为您解决了很多问题 - 只需更新内存 - 兵马俑是您的外观/中介。他们刚刚为您注入了这些方面。

Terracotta(我不为他们工作) - 引入了“超级静态”的概念,所以你会得到这些很酷的集群范围的单例,但你只需要知道这将如何影响测试和开发工作流程 - 即。使用大量组合,而不是继承具体实现以实现良好的重用。

对于 Web 应用程序 - 一个好的应用程序服务器可以帮助复制会话变量,一个好的负载均衡器可以工作。在某种程度上,通过 REST(或您选择的 Web 服务方法)使用它是编写多线程服务的一种简单方法。但它会对性能产生影响。再次取决于您的问题域。

消息服务(例如 jms)通常用于在不同服务之间引入松散耦合。使用体面的消息服务器,您可以进行大量消息路由(再次,apache camel 或类似的工作做得很好),即。说一个针对 jms 生产者集群的粘性消费者等,这也可以实现良好的故障转移。Jms 队列等可以提供一种在集群中分发 cmds 的简单方法,不依赖于主/从。(同样,这取决于您是在做 LOB 还是从头开始编写服务器/产品)。

(如果我以后有时间我会整理一下,也许会在修复拼写语法等方面提供更多细节)

于 2009-05-02T13:58:39.917 回答
5

创建可靠软件的一种方法是仅崩溃软件

仅崩溃软件是可以安全崩溃并快速恢复的软件。阻止它的唯一方法是让它崩溃,而启动它的唯一方法是恢复。仅崩溃系统由与可重试请求通信的仅崩溃组件组成;通过崩溃和重新启动故障组件并重试任何已超时的请求来处理故障。生成的系统通常更加健壮和可靠,因为崩溃恢复是开发过程中的一等公民,而不是事后的想法,并且您不再需要额外的代码(以及相关的接口和错误)来显式关闭。所有软件都应该能够安全地崩溃并快速恢复,但仅崩溃软件必须具备这些品质,否则它们的缺失很快就会显现出来。

于 2009-05-02T13:07:45.157 回答
4

我建议阅读Release it!迈克尔·尼加德。他概述了许多影响生产系统的反模式,以及有助于防止一个错误组件导致整个系统瘫痪的模式。本书涵盖三个主要领域;稳定性、容量和一般设计(包括网络、安全性、可用性和管理)。

我以前的工作场所(有时)被 Nygard 概述的几乎每一个故障场景所困扰(每次停机都会造成收入损失)。实施他建议的一些技术和模式可以显着提高系统的稳定性和可预测性(是的,这本书有点以 Java 为中心,但这些原则适用于许多情况)。

于 2009-05-02T14:03:52.510 回答
2

错误的:

...并且会有一个存储服务器

好的:

...并且将有一个(多个)存储服务器场,前面有(多个)负载平衡器

  • 将负载平衡器放在一切前面。现在你可以有 4 个后端,但将来你可以有 400 个,所以明智的做法是只在 LB 上管理它,而不是所有使用后端的应用程序。

  • 使用多级缓存。

  • 寻找加速thigs的流行解决方案(例如memcached)。

  • 如果您要更新系统,请分多个小步骤逐步进行。如果您迈出一大步(关闭旧的,打开新的并祈祷它会起作用),它很可能会失败。

  • 使用 DNS 名称,festorage-lb.servicename解析为所有存储负载均衡器的地址。如果你想添加一个,只需修改 dns,所有服务将自动开始使用它。

  • 把事情简单化。您依赖的系统越多,您的服务就越会受到影响。

于 2009-05-02T12:47:56.747 回答
2

设计高可用性 (HA) 系统是一个活跃的研究和开发领域。如果您查看 ACM 或 IEEE,有大量关于服务质量(可用性、可靠性、可扩展性等)以及如何实现它们(松散耦合、适应等)的研究论文。如果您正在寻找更多实际应用程序,请查看容错系统和中间件,这些系统和中间件旨在支持集群、网格或类似云的功能。

复制和负载平衡(也称为反向代理)是实现 HA 系统的一些最常见模式,并且通常可以在不对底层软件进行代码更改的情况下完成,假设它不是太紧密耦合。甚至最近的许多云产品基本上都是通过复制和负载平衡来实现的,尽管它们倾向于建立弹性来处理广泛的系统需求。

使软件组件无状态可以减轻复制的负担,因为状态本身不需要与软件组件一起复制。无状态是 HTTP 可扩展性如此之好的主要原因之一,但它通常需要应用程序添加它们自己的状态(例如会话),然后需要复制这些状态。

因此,使松散耦合的系统比紧密耦合的系统具有高可用性更容易。由于系统组件的可靠性决定了整个系统的可靠性,因此可能需要更换不可靠的组件(硬件故障、软件错误等)。允许在运行时进行动态调整,可以在不影响整个系统可用性的情况下更换这些故障组件。松散耦合是使用可靠消息传递系统的另一个原因,其中发送者和接收者不必同时可用,但系统本身仍然可用。

于 2009-05-02T14:36:53.570 回答
1

据我了解,您正在寻找在 HA 架构的 java 应用程序部分中使用的特定模式。当然,可以使用大量的模式和最佳实践,但这些并不是真正的“HA 模式”。相反,它们是可以在许多情况下使用的好主意。

我想我想说的是:高可用性架构由许多小部分组成。如果我们选择其中一个小部件并检查它们,我们可能会发现这个小部件没有神奇的 HA 属性。如果我们检查所有其他组件,我们会发现同样的事情。当它们以一种智能的方式组合在一起时,它们就成为了一个 HA 应用程序。

HA 应用程序是您从一开始就做好最坏打算的应用程序。如果您曾经认为“这个组件非常稳定,我们不需要额外的冗余”,那么它可能不是 HA 架构。毕竟,处理您预见的问题场景很容易。让您感到惊讶的是,它会导致系统崩溃。

尽管如此,仍有一些模式在 HA 环境中特别有用。其中许多都记录在Martin Fowler的经典著作《企业应用程序架构模式》中。

于 2009-05-02T13:13:52.660 回答
1

我将“高可用性”解释为“零停机时间”`,可以根据其他 SE 问题实施:

Java 应用程序的零停机部署

  1. A/B切换:(滚动升级+回退机制)
  2. 并行部署 – Apache Tomcat:(仅适用于 Web 应用程序)
  3. 延迟端口绑定
  4. 高级端口绑定

我将使用其中一些概念从软件角度提出高可用性系统的设计模式,这与上述方法相得益彰。

要使用的模式:

代理/工厂

有一个代理对象,代理将决定将请求重定向到哪里。假设您拥有版本 1 和版本 2 的软件。如果客户端使用旧协议连接,请将它们重定向到版本 1 软件。新客户端可以直接连接到版本 2。Proxy 可以使用 Factory 方法或 AbstractFactory 来渲染新版本的软件。

战略

您可以在运行时通过从一系列算法中选择一个算法来更改算法。如果以航空公司为例,您可以在非高峰和高峰流量月份在 DiscountFare 和 NormalFare 算法之间切换。

装饰者

您可以在运行时更改对象的行为。添加一个新类并装饰额外的责任。

适配器

当您在版本 1 和版本 2 之间更改接口或合同时很有用。适配器将适当地响应新旧客户端请求。

一般准则:

  1. 对象之间的松散耦合
  2. 在您的应用程序中遵循SOLID原则

有关上述模式,请参阅sourcemaking网站文章以更好地理解。

什么不可以使用:

除了设计模式之外,您还必须采取一些预防措施来实现应用程序的零停机时间。

  1. 不要在系统中引入单点故障。
  2. 谨慎使用分布式缓存(例如 Terracotta)/锁。
  3. 移除服务之间的硬耦合。通过使用消息传递总线/框架(JMS、ActiveMQ 等)消除服务之间的紧密耦合
于 2016-02-07T18:20:11.530 回答
-6

高可用性更多的是关于硬件可用性和冗余,而不是编码约定。在几乎所有 HA 案例中,我都会使用几种模式:我会为我的数据库对象选择单例模式,并使用工厂模式来创建单例模式。然后工厂可以有逻辑来处理数据库的可用性问题(这是大多数可用性问题发生的地方)。例如,如果 Master 宕机,则连接到第二个 Master 进行读写,直到 Master 回来。我不知道这些是否是最常用的模式,但它们在我的代码中是最常用的。

当然这个逻辑可以在 __construct 方法中处理,但是工厂模式可以让你更好地控制你的代码和如何处理数据库连接问题的决策逻辑。工厂还可以让您更好地处理单例模式。

我绝对会避免使用装饰器模式观察者模式。它们都在您的代码中创建了复杂性,使其难以维护。在他们的情况下,这些是满足您需求的最佳选择,但大多数情况下并非如此。

于 2009-05-02T12:28:16.450 回答