如何在微服务架构中处理共享状态?

20
在我们公司中,我们正在从一个巨大的单块应用转换为微服务架构。做出这个决定的主要技术因素是需要能够独立扩展服务以及开发可扩展性——我们有十个敏捷团队在不同的项目(或“微服务”)上工作。
过渡过程非常顺利,我们已经开始受益于这种新的技术和组织结构的优势。但是,现在我们面临的主要问题是如何管理这些微服务之间“状态”的依赖关系。
举个例子:其中一个微服务处理用户和注册。该服务(我们称之为X)负责维护身份信息,因此是用户“id”的主要提供者。其余的微服务都对此有很强的依赖性。例如,一些负责用户资料收集(A)、用户权限(B)、用户组(C)等事项的服务依赖于这些用户ID,因此需要在这些服务之间维护一些数据同步(即,服务A不应具有未在服务X中注册的userId信息)。我们目前通过使用RabbitMQ通知状态更改(例如,新注册)来维护此同步。
正如你所想象的那样,有许多“Xs”:许多“主”服务和更复杂的它们之间的依赖关系。
当管理不同的开发/测试环境时,主要问题出现了。每个团队(因此,每个服务)都需要通过几个环境才能使代码在线:持续集成、团队集成、验收测试和生产环境等。
显然,我们需要所有服务在所有这些环境中工作,以检查系统作为一个整体是否正常运行。这意味着为了测试依赖服务(A、B、C等),我们不仅必须依赖于服务X,还必须依赖于其状态。因此,我们需要以某种方式维护系统完整性并存储全局且一致的状态。我们目前的方法是从现场环境获取所有数据库的快照,对其进行一些转换以缩小和保护数据隐私,并在特定环境中测试之前将其传播到所有环境。这显然是一个巨大的开销,无论是组织上还是在计算资源上:我们有十个持续集成环境、十个集成环境和一个验收测试环境,所有这些环境都需要经常用来刷新与现场共享数据和最新版本的代码。
我们正在努力寻找更好的方法来减轻这种痛苦。目前,我们正在评估两个选项:
1. 对于所有这些服务使用类似Docker容器的方法 2. 每个服务都有两个版本(一个用于该服务的开发,另一个作为沙箱由其他团队用于开发和集成测试) 没有这些解决方案能够缓解服务之间共享数据的问题。 我们想知道其他公司/开发人员如何解决这个问题,因为我们认为这在微服务架构中肯定很普遍。
你们是怎么做的?你们也有这个问题吗?有什么建议吗?
非常感谢!

当你说“存储全局和一致的状态”时,你是指与实际系统相同的状态还是某种合成状态?据我所见,你有几个集成环境级别,每个级别都专注于特定的微服务。 - neleus
感谢您的评论,neleus。请阅读我对Eugene答案的澄清。 - Víctor
所有团队都同意为整个系统制定一个“主”测试数据集。那么,您是否考虑仅将这些数据/状态提供给暂存环境,而不是所有团队?对于每个团队,设置部分仿真器来覆盖一组明确定义的测试用例将需要更少的工作量。 - neleus
我刚看到这篇非常有趣的文章:链接。请查看“为每个微服务创建单独的数据存储”部分;你们中有人使用建议的主数据管理方法吗?如果是,你们使用哪个工具? - Víctor
很棒的帖子!已经收藏了。 - neleus
显示剩余3条评论
3个回答

11

这一次我从不同的角度阅读了您的问题,所以这里有一个“不同的观点”。我知道可能已经太晚了,但希望对进一步的发展有所帮助。

看起来“共享状态”是错误解耦的结果。在“正确”的微服务架构中,所有微服务都必须在功能上而不是逻辑上隔离。我的意思是,所有三个用户配置文件信息(A)、用户权限(B)和用户组(C)在功能上看起来相同,或多或少是功能上连贯的。它们似乎是一个具有连贯存储的单一“用户服务”,尽管它可能不像一个服务。我没有看到在这里解耦它们的任何理由(或者至少您没有提及它们)。

从这一点出发,将其拆分为更小的独立部署单元可能会带来比好处更多的成本和麻烦。应该有一个重要的原因(有时是政治原因,有时仅仅是缺乏产品知识)。

因此,真正的问题与微服务隔离有关。理想情况下,每个微服务都可以作为一个完整独立的产品存在,并提供明确定义的业务价值。在详细说明系统架构时,我们将其分解为微小的逻辑单元(您的情况下为A、B、C等甚至更小),然后定义功能上连贯的子组。我无法告诉您如何确切地做到这一点,也许可以举些例子。单元之间的复杂通信/依赖关系,在它们的普遍语言中有很多共同术语,因此看起来这些单元属于同一功能组,从而属于一个单一服务。

所以从您的例子中可以看出,由于只有一个存储方式,您只有像您所做的那样管理其一致性。

顺便说一句,我想知道您实际上是通过什么方式解决了您的问题?


