分布式系统的设计模式——每个开发人员都应该知道的关键概念

发布时间 2023-06-06 11:06:15作者: 晓风晓浪

当我刚开始作为后端工程师的职业生涯时,我总是使用单体系统。

工作很好,但我的脑海里一直有这样的想法:

“伙计,我想在大型系统上工作,比如谷歌、Netflix 等……”

我当时 19 岁,是一名初级开发人员,所以在这里让我放松一下。

在我的一位同事开始谈论它之前,我什至不知道分布式系统这个术语。

然后我研究了它——我研究了很多。看起来很复杂,我觉得很傻。

但我很兴奋。

我研究了分布式系统的这个概念一段时间,但直到几年后我看到它在运行时才完全理解它。

现在有了一些经验,我想和大家分享一下我对分布式系统的了解。

(更多优质教程:java567.com,搜"分布式")

必备知识

我将在此处讨论的主题对于初学者来说可能有点高级。为了帮助您做好准备,以下是我假设您知道的内容:

  • 中级编程(任何语言都可以)

  • 基本计算机网络(TCP/IP、网络协议等)

  • 基本数据结构和算法(大 O 符号、搜索、排序等)

  • 数据库(关系型、NoSQL 等)

如果这听起来很多,请不要气馁。

什么是分布式系统?

Netflix 架构。来源

当我开始我的职业生涯时,我在一家机构担任前端开发人员。

我们过去常常收到客户的请求,然后我们只是建立他们的网站。

那时我还没有完全理解我正在构建的东西背后的架构和基础设施。

现在回想起来,一点都不复杂。

我们有一个用 PHP 和 Yii2(PHP 框架)编写的后端服务,以及一个用 JavaScript 和 React 编写的前端。

这种架构适用于大多数项目。但是一旦您的应用程序变得更加复杂和流行,裂缝就会开始显现。

您会遇到以下问题:

  • 复杂性——代码库太大太复杂,一个人无法在头脑中处理。创建新功能和维护旧功能也很困难。

  • 性能问题——你的应用程序的流行导致它有大量的网络流量,并且它已被证明对于你的单个服务器来说太多了。因此,该应用程序开始面临性能问题。

  • 不灵活——只有一个代码库意味着你会被一开始的技术栈所困。如果你想改变它,那么你要么必须用另一种语言重写整个东西,要么分解应用程序。

  • 脆弱的系统——代码高度耦合在一起意味着如果任何功能中断,那么整个应用程序都会崩溃。这会导致更多的停机时间,从而使企业损失更多的钱。

有很多方法可以优化单体应用程序,而且可以走得很远。许多大型科技公司,如 Netflix、谷歌和 Facebook (Meta),一开始都是作为单体应用程序开始的,因为它们更容易启动。

但他们都开始面临大规模单体应用的问题,并且必须找到解决问题的方法。

他们做了什么?他们重组了架构。因此,他们现在拥有多个相互通信的独立服务,而不是拥有包含其业务所有功能的单一超级服务。

这是分布式系统的基础。

有些人将分布式系统误认为是微服务。这是真的——微服务是一个分布式系统。但是分布式系统并不总是遵循微服务架构。

因此,考虑到这一点,让我们为分布式系统提出一个正确的定义:

分布式系统是一种计算环境,其中各种组件分布在网络上的多台计算机(或其他计算设备)上。

分布式系统中的常见挑战

分布式系统远比单体系统复杂得多。

这就是为什么在迁移或开始新项目之前,您应该问这个问题:

我真的需要它吗?

如果您决定确实需要分布式系统,那么您将面临一些常见的挑战:

  • 异构性——分布式系统允许我们使用范围广泛的不同技术。问题在于我们如何在所有不同服务之间保持一致的通信。因此,重要的是要商定并采用通用标准以简化流程。

  • 可扩展性——扩展不是一件容易的事。有许多因素需要牢记,例如规模、地理位置和管理。有许多边缘案例,每个案例都有自己的优缺点。

  • 开放性——分布式系统如果可以扩展和重新开发,就被认为是开放的。

  • 透明度——透明度是指分布式系统隐藏其复杂性并散发出单一系统外观的能力。

  • 并发——分布式系统允许多个服务使用共享资源。当多个服务试图同时访问相同的资源时,可能会出现问题。我们使用并发控制来确保系统保持稳定状态。

  • 安全性——安全性由三个关键组成部分组成:可用性、完整性和机密性。

  • 故障处理——分布式系统中出现错误的原因有很多(例如,软件、网络、硬件等等……)。最重要的是我们如何以优雅的方式处理这些错误,以便系统能够自我修复。

是的,设计健壮且可扩展的分布式系统并不容易。

但你并不孤单。其他聪明人也遇到过类似的问题,并提供了称为设计模式的通用解决方案。

让我们介绍最流行的。

附言。我们不仅会涵盖模式,还会涵盖任何对分布式系统有帮助的内容。这可能包括数据结构、算法、常见场景等……

命令和查询责任分离 (CQRS) 模式

来源

想象一下,您有一个拥有数百万用户的应用程序。您有多个处理后端的服务,但作为单个数据库。

当您在同一个数据库上进行读写操作时,就会出现问题。写入的计算成本比读取高得多,系统开始受到影响。

这就是 CQRS 模式解决的问题。

它指出写入(命令)和读取(查询)应该分开。通过分离写入和读取,我们可以让开发人员分别优化每个任务。

例如,您可以为写操作选择高性能数据库,为读操作选择缓存或搜索引擎。

优点

  • 代码简化——通过分离写入和读取来降低系统复杂性。

  • 资源优化——通过单独的数据库进行写入和读取来优化资源使用。

  • 可扩展性——提高读取的可扩展性,因为您可以简单地添加更多数据库副本。

  • 减少错误数量——通过限制可以修改共享数据的实体,我们可以减少意外修改数据的机会。

缺点

  • 代码复杂性——要求开发人员分别管理读取和写入,从而增加了代码复杂性。

  • 增加开发时间——这会增加开发时间和成本(仅在开始时)。

  • 额外的基础设施——这可能需要额外的基础设施来支持单独的读写模型。

  • 延迟增加——发送高吞吐量查询时可能会导致延迟增加。

用例

当应用程序的写入和读取具有不同的性能要求时,最好使用 CQRS。但这并不总是最好的方法,开发人员在采用该模式之前应仔细考虑利弊。

以下是一些使用 CQRS 模式的用例:

  • 电子商务——产品目录和推荐的独立读取模型,而写入端处理订单处理和库存管理。

  • 银行——优化余额查询和报告的读取模型,而写入端处理交易和计算。

  • 医疗保健——CQRS 可用于优化患者搜索、医疗记录检索和生成报告的读取,而写入端则管理数据更新、调度和治疗计划。

  • 社交媒体——通过应用 CQRS,读取模型可以有效地处理提要生成、个性化内容推荐和用户配置文件查询,而写入端处理内容创建、更新和参与跟踪。

两阶段提交 (2PC) 模式

来源

2PC解决了数据一致性问题。当您有多个服务与关系数据库通信时,很难保持数据一致,因为一个服务可以创建一个事务,而另一个服务会中止它。

2PC 是一种协议,可确保所有服务在事务完成之前提交或中止事务。

它分两个阶段工作。第一阶段是准备 阶段,事务协调器告诉服务准备数据。然后是提交阶段,它向服务发送准备好的数据并提交事务。

2PC 系统确保所有服务在默认情况下都处于锁定状态。这意味着他们不能只写入数据库。

锁定时,服务完成准备阶段以准备好数据。然后事务协调器一个一个地检查每个服务,看看他们是否有任何准备好的数据。

如果他们这样做,则服务将被解锁并且数据将被提交。如果不是,则事务协调器转移到另一个服务。

2PC 确保一次只能运行一个服务,这使得该过程比 CQRS 更具抵抗力和一致性。

优点

  • 数据一致性——确保分布式事务环境中的数据一致性。

  • 容错——提供处理事务失败和回滚的机制。

缺点

  • 阻塞——该协议可能会在系统中引入延迟或阻塞,因为它可能必须等待无响应的参与者或解决网络问题才能继续进行交易。

  • 单点故障——依赖单个协调器会引入潜在的故障点。如果协调器出现故障,协议可能会中断,从而导致交易失败或延迟。

  • 性能开销——协议中额外的通信轮次和协调步骤会引入开销,这会影响整体性能,尤其是在参与者众多或网络延迟高的情况下。

  • 缺乏可扩展性——随着参与者数量的增加,协调和通信开销也会增加,这可能会限制协议的可扩展性。

  • 恢复期间阻塞——协议可能会在恢复期间引入阻塞,直到失败的参与者重新上线,从而影响系统可用性和响应能力。

用例

2PC 最适用于处理必须准确的重要事务操作的系统。

以下是 2PC 模式有益的一些用例:

  • 分布式数据库——在分布式数据库系统中跨多个数据库协调事务提交或中止。

  • 金融系统——确保跨银行、支付网关和金融机构的原子和一致的交易处理。

  • 电子商务平台——协调库存管理、支付处理和订单履行等服务,以实现可靠且一致的交易处理。

  • 预订系统——协调分布式资源和预订过程中的参与者以实现一致性和原子性。

  • 分布式文件系统——协调分布式文件系统中多个节点或服务器的文件操作以保持一致性。

传奇模式

因此,让我们假设您有一个电子商务应用程序,该应用程序包含三个服务,每个服务都有自己的数据库。

您有一个供商家调用的 API,/products您可以向其中添加产品及其所有信息。

每当您创建产品时,您还必须创建其价格和元数据。这三者都在不同的服务中使用不同的数据库进行管理。

因此,您实现了以下简单方法:

创建产品 -> 创建价格 -> 创建元数据

但是,如果您创建了产品但未能创建价格怎么办?一项服务如何知道另一项服务的交易失败?

传奇模式解决了这个问题。

有两种实现传奇的方法:编排和编排。

编排

来源

第一种方法称为编排。

您有一个中央服务,它以正确的顺序调用所有不同的服务。

中央服务确保如果出现故障,它将知道如何通过恢复事务或记录错误来进行补偿。

优点

  • 适用于涉及多项服务或随时间增加的新服务的复杂交易。

  • 适用于对过程中的每个参与者都进行控制并控制活动流的情况。

  • 不引入循环依赖,因为编排器单方面依赖于 saga 参与者。

  • 服务不需要知道其他服务的命令。明确的关注点分离降低了复杂性。

缺点

  • 额外的设计复杂性要求您实施协调逻辑。

  • 如果编排器失败,那么整个系统就会失败。

何时使用编排?

您应该考虑使用这种模式:

  • 如果您需要协调工作流程的集中式服务。

  • 如果您想要一个清晰且集中的工作流视图,这样可以更轻松地理解和管理整个系统行为。

  • 如果您有需要高度协调和集中控制的复杂动态工作流。

编舞

来源

另一方面,编排方法不使用中央服务。相反,服务器之间的所有通信都是通过事件发生的。

服务将对事件作出反应,并知道在成功或失败的情况下该怎么做。

因此,对于我们上面的示例,当用户创建产品时,它将:

  1. 创建一个名为product-created-successfully

  2. 然后价格服务将通过为产品创建价格来对事件做出反应,然后它将创建另一个名为price-created-successfully

  3. 相同的逻辑适用于元数据服务。

优点

  • 适用于不需要复杂协调逻辑的简单工作流。

  • 易于实施,因为它不需要额外的服务实施和维护。

  • 由于责任在服务之间分配,因此没有单点故障。

缺点

  • 难以调试,因为很难跟踪哪些 saga 服务监听哪些命令。

  • Saga 服务之间存在循环依赖的风险,因为它们必须使用彼此的命令。

  • 集成测试很困难,因为所有服务都必须运行以模拟事务。

何时使用编排:

如果出现以下情况,您应该考虑使用此模式:

  • 应用程序需要在不紧耦合的情况下跨多个微服务保持数据一致性。

  • 存在长期存在的事务,如果一个微服务运行了很长时间,您不希望其他微服务被阻塞。

  • 如果序列中的操作失败,您需要能够回滚。

复制负载平衡服务 (RLBS) 模式

来源

这本质上是一个负载平衡器——我不知道他们为什么把它说得这么吓人。

负载均衡器是在一组资源之间平均分配网络流量的软件或硬件。

但情况并非总是如此——它还可以将不同的路线路由到不同的服务。

例如:

  • /frontend转到前端服务。

  • /api转到后端服务。

优点

  • 性能——负载平衡将工作负载平均分配到多个资源中,防止任何单个资源过载。这会缩短响应时间,减少延迟,并为访问系统的用户或客户端带来更好的整体性能。

  • 可扩展性——负载平衡允许您横向扩展,这意味着您可以获得更多服务器,而不是获得更强大的服务器。

  • 高可用性——如上所述,负载均衡允许我们垂直扩展,这意味着我们有多个服务器。如果一台服务器发生故障,那么负载均衡器将检测到这一点,并且可以将流量重定向到其他工作服务器。

  • 更好的资源利用率——负载平衡有助于通过在多个服务器或资源之间平均分配流量来优化资源利用率。这确保了每个服务器或资源都得到有效使用,有助于降低成本并最大限度地提高性能。

