《微服务设计》读书笔记(上)

第一章:微服务

  关键词:轻量、自治、

1. 什么是微服务

  微服务是一些协同工作的小而自治到服务。
  特点:小、自治。

1.1 专注于做好一件事

  功能迭代带来的问题:随着新功能的增加,代码库会越变越大。时间久了代码库会非常庞大,以至于想要知道该在什么地方做修改都很困难。
  解决方法是:在一个单块系统内,通常会创建一些抽象层或者模块来保证代码的内聚性,从而避免上述问题。微服务将这个理念应用在独立的服务上。根据业务的边界来确定服务的边界,这样就很容易确定某个功能代码应该放在哪里。而且由于该服务专注于某个边界之内,因此可以很好地避免由于代码库过大衍生出的很多相关问题。
仓库大小:足够小即可,不要过小。

1.2 自治性

  一个微服务就是一个独立的实体。
  服务之间均通过网络调用进行通信,从而加强了服务之间的隔离性,避免紧耦合。

2. 主要好处

  微服务有很多不同的好处,其中很多好处也适用于任何一个分布式系统。但相对于分布式系统或者面向服务的架构而言,微服务要更胜一筹,它会把这些好处推向极致。

2.1 技术异构性

  在一个由多个服务相互协作的系统中,可以在不同的服务中使用最适合该服务的技术。

2.2 弹性

  一个组件不可用不会导致级联故障。

2.3 扩展

  更方便对有性能瓶颈的部分服务进行扩展。

2.4 简化部署

  服务独立部署,能降低部署风险,以及快速回滚。

2.5 与组织架构相匹配

  小型代码库上工作的小团队更加高效。

2.6 可组合性

  易于重用已有功能,并有多种方法使用同一功能。

2.7 对可替代性的优化

  方便重写、删除服务

3. 面向服务的架构

  SOA (Service-Oriented Architecture,面向服务的架构)是一种设计方法,其中包含多个服务,而服务之间通过配合最终会提供一系列功能。一个服务通常以独立的形式存在于操作系统进程中。服务之间通过网络调用,而非采用进程内调用的方式进行通信。
SOA优点:可以用来应对臃肿的单块应用程序,从而提高软件的可重用性。

4. 其他分解技术

  基于微服务的架构主要有两个优势:较小的颗粒度、解决问题的方法上能有更多选择。那其他的分解技术是否有相应的好处呢?

4.1 共享库

  优点:可重用
  缺点:无法技术异构性;可扩展性差;无法独立部署和变更

c4.2 模块

  优点:支持热更新
  缺点:无法技术异构性;耦合

5. 没有银弹

  微服务不是免费的午餐,更不是银弹。需要面对分布式系统面对的复杂性。

6. 面临的挑战

  • 依赖关系复杂
  • 部署
  • 测试
  • 监控
  • 扩展系统
  • 分布式事务
  • CAP

第二章:演化式架构师

1. 职责

  确保团队有共同的技术愿景,以帮助我们向客户交付他们想要的系统。

2. 架构师的演化视角

  架构师类似城市规划师,优化城镇布局,使其更易于现有居民生活,同时也会考虑些未来的因素。

2.1 总结

  1. 设计出一个合理的框架,而不是一开始就要设计出完美产品;
  2. 尽量预期可能变化,不可能预测全部变化,不如做允许变化的计划
  3. 专注于大方向规划
  4. 避免过于详尽设计,让团队内自治
  5. 制止破坏性行动

3. 分区

  将架构师比作城市规划师,那在比喻中,区域的概念对应是什么呢?

3.1 区域

  它们应该是我们的服务边界,或者是一些粗粒度的服务群组。

3.2 关注点

  • 不过多关注每个区域发生的事件
  • 多关注区域之间的事情

3.3 考虑点

  1. 需要考虑不同的服务之间如何交互,保证我们能够对整个系统的健康状态进行监控。
  2. 区域之间,犯的错误会很难纠正,需要非常小心。

3.4 区域自治

  • 允许团队对负责区域内负责选择不同技术栈
  • 但不会无限允许团队选择任意技术栈,原因:
  • 招聘困难
  • 团队之间交换人员困难
  • 太多技术可能导致都不够熟悉
  • 服务之间的交互各式各样,对消费者不友好