1
让我试着重新阐述问题:
演员们:
- X:用户ID(账户状态) - 提供服务以获取基于凭据的ID和账户状态 - A:用户个人资料 - 使用X来检查用户账户的状态。存储名称以及带有链接的账户 - 提供基于ID获取/编辑名称的服务 - B:用户博客 - 以与X相同的方式使用X。当用户写博客时,将博客文章与链接到账户存储在一起 - 使用A根据用户名搜索博客文章 - 提供基于ID获取/编辑博客条目列表的服务 - 提供基于名称搜索博客文章的服务(依赖于A) - C:移动应用程序 - 将X、A、B的功能包装成一个移动应用程序 - 提供上述所有服务,并依赖于与所有其他人定义良好的通信契约(遵循@neleus的声明)
要求:
  1. 需要解耦团队X、A、B、C的工作
  2. 需要更新X、A、B、C的集成环境,以便进行集成测试
  3. 需要为X、A、B、C的集成环境提供“足够”的数据集(以便执行负载测试,并找到边缘案例)

根据@eugene的想法:每个团队提供的服务都有模拟将允许1)和2)

  • 成本是来自团队的更多开发
  • 还要维护模拟以及主要功能
  • 障碍是您拥有一个单块系统(尚未拥有一组清晰定义/隔离服务)

建议解决方案:

那么,使用共享环境和主数据集合来解决3)如何?每个“交付的服务”(即在生产中运行)都将可用。每个团队可以选择从此处使用哪些服务以及从自己的环境中使用哪些服务

我能看到的一个直接缺点是共享状态和数据的一致性。

让我们考虑针对主数据运行的自动化测试,例如:

  • B更改名称(由A拥有)以便在其博客服务上工作
    • 可能会破坏A或C
  • A更改帐户状态以便处理某些权限场景
    • 可能会破坏X、B
  • C更改同一帐户的所有内容
    • 破坏所有其他人

主数据集很快就会变得不一致,失去了对上述需求3)的价值。

因此,我们可以在共享的主数据上添加一个“常规”层:任何人都可以从完整集合中读取,但只能修改他们创建的对象?


0

从我的角度来看,只有使用服务的对象应该拥有状态。我们来考虑您的例子:服务X负责用户ID,服务A负责配置文件信息等。假设用户Y具有某些安全令牌(例如可以使用用户名和密码创建-应该是唯一的),进入系统。然后客户端包含用户信息,将安全令牌发送到服务X。服务X包含与该令牌相关联的用户ID的信息。如果是新用户,则服务X会创建新的ID并存储其令牌。然后服务X向用户对象返回ID。用户对象通过提供用户ID询问服务A有关用户配置文件的信息。服务A获取ID并询问服务X该ID是否存在。服务X发送肯定的答复,然后服务A可以通过用户ID搜索配置文件信息或要求用户提供此类信息以创建它。相同的逻辑应该适用于B和C服务。他们必须相互通信,但不需要了解用户状态。

关于环境的几句话。我建议使用puppets。这是自动化服务部署过程的方法。我们正在使用木偶来在不同的环境中部署服务。木偶脚本非常丰富,允许灵活配置。

谢谢你的回答和关于Puppet的建议,看起来非常有趣。关于服务,让我再详细解释一下。按照已经设定的例子,服务X将负责userId(这意味着它以某种方式存储<userId,accountStatus>对)。服务A负责用户配置文件,因此需要存储<userId,profileInfo数据>。 - Víctor
正如您所提到的,当检索给定userId的userProfile时,服务A会与服务X通信以检查该帐户是否处于活动状态。采用这种方法,服务X和服务A在逻辑上是独立的,因此它们的代码可以独立演进和部署,没有任何问题。 - Víctor
为了进一步解释共享状态问题,让我们假设我们使用类似于Docker的容器来运行我们的服务。在创建新的测试/集成/等环境时,服务X和服务A的容器可以部署到同一台机器/环境中。现在,这两个服务所依赖的数据会发生什么?为了使整个系统保持一致,服务X存储的数据和服务A存储的数据需要是一致的。这意味着即使服务的代码可以独立部署,它们使用的数据也不能独立部署。 - Víctor
这个问题的一个可能解决方案是所有团队都同意为整个系统制定一个“主”测试数据集(所有服务都应该有关于规范化的userId集合的信息)。当服务数量很高且它们之间存在许多依赖关系时,这真的非常麻烦。 - Víctor
另一个解决方案是我们目前正在使用的:为了保持系统一致性,我们从实时环境获取所有服务的数据,并将其复制到适当的集成/测试环境中。这个解决方案适用于少量环境和服务,但需要大量移动数据,耗费很长时间,而且不可扩展。这就是为什么我们正在寻找更好的方法来解决这个问题。希望我现在已经更好地解释了这个问题 :) - Víctor
是的,现在清楚了 - 谢谢。你能详细说明一下你想要实现什么样的测试吗?是功能性的还是性能方面的?或者你需要服务数据来进行调试? - Eugene

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接