缺点

  • 复杂性——实施和配置负载平衡可能很复杂,尤其是对于大型系统。

  • 单点故障——虽然负载平衡器提高了系统可用性,但它们也可能成为单点故障。如果负载均衡器本身出现故障,可能会导致其背后所有资源的服务中断。

  • 增加的开销——负载均衡器的计算不是免费的,如果它们不受控制,它们就会成为整个系统的瓶颈。

  • 会话处理挑战——负载平衡有状态应用程序有点棘手,因为您需要维护会话。它需要额外的机制,例如粘性会话或会话同步,这增加了复杂性。

何时使用负载均衡

负载均衡器主要用于以下情况:

  • 你有一个高流量的网站,你想分散负载,这样你的服务器就不会崩溃。

  • 您有来自世界各地的用户,并希望从他们最近的位置为他们提供数据。你可以在亚洲有一台服务器,在欧洲有另一台。然后,负载均衡器会将所有用户从亚洲路由到亚洲服务器,将欧洲用户路由到欧洲服务器。

  • 你有一个面向服务的架构,API 对应于不同的服务。负载均衡可以作为一个简单的 API 网关。

分片服务模式

来源

在上一节中,我们讨论了复制服务。任何请求都可以由任何服务处理。这是因为它们是彼此的复制品。

这有利于无状态服务。但是如果你有一个有状态的服务呢?那么分片方法会更合适。

分片服务只接受某些类型的请求。

例如,您可以让一个分片服务接受所有缓存请求,而另一个分片服务接受高优先级请求。

但是,我们如何实施呢?

好吧,如果我们严格地谈论应用程序服务,那么您可以采用面向服务的体系结构方法。您可以独立开发和部署多个服务。

然后,您可以使用负载平衡器通过 URL 路径将请求路由到适当的服务。

附言。分片不仅用于应用程序服务,还可以用于数据库、缓存、CDN 等...

优点:

  • 可扩展性——分片允许您跨多个节点或服务器分配负载,从而实现水平扩展。随着工作量的增加,您可以添加更多分片。

  • 性能——单个节点不必处理所有请求,尤其是在计算量大的情况下。每个节点都可以接收请求的子集,从而提高系统性能。

  • 成本效益——分片可以成为扩展系统的一种经济高效的解决方案。您可以使用商品硬件并将工作负载分配到多个更便宜的服务器,而不是投资于单个高容量服务器。

  • 故障隔离——分片提供了一定程度的故障隔离。如果一个分片或节点出现故障,其余分片可以继续为请求提供服务。

缺点:

  • 复杂性——分片不容易实现。它需要仔细规划和设计以处理数据分布、一致性和查询协调。

  • 运营开销——管理分片系统涉及额外的运营任务,例如监控、维护和备份,这可能需要更多的资源和专业知识。

用例

Sharded Services Pattern 通常用于以下场景:

  • 性能要求——如果您的系统正在处理单个服务器无法处理的大数据量或高读/写工作负载,则分片可以将工作负载分布到多个分片。这可以实现并行处理并提高整体性能。

  • 可扩展性要求——当您预计将来需要水平可扩展性时,可以从一开始就实施分片,以提供添加更多分片的灵活性,并随着工作负载的增长扩展系统。

  • 成本考虑——如果垂直扩展(升级到更强大的硬件)变得成本过高,分片通过将工作负载分布在多个更便宜的服务器或节点上,提供了一种经济高效的替代方案。

  • 地理分布——当您需要在不同的地理位置或数据中心分布数据时,分片可能会很有用,从而可以提高性能并减少不同地区用户的延迟。

但请记住,在分片服务时必须仔细考虑,因为实施和恢复非常复杂且成本高昂。

边车模式

来源

在面向服务的架构中,您可能有很多通用功能——例如错误处理、日志记录、监控和配置。

过去,有两种方法可以解决这个问题:

在服务中实现通用功能

这种方法的问题在于,通过有效利用共享资源,实用程序紧密链接并在同一进程中运行。这使得组件相互依赖。

如果一个功能失败,那么这可能会导致另一个功能失败或整个服务失败。

在单独的服务中实现通用功能

这似乎是一个不错的方法,因为实用程序可以用任何语言实现,并且它不与其他服务共享资源。

缺点是,当我们在不同的容器上部署两个服务时,它会增加应用程序的延迟,并且会增加托管、部署和管理方面的复杂性。

我们怎样才能做得更好?

一种方法是使用边车模式。它指出容器应该只解决一个问题并且把它做好。

所以要做到这一点,我们有一个带有两个容器的节点(虚拟机或物理机)。

第一个是包含业务逻辑的应用程序容器。第二个容器,通常称为 sidecar,用于扩展/增强应用程序容器的功能。

现在你可能会问,“但是,这有什么用呢?”

您应该记住,sidecar 服务与应用程序容器在同一节点中运行。所以他们共享相同的资源(比如文件系统、内存、网络等等……)

一个例子

假设您有一个遗留应用程序,它生成日志并将它们保存在一个卷中(持久数据),并且您希望将它们提取到外部平台(如 ELK)中。

一种方法是扩展主应用程序。但由于代码混乱,这很困难。

因此,您决定使用 sidecar 方法并开发一个实用服务:

  • 从卷中捕获日志。

  • 将日志传输到 Elastic。

节点的架构看起来像这样:

来源

万岁,您没有更改应用程序中的任何代码,并且通过插入 sidecar 扩展了它的功能。

哎呀,您甚至可以将这个日志聚合器 sidecar 插入到其他应用程序中。

优点:

  • 模块化——Sidecar 允许您独立开发和维护实用程序功能。

  • 可扩展性——如果 Sidecar 负载过多,您可以通过添加更多容器轻松地对其进行水平扩展。

  • 隔离——sidecar 与主应用程序隔离,提供额外的安全层。

缺点:

  • 复杂性——它需要对多个容器及其依赖项进行额外管理。

  • 资源开销——因为我们有一个额外的容器,这会增加应用程序的整体资源使用。

  • 协调——sidecar 必须以一种与主应用程序正确工作的方式进行协调,这增加了复杂性。

  • 调试——使用 sidecar 模式进行调试更加困难,因为它需要跟踪主应用程序和 sidecar 之间的交互。

用例

当您想在不触及核心业务逻辑代码的情况下向应用程序添加额外功能时,sidecar 模式很有用。

