分解单块系统

1.关键是接缝

接缝的概念:从接缝处可以抽取出相对独立的一部分代码,对这部分代码进行修改不会影响系统的其他部分。

那么什么样的接缝才是好接缝呢?限界上下文就是一个非常好的接缝,因为它的定义就是组织内高内聚和低耦合的边界。

2.分解MusicCorp

想象,现在有个巨大的后台单块服务,其中包含了MusicCorp在线系统所需要的所有行为。

假设识别出这个单块后台系统包含以下四个上下文。

  • 产品目标 - 与正在销售的商品相关的元数据
  • 财务 - 账号,支付,退款等项目的报告
  • 仓库 - 分发客户订单、处理退货、管理库存等
  • 推荐

3. 分解单块系统的原因

决定把单块系统变小是一个很好的开始。

增量的方式可以让你在进行的过程中学习微服务,同时也可以限制出错所造成的影响。

接下来考虑一些指导因素。

3.1 改变的速度

接下来,我们可能会对库存管理方面的代码做大量修改。

所有如果现在把仓库接缝抽出来作为一个服务,使其成为一个自治单元,那么后期开发的速度将大大加快。

3.2 团队结构

MusicCorp的交付团队事实上分布在两个不同的地区,可以把大部分代码分离出来,这样就能对此全权负责。

3.3 安全

可以对独立的服务做监控,传输数据的保护和静态的数据的保护等。

3.4 技术

4. 杂乱的依赖

当你已经失败出一些备选接缝,另一个要考虑的点是:这部分代码与系统剩余部分之间的依赖有多乱。

我们想要拉取出来的接缝应该尽量少的被其他组件所依赖。

通常时候,你会发现数据库是所有杂乱依赖的源头。

5.数据库

前面讨论了使用数据库作为服务之间集成方式的做法。

但是这种方式需要去找到数据库中的接缝,这样就可以把它们分离干净。但是这会比较棘手。

6.找到问题的关键

第一步是看看代码中对数据库进行读写的部分。

通常这部分代码会存在于一个仓储层中。

把数据库映射相关的代码和功能代码放在同一个上下文中,可以帮助我们理解哪些代码用到了数据库中的哪些部分。

但是,有时候一张表可能会被分离到不同的限界上下文中,对于这种场景比较难回答。

7. 例子:打破外键关系

例如,我们卖了400个产品,挣了3000元钱。

为了做到这一点,财务包中生成报告的代码,需要从行条目表中获取产品标题名称。总账表和行条目表之间可能存在外键关系。

快速的修改方式是:让财务部分的代码通过产品目录服务暴露的API来访问数据,而不是直接访问数据库。

那外键关联了怎么办?我们只能放弃它了。

所以你可能需要把这个约束从数据库移到代码中来实现。

这也就意味着,我们可能需要跨服务的一致性检查,或者周期性触发清理数据的任务。

8. 例子:共享静态数据

这些将共享静态数据存在数据库中的例子非常多。

所以在我们的音乐商店中,如果所有的服务都要从同一张像国家这样的表中读取数据,该怎么办?

有这么几个解决方案可供选择。

第一个方法是为每个包复制一份该表的内容,也就是说,未来每个服务也都会保存这样一份副本。

第二个方法是,把这些共享的静态数据放入代码,比如放在属性文件中,或则简单的放在枚举中。

第三个方法有些极端,即把这些静态数据放入一个单独的服务。

在大部分场景下,都可以通过把这些数据放入配置文件或者代码中来解决问题,而且它对于大部分场景来说都是很容易实现。

9. 例子:共享数据

共享的可变数据对于分离系统来说通常是一个大麻烦。

无论是财务相关的代码还是仓库相关的代码,都会向同一个表写入数据,

有时还会从中读取数据。这种情况下,如何做分离?

其实这种情况很常见:领域概念不是在代码中建模,相反是在数据库中隐式的进行建模。这里缺失的领域概念是客户。

需要把客户概念具象化。作为一个中间步骤,我们可以创建一个新的包Customer。