4. 架构师原则

  基于要达到的目标去定义一些原则和实践对做设计来说非常有好处。接下来让我们对它们做一些讨论。

4.1 战略目标

  战略目标关心的是公司的走向以及如何才能让自己的客户满意。例如:“开拓东南亚的新市场”或者“让用户尽量使用自助服务”。

4.2 原则

  为了和更大的目标保持一致,我们会制定一些具体的规则,并称之为原则。
注意:

  • 原则不是一成不变,会随着公司战略目标而变;
  • 原则不宜过多,最好不超过10个或者能够写在一张海报上;

4.3 实践

  我们通过相应的实践来保证原则能够得到实施,这些实践能够指导我们如何完成任务。
特点:

  1. 通常这些实践是技术相关的,而且是比较底层;
  2. 包括代码规范、日志数据集中捕获或者HTTP/REST作为标准集成风格等;
  3. 改变的频率会高于原则;
  4. 实践也会反映出组织内的一些限制;
  5. 实践应该现固原则;

4.4 将原则和实践相结合;

  对应一些东西可以理解为原则也可以理解为实践,关键是要有一些重要的原则来指导系统的演化,同时也要有一些细节来指导如何实现这些原则。

5 要求的标准

  在考虑取舍时,需要注意一个很重要的因素:系统允许多少可变性。

  1. 需要识别各个服务需要遵守的通用规则
  2. 清楚地定义出一个好服务应有的属性

5.1 监控

  能够清晰地描绘出跨服务系统的健康状态非常关键。这必须在系统级别而非单个服务级别进行考虑。

5.2 接口

  选用少数几种明确的接口技术有助于新消费者的集成。

5.3 架构安全性

  所以必须保证每个服务都可以应对下游服务的错误。

6. 代码治理

怎么更好履行上面的要求标准

  • 提供范例
  • 提供服务代码模板(框架)

7. 技术债务

7.1 产生原因

  • 为了发布紧急的特性,走捷径而违背了技术愿景
  • 系统目标发生改变,并与现有的实现不符

7.2 如果偿还技术债务

架构师提供温和的指导,团队自行决定然后偿还技术债务
维护一个债务列表,并且定期回顾

8. 例外管理

如果系统偏离了这些指导又会发生什么呢?

  1. 有时候我们会决定针对某个规则破一次例,然后把它记录下来;
  2. 如果这样的例外出现了很多次,就可以通过修改原则和实践的方式把我们的理解固化下来;

9. 治理

  架构师的部分职责是治理,那么什么是治理?
  治理是通过评估需求、当前情况、下一步可能性确保企业目标达成,通过排优先级和做决策来设定方向。对已达成一致的方向和目标进行监督。如果说,架构师的一个职责是确保有一个技术愿景,那么治理就是要确保我们构建的系统符合这个愿景,并在需要的时候对愿景进行演化。

10. 建设团队

  • 帮助队友成长
  • 帮忙队友理解愿景
  • 保证队友可以积极地参与到愿景的实现和调整

第三章:如何建模服务

1. 什么样的服务是好服务

1.1 松耦合

  能够独立修改及部署单个服务而不需要修改系统的其他部分。

1.2 高内聚

把相关的行为聚集在一起,把不相关的行为放在别处。

2. 限界上下文

  限界上下文:任何一个给定的领域都包含多个限界上下文,每个限界上下文中的东西分成两部分,一部分不需要与外部通信,另一部分则需要。每个上下文都有明确的接口,该接口决定了它会暴露哪些模型给其他的上下文。
  限界上下文:一个由显式边界限定的特定职责。

2.1 共享的隐藏模型

  1. 一个界限上下文依赖例外一个界限上下文的某个部分,而该部分不会对外共享;
  2. 同一个名字在不同的上下文中有着完全不同的含义;

2.2 模块和服务

  1. 不要共享内部表达,避免潜在的紧耦合;
  2. 识别领域的边界,边界内部是相关性比较高的业务功能,从而得到高内聚;
  3. 微服务应该清晰地和限界上下文保持一致;
  4. 边界搞错了,修复代价很大;