通过部署 sidecar,核心逻辑可以保持轻量级并专注于其主要任务,而 sidecar 可以处理其他功能。

如果需要,您也可以将 Sidecar 重新用于其他应用程序。

现在我们知道何时使用此模式,让我们看一下它有益的一些用例:

  • 日志记录和监控——边车容器从主容器收集日志和指标,提供集中存储和实时监控以提高可观察性。

  • 缓存——sidecar 容器缓存经常访问的数据或响应,通过减少对外部服务重复请求的需要来提高性能。

  • 服务发现和负载平衡——sidecar 容器向服务发现系统注册主容器,从而在主容器的多个实例之间实现负载平衡和容错。

  • 安全和身份验证——sidecar 容器处理身份验证任务,从主容器中卸载 OAuth、JWT 验证或证书管理等职责。

  • 数据转换和集成——sidecar容器执行数据转换和集成任务,促进主容器和外部系统之间的无缝通信和同步。

  • 代理和网关——sidecar 容器充当代理或网关,提供诸如速率限制、SSL 终止或协议转换等功能以增强通信能力。

  • 性能优化——sidecar 容器处理 CPU 密集型任务或后台进程,优化资源使用并提高主容器的性能。

预写日志技术

想象一下:您正在开发一项连接到包含敏感用户信息的数据库的服务。

有一天,服务器崩溃了。你的数据库崩溃了。除了备份,所有数据都消失了。

您将数据库与备份同步,但备份不是最新的。这是 1 天大。你坐在角落里哭泣。

谢天谢地,这很可能永远不会发生。

因为大多数数据库都有一种叫做预写日志(WAL)的东西。

什么是预写日志?

预写日志是一种流行的技术,用于保存:

  • 原子性——每个事务都被视为一个单元。要么执行整个事务,要么不执行任何事务。这可确保数据不会损坏或丢失。

  • 持久性——确保数据不会丢失,即使在系统出现故障的情况下也是如此。

但是它是如何工作的呢?

WAL 将您对文件所做的每个更改存储在硬盘上。

例如,假设您创建了自己的名为 KVStore 的内存数据库。如果系统出现故障,您需要:

  • 数据不丢失。

  • 要恢复到内存中的数据。

因此,您决定实施预写日志。

每次执行任何事务(SET 或 REMOVE)时,命令都会记录到硬盘上的一个文件中。这使我们能够在系统出现故障时恢复数据。内存会被刷掉,但是日志还是保存在硬盘里。

整体架构看起来像这样:

来源

不全是阳光和彩虹

尽管很有用,但 WAL 并不容易实现。有许多细微差别,但最常见的是:

表现

如果您使用大多数编程语言中的标准文件处理库,您很可能会将文件“刷新”到硬盘上。

冲洗每一根日志将为您提供耐用性的有力保证。但这严重限制了性能,很快就会成为瓶颈。

您可能会问,“为什么我们不延迟刷新或异步进行?”

好吧,这可能会提高性能,但如果服务器在刷新条目之前崩溃,则有丢失日志条目的风险。

这里的最佳实践是实施 Batching 等技术,以限制刷新操作的影响。

数据损坏

另一个考虑因素是我们必须确保检测到损坏的日志文件。

要处理此问题,请通过 CRC(循环冗余校验)记录保存日志文件,这会在读取时验证文件。

贮存

单个日志文件可能难以管理,并且会占用所有可用存储空间。

要处理此问题,请使用以下技术:

  • 分段日志– 将单个日志拆分为多个段。

  • Low-Water Mark——这项技术告诉我们可以安全丢弃日志的哪一部分。

这两种技术是一起使用的,因为它们相互补充。

重复条目

WAL 是仅附加的,这意味着您只能添加数据。由于这种行为,我们可能有重复的条目。所以在应用日志时,需要确保忽略重复项。

解决这个问题的一种方法是使用哈希图,其中对同一键的更新是幂等的。如果不是,则需要一种机制来使用唯一标识符标记每个事务并检测重复项。

用例

整体 WAL 主要用于数据库,但在其他领域也很有用:

预写日志 (WAL) 广泛用于各种系统和数据库中。以下是预写日志的一些常见用例:

  • 文件系统——文件系统可以使用预写日志来保持数据的一致性。通过在将更改应用到文件系统之前记录更改,WAL 允许崩溃恢复并有助于防止系统故障时的数据损坏。

  • 消息队列和事件源——预写日志通常用于消息队列和事件源架构。日志作为可靠且有序的事件记录,允许可靠的消息传递、事件重放和系统状态恢复。

  • 分布式系统——需要在多个节点之间保持一致性的分布式系统可以从预写日志中获益。通过协调日志复制和重放,WAL 有助于同步数据更新并确保分布式环境中的一致性。

裂脑模式

来源

这绝对是一个有趣的名字,不是吗?它可能会让你想到大脑的两半。

嗯,其实有点像。

当分布式系统中的节点彼此断开连接但仍继续运行时,就会发生分布式系统中的裂脑。

裂脑?节点在应该协同工作时独立工作?听起来很像?但愿如此。

无论如何,最大的问题是它会导致:

  • 数据不一致

  • 争夺资源

这通常会在开发人员尝试修复问题时关闭集群。这会导致停机,从而使企业蒙受损失。

解决办法是什么?

一种解决方法是使用世代编号。每次选举领导者时,世代号都会增加。

例如,如果老领导的世代数为一,那么第二任领导的世代数将为二。

每个请求中都包含代号,现在客户可以只信任代号最高的领导者。

但请记住,代号必须保存在磁盘上。

一种方法是使用预写日志。看,事物是相互关联的。

此解决方案被归类为领导者选举,但还有其他解决方案:

  • 基于 Quorum 的共识——使用 Raft 或 Paxos 等算法来确保只有大多数节点可以做出决策,从而防止在裂脑场景中做出冲突的决策。

  • 网络分区检测——使用监控技术或分布式故障检测器来识别网络分区并采取适当的措施。

  • 自动协调——实施自动解决冲突的机制,并在解决裂脑问题后确保数据一致性,例如合并冲突的更改或使用时间戳或矢量时钟。

  • 应用程序级解决方案——通过采用最终一致性模型、无冲突数据结构或 CRDT,设计应用程序以容忍裂脑场景。

  • 手动干预——在某些情况下,可能需要手动干预来解决裂脑场景,涉及人为决策或管理操作以确定正确的系统状态并执行数据协调。

优点

  • 数据一致性——实施修复可确保共享数据在整个分布式系统中保持一致。

  • 系统稳定性——解决裂脑场景可以通过避免冲突操作和保持一致的行为来提高系统稳定性。