然后让财务和仓库这些包,通过API来访问此新创建的包。如图

10.例子:共享表

这里可以分成两个表。

11.重构数据库

实施分离

 表结构分离之后,对于原先的某个动作而言,对数据库的访问次数可能会变多。

因为以前简单的用一个select语句就能得到所有的数据,现在则需要分别从不同的地方拿到数据,

然后在内存中进行连接。还有,分成两个表结构会破坏事务完整性。

先分离数据库结构但不分离服务的好处在于,可能随时选择回退这些修改或是继续做,

而不影响服务的任何消费者。我们对数据库分离感到满意之后,就可以考虑对整个应用程序的分离了。

12.事务边界

简单的说,一个事务可以帮助我们的系统从一个一致的状态迁移到另一个一致的状态:要么全部做完,要么什么都不变。

使用单块表结构时,所有的创建或者更新操作都可以在一个事务边界内完成。

 分离数据库之后,这种好处就没有了。

下订单操作现在跨越了两个事务边界,如下图。

如果这个插入订单表的操作失败,我们可以显式的清除所有的状态,

从而保证系统状态的一致性。可如果插入订单表成功,但插入提取表失败了呢?

 12.1 再试一次

我们可以把这部分操作放在一个队列或者日志文件中,之后再尝试对其进行触发。

对于某些操作来说这是合理的,但要保证重试能够修复这个问题。

很多地方会把这种形式叫做最终一致性。

相对于使用事务来保证系统处于一致的状态,最终一致性可以接受系统在未来的某个时间达到一致。

这种操作对于长时间的操作来说尤为有效。

12.2 终止整个操作

另一个选择是拒绝整个操作。

在这种情况下,我们需要把系统重置到某种一致的状态。

提取表的处理比较简单,因为插入失败会导致事务的回退。

但是订单表已经提交了事务该怎么处理呢?

解决方案是,在发起一个补偿事务来抵消之前的操作。对于我们来说,可能就是简单的一个delete操作来把订单从数据库中删除。

然后还需要向用户报告该操作失败了。

那如果补偿事务失败了该怎么办呢?

在这种情况下,你要么重试补偿事务,要么使用一些后台任务来清除不一致的状态。

可以给后台维护人员提供一个界面来进行该操作,或者将其自动化。

不同情况下的补偿事务会非常难以理解,更不用说实现了。

12.3 分布式事务

手动编配补偿事务非常难以操作,一种替代方案是使用分布式事务。

分布式事务会跨越多个事务,然后使用一个叫做事务管理器的工具来同一编配其他底层系统中运行的事务。

这就像普通的事务一样,一个分布式事务会保证整个系统处于一致的状态。

唯一不同的是,这里的事务会运行在不同系统的不同进程中,通常它们之间使用网络进行通信。

处理分布式事务(尤其是上面处理客户订单这类的短事务)常用的算法是两阶段提交。

在这种方式中,首先是投票阶段。

在这个阶段,每个参与者(在这个上下文中叫做cohort)会告诉事务管理器它是否应该继续。

如果事务管理器收到的所有投票都是成功的,则会告诉它们进行提交操作。

只要收到一个否定的投票,事务管理器就会让所有的参与者回退。

这种方式会使得所有的参与者暂停并等待中央协调进程的指令,从而很容易导致系统的中断。

如果事务管理器宕机了,处于等待状态的事务就永远无法完成。如果一个cohort在投票阶段发送消息失败,

则所有其他参与者都会被阻塞,投票结束后的提交也有可能会失败。

该算法隐式的任务上述这些情况不会发生,即如果一个cohort在投票阶段投了赞成票,则它一定能提交成功。

cohort需要一种机制来保证这件事情的发生。这意味着此算法并不是万无一失的,而只是尝试捕获大部分的失败场景。

分布式事务在某些特定的技术栈上已有现成的实现,比如Java的事务API,该API允许你把类似数据库和消息队列这样完全不同的资源,

放在一个事务中进行操作。

12.4 应该怎么办呢

如你所见,分布式事务很容易出错,而且不利于扩展。

