分解单块系统

 

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做为查询接口,它可以和不少现成的报表工具协同工做,

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

15.经过服务调用来获取数据

这个模型有不少变体,但它们都依赖API调用来获取想要的数据。

对于一个很是简单的报表系统(好比展现过去15分钟内下的订单数量的系统)来讲,这是可行的。

为了从两个或者多个系统中获取数据,你须要进行屡次调用,而后进行组装。

 

可是当你须要访问大量数据时,这种方法就彻底不适用了。

你能够经过批量API来简化这个过程。发起调用的系统能够POST一个BatchRequest,

其中携带一个位置信息,服务器能够将全部数据写入该文件。

经过这种方式能够将大数据文件导出,而不须要HTTP之上的开销,只是简单的把一个CSV文件存储到共享的位置而已。

16.数据导出

和报表系统拉取数据的方式相比,咱们还能够把数据推送到报表系统中。

使用标准的HTTP来进行大量调用时,会带来很大的额外开销,更不用提为报表系统建立专用API所带来的开销。

一种替代方式是,使用一个独立的程序直接访问其余服务使用的那些数据库,把这些数据导出到单独的报表数据库

 

 一开始,相应服务的维护团队能够负责数据导出工做。简单的使用Cron去触发一些命令行程序就能够完成这个任务。

 

在报表数据库中包含了全部服务的表结构,而后使用视图之类的技术来建立一个聚合。

使用这种方式,导出数据的服务只须要知道本身的报表视图结构便可。可是这种方式的

性能就取决于你所选用的数据库系统了。

 

 另外一个方向,咱们把一系列数据以JSON格式导出到AWS S3 ,有效的把S3变成了一个巨大的数据集市。

17.事件数据导出

每一个微服务能够在其管理的实体发生状态改变时发送一些事件。

 

 使用这种方式的话,与源微服务底层数据库之间的耦合就被消除掉了。咱们只须要绑定

到服务所发送的事件便可,而设计这些事件,原本就是用来暴露给外部消费者的。

这种方式主要的缺点是,全部须要的信息都必须以事件的形式广播出去,

因此在数量比较大时,不容易像数据导出方式那样直接在数据库级别进行扩展。

18.数据导出的备份

19.走向实时

如今咱们愈来愈靠近可以把数据按需路由到多个不一样地方的通用事件系统了。

20.修改的代价

咱们能够,也必定会犯错误,须要接受这个事实。可是另一件咱们应该作的事情是,

理解如何下降这些错误形成的影响。

CRC(class-responsibility-collaboration,类-职责-交互)卡片

21.理解根本缘由

咱们作了不少关于如何把大服务拆分红一些小服务的讨论,可是这些大服务又是怎么产生的呢?

第一件须要理解的事情是,服务必定会慢慢变大,直到大到须要拆分。咱们但愿系统的架构随着

时间的推移增量的变化。关键是要在拆分这件事情变得太过昂贵以前,意识到你须要作这个拆分。

尽管知道相比于巨大的怪兽,一系列的小服务更容易应对,但咱们仍然在一点点的帮助它成长。

 

对库和轻量级服务框架的投资能减少建立新服务的代价。

22.小结

咱们经过寻找服务边界把系统分解开来,且这能够是一个增量的过程。

在最开始就要养成及时寻找接缝的好习惯,从而减小分隔服务的代价,这样才能在将来遇到新需求时,

继续演化咱们的系统。

相关文章
相关标签/搜索