缺点

  • 增加的复杂性——由于需要复杂的逻辑和机制,修复裂脑场景会增加系统的复杂性。

  • 性能开销——由于额外的处理和通信要求,裂脑解析机制可能会影响系统性能和延迟。

  • 更高的资源利用率——解决裂脑场景可能需要分配更多资源,这可能会增加成本。

  • 增加故障面——引入裂脑解决机制可能会无意中引入新的故障模式或漏洞。

  • 配置和调整的复杂性——实施修复需要仔细的配置和持续的维护,以确保在不同情况下的最佳行为。

提示切换模式

来源

Hinted Handoff 技术确保您拥有:

  • 容错——即使一个或多个组件发生故障,系统仍能继续工作的能力。

  • 数据的可用性——在任何给定时间访问和修改数据的能力。

它解决了什么问题?

想象一下,您有一项银行服务与具有三个副本的节点进行通信。没有一个节点是领导者,因此架构是无领导者的。

您发送请求将客户的余额更新为 100 美元。您将其发送给所有副本。

前两个副本请求成功,但最后一个副本已关闭。几秒钟后,关闭的副本再次恢复,但它有旧数据。

我们如何解决这个问题?

暗示切换技术表示,当特定数据的节点离线时,系统的其他节点将临时存储用于不可用节点的更新或修改。

因此得名“提示”。

因此,当不可用节点恢复正常时,它可以检索提示并应用它们。

所以这个过程是这样的:

  1. 检测——当一个节点发生故障时,其他节点会检测到该故障并将该节点标记为不可用。

  2. 提示生成——当节点收到发送到不可用节点的请求时,它会将其存储在本地,通常存储到磁盘上的文件中。

  3. 提示传递——当可用节点重新联机时,它会向其他节点发送一条消息,请求在其离线时提供的任何提示。其他节点发送提示,该节点应用它们。

通过使用这种技术,即使节点出现故障或暂时不可用,我们也能确保数据的一致性和可用性。

优点

  • 提高数据可用性——提示切换通过将责任转移到其他节点来确保在临时节点故障期间数据仍然可访问。

  • 数据一致性——提示切换通过在故障节点恢复时与其他节点同步来帮助维护数据一致性。

  • 减少延迟——提示切换最大限度地减少节点故障对系统性能的影响,将请求路由到替代节点并减少延迟。

  • 可扩展性——提示切换支持数据责任的动态重新分配,允许系统处理增加的工作负载和节点变化。

缺点

  • 复杂性增加——实施提示切换增加了系统的复杂性,使开发、调试和维护更具挑战性。

  • 存储开销——提示切换需要存储额外的元数据,从而产生存储开销来跟踪切换状态。

  • 潜在的数据陈旧– 发生故障后,恢复的节点可能具有临时陈旧的数据,直到发生同步,从而导致潜在的不一致。

  • 网络流量增加——暗示切换涉及转移数据责任,导致网络流量增加并对网络性能产生潜在影响。

用例

提示切换通常在分布式数据库系统或分布式存储系统中实现,其中数据可用性和一致性至关重要。

以下是实施提示切换有益的一些场景:

  • 云存储系统——提示切换可以在节点暂时不可用时将客户端请求无缝重定向到云存储系统中的可用节点。

  • 消息系统——提示切换允许分布式消息系统在代理节点出现故障时将消息路由到其他活动代理,确保消息传递和系统可操作性。

  • 分布式文件系统——分布式文件系统中的提示切换允许在数据节点发生故障时将数据责任临时转移到其他节点,确保数据可用性和不间断的读/写操作。

  • 内容交付网络 (CDN) – CDN 中的暗示切换有助于在服务器暂时不可用时将内容交付请求重定向到网络中的其他服务器,从而确保向用户持续交付内容。

读取修复模式

来源

在分布式系统中,您可以将数据分区到多个节点中。

这引入了一个新的挑战,我们必须保持所有节点中的数据一致。

例如,如果您更新节点 A 上的数据,由于各种原因,更改可能不会立即传播到其他节点。

所以我们使用“读取修复”模式

当客户端从节点读取一条数据时,该节点将检查数据是否是最新的。如果不是,则它从另一个节点接收最新数据。

一旦它收到最新的数据,它就会用新数据更新旧数据的节点。因此,“修复”。

但这是在应用程序端完成的

在应用程序端做事非常灵活,因为您可以为每个服务拥有自己的自定义逻辑,但这会增加复杂性和开发时间。

值得庆幸的是,还有其他三种实现读取修复的方法:

  • 基于写入的读取修复——在写入操作期间主动更新多个副本或节点的最新数据。

  • 后台修复——安排定期的后台修复进程来扫描和修复数据库中的不一致。

  • 特定于数据库的读取修复——利用数据库提供的内置读取修复机制或冲突解决功能。

优点

  • 数据一致性——读取修复通过自动检测和纠正副本或节点之间的不一致来维护数据一致性。

  • 改进的用户体验——读取修复提供可靠和准确的数据,通过减少冲突或过时的信息来增强用户体验。

  • 容错——读取修复通过解决数据不一致和降低级联故障的风险来提高系统弹性。

  • 性能优化——读取修复通过最小化对单独修复过程的需求和分配修复工作负载来提高性能。

  • 简化开发——读取修复自动执行一致性检查,简化应用程序开发。

缺点

  • 复杂性增加——实施读取修复增加了系统设计、开发和维护工作的复杂性。

  • 性能开销——读取修复可能会引入额外的延迟和计算开销,从而影响整体系统性能。

  • 放大故障的风险——读取修复的不正确实施会传播不一致或放大故障。

  • 可扩展性挑战——在大型系统中协调修复可能具有挑战性,会影响性能和可扩展性。

  • 兼容性和可移植性——读取修复机制可能特定于某些数据库或技术,限制了兼容性和可移植性。

用例

在维护跨副本或节点的数据一致性至关重要的各种场景中,读取修复可能是有益的。

以下是您应该考虑使用读取修复的一些情况:

  • 分布式数据库系统——在分布式数据库系统中使用读取修复,其中数据在多个节点或副本之间进行复制,以确保数据的一致性。

  • 高数据一致性要求——当您的应用程序需要高水平的数据一致性时,例如在金融系统、协作编辑平台或实时分析中,实施读取修复。

  • 读取繁重的工作负载——考虑对读取繁重的工作负载进行读取修复,以检测和协调读取操作期间的不一致,通过减少对单独修复过程的需求来提高性能。

  • 具有网络延迟或故障的系统——在具有网络延迟或偶尔出现节点故障的环境中使用读取修复来自动检测和纠正由这些问题引起的数据不一致。

