继续我关于微服务实现的系列(请参阅“ 为什么要微驱动事件驱动 微服务 ”,“ 使微服务更具弹性的三件事 ”,“ 精通Java EE Monolith:更喜欢垂直,而不是层 ”作为背景),我们将继续探索创建和开发微服务时可能最棘手的问题。 您的数据。 使用Spring Boot / Dropwizard / Docker并不意味着您在做微服务。 仔细查看您的域和数据将有助于您使用微服务。

请继续阅读本系列的其余部分

在我们尝试微服务架构的原因中,首要原因是使您的团队能够以不同的速度在系统的不同部分上工作,而对整个团队的影响却最小。 因此,我们希望团队能够自治,能够做出有关如何最好地实施和运营服务的决策,并能够自由地根据业务需求进行更改。 如果我们有组织的团队来做到这一点,那么我们系统体系结构中的反思将开始演变为看起来像微服务的事物。

为了获得这种自主权,我们需要“摆脱依赖”,但是说起来容易做起来难。 我已经看到人们在某种程度上简单地提到了这个想法,因为“每个微服务都应该拥有并控制自己的数据库,而没有两个服务应该共享一个数据库。” 这个想法很合理:不要跨服务共享单个数据库,因为那样会遇到诸如竞争的读/写模式,数据模型冲突,协调挑战等冲突。但是单个数据库确实为我们提供了很多安全和便利。 :ACID交易,一个单一的地方,一个易于理解的(有点?),一个管理的地方等。那么,在构建微服务时,如何通过将数据库拆分成多个较小的数据库来协调这些安全性?

让我们来看看。 首先,对于“企业”构建微服务,我们需要明确以下几点:

什么是域? 什么是现实?

交易界限在哪里?

微服务应如何跨边界通信?

如果我们仅将数据库内翻怎么办?

什么是域?

这似乎在很多地方都被忽略了,但是这是互联网公司如何实践微服务与传统企业如何(或者由于忽略这一点而失败)实现微服务之间的巨大差异。

在构建微服务以及对其使用的数据(生产/消费等)进行推理之前,我们需要对数据所代表的内容有一个相当好的,清晰的了解。 例如,在我们可以将有关TicketMonster的 “预订”及其向微服务的迁移的信息存储到数据库之前,我们需要了解“什么是预订”。 就像在您的域中一样,您可能需要了解什么是帐户,员工或索赔等。

为此,我们需要深入研究现实中的“它”是什么? 例如,“什么是书”? 尝试停下来思考一下,因为这是一个非常简单的示例。 试着思考什么是书。 我们将如何在数据模型中表达这一点?

一本书有页吗? 报纸是书吗(有页)? 那么也许一本书有精装书吗? 还是不是每天发布/发布的内容? 如果我写了一本书(我做了:) Java开发者的微服务,那么出版商可能会为我提供一个条目,其中一行代表我的书。 但是一家书店可能有我的5本书。 每个都一本书吗? 还是他们复制? 我们将如何表示呢? 如果一本书这么长必须将其分解成册怎么办? 每本书都是一本书吗? 还是它们全部结合在一起? 如果许多小的成分组合在一起怎么办? 组合是书吗? 还是每个人一个? 因此,基本上,我可以出版一本书,在书店里有很多副本,每本书都有多册。 那么什么是书呢?

现实是没有现实。 关于现实,没有“什么是书”的客观定义,因此要回答这样的问题,我们必须知道“谁在问这个问题,什么是背景”。 情境为王。 作为人类,我们可以快速(甚至不自觉地)解决这种理解的模棱两可,因为我们在头脑,环境和问题中都有自己的背景。 但是计算机没有。 在构建软件和对数据建模时,我们需要使此上下文明确。 用书来说明这一点很简单。 您的域(企业)及其帐户,客户,订舱,索偿等将变得更加复杂,而且冲突/模棱两可。 我们需要界限。

我们在哪里划定界限? 域驱动设计社区中的工作有助于我们应对域中的这种复杂性。 我们围绕实体,域,值对象和集合绘制一个有界上下文 。 换句话说,我们建立并完善了一个代表我们领域的模型,并且该模型包含在定义我们的上下文的边界内。 这是明确的。 这些边界最终是我们的微服务,或者边界内的组件最终是微服务,或两者。 无论哪种方式,微服务都是关于边界的,DDD也是如此。

微服务唯一id 微服务之间用https吗_python

我们的数据模型(我们希望如何表示物理数据存储中的概念,请注意此处的明显区别)是由我们的领域模型驱动的,而不是相反的。 当我们有了这个边界时,我们知道并且可以断言模型中什么是“正确的”以及什么是不正确的。 这些界限也意味着一定程度的自主权。 有界上下文“ A”可能对有界书“ B”有不同的理解(例如,有界上下文“ A”可能是搜索标题为“书”的标题的搜索服务;可能有界上下文“ B”是一种结帐服务,它根据您要购买的图书(书名和份数)等来处理交易。

您可能会停下来说:“等一下... Netflix没有说任何有关域驱动设计的信息。Twitter也不是LinkedIn。我为什么要听有关DDD的信息”?

那么这就是为什么:

人们尝试复制Netflix,但他们只能复制自己看到的内容。 他们复制结果,而不是过程” –前Netflix首席云架构师Adrian Cockcroft

通往微服务的旅程就是这样:一段旅程。 每个公司的情况会有所不同 。 没有硬性规定,只有权衡取舍。 仅因为某公司似乎在这一刻就可以工作而复制对某公司有效的内容是一种试图跳过流程/旅程的尝试,并且将不起作用。 这里要说的是您的企业不是 Netflix。 实际上,我想说的是,无论网域在Netflix上多么复杂,它都没有在您的旧企业中那么复杂。 搜索和显示电影,发布推文,更新LinkedIn配置文件等都比您的保险索赔处理系统简单得多。 这些互联网公司因为推向市场的速度和庞大的规模/规模而选择了微服务(将推文发布到Twitter很简单……发布推文并为5亿用户显示推文流非常复杂)。 今天的企业将不得不面对领域和规模上的复杂性。 所以接受这个事实,这是一个平衡领域,规模,以及旅程 的组织变革 。 每个组织都会有所不同。 不要忽略它。

交易边界是什么?

回到故事。 我们需要诸如域驱动设计之类的东西,以帮助我们理解用于实现系统的模型,并在上下文中围绕这些模型划定边界。 因此,我们接受客户,帐户,预订等对于不同的受限上下文可能具有不同的含义。 但是最终,我们可能最终将这些相关概念分布在我们的体系结构中,但是当发生更改时,我们需要某种方法来协调这些不同模型之间的更改。 我们需要考虑这一点,但是首先我们需要确定我们的交易边界。

不幸的是,作为开发人员,我们似乎仍然错误地构建分布式系统:我们仍然通过一个,单个,关系,ACID,数据库的眼光来看待。 我们还忽略了异步,不可靠网络的危险。 总而言之,我们会做一些事情,例如编写精美的框架,使我们不必了解网络的任何知识(包括RPC框架,也忽略网络的数据库抽象),并尝试通过点对点同步调用(REST, SOAP,其他CORBA(如对象序列化RPC库等)。 我们构建的系统不考虑权限与自主性 ,最终尝试通过跨许多独立服务的两阶段提交之类的方法来解决分布式数据问题。 还是我们一起忽略这些担忧? 这种心态导致构建无法扩展的非常脆弱的系统……而且,无论您称其为SOA,微服务,小型服务还是什么都没有关系。

那么,交易边界是什么意思? 我的意思是相对于业务不变量,您需要的最小原子单位。 究竟使用数据库的ACID属性实现原子性还是两阶段提交等,都没有关系。 关键是我们希望使这些事务边界尽可能小(理想情况下是在单个对象上进行单个事务:Vernon Vaughn有一系列文章用DDD Aggregates描述了这种方法 ),以便我们进行扩展。 当我们使用DDD术语构建域模型时,我们会确定实体,值对象和集合。 在此上下文中,聚合是封装其他实体/值对象并负责强制不变式的对象(在绑定上下文中可以有多个聚合)。

例如,假设我们有以下用例:

  • “允许客户搜索航班”
  • “允许客户在特定航班上选择座位”
  • “允许客户预订航班”

我们可能在这里有三个有界上下文:搜索,预订和票务(我们可能有更多类似付款,忠诚度,备用,升级等内容,但我们将其限制在这三个范围内)。 搜索负责显示给定时间范围(天,时间等)中特定路线和路线的航班。 预订将负责在预订过程中准备好客户信息(姓名,地址,常旅客号码等),座位偏好和付款信息。 出票将负责与航空公司实际解决预订事宜并出票。 在每个有界上下文中,我们都希望标识可强制执行约束/不变性的事务边界。 我们将不考虑跨边界上下文的原子事务(我们将在下一节中讨论)。

考虑到我们想要小的交易范围(这是预订航班的非常简化的版本),我们将如何建模呢? 也许某个航班集合汇总了诸如时间,日期,航线之类的值以及诸如客户,飞机和预订之类的实体? 这似乎是有道理的:航班有飞机,座位,顾客和预订。 飞行集合负责跟踪飞机,座位等,以创建预订。 从数据库内部的数据模型的角度(具有约束和外键的精细关系模型等)的角度来看,这可能是有道理的,或者在我们的源代码中建立一个不错的对象模型(继承/组合),但是让我们看看会发生什么。

微服务唯一id 微服务之间用https吗_人工智能_02

仅仅是为了创建预订,所有预订,飞机,航班等中是否真的存在不变性? 也就是说,如果我们向Flight集合中添加新的Plane,我们是否应该在该交易中真正包括客户和预订? 可能不是。 我们这里拥有的聚合是考虑到结构和数据模型的便利性而构建的。 但是,交易边界太大。 如果我们对航班,座位,预订等进行了很多更改,则将发生很多事务冲突(无论使用乐观锁定还是悲观锁定都无关紧要)。 而且这显然无法扩展(不要介意每次都在失败的订单,只是因为航班时刻表的变化是一种糟糕的客户体验)。

如果我们打破交易界限小一点怎么办。

也许预订,座位可用性和航班是他们自己的独立汇总。 预订包含客户信息,偏好以及可能的付款信息。 SeatAvailability聚合封装了飞机和飞机配置。 航班总计由时间表,航线等组成。。。但是我们可以继续进行预订,而不会影响航班时间表和飞机/座位可用性的交易。 从域的角度来看,我们希望能够做到这一点。 我们不需要在飞机/航班/预订之间实现100%严格的一致性,但是我们确实想正确地记录管理员的航班时间表更改,作为供应商的飞机配置以及来自客户的预订。 那么,我们如何在航班上实施“选择特定座位”之类的事情?

在预订过程中,我们可能会调用SeatAvailability总量,并要求其在飞机上预订座位。 该座位预定将被实现为单个交易,例如,(保持座位23A)并返回预定ID。 我们可以将此预订ID与预订相关联,并在知道座位已“预订”时提交预订。 这些(保留座位并接受预订)中的每一个都是单独的交易,并且每个交易都可以独立进行,而无需任何两阶段提交或两阶段锁定。 请注意,此处使用“保留”是一项业务要求。 我们不在这里分配座位,我们只是保留座位。 可能需要通过模型的迭代来限制此要求,因为起初用例的语言可能只是说“允许客户选择座位”。 开发人员可以尝试推断该需求意味着“从剩余席位中拣选,将其分配给客户,将其从库存中删除,并且售出的门票不超过席位”。 这将是多余的,不必要的不变式,这将给我们的事务处理模型增加额外的负担,而业务实际上并没有将其视为不变式。 在没有完成座位分配甚至超额销售机票的情况下,预订业务当然还可以。

微服务唯一id 微服务之间用https吗_大数据_03

这是一个示例,它允许真正的域引导您为所涉及的各个集合提供较小,简化但完全原子的交易边界。 尽管故事还不能到此结束,因为我们现在必须纠正一个事实,即所有这些单独的交易都需要在某个时候合并在一起。 涉及数据的不同部分(即,我创建了预订和座位预订,但这些不是获得登机证/机票等已结算的交易)

微服务应如何跨边界通信?

我们希望保持真正的业务不变性不变。 使用DDD,我们可以选择将这些不变量建模为集合,并使用单个事务对集合进行强制。 在某些情况下,我们可能会在单个事务中(跨单个数据库或多个数据库)更新多个聚合,但这些情况除外。 我们仍然需要在聚合之间(以及最终在有限的上下文之间)保持某种形式的一致性,那么我们应该如何做呢?

我们应该了解的一件事:分布式系统是挑剔的。 如果能在有限的时间内对分布式系统中的任何内容做出任何保证,我们几乎无法保证 (事情将会失败,事物不确定地变慢或出现故障,系统具有不同步的时间边界等),所以为什么尝试打吗? 如果我们接受这一点并将其融入我们整个领域的一致性模型中怎么办? 如果我们说“在必要的交易边界之间,我们可以与数据和域的其他部分一起生活,以便在以后的某个时间点进行协调并保持一致”,该怎么办?

就像我们一直说的那样,对于微服务,我们重视自治。 我们重视能够独立于其他系统进行更改(在可用性,协议,格式等方面)。 时间的分离以及任何有限时间内服务之间的任何保证都使我们能够真正实现这种自治( 这不是计算机系统或任何与此相关的系统所独有的 。所以我说,在事务边界之间和有界之间上下文,使用事件传达一致性。事件是不可变的结构,捕获了一个有趣的时间点,该时间点应该广播给对等方。对等方将听取他们感兴趣的事件,并根据该数据做出决策,存储该数据,存储该数据的某些衍生数据,根据对该数据做出的某些决定等更新自己的数据,等等。

继续我以某种方式开始的航班预订示例(大声笑,而不是使用我的TicketMonster示例……这就是我开始编写时发生的事情!),当通过ACID风格的交易存储预订时,我们如何对它进行票务处理? 这就是前面提到的票证有界上下文出现的地方。票证有界上下文将发布“ NewBookingCreated”之类的事件,票务有界上下文将使用该事件并继续与后端(可能是遗留的)票务系统进行交互。 显然,这需要某种集成和数据转换,而Apache Camel则擅长于此 。 它还提出了其他一些问题。 我们如何写数据库并原子地发布到队列/消息传递设备? 如果我们在两次活动之间有订购要求/因果要求怎么办? 每种服务一个数据库呢?

理想情况下,我们的聚合将直接使用命令和域事件 (作为一等公民。..也就是说,任何操作都实现为命令,任何响应都实现为对事件的反应),并且我们可以更清晰地在所使用的事件之间进行映射在我们的受限上下文内部以及我们在上下文之间使用的上下文。 我们可以将事件(即NewBookingCreated)发布到消息队列中,然后让侦听器从队列中使用该事件并将其幂等地插入数据库中,而不必使用XA / 2PC事务,而不必自己插入数据库中。 我们可以将事件插入一个专用的事件存储中 ,该事件存储的作用类似于数据库和消息发布-订阅主题(这可能是首选途径)。 或者,您可以继续使用ACID数据库,并使用Debezium之类的东西将对数据库的更改流式传输到一个持久的,复制的日志(如Apache Kafka),并使用某种事件处理器/蒸汽处理器推断事件。 无论哪种方式,重点都是我们希望在具有不变的时间点事件的边界之间进行通信。

微服务唯一id 微服务之间用https吗_人工智能_04

这具有一些很大的优点:

  • 我们避免跨边界的昂贵的,可能不可能的交易模型
  • 我们可以对我们的系统进行更改,而不会影响系统其他部分的进度(时间和可用性)
  • 我们可以决定要多快或慢一点,我们希望看到外部世界并最终保持一致
  • 我们可以将数据存储在我们自己的数据库中,但是我们想使用适合我们服务的技术
  • 我们可以在闲暇时更改架构/数据库
  • 我们变得更具可扩展性,容错性和灵活性
  • 您必须更加关注CAP定理和您选择的用于实现存储/队列的技术

值得注意的是,这有缺点:

  • 比较复杂
  • 难以调试
  • 由于您在看到事件时会有延迟,因此您无法对其他系统知道什么做任何假设(无论如何您都无法做,但是在此模型中更明显)
  • 更难以操作
  • 您必须更加关注CAP定理和您选择的用于实现存储/队列的技术

我在两栏中都列出了“注意CAP等”,因为尽管这给您带来了更多负担,但无论如何还是必须这样做! 必须始终注意分布式数据系统中数据一致性和并发性的不同形式! 不再需要依靠“我们的ACID数据库”(尤其是当该ACID数据库很可能仍默认为某些较弱的一致性时…对于您的ACID属性而言是如此之多)。

这种方法中出现的另一个有趣的概念是实现称为“命令查询分离责任”的模式的能力,其中我们将读取模型和写入模型分离到单独的服务中。 记住,我们对互联网公司没有非常复杂的域模型感到遗憾。 这在他们的写模型很简单的过程中就很明显(例如, 在分布式日志中插入一条推文 )。 但是,由于它们的规模,它们的读取模型非常复杂。 CQRS有助于分离这些问题。 另一方面,在企业中,写模型可能非常复杂,而读模型可能是简单的平面选择查询和平面DTO对象。 CQRS是一种功能强大的关注点分离模式,可以在您确定适当的边界后进行评估,并且是一种在聚合之间以及有界上下文之间传播数据更改的好方法。

那么,只有一个数据库却不与任何其他服务共享的服务呢? 在这种情况下,我们可能有侦听器订阅事件流,并且可能将数据插入到最终聚集可能使用的共享数据库中。 这个“共享数据库”非常好。 记住,没有规则,只有权衡。 在这种情况下,我们可能有多个服务与同一个数据库协同工作,并且只要我们(我们的团队)拥有所有流程,我们就不会否定任何自治优势。 因此,当您听到有人说“微服务应该拥有自己的数据库而不要与其他人共享”时,您可以回答“很好,有点” :)

如果我们仅将数据库内翻怎么办?

如果我们将上一节中的概念推向逻辑极限怎么办? 如果我们只是说我们将对事件使用所有事件/流,并永久保留这些事件怎么办? 如果我们说数据库/缓存/索引实际上只是过去发生的事件的持久日志/流的物化视图,而当前状态是所有这些事件的左折,那该怎么办?

这种方法带来了更多好处,您可以在通过事件进行通信的好处(上面列出)中添加这些好处:

  • 现在,您可以将数据库视为记录的“当前状态”,而不是真实的记录
  • 您可以介绍新的应用程序并重新阅读过去的事件,并根据“会发生什么”来检查它们的行为。
  • 您可以免费完善审核日志记录
  • 您可以引入新版本的应用程序,并通过重播事件对它进行详尽的测试
  • 您只需将事件重播到新数据库中,就可以更轻松地推断出数据库的版本控制/升级/架构更改
  • 您可以迁移到全新的数据库技术(例如,也许您发现关系数据库已不复存在,并且您想要切换到专门的数据库/索引)

有关更多信息,请查看Martin Kleppmann的演讲/博客文章“使用Apache Samza彻底颠倒数据库”。

微服务唯一id 微服务之间用https吗_人工智能_05

当您在aa.com,delta.com或united.com上预订机票时,您会发现其中一些概念正在发挥作用。 当您选择一个座位时,您实际上并没有得到分配,而是保留了它。 预订机票时,实际上没有机票。 稍后您会收到一封电子邮件,告知您已被确认/票务确认。 您是否曾经换过飞机并为实际航班分配了其他座位? 还是去登机口,听到他们因为超售而要求志愿者放弃座位? 这些都是事务边界,最终一致性,补偿事务甚至工作中的道歉的示例。

故事的道德启示

这里故事的寓意是数据,数据集成,数据边界,企业使用模式,分布式系统理论,时间安排等都是微服务的硬部分(因为微服务实际上只是分布式系统!)。 我对技术感到太多困惑(“如果我使用Spring Boot我正在做微服务”,“我需要解决服务发现,在进行微服务之前先在云中进行负载平衡”,“我必须拥有一个数据库每个微服务”)和关于微服务的无用“规则”。 不用担心 一旦大型供应商来到您手中,并向您出售了所有精美的产品套件(嗯……SOA响起了钟声),您仍然可以做上面列出的困难部分。

翻译自: https://www.javacodegeeks.com/2016/07/hardest-part-microservices-data.html