这种通过重试和补偿达成最终一致性的方式,会使得定位问题更加困难,而且有可能需要其他补偿措施来修复潜在的数据的不一致。

如果你遇到的场景确实是需要保持一致性,那么尽量避免把它们放在不同的地方,一定要尽量这样做。

如果实在不行,那么要避免仅仅从纯技术的(比如数据库事务)的角度考虑,而是显示的创建一个概念来表示这个事务。

你可以把这个概念当做一个句柄或者钩子,在此之上,能够相对容易的进行类似补偿事务这样的操作,这也是在系统中

监控这些复杂概念的一种方式。

13.报表

把架构网微服务的方向进行调整会颠覆很多东西,但这并不意味着我们需要抛弃现有的一切。

这里并不是说报表不能颠覆,但是首先应该搞清楚现有流程是如何工作的。

14.报表数据库

报表通常需要来自组织内各个部分的数据生成有用的输出。

在标准的单块服务架构中,所有的数据都存储在一个大数据库中。

通常为了防止对主系统性能产生影响,报表系统会从副本数据库中读取数据,如图

 这种方式有一个很大的好处,即所有的数据存储在同一个地方,因此可以使用非常简单的工具来做查询。

但也存在一些缺点。

首先数据库结构成了单块服务和报表系统之间的共享API,所以对表结构的修改需要非常小心。

其次,无论是在线上系统还是报表系统的数据库中,可用的优化手段都比较有限。

最后,来看看有哪些数据库可供选择。

标准的关系型数据库使用SQL作为查询接口,它能够和很多现成的报表工具协同工作,

但不一定是适用产品数据库的最佳选择。

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏杨建荣的学习笔记

最近的一些读者提问和解答

可以举个生活的例子来说明,假设有一个两居室,客厅就是MySQL Server,主卧是InnoDB,次卧是MyISAM,对于一个房间来说,假设住户是数据,那么住户...

10830
来自专栏不止dotNET

dotNET Core 中怎样操作 AD?

做企业应用开发难免会跟 AD 打交道,在之前的 dotNET FrameWork 时代,通常使用 System.DirectoryServices 的相关类来操...

13780
来自专栏mall学习教程

那些年,我们见过的Java服务端“问题”

明代著名的心学集大成者王阳明先生在《传习录》中有云:“道无精粗,人之所见有精粗。如这一间房,人初进来,只见一个大规模如此。处久,便柱壁之类,一一看得明白。再久,...

14520
来自专栏未闻Code

一日一技:使用Pymongo实现更新并返回数据

我有100篇故事,放在 MongoDB 里面。我做了一个 web 接口,每次请求返回一篇故事。希望能够实现:

15010
来自专栏云产品优惠

腾讯云代金券使用方法与说明

用户在购买、续费、变更配置包年包月产品时,可在付款页面自主选用代金券。(选购流程可参考 购买云服务)

9610
来自专栏Java研发军团

超,超,超级全面的MySQL优化面试解析!!!

price decimal(8,2)有2位小数的定点数,定点数支持很大的数(甚至是超过int,bigint存储范围的数)

10540
来自专栏谭广健的专栏

MSSQL 数据库限制值

以为趁国庆陪家人出去游玩一番领略祖国河山,谁知刚上高速就被同事的微信和电话轰炸。说客户反馈出现问题,一开始以为可能是前任挖的通信协议坑(每隔一段时间要重...

2910
来自专栏芝麻实验室

关于“Confluence数据库排序规则错误”的解决方案

11220
来自专栏武培轩的专栏

一条SQL查询语句是如何执行的?

首先有一个 user_info 表,表里有一个 id 字段,执行下面这条查询语句:

8030
来自专栏生信宝典

Nature综述|整合组学分析护航健康,推动精准医学时代的到来!

Konrad J. Karczewski, and Michael P. Snyder撰写的关于整合多组学在疾病研究中的应用一文《Integrative omi...

19820

扫码关注云+社区

领取腾讯云代金券

年度创作总结 领取年终奖励