服务注册模式

当您使用分布式系统时,您将拥有具有可以向上或向下扩展的实例的服务。

一项服务一次可以有十个实例,另一次可以有两个。

这些实例的 IP 地址是动态创建的。问题就出现在这里。

想象一下,您有一个客户,它想与服务对话。如果 IP 地址是动态创建的,它如何知道服务的 IP 地址?

答案是服务注册表。

那是什么呀?

服务注册表通常是一个单独的服务,它运行以保存有关可用实例及其位置的信息。

但是服务注册中心如何知道所有这些信息呢?

当我们创建任何服务的实例时,我们会使用我们的名称、IP 地址和端口号将自己注册到服务注册表。

服务注册表然后将此信息存储在其数据存储中。

当客户端需要连接到服务时,它会查询服务注册表以获取连接到服务所需的信息。

现在我们知道什么是服务注册中心,让我们来谈谈服务发现的模式。

客户端发现

来源

第一种也是最简单的方法是让客户端调用服务注册表并获取有关服务的所有可用实例的信息。

当您想要:

  • 有一些简单明了的东西。

  • 让客户端决定调用哪些实例。

但这里的显着缺点是它将客户端与服务注册表耦合在一起。

因此,您必须为服务使用的每种编程语言和框架实现客户端发现逻辑。

服务器端发现

另一方面,服务器端发现强制客户端通过负载均衡器发出请求。

负载均衡器将调用服务注册表并将请求路由到特定实例。

服务器端发现具有以下优点:

  • 将服务注册表的细节抽象到负载均衡器意味着客户端可以简单地向负载均衡器发出请求。

  • 它内置在大多数流行的提供商中,例如 AWS ELB(弹性负载均衡器)。

唯一的缺点是您必须维护基础架构中的另一个组件(服务注册表)。

断路器模式

来源

假设您有三个服务,A、B 和 C。它们都按顺序相互调用 – A 调用 B,B 调用 C。

只要服务正常,一切都会顺利。但是,如果其中一项服务出现故障,那么其他服务将失败。如果服务 C 宕机,那么 B 和 A 也会宕机。

我们如何解决这个问题?

我们使用断路器模式作为两个服务之间的中间件。

它监视第二个服务的状态,并在出现故障或无响应的情况下停止对服务的请求,并向组件返回回退响应或错误消息。

中间件有三种状态:

  • 已关闭——该服务可以与第二个服务正常通信。

  • 打开——当中间件检测到一定数量的连续故障时,它会转换为打开状态,并立即阻止对服务的所有请求。

  • 半开– 一段时间后,中间件转换为半开状态,允许将有限数量的请求发送到第二个服务。如果成功,则中间件转换为关闭状态,否则它将转换为打开状态。

总的来说,断路器模式通过提供回退机制和减少失败服务的负载来提高弹性。

它还提供对服务状态的洞察力,帮助我们更快地识别故障。

优点

  • 容错——断路器通过防止级联故障和减少不可用或容易出错的依赖项的影响来增强系统稳定性。

  • 快速失败机制——断路器可以快速检测到故障,从而通过避免等待失败的请求完成来实现更快的恢复并减少延迟。

  • 优雅降级——断路器通过在故障期间提供替代响应或回退机制,使系统能够优雅地降级功能。

  • 负载分配——断路器可以在高流量期间或服务遇到问题时平衡可用资源之间的负载。

缺点

  • 复杂性增加——实施断路器会增加系统的复杂性,影响开发、测试和维护工作。

  • 开销和延迟——断路器引入了处理开销和延迟,因为请求被拦截并根据电路状态进行评估。

  • 误报——即使依赖项可用,断路器也可能错误地阻止请求,从而导致误报并影响系统可用性和性能。

  • 对监控的依赖——断路器依赖于准确的监控和健康检查,如果这些不可靠,断路器的有效性可能会受到影响。

  • 对远程服务的控制有限——断路器提供保护,但缺乏对底层服务的直接控制,需要外部干预来解决某些问题。

用例

断路器模式在系统依赖远程服务或外部依赖项的特定场景中很有用。

以下是一些建议使用断路器模式的情况:

  • 分布式系统——在构建与多个服务或外部 API 通信的分布式系统时,断路器模式通过减轻这些依赖项中故障的影响来帮助提高容错性和弹性。

  • 不可靠或间歇性服务——如果您正在与已知不可靠或具有间歇性可用性的服务或依赖项集成,实施断路器可以保护您的系统免受由这些依赖项引起的长时间延迟或故障。

  • 微服务架构——在微服务架构中,各个服务都有自己的依赖关系,实施断路器可以防止跨服务的级联故障,并在故障期间实现功能的优雅降级。

  • 高流量场景——在系统遇到高流量或负载的情况下,断路器可以通过将请求重定向到替代服务或提供回退响应来帮助有效地分配负载,从而保持系统稳定性和性能。

  • 弹性和响应系统——当您想要构建弹性和响应故障的系统时,断路器模式很有用。它允许系统快速检测问题并从中恢复,从而减少对用户的影响并确保更流畅的用户体验。

领导人选举模式

 

领导者选举模式是一种在分布式系统中赋予单一事物(进程、节点、线程、对象)超能力的模式。

该模式用于确保一组节点能够有效且高效地协调。

因此,当您有三个或五个节点执行类似的任务(例如数据处理或维护共享资源)时,您不希望它们相互冲突(即争用资源或干扰彼此的工作)。

选出一位协调一切的领导者

领导者将负责关键决策或在其他流程之间分配工作量。

任何节点都可以成为领导者,所以我们在选举他们时要小心。我们不希望同时有两位领导人。

所以我们必须有一个很好的选择领导者的制度。它必须是健壮的,这意味着它必须应对网络中断或节点故障。

如何选择领导者

一种名为 Bully 的算法为每个节点分配了一个唯一标识符。具有最高标识符的节点最初被视为领导者。

如果低级别节点检测到领导者失败,它会向所有其他高级别节点发送信号以接管。如果他们都没有响应,那么该节点将使自己成为领导者。

另一种流行的算法称为 Ring 算法。每个节点都排列成一个逻辑环。具有最高标识符的节点被初始化为一个环。

如果排名较低的节点检测到环发生故障,则它将请求所有其他节点将其领导者更新为下一个最高节点。

上述两种算法都假设每个节点都可以被唯一标识。

优点

  • 协调——领导者选举通过建立集中控制点可以在分布式系统中更好地组织和协调。

  • 任务分配——领导者可以在节点之间有效地分配和分配任务,优化资源利用率和工作负载平衡。

  • 容错——领导者选举通过在当前领导者失败时迅速选举新领导者来确保系统弹性,从而最大限度地减少中断。

  • 一致性和顺序——领导者维护一致的操作并在分布式系统中强制执行正确的任务顺序,确保连贯性。

缺点

  • 开销和复杂性——领导人选举引入了额外的复杂性和通信开销,增加了网络流量和计算要求。

  • 单点故障——如果领导者出现故障或不可用,则依赖于单个领导者可能会导致系统中断。

  • 选举算法——由于在性能、容错和可扩展性方面的不同权衡,实施和选择适当的领导人选举算法可能具有挑战性。

  • 对网络条件的敏感性——领导者选举算法可能对网络条件敏感,在延迟、数据包丢失或网络分区的情况下可能会影响准确性和效率。

用例

  • 分布式计算——在分布式计算系统中,领导者选举对于协调和同步多个节点的活动至关重要。它支持选择负责分配任务、保持一致性和确保有效资源利用的领导者。

  • 共识算法——领导者选举是共识算法(如 Paxos 和 Raft)的基本组成部分。这些算法依赖于选举领导者来在分布式节点之间就状态或操作顺序达成一致。

  • 高可用性系统——领导者选举用于需要高可用性和容错的系统。通过在当前领导者出现故障时快速选出新领导者,这些系统可以确保不间断运行并减轻故障的影响。

  • 负载均衡——在负载均衡场景中,领导者选举可用于选择负责在多个服务器节点之间分发传入请求的领导节点。这有助于优化资源利用率并平均分配工作负载。

  • 主从数据库复制——在数据库复制设置中,领导者选举用于确定负责接受写操作的主节点。选出的主节点协调与从节点的数据同步,确保整个副本集的一致性。

  • 分布式文件系统——领导者选举通常用于分布式文件系统,例如 Apache Hadoop 的 HDFS。它有助于保持元数据的一致性,并实现跨节点集群的高效文件访问和存储管理。

大头图案

来源

在分布式系统中,由于各种原因,事情会失败。这就是为什么我们需要确保我们的系统在发生故障时具有弹性。

改进它的一种方法是使用舱壁模式。

假设我们有两个服务,A 和 B。

A 的一些请求依赖于 B。但问题是服务 B 非常慢,这使得 A 变慢并阻塞了它的线程。这使得对 A 的所有请求变慢,即使是那些不依赖于 B 的请求。

隔板模式通过为 B 的请求分配特定数量的线程来解决这个问题。这可以防止 A 由于 B 而消耗所有线程。

优点

  • 故障隔离——隔板模式包含单个服务中的故障,最大限度地减少它们对整个系统的影响。

  • 可扩展性——服务可以独立扩展,允许在不影响整个系统的情况下根据需要分配资源。

  • 性能优化——特定服务可独立进行性能优化,确保高效运行。

  • 开发敏捷性——代码模块化和关注点分离促进了并行开发工作。

缺点

  1. 复杂性——实施隔板模式增加了系统架构的复杂性。

  2. 增加资源使用——跨组件重复资源可能会增加资源消耗。

  3. 集成挑战——协调和集成服务可能具有挑战性,并会引入潜在的故障点。

用例

以下是隔板模式有益的一些常见场景:

  • 微服务架构——隔板模式用于隔离各个微服务,确保分布式系统的容错性和可扩展性。

  • 资源密集型应用程序——通过将系统划分为组件,隔板模式优化了资源分配,从而能够在不影响其他组件的情况下高效处理资源密集型任务。

  • 并发处理——隔板模式通过为每个处理单元分配专用资源来帮助处理并发处理,防止故障影响其他单元。

  • 高流量或高需求系统——在经历高流量或需求的系统中,舱壁模式将负载分布在组件之间,防止瓶颈并实现对增加的流量的可扩展处理。

  • 模块化和可扩展系统——隔板模式有利于模块化开发和维护,允许独立更新和部署系统中的特定组件。

重试模式

 

重试模式用于处理临时故障。

请求失败的原因有很多,从错误的连接到部署后的站点重新加载。

假设我们有一个客户和一个服务。客户端向服务发送请求并收到返回的响应代码 500。

根据重试模式,有多种方法可以处理此问题。

  • 如果问题很少见,请立即重试。

  • 如果问题更常见,则在一定延迟后重试(例如 50 毫秒、100 毫秒等)

  • 如果问题不是暂时的(无效凭证等),则取消请求。

但是有时候,客户端会认为请求失败了

有时操作成功但请求无法返回某些内容。当请求不是无状态的时会发生这种情况,这意味着它们会以某种方式改变系统的状态(比如数据库上的 CRUD 操作)。

所以我们所做的是发送一个重复的请求,这可能会导致系统出现问题。

为此,我们需要某种跟踪系统。我们可以使用断路器模式来限制重复重试失败/恢复服务的影响。

优点

  • 弹性和可靠性——通过自动重试失败的请求,您可以增加系统从故障中恢复的机会(弹性)并增加成功请求的机会(可靠性)。

  • 错误处理——重试操作透明地向最终用户隐藏错误,使系统看起来更健壮。

  • 简化实施——重试模式易于实施。您可以将所有请求都用retry pattern class.

  • 性能优化——在某些情况下,临时错误可能会很快得到解决,并且后续重试可以成功而不会出现明显延迟。这可以通过避免不必要的故障和减少对最终用户的影响来帮助优化性能。

缺点

  • 延迟增加——尝试重试时,系统中会引入固有延迟。如果重试频繁或操作需要很长时间才能完成,系统的整体延迟可能会增加。

  • 无限循环的风险——如果没有适当的保护措施,如果潜在的错误条件持续存在,重试可能会导致无限循环。

  • 增加网络和资源使用率——重试失败的操作会导致额外的网络流量和资源利用率。

  • 级联故障的可能性——如果故障的根本原因没有解决,重试该操作可能会导致整个系统出现级联故障。

  • 难以处理幂等操作——如果多次执行请求,重试有状态请求可能会导致意想不到的后果。

用例

重试模式的适用性取决于正在开发的系统的具体要求和特性。

该模式对于处理临时错误最有用。但是,您还必须牢记涉及长时间延迟、有状态请求或需要手动干预的情况。

