如何将应用程序分解为细粒度的微服务会带来可能最终导致巨大灾难的复杂性,以及如何避免这种情况。
在ironSource,我们与面向服务的体系结构(SOA)一起工作,该体系已经存在了数十年,并且已经接受了其最新的迭代-微服务。使用微服务方法进行应用程序开发使我们能够提高弹性并缩短上市时间。当我们将整个应用程序堆栈分解成小块时,它更易于开发,测试,部署,最重要的是更改和维护。
也就是说,将应用拆分为多个较小的单元并不意味着一切都可以立即正常运行。去年,我们观察到了几个服务可用性问题,经过大量调查,我们意识到要避免这些问题,以特定方式实现微服务很重要。在我们的研究过程中,我们发现并减轻了一些不良做法,以防止可能的世界末日场景。在本文中,我将深入探讨一种技术,这些技术已帮助我们的研发团队享受微服务的全部好处,从而使他们在晚上睡个好觉。
我的服务是否彼此耦合?
传统的“请求驱动”架构(例如REST)是最简单,最常见的服务通信模式(例如,服务A向服务B询问一些信息并等待。服务B随后作出响应并将信息发送给服务A)。
使用HTTP API是开发人员学习和常用的基本知识之一。很显然,当收到并确认的请求由相应的服务,并有大量的工具来调试HTTP的API。因此,这是我们跨系统服务之间进行通信的默认方法。“请求驱动”通信的简单性为我们快速移动,提供新功能以及扩展我们的系统以满足所有需求提供了很好的帮助。
不幸的是,这种模式导致服务紧密耦合。在小型系统中,它运行得很好,但是对于由数十种服务构建的应用程序而言,这种耦合会阻碍开发敏捷性并阻止快速扩展。
使用此模式时要注意的主要风险是每个“核心”服务,都将可能成为单个故障点。这意味着,它可能会造成性能瓶颈,或者更糟的是,依赖服务的停机时间。因此,整个依赖链都被打乱了(即大灾难)。听起来似乎很容易解决,但是添加到链中的每个服务都需要一个服务发现机制(甚至大型系统中的服务网格),故障转移/重试,断路器,超时和缓存机制到位,端到端完美地工作是一个巨大的挑战。
实际上, 在整个系统上使用诸如REST之类的同步通信使其表现得像一个整体,或者更确切地说是一个分布式整体,从而无法享受微服务的全部好处 。
为了消除混乱,在IronSource,我们正在移动许多核心服务以使用异步事件驱动的体系结构进行通信。我们的方法是让我们的核心服务在信息更新时发布其提供的信息,而不是等待其他服务请求该信息。通过使用“推”而不是“拉”,我们的系统可以实时处理数据。现在,我们可以处理许多复杂的缓存管理和清除机制,服务发现和重试技术,这些技术用于在使用同步服务通信时维护系统的可靠性和性能。
而且,服务现在可以异步地将事件发布到弹性消息代理(在我们的例子中为Kafka)。他们信任经纪人将消息路由到正确的服务,并且接收方订阅了他们感兴趣的关键事件。添加订阅者很容易,这样就不会给发布者服务带来更多负担。
为什么选择Kafka呢?
我们选择Kafka是因为它非常有弹性且可靠。它具有强大的社区和文档资源。与其他消息代理不同,Kafka使我们能够以高度可用的方式复制数据并控制数据保留策略,因此即使事件被多个使用者使用后也可以保留。事件会根据保留策略在流中自动过期。因此,如果您正在使用事件源,并且想从事件日志中重现状态,则它可以用作持久存储源,从中重建当前和过去的状态。此外,Kafka为流和实时聚合(使用KSQL)等高级方案提供了本地支持,并与我们基础架构中的许多组件(例如Spark流,Cassandra,Elasticsearch,S3,
在异步通信中,一个服务可能仍然依赖于另一个服务,这意味着各方之间的API和依赖关系仍然存在。但是,如果一项服务失败或过载并且响应速度很慢,它将不会影响其他服务,因为它们现在彼此松散耦合并且包含了它们需要响应的所有内容。
因此,公共事件总线的好处在于,它消除了我们的核心服务同步通信时出现的单点故障和性能瓶颈-例如,队列仍可以保留发送到服务B的消息,直到消息备份并能够使用它们为止。 。
这导致我们遵循“单跳”规则:
“ 默认情况下,除非在特殊情况下,否则服务不应调用其他服务来响应请求。 ”
服务应该是自包含的,并管理自己的数据。允许服务调用其他服务会增加请求的开销,并可能导致服务非常缓慢或无响应。如果您发现需要在多个服务之间来回调用,则建议您探索使用异步事件驱动模式还是将这些服务合并为一个(微型组件)服务可以为您提供更健康的服务。
当然,每个规则都有例外。在某些情况下,您需要做出明智的决定,以便在服务之间进行同步通信以响应请求。
例如,同步通信的一种典型情况是拥有一个集中的身份验证服务,该服务从多个面向用户的API获取同步服务器调用,以验证和验证用户令牌。身份验证服务的分离在开发和部署方面释放了敏捷性,为需要身份验证的任何服务创建了高度的凝聚力,并使团队可以独立高效地工作。此外,分离为身份验证服务和其他使用该服务的其他服务启用了不同的自动缩放模式和资源分配。最重要的是,它使您能够同步阻止任何未通过身份验证的用户请求。
等待,如果事件总线变成单点故障该怎么办?
在使用事件总线来消除系统内的故障点的同时,您可能会担心事件总线本身是否是单点故障。好吧,继续做您为了增强健壮性和扩展性而总是做的事情:分发它,部署事件总线的多个实例以实现高可用性,并弄清楚是否需要重试。使用Kafka,您将获得许多选项和配置,以对其进行调整以实现高可用性。如果您有多个不同的用例和高负载,我的建议是考虑旋转多个集群,甚至考虑在多个数据中心上部署集群(使用主动-主动或主动-被动拓扑)。考虑到这一点,我建议检查如果完全丢失事件总线会发生什么情况。虽然结果似乎很明显,实际上,您测试的不仅仅是故障,还测试了恢复。了解系统如何应对故障对于确保弹性至关重要。
全部放在一起
我们的服务现在正在发出事件,从而导致事实日志:
该日志使我们能够通过处理事件(即事件溯源)来重构当前和过去的状态。事实的唯一来源成为存储事件的数据存储库。消耗日志中数据的每个服务都变得独立,不再与任何微服务集(无论它们处于启动,关闭还是缓慢)耦合。从同步依赖项取消链接服务改善了我们的故障隔离,并且我们的系统几乎不受单个模块故障的影响。整体可靠性和性能提高了,这对我们的业务来说是一个巨大的胜利。
下一步是什么?
我们已经将我们的应用程序细分为细粒度的微服务,我们在所有内容的中间放置了一个弹性事件总线,以协调通信,并使每个服务都是独立的。我们甚至将我们的团队组织成“小队”或足够小的团队,以便可以用两个比萨饼来喂养他们。在本系列的下一篇文章中,我们将处理可能引起危险信号的其他问题: