对于刚开始使用微服务的团队来说,最大的挑战之一是遵守金发姑娘原则:不大不小,耦合度不高不低。 部分挑战源于对究竟是什么构成了良好设计的微服务的困惑。
数十位 CTO 分享了他们的访谈经验,这些对话阐明了良好设计的微服务的五个特征。 本文将帮助指导团队设计微服务。(有关更多信息,请查看即将出版的书籍 初创公司微服务)。 本文将简要介绍微服务边界和要避免的任意“规则”,然后再深入探讨指导您设计微服务的五个特征。
微服务边界
使用微服务开发新系统的核心优势之一是该架构允许开发人员独立构建和修改各个组件——但当涉及到最小化每个 API 之间的回调次数时,可能会出现问题。 SparkPost 工程副总裁 Chris McFadden 认为,解决方案是应用适当的服务边界。
关于边界,与有时难以掌握和抽象的领域驱动设计 (DDD)(微服务框架)概念相比,本文侧重于与我们行业顶级 CTO 共同创建明确定义的微服务边界的实用原则。
避免任意的“规则”
如果您阅读了足够多关于设计和创建微服务的建议,您一定会遇到以下一些“规则”。 尽管将它们用作创建微服务的指导原则很诱人,但坚持这些任意规则并不是确定微服务周全边界的明智方法。
“一个微服务应该有 X 行代码”
让我们明确一件事:微服务中的代码行数没有限制。 微服务不会仅仅因为您编写了几行额外的代码就突然变成单体应用。 关键是确保服务内部代码的高度内聚性(稍后会详细介绍)。
“将每个函数都变成一个微服务”
如果一个函数基于三个输入值计算某些内容并返回结果,那么它是否是微服务的良好候选者? 它应该是一个单独可部署的应用程序吗? 这实际上取决于函数是什么以及它如何为整个系统服务。 在您的上下文中,将每个函数都变成一个微服务可能根本没有意义。
其他任意规则包括那些没有考虑您的整个上下文的规则,例如团队的经验、DevOps 能力、服务正在做什么以及数据的可用性需求。
良好设计的服务的 5 个特征
如果您阅读过有关微服务的文章,您无疑会遇到关于什么使服务设计良好的建议。 简而言之,高内聚和低耦合。 如果您不熟悉这些概念,可以查看 许多 文章 来回顾这些概念。 虽然它们提供了合理的建议,但这些概念相当抽象。 以下是基于与经验丰富的 CTO 的对话,在创建良好设计的微服务时要牢记的关键特征。
#1:它不与其他服务共享数据库表
在 SparkPost 的早期,Chris McFadden 和他的团队不得不解决每个 SaaS 企业都面临的问题:他们需要提供身份验证、帐户管理和计费等基本服务。
为了解决这个问题,他们创建了两个微服务:Users API 和 Accounts API。 Users API 将处理用户帐户、API 密钥和身份验证,而 Accounts API 将处理所有与计费相关的逻辑。 这是一个非常符合逻辑的分离——但在不久之后,他们发现了一个问题。
“我们有一个名为 User API 的服务,我们还有另一个名为 Account API 的服务。 问题是它们实际上在它们之间有多次来回调用。 因此,您会在帐户中执行某些操作,并在用户中或反之亦然地调用和访问端点,”McFadden 解释道。
这两个服务耦合太紧密了。
在设计微服务时,如果多个服务引用同一个表,这是一个危险信号,因为它可能意味着您的数据库是耦合的来源。
这实际上是关于服务如何与数据相关联,这正是 Swiftype SRE, Elastic 的负责人 Oleksiy Kovrin 告诉我的。 “我们在开发新服务时使用的主要基本原则之一是它们不应跨越数据库边界。 每个服务都应依赖于自己的一组底层数据存储。 这使我们能够集中访问控制、审计日志记录、缓存逻辑等,”他说。
Kovyrin 接着解释说,如果您的数据库表子集“与数据集的其余部分没有或只有很少的连接,这是一个强烈的信号,表明该组件可以隔离到单独的 API 或单独的服务中。”
Lead Honestly 的联合创始人 Darby Frey 也呼应了这种观点:“每个服务都应该有自己的表 [并且] 永远不应该共享数据库表。”
#2:它拥有的数据库表数量最少
微服务的理想大小是足够小,但不能更小。 每个服务的数据库表数量也是如此。
Scaylr 工程主管 Steven Czerwinski 在一次采访中解释说,Scaylr 的最佳点是“一个服务一到两个数据库表”。
SparkPost 的 Chris McFadden 同意:“我们有一个抑制微服务,它处理、跟踪数百万和数十亿的抑制条目,但它都非常专注于抑制,所以那里实际上只有一两个表。 Webhooks 等其他服务也是如此。”
#3:它经过深思熟虑地设计为有状态或无状态
在设计微服务时,您需要问问自己,它是否需要访问数据库,或者它是否将成为处理 TB 级数据(如电子邮件或日志)的无状态服务。
Algolia 的 CTO Julien Lemoine 解释说:“我们通过定义服务的输入和输出来定义服务的边界。 有时服务是网络 API,但它也可以是消耗文件并在数据库中生成记录的进程(这是我们的日志处理服务的情况)。”
在最开始就明确状态性,这将有助于设计出更好的服务。
#4:其数据可用性需求已考虑在内
在设计微服务时,请记住哪些服务将依赖于这项新服务,以及如果该数据不可用,对整个系统的影响。 考虑到这一点,您可以为此服务正确设计数据备份和恢复系统
Steven Czerwinski 提到,在 Scaylr,关键客户行空间映射数据由于其重要性而被复制并以不同的方式分离。
相比之下,他补充说,“每个分片的信息都在其自身的小分区中。 如果它崩溃了,那很糟糕,因为那部分客户群将无法使用他们的日志,但这只会影响 5% 的客户,而不是 100% 的客户。”
#5:它是单一事实来源
将服务设计为系统中某些事物的单一事实来源
例如,当您从电子商务站点订购商品时,会生成一个订单 ID。 其他服务可以使用此订单 ID 查询订单服务以获取有关订单的完整信息。 使用 发布/订阅模式,在服务之间传递的数据应该是订单 ID,而不是订单本身的属性/信息。 只有订单服务拥有完整的信息,并且是给定订单的单一事实来源。
对大型团队的考虑因素
在牢记上面列出的五个考虑因素的同时,大型团队应注意其组织结构对微服务边界的影响。
对于大型组织,在这些组织中,整个团队可以专注于拥有某项服务,在确定服务边界时,组织考虑因素会发挥作用。 有两个考虑因素需要考虑:独立的发布计划和不同的正常运行时间重要性。
Cloud66. 的 CEO Khash Sajadi 说:“我们见过的最成功的微服务实施要么基于软件设计原则(例如领域驱动设计)和服务导向架构,要么反映组织方法。”
Sajadi 继续说道:“所以 [对于] 支付团队来说,他们有支付服务或信用卡验证服务,这就是他们向外界提供的服务。 因此,这不一定与软件有关。 这主要与 [向外界] 提供一项或多项服务的业务部门有关。”
双披萨原则
亚马逊是拥有多个团队的大型组织的完美示例。 正如 API Evangelist 上发表的一篇文章中提到的,杰夫·贝佐斯向所有员工发布了一项指令,告知他们公司内的每个团队都必须通过 API 进行通信。 任何不这样做的人都将被解雇。
这样,所有数据和功能都通过接口公开。 贝佐斯还设法让每个团队都解耦,定义他们的资源是什么,并通过 API 使它们可用。 亚马逊正在从头开始构建系统。 这使公司内的每个团队都成为彼此的合作伙伴。
我与 Iron.io 的 CTO Travis Reeder 谈到了贝佐斯的内部倡议。
Reeder 说:“杰夫·贝佐斯强制要求所有团队都必须构建 API 才能与其他团队进行通信。 他也是提出‘双披萨’规则的人:一个团队的规模不应超过两个披萨可以喂饱的人数。
“我认为这里也适用同样的道理:一个小团队可以开发、管理和高效处理任何事情。 如果它开始变得笨拙或开始减速,那么它可能变得太大了,”Reeder 告诉我。
最终考虑因素:您的服务大小合适且定义明确吗?
在微服务系统的测试和实施阶段,有一些指标需要牢记。
指标 #1:服务之间是否存在过度依赖?
如果两个服务不断地相互调用,那么这是一个强烈的耦合迹象,也是它们最好合并为一个服务的信号。
回到 Chris McFadden 的例子,他有两个 API 服务,帐户和用户,它们不断地相互通信,McFadden 想出了一个合并这些服务的想法,并决定将其称为 Accuser's API。 事实证明这是一个富有成效的策略。
McFadden 告诉我:“我们开始做的是消除这些链接 [这些链接是] 它们之间的内部 API 调用。 这有助于简化代码。”
指标 #2:设置服务的开销是否超过了服务独立带来的好处?
Darby Frey 解释说:“每个应用程序都需要将其日志聚合到某个地方,并且需要对其进行监控。 您需要为其设置警报。 您需要拥有标准操作程序和运行手册,以应对出现故障的情况。 您必须管理对该事物的 SSH 访问。 为了使应用程序能够运行,必须存在大量基础事物。”
主要要点
设计微服务通常感觉更像是一门艺术而不是一门科学。 对于工程师来说,这可能不太好接受。 有很多通用建议,但有时可能有点过于抽象。 让我们回顾一下在设计下一组微服务时要寻找的五个具体特征
- 它不与其他服务共享数据库表
- 它拥有的数据库表数量最少
- 它经过深思熟虑地设计为有状态或无状态
- 其数据可用性需求已考虑在内
- 它是单一事实来源
下次您设计一组微服务并确定服务边界时,参考这些原则应该会使任务更容易。
[请参阅我们的相关报道,什么是微服务?]
评论已关闭。