现在,让我们看看重试请求有帮助的一些用例:

  • 网络通信——在网络上使用第三方服务或 API 时,可能会发生各种错误。

  • 数据库操作——由于临时不可用、超时或死锁情况,数据库也面临同样的问题。

  • 文件或资源访问——访问文件、外部资源或依赖项时,可能会因锁定、权限或暂时不可用而导致失败。

  • 队列处理——依赖消息队列或事件驱动架构的系统可能会遇到队列拥塞、消息传递失败或服务可用性波动等临时问题。

  • 分布式事务——在分布式系统中,跨多个服务或组件执行协调事务可能会面临网络分区或临时服务故障等挑战。

  • 数据同步——在不同系统或数据库之间同步数据时,临时错误可能会中断同步过程。

分散聚集模式

 

假设我们有一个流程繁重的任务,比如视频压缩。

我们得到一个视频,必须将它压缩成 5 种不同的分辨率,例如 240p、360p、480、720p 和 1080p。

我们有一个单一的图像压缩服务,它接收视频并按顺序处理每个分辨率。这里的问题是它非常慢。

分散-聚集模式建议我们同时运行多个这些进程并将所有结果收集在一个地方。

因此,对于我们的视频处理示例,它的外观如下:

  1. 分散——我们将把任务分成多个节点,这样一个节点将负责将视频压缩为 240p,另一个节点将压缩为 360p,等等。

  2. 处理——每个节点将单独并同时压缩其视频。

  3. 收集——一旦所有节点都完成了视频压缩,视频将存储在某个服务器上,我们收集所有不同版本的链接。

现在我们可以并行化整个过程并显着提高性能,而不是等待我们的每个视频按顺序压缩。

优点

  • 并行处理——分散-聚集模式通过启用子任务的并行处理来提高性能。任务可以跨多个节点或处理器并发执行。

  • 可扩展性——分散聚集模式允许我们水平扩展,我们的工作负载越高,我们提供的节点越多。

  • 容错——这种模式通过将失败的子任务重新分配到其他可用节点来增强容错能力,确保整体任务仍然可以成功完成。

  • 资源利用——分散-聚集模式通过有效地利用跨多个节点或处理器的可用计算资源来优化资源利用。

缺点

  • 通信开销——这种模式涉及节点之间的通信,这会引入潜在的延迟和网络拥塞,这可能会影响整体性能,尤其是在数据量很大的情况下。

  • 负载平衡——在节点之间均匀地平衡工作负载可能具有挑战性,如果某些节点空闲而其他节点过载,则可能导致效率低下和性能瓶颈。

  • 复杂性——这种模式不容易实施,并为系统增加了一层额外的复杂性。它需要仔细规划和同步机制来协调子任务。

  • 数据依赖性——处理子任务之间的依赖性,其中一个子任务的输出需要作为另一个子任务的输入,与其他模式相比,在分散-聚集模式中可能更复杂。

用例

分散-聚集模式在分布式系统和并行计算场景中很有用,在这些场景中,您可以将任务划分为更小的子任务,这些子任务可以跨多个节点和处理器并发执行。

以下是一些可以使用分散-聚集模式的用例:

  • 网页抓取– 您可以使用分散收集模式同时获取和抓取多个网页。

  • 数据分析——应用分散-聚集将大型数据集分成块,以便在数据分析任务中进行并行处理,结合各个结果以获得最终见解。

  • 图像/视频处理——利用分散-聚集进行分布式图像/视频处理,例如并行编码帧并收集最终输出的结果。

  • 分布式搜索——在分布式搜索系统中实施分散-聚集,以跨节点分布搜索查询,为最终搜索输出收集和排序结果。

  • 机器学习——在分布式机器学习中应用分散-聚集,对分散数据进行并行模型训练,并将模型组合为最终训练模型。

  • MapReduce –在 MapReduce 模型中结合分散聚集,用于大数据处理、分散输入、中间结果的并行处理以及最终输出的收集。

布隆过滤器(数据结构)

 

Bloom 过滤器是一种数据结构,旨在以内存和速度高效地告诉您某个项目是否在集合中。

但这种效率的代价是它是概率性的。它可以告诉你一个项目是:

  • 绝对不在一个集合中。

  • 可能在一个集合中。

优点

  • 空间效率——与哈希表或其他类似数据结构相比,布隆过滤器通常需要更小的内存来存储相同数量的元素。

  • 快速查找——检查一个元素是否在布隆过滤器中具有恒定的时间复杂度,无论过滤器的大小或其包含的元素数量如何。

  • 无漏报——如果某个元素不在过滤器中,布隆过滤器会提供明确的“否”答案。没有假阴性,这意味着如果过滤器声称某个元素不存在,则保证不存在。

  • 可并行化——布隆过滤器可以很容易地并行化,允许在多核系统或分布式环境中高效实施。

缺点

  • 误报——由于它们的概率性质,过滤器错误地声称某个元素存在而实际上不存在的可能性很小。随着过滤器变得更加拥挤或元素数量增加,误报的概率会增加。

  • 有限操作——布隆过滤器仅支持插入和成员测试。它们不允许删除元素或提供一种机制来检索存储在过滤器中的原始元素。一旦一个元素被插入,它就不能被单独移除。

  • Fixed Size——布隆过滤器的大小必须事先确定,不能动态调整大小。如果这些参数估计不准确,过滤器可能会变得无效或浪费内存。

  • 无序数据——布隆过滤器不维护顺序或提供有关存储元素的任何信息。它们不适合需要根据特定条件对元素进行排序或检索的场景。

用例

Bloom 过滤器在可以接受具有低误报率的近似成员测试以及内存效率优先的情况下最有用。

以下是布隆过滤器特别有用的一些场景:

  • 缓存系统——布隆过滤器可用于快速检查某个项目是否可能存在于缓存中。通过检查布隆过滤器,系统可以避免从缓存或底层存储中获取不太可能存在的项目的代价高昂的操作,从而提高整体性能。

  • 内容交付系统——CDN 使用布隆过滤器来处理缓存失效。我们没有检查每个边缘服务器,而是使用布隆过滤器来识别边缘服务器是否可能具有特定资源。

  • 反垃圾邮件系统——反垃圾邮件系统根据常见的垃圾邮件数据库检查电子邮件。我们没有使用昂贵的数据库检查,而是使用效率更高的布隆过滤器。

  • 分布式数据处理——在 Hadoop 或 Spark 等分布式数据处理框架中。我们使用布隆过滤器通过预过滤数据和删除不必要的混洗来优化连接操作。

结论

你不需要成为所有这些事情的专家——我不是。即使您从未直接使用或处理其中一些概念,了解它们仍然是件好事。

尝试在简单的项目中实现它们。保持项目足够简单是个好主意,这样你就不会走神。

感谢您阅读。

(更多优质教程:java567.com,搜"分布式")