2.3 不要过早划分

  • 边界划分不是一件容易的事,不建议很早就划分微服务,原因:
  • 理解服务边界需要时间
  • 服务边界搞错修复代价较大
  • 识别出稳定边界后再将单体系统拆分成多个微服务
  • 将已有代码库划分成微服务比从头开始构建微服务简单得多

3. 按业务功能划分

不应该从共享数据角度考虑划分;
应该从上下文能够提供的业务功能来考虑;

4. 逐步划分上下文

  一开始你会识别出一些粗粒度的限界上下文,而这些限界上下文可能又包含一些嵌套的限界上下文。
方式

  • 嵌套结构:对外只暴露粗粒度限界上下文,隐藏内部细粒度限界上下文
  • 完全分离结构:进一步分解出来细粒度的限界上下文都是顶层服务,直接对外暴露

选型方法
  组织架构和软件架构一致。如果拆分出细粒度的限界上下文都属于一个团队管理,嵌套结构更加合理。反之,拆分出细粒度的限界上下文属于不同团队管理,完全分离结构更加合理;
  嵌套结构方便下游消费者整体打桩测试;

5. 关于业务概念的沟通

  某个功能所要做的修改,应该更多的局限在一个单独的微服务边界之内,微服务之间如何就同一个业务概念进行通信需要考虑的点:

  1. 基于业务领域的软件建模不应该止于限界上下文的概念;
  2. 在组织内部共享的那些相同的术语和想法,也应该被反映到服务的接口上;
  3. 以跟组织内通信相同的方式,来思考微服务之间的通信形式是非常有用的;
  4. 技术边界

  按照技术接缝对服务边界进行建模也并不总是错误的。比如,我见过当一个组织想要达到某个性能目标时,这种划分方式反而更合理。然而一般来讲,这不应该成为你考虑的首要方式。

第四章:集成

1. 什么是集成

  集成把各个微服务、组件、外部服务全部关联起来,成为一个整体系统。

2. 理想的集成技术

  • 避免破坏性修改,添加一个字段对已有消费方没有影响
  • 保证API技术无关性,原因:
  • 技术迭代快,方便使用使用新技术(扩展性)
  • 不同微服务之间可以使用不同技术实现(技术异构性)
  • 便于消费方使用
  • 隐藏内部实现细节,避免服务内部修改,消费方需要跟着修改(低耦合)

3. 共享数据库集成

  一个服务获取另一个服务信息,通过直接访问数据库;修改数据也是通过直接在数据库中修改。

3.1 为什么数据库集成流行

  • 简单
  • 快速集成

3.2 弊端

  • 暴露内部实现细节(违反松耦合),不方便自治、轻松修改内部实现,比如:
  • 表结构修改影响消费方正常工作
  • 消费方与特定技术选择绑定,但意识到相比关系型数据库,非关系型数据库更适合,难更改替换
  • 暴露内部细节,很难做到无破坏性修改
  • 逻辑分散(违反高内聚),当逻辑出现bug,修复一个Bug需要修改不同的服务并分别做部署
  • 只能共享数据,无法共享行为

4. 同步与异步

  微服务之间如何通信呢?
####4.1 通信模式

  • 同步:发起远程调用后,调用方阻塞自己并等待整个操作完成
  • 异步:调用方不需要等待操作完成就可以返回,甚至可能不需要关心这个操作完成与否。

4.2 协作方式

  两种通信模式有着各自的协作风格,分为请求/响应方式 和 基于事件方式。

4.2.1 请求/响应

客户端发起一个请求,然后等待响应,通信方式:

  • 同步通信方式
  • 异步通信方式,发起请求->注册回调->服务端返回->调用回调
4.2.2 基于事件

  发布事件,协作者收到消息自行决定处理方式。业务逻辑并非集中一处,而是分布在不同协作者中。

  • 特点:低耦合
  • 通信方式:异步通信方式

5. 编排与协同

5.1 编排

  赖于某个中心大脑来指导并驱动整个流程。

  • 优点:调用简单,容易知道整个流程的工作是否正常
  • 缺点:客户服务作为中心控制点承担了太多职责;耦合度高;

5.2 协同

  仅仅会告知系统中各个部分各自的职责,而把具体怎么做的细节留给它们自己。

  • 优点:耦合度低;修改灵活;
  • 缺点:看不到明显的业务流程;不容易知道整个流程工作情况需要做跨服务监控;

6. 远程过程调用

  远程过程调用允许你进行一个本地调用,但事实上结果是由某个远程服务器产生的。RPC的主要卖点之一:易于使用。

6.1 选型

  避免技术耦合。有些RPC机制(如Java RMI)与特定的平台紧密绑定,对服务端和客户端的技术选型造成一定限制。
  本地调用和远程调用并不相同,避免隐藏过头。隐藏过头会让开发人员会在不知道时远程调用情况下对其进行调用,以至于把网络因素忽略起来。
  反脆弱性,服务方新增字段,不使用该字段的客户端也需要修改。

7. REST

  REST是受Web启发而产生的一种架构风格。REST风格包含了很多原则和限制,REST是RPC的一种替代方案。REST中最主要的概念是资源。

8. 事件异步协作方式

8. 1 技术选择

  主要有两个部分需要考虑:微服务发布事件机制和消费者接收事件机制。
  原则:尽量让中间件保持简单,而把业务逻辑放在自己的服务中。

8.2 异步架构的复杂性

  事件驱动的系统看起来耦合非常低,而且伸缩性很好。但是这种编程风格也会带来一定的复杂性,这种复杂性并不仅仅包括对消息的发布订阅操作。
  事件驱动架构和异步编程会带来一定的复杂性,所以我通常会很谨慎地选用这种技术。你需要确保各个流程有很好的监控机制,并考虑使用关联ID,这种机制可以帮助你对跨进程的请求进行追踪。

9. 代码重用的危险

  一般观念,如果代码在很多系统上都有重复实现,防止遗漏修改,一般都把重复性代码抽取出来成为共享库。但是在微服务不一定适合。微服务中重用会导致微服务和消费者之间的过渡耦合。
  作者经验:在微服务内部不要违反DRY,但在跨服务的情况下可以适当违反DRY。服务之间引入大量的耦合会比重复代码带来更糟糕的问题。

10. 按引用访问

  微服务应该包含核心领域实体(比如客户)全生命周期的相关操作。从消息产生到消费期间数据可能发生变更,资源应该是使用引用以便于查询。
  原则:应该在不确定数据是否能够保持有效的情况下,谨慎地进行处理。

11. 版本控制

11.1 尽可能推迟

  避免过早地将客户端和服务端紧密绑定起来。

11.2 及早发现破坏性修改

  及早发现会对消费者产生破坏的修改非常重要,因为即使使用最好的技术,也难以避免破坏性修改的出现。
  使用消费者驱动的契约来及早定位这些问题。

11.3 同时使用多个版本的服务

  同时运行不同版本的服务,然后把老用户路由到老版本的服务,而新用户可以看到新版本的服务。
  短期内同时使用两个版本的服务是合理的,尤其是当你做蓝绿部署或者金丝雀发布时。在这些情况下,不同版本的服务可能只会共存几分钟或者几个小时,而且一般只会有两个版本。升级消费者到新版本的时间越长,就越应该考虑在同一个微服务中暴露两套API的做法。

第五章:分解单块系统

1. 分解单块系统关键——接缝

  从接缝处可以抽取出相对独立的一部分代码,对这部分代码进行修改不会影响系统的其他部分。识别出接缝不仅仅能够清理代码库,更重要的是,这些被识别出的接缝可以成为服务的边界。

1.1 那么什么样的接缝才是好接缝呢?

  限界上下文就是一个非常好的接缝,因为它的定义就是组织内高内聚和低耦合的边界。所以第一步是开始识别出代码中的这些边界。

2. 分解单块系统的原因

  1. 可以增快后期开发速度;
  2. 不同团队可以各自全权负责自己的模块;
  3. 便于对敏感信息服务做更加严密的保护;
  4. 便于对部分功能使用新的技术;

3. 杂乱的依赖

  服务分解之后,各模块之间依赖可能存在依赖,而数据库数所有杂乱依赖的源头。

4. 数据分离

4.1 方法
  找到数据库中的接缝,按照接缝进行分离。

4.2 分离过程

  1. 把代码和数据库进行绑定,简化对象和数据库之间的读写操作;
  2. 把数据库映射相关的代码和功能代码放在同一个上下文中;
  3. 外键关联处理,把约束从数据库移到代码中实现;

5. 重构数据库

5.1 实施分离

  先分离数据库表结构,暂时不对服务进行分离。好处是:可以随时选择回退这些修改或是继续做,而不影响服务的任何消费者;可能会带来的影响:访问次数可能会增多,破坏事务完整性。

5.2 事务边界

  保证一些事件要么都发生,要么都不发生。使用单块表结构时,所有的创建或者更新操作都可以在一个事务边界内完成。分离数据库之后,这种好处就没有了。我们有两种选择:

  1. 最终一致性
      相对于使用事务来保证系统处于一致的状态,最终一致性可以接受系统在未来的某个时间达到一致。这种方法对于长时间的操作来说尤其管用。
  2. 强制一致性
      当出现失败,需要拒绝整个服务上的操作,在这种情况下,我们需要把系统重置到某种一致的状态。解决方法是,再发起一个补偿事务来抵消之前的操作。

5.3 分布式事务

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

分布式事务特点:
  分布式事务会横跨多个事务,然后使用一个叫作事务管理器的工具来统一编配其他底层系统中运行的事务。就像普通的事务一样,一个分布式的事务会保证整个系统处于一致的状态。唯一不同的是,这里的事务会运行在不同系统的不同进程中,通常它们之间使用网络进行通信。

分布式事务算法:
  常用的算法是两阶段提交。在这种方式中,首先是投票阶段。在这个阶段,每个参与者会告诉事务管理器它是否应该继续。如果事务管理器收到的所有投票都是成功,则会告知它们进行提交操作。只要收到一个否定的投票,事务管理器就会让所有的参与者回退。

两阶段提交提交算法问题:

  1. 增加复杂度;
  2. 容易导致系统中断。例如,事务管理器宕机了、发送消息失败 等会导致其他参与者被阻塞;
  3. 该算法隐式地认为上述这些情况不会发生;
  4. 此算法并不是万无一失的,而只是尝试捕获大部分的失败场景;

扩展阅读——还有别的分布式事务吗?
  除了两阶段提交(2PC)方案还有:

  1. 补偿事务(TCC)
  2. 本地消息表(异步确保)
  3. MQ 事务消息
  4. Sagas 事务模型
    这些方案,后续作为延时阅读,单独说明。

6. 报告

在对服务进行分离的同时,可能也需要对数据存储进行分离。但是就会在进行一个很常见的操作时出问题,这个操作就是报告。

6.1 报表数据库

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

7. 通过服务调用来获取数据

存在的问题:
  通过服务调用有些依赖第三方工具获取,不同的微服务暴露的API不一定能够很好地适用于报告这个场景。

解决方法:
  对于某些服务暴露的资源来说,可以通过添加一些缓存头来加快数据的获取速度,还可以把这些数据缓存在反向代理之类的地方。但是报告天然就允许用户访问不同时期的历史数据,这意味着,如果用户访问的资源是别人没有访问过的(或者在很长一段时间内没有人访问),则缓存无法命中。

8. 事件数据导出

  每个微服务可以在其管理的实体发生状态改变时发送一些事件。
实例:
  我们的客户服务可能会在客户增删改时发送一些事件。对于这些暴露事件聚合(feed)的微服务来说,可以编写自己的事件订阅器把数据导出到报告数据库中。

优点:
  与源微服务底层数据库之间的耦合就被消除掉了。我们只需要绑定到服务所发送的事件即可,而设计这些事件,本来就是用来暴露给外部消费者的。

缺点:
  所有需要的信息都必须以事件的形式广播出去,所以在数据量比较大时,不容易像数据导出方式那样直接在数据库级别进行扩展。

建议:
  考虑这种方式,因为它能够带来低耦合及更好的数据时效性。

9. 理解根本原因

  大服务是怎么产生的呢?第一件需要理解的事情是,服务一定会慢慢变大,直至大到需要拆分。我们希望系统的架构随着时间的推移增量地进行变化。关键是要在拆分这件事情变得太过昂贵之前,意识到你需要做这个拆分。

第六章:部署

1. 持续集成简介

