zoukankan      html  css  js  c++  java
  • 分解单块系统

    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.小结

    我们通过寻找服务边界把系统分解开来,且这可以是一个增量的过程。

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

    继续演化我们的系统。

  • 相关阅读:
    HTML
    MySQL 表操作
    MySQL 库操作
    MySQL
    python 客户端的安全性验证和服务端对客户端的多端连接
    python 黏包
    python 通信
    SpringData —— HelloWorld
    JPA
    Hibernate ——二级缓存
  • 原文地址:https://www.cnblogs.com/Vincent-yuan/p/11729878.html
Copyright © 2011-2022 走看看