CI (Continuous Integration,持续集成)能够保证新提交的代码与已有代码进行集成,从而让所有人保持同步。CI服务器会检

优点:

  1. 通过它,我们能够得到关于代码质量的某种程度的快速反馈;
  2. CI可以自动化生成二进制文件。可以重新生成这个版本的构建物;
  3. 通过CI我们能够从已部署的构建物回溯到相应的代码;
  4. 可以使在这些代码和构建物上运行过的测试可视化;

2. 把持续集成映射到微服务

  当持续集成遇上微服务时,需要考虑如何把CI的构建和每个微服务映射起来。所以如何在微服务、CI构建及源代码三者之间,建立起合适的映射呢?

  1. 每个微服务都有自己的CI,这样就可以在将该微服务部署到生产环境之前做一个快速的验证。
  2. 当对代码库进行修改时,可以只运行相关的构建以及其中的测试。
  3. 代码库与团队所有权的匹配程度也更高了。

3. 构建流水线和持续交付

  CD (Continuous Delivery,持续交付),到了把一个构建分成多个阶段是很有价值的。将构建分解成为多个阶段,从而得到我们熟知的构建流水线。在第一个阶段运行快速测试,在第二个阶段运行耗时测试。
  在微服务的世界,我们想要保证服务之间可以独立于彼此进行部署,所以每个服务都有自己独立的CI。在流水线中,构建物会沿着上线方向进行移动。

4. 平台特定的构建物

  大多数技术栈都有相应的构建物类型,同时也有相关的工具来创建和安装这些构建物。不同技术栈生成的构建物各不相同,所以混合不同的构建物进行部署就会很复杂。

5. 操作系统构建物

  有一种方法可以避免多种技术栈下的构建物所带来的问题,那就是使用操作系统支持的构建物。
优点:

  1. 在做部署时不需要考虑底层使用的是什么技术;
  2. 只需要简单使用内置的工具就可以完成软件的安装;
  3. 这些操作系统工具也可以进行软件的卸载及查询,甚至还可以把CI生成的构建物推送到软件包仓库中;
  4. OS包管理工具,可以帮你完成很多原本需要使用Chef或者Puppet来完成的工作;

缺点:

  1. 刚开始编写构建脚本的过程可能会比较困难
  2. 不同操作系统支持难易程度不一样
  3. 维护不同版本构建物的开销就会很大

建议:
  尽量减少需要维护的操作系统的数量,最好只维护一种。

6. 定制化镜像

优点:

  1. 安装简单;

缺点:

  1. 构建镜像会花费大量的时间;
  2. 产生的镜像可能会很大,导致上传时间久;

7. 服务配置

  服务需要一些配置。理想情况下,这些配置的工作量应该很小,而且仅仅局限于环境间的不同之处。如果配置较多需要使用配置中心。

8. 服务与主机之间的映射

8.1 单主机多服务

存在的挑战:
1.监控会变得更加困难;
2. 不利于团队的自治;
3. 会限制部署构建物的选择;

8.2 应用程序容器

  把不同的服务放在同一个容器中,再把容器放置到单台主机上的模式
优点:

  1. 可以节省语言运行时的开销

缺点:

  1. 它会不可避免地限制技术栈的选择。你只能使用一种技术栈;
  2. 还会限制自动化和系统管理技术的选择;

8.3 每个主机一个服务

优点:

  1. 可避免单主机多服务的问题;
  2. 简化了监控和错误恢复;
  3. 这种方式也可以减少潜在的单点故障;

缺点:

  1. 主机数量的增加;
  2. 管理更多的服务器,运行更多不同的主机也会引人很多的隐式代价;
  3. 服务器成本增加;

9. Docker

Docker是构建在轻量级容器之上的平台。

  1. 它帮你处理了大多数与容器管理相关的事情。
  2. 你可以在Docker中创建和部署应用,这些基于容器的应用与VM世界中的镜像很类似。
  3. Docker也能管理容器的配置,并帮你处理一些网络问题
  4. 允许你存储Docker应用程序的版本。

10. 一个部署接口

  不管用于部署的底层平台和构建物是什么,使用统一接口来部署给定的服务都是一个很关键的实践。

你可能感兴趣的:(读书笔记,架构,微服务)