简评:这本书是很“宽”的一本书,描述了许多构建微服务中将面临的挑战,不过大多数知识点还是点到为止。个人觉得适合有一定基础的读者,能扩展知识的宽度,之后再针对其中的知识点深造。

本文是读书笔记,由 XMind 导出(XMind 源文件 )。 不建议阅读。

Ch01: 微服务

什么是微服务

  • 很小,专注好做一件事

    • 构建服务时,内聚性很重要
    • 单一职责原则:把因相同原因而变化的东西聚合到一起,把因不同原因而变化的东西分离开来
    • 服务越小,独立性的好处越多,但管理大量服务也会越复杂
  • 自治性

    • 能独立部署
    • 通过网络通信
    • 能独立修改(不对调用方产生影响)

主要好处

  • 技术异构性

  • 弹性

    • 舱壁,一个组件失效不会影响其它部分
  • 扩展

    • 粒度更细
  • 简化部署

    • 局部更新(C端常用)
    • 快速迭代,容易回滚
  • 与组织结构相匹配

  • 可组合性

    • 利于重用已有功能
  • 对可替代性的优化

    • (可以重写小服务)

面向服务的架构

  • Service-Oriented Architecture(SOA)
  • 可以认为微服务架构是 SOA 的一种特定方法

其它分解技术

  • 微服务两个优势

    • 较小的粒度
    • 解决问题方法上的更多选择
  • 共享库

    • 无法选择异构的技术
    • 无法保证弹性
    • 个人观点:共享库通常业务信息太浓
  • 模块

    • OSGI:语言对生命周期管理支持不足,模块作者工作很大,进程内模块间耦合太重
    • Erlang 在语言层面内支持模块,很强大。但还是无法采用异构的技术,局限性
    • 模块在实际中会迅速耦合(强烈同意)
  • 没有银弹

Ch02: 演化式架构师

不准确的比较

  • Architect 这个词是有问题的
  • 建筑领域内,是详细的计划和按部就班的实施
  • 但在软件领域内,这是不可能的

架构师的演化视角

  • 大量的需求变更
  • 使用的工具、技术多样
  • 发布之后依然在变化
  • 类比城市规划师,应当聚焦于规划,保证系统适合开发人员在其上工作

分区

  • 服务边界(对比于城市里的区域)
  • 应该考虑服务间如何交互,较少关心服务内

一个原则性方法

  • 战略目标

    • 通常我们不需要去定义战略目标
  • 原则

    • 为了与目标保持一致,制定一些规则,称为原则
    • 一般不要超过 10 个
    • 区分“原则”和“约束”
  • 实践

    • 通过实践来保证原则能得到实施

    • 通常实践是技术相关的、偏底层的

    • 实践应该巩固原则

      如原则:开发团队应该对软件的开发全流程有控制权则对应实践:所有的服务部署在不同的 AWS 帐户中,从而提供资源的自助管理和其它团队的资源隔离

  • 将原则和实践结合

    • 有时原则和实际会混淆

      如 HTTP/REST 是实践还是原则?

    • 重要的是需要有原则指导系统的演化

  • Real World Example

要求的标准

在考虑取舍时,要考虑:系统允许多少可变性?如何在优化单个服务自治性的同时,兼顾全局?一种方法是:清楚地定义出一个好服务应用的属性

换句话说,有什么是系统的所有服务要统一遵守的?

  • 监控

    • 能够清楚地描绘出跨服务系统的健康状态非常关键
    • 必须在系统级别而非单个服务级别进行考虑
    • 建议所有服务使用同样的方法报告健康状态
    • Subtopic 4
  • 接口

    • 选用几种少数明确的接口技术有助于新消费者集成
    • 使用一种或两种接口技术作为标准,不要多
  • 架构安全性

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

    • 返回码也应该遵守一定规则

    • 处理不同的请求

      • 正常且正确处理
      • 错误请求且被系统识别
      • 被访问的服务宕机,无法判断请求是否正常

代码治理

  • 如何保证共识?

  • 范例

    • 理想情况下,范例应该来自真实项目
  • 裁剪服务代码模板

    • 即新服务的核心属性最好是一致、现成的
    • 对标:maven 的 architecture
    • 注意:服务模板不是中心化的职责,也不是指导
    • 重用代码有危险,可能会引入服务间的耦合

技术债务

  • 有时可能会因为紧急发布某些特性而忽略一些约束,但最终是需要还债的
  • 架构师需要从更高的层次出发,理解如何做权衡
  • 理解债务的层次及对系统的影响

例外管理

  • 多次对原则的偏离可能意味着需要修改原则

集中治理和领导

  • 治理通过评估干系人的需求、当前情况及下一步的可能性来确保企业目标的达成,通过排优先级和做决策来设定方向。对于已经达成一致的方向和目标进行监督
  • 架构师不应该独自做这些事
  • 有时架构师不认同小组的决定,最好尊重他们

建设团队

  • 重要的是帮助你的队友成长

小结:架构师的职责

  • 愿景:确保系统有一个充分沟通的技术愿景,可能帮助满足客户和组织的需求
  • 同理心:理解你的决定对客户和同事的影响
  • 合作:和尽量多的同事沟通,更好地对愿景进行定义、修订和执行。
  • 适应性:确保在你的客户和组织需求的时候调整技术愿景
  • 自治性:在标准化和团队自治之间寻找一个正确的平衡点
  • 治理:确保系统按照技术愿景实现

Ch03: 如何建模服务

什么样的服务是好服务

  • 低耦合+高内聚

  • 低耦合

    • 修改一个服务不需要修改另一个服务
    • 尽可能少地知道其它服务的信息
  • 高内聚

    • 把相关的行为聚在一起,不相关的放在别处
    • 最好能只在一个地方进行修改,就可以尽快发布

限界上下文

  • 一个由显式边界限定的特定职责,类比“细胞”

  • 共享的隐藏模型

    • 同一个名字在不同的上下文中有完全不同的含义
    • 应当共享特定模型,不应该共享内部表示
  • 模块和服务

    • 一旦发现领域内的限界上下文,一定要用模块对其进行建模,同时共享和隐藏模型
    • 一般来说,服务应该和限界上下文保持一致
  • 过早划分

    • 很多时候,将一个已有代码库划分成微服务,比重头构建要简单得多

业务功能

  • 限界上下文应从业务功能出发,而非共享数据

逐步划分上下文

  • 嵌套上下文 vs 完全分离?
  • 也许应该根据组织结构决定
  • 嵌套的另一个好处是方便成块测试

关于业务概念的沟通

技术边界

  • 边界划分时要考虑组织结构(如地理位置)

Ch04: 集成

寻找理想的集成技术

  • SOAP? XML-RPC? REST? Protocol Buffers?

  • 避免破坏性修改

    • 比如在响应中添加一个字段,已有的消费方不应该受到影响
  • 保证 API 的技术无关性

    • 用以兼容未来的技术
  • 隐藏内部的实现细节

共享数据库

  • 最快的集成方式

  • 暴露了内部实现,后续修改困难

  • 消费方与特定技术绑定

    • 如果今后要使用 NoSQL?
  • 行为上,如果隐藏修改的 API?

    • 修改的逻辑可能散落在各个地方,破坏内聚性

同步与异步

  • 同步一般基于请求/响应
  • 异步一般基于事件

编排与协同

  • 如何处理跨服务业务流程的逻辑

  • 编排(orchestration):中心化管理

    • 相当于有一个统一的协调者
    • 比如规则引擎(商业流程建模软件)
    • 缺点:编排的任务过于重大
  • 协同(chereography):各自为政

    • 只触发事件,各方作出各自的响应
    • 缺点:缺少明确的流程视图
    • 需要额外工作来监控流程是否正确执行
    • 总评:能降耦合,但需要跨服务监控,微服务下优先考虑

远程过程调用(RPC)

  • 技术的耦合

    • 耦合的技术方便使用,解耦的技术方便扩展
    • 如 Java RMI 只能在 Java 中使用,方便但限定技术栈
    • 如 Thrift, protocol buffers 能对接各种语言,但使用过程比较繁琐
  • 本地调用与远程调用并不相同

    • RPC 的核心是隐藏远程调用的复杂性

    • 封装得太好会导致人们忽略了性能、可靠性

      JPA 也是类似,相比于 Mybatis 封装性更好,更易用,但是也容易过度使用导致性能问题

  • 脆弱性

    • 如 Java RMI,添加接口的新方法需要修改所有客户端
    • 如 Java RMI,仅在服务端删除某个字段会导致序列化和反序列化逻辑不一致,从而导致服务失败
  • RPC 很糟糕吗?

    • 如果决定使用 RPC,注意:不要对远程调用过度抽象

REST

  • REST 和 HTTP

    • 它们在一起会更强
  • Hypermedia As The Engine Of Application State(HATEOAS)

    • 避免客户端和服务端耦合的一个原则
    • 如 Amazon,用户点击购物车,这是一种隐式约定,即使购物车的真实地址变化,用户还是能找到
    • 对电子用户而言,我们希望它总是从“商品”找到购买链接,尽管链接会变化,商品本身不变
    • 缺点是:通信次数比较多
    • 实践中,可以先让用户自行遍历,有必要时再优化
  • JSON、XML 及其它

  • 留心过多的约定

    • 不要直接暴露内部的对象(转成 JSON)
  • 基于 HTTP 的 REST 的缺点

    • 易用性:无法生成客户端的 stub 代码
    • 有些 Web 框架无法很好地支持所有的 HTTP 动词
    • 性能上:性能不如二进制协议(如 Thrift),不适用于低延迟的通信

实现基于事件的异步协作方式

  • 技术选择

    • 发布事件和接收事件的机制

    • 如 RabbitMQ 的消费代理

      • 能够处理发布和接收的问题
      • 同时还能对消息进行管理追踪
      • 代价是增加开发复杂度,因为需要额外中间件
      • 尽量保持中间件简单,把业务逻辑放在自己的服务里
    • 通过 HTTP 传播事件

      • 例如 ATOM 协议(feed)
      • 有事件时发布到该聚合上,消费者会轮询
  • 异步架构的复杂性

    • 复杂性,不仅来源于发布、订阅操作

    • 银行系统的示例

      • 如何处理消费者故障?
      • 如何处理失败消息?最大重试次数?死信队列?
      • 简评:异步架构看似各自为政,但会隐藏一些全局的需求。这些需求恰恰是复杂性的来源

服务即状态机

  • 将领域的生命周期显示建模
  • 不仅是对 CRUD,也对状态转换封装一些行为

响应式扩展(Reactive Extensions, Rx)

  • 组装一个或多个异步调用,简化代码
  • 简评:异步是坑,Think Twice

微服务中的 DRY 和代码重用的危险

  • 个人认为 DRY 抽取重复代码是增加内聚性的过程,不需要修改多个地方
  • 共享代码会导致微服务间的耦合,一处修改会影响多个服务
  • 经验法则:微服务内 DRY,跨服务适当违反

按引用访问

  • 领域实体的生命周期应当只在某个服务内管理
  • 如果保存本地副本,应该保留原始实体的引用,方便后续查询,更新
  • 需要知道事件是否发生,还需要知道发生了什么
  • 具体是否引用要看场景,是否数据切片能满足要求

版本管理

  • 尽可能推迟

    • 采用一些耦合小的技术(如 REST 而非数据库集成)

    • 鼓励客户端的正确行为

      • 宽进严出(对自己发送的东西要严格,对接收的东西要宽容)
  • 及早发现破坏性修改

  • 使用语义化的版本管理

    • MAJOR.MINOR.PATCH
    • 个人觉得并没有什么用
  • 不同的接口共存

    • 平滑过渡
  • 同时使用多个版本的服务

    • 坏处:老版本永远不会升级

用户界面

  • 是否轻界面,重后端

  • 走向数字化

    • API 的粒度,细 vs 粗
  • 约束

    • 如移动端的带宽、电量等的影响
  • API 组合(让 UI 直接访问 API)

    • 不同的设备可能需要不同的数据,一个解决方案是允许客户选择字段(如 GraphQL?)
    • 谁来创建 UI?如果是不同团队,会回到分层合并的模式,则细微的修改都需要多个团队参与
  • UI 版本的组合:服务直接暴露部分 UI,上层应用对其进行组合

  • 为前端服务的后端:即增加一层代理,将细粒度 API 拼接成粗粒度,提供给前端

    • 问题是最终代理层会变得巨大无比
  • 混合方式

与第三方软件集成

  • 缺乏控制

  • 定制化

    • 一般购买的定制化成本很大
  • 在自己可控的平台上进行定制化

    • 把三方软件当成服务,以此为基础搭建自己的系统
  • 绞杀者模式

    • 拦截对三方系统的应用,路由部分到新系统

Ch05: 分解单块系统

服务怎么拆?拆了怎么办?源头是业务,找到上下文的边界。拆的过程中很可能最终发现数据库层面的耦合。拆成服务后可能会遇到分布式事务的要求还有一些跨数据库(报告)的需求和方法。

关键是接缝

  • 从接缝处抽取相对独立的代码,对其修改不影响其它部分
  • 尝试找到服务的边界

分解单块系统的原因

  • 改变的速度

    • 抽成服务,加速后期开发
  • 团队结构

  • 安全

  • 技术

    • 可以用其它的技术栈

杂乱的依赖

  • 经常会发现:数据库是所有杂乱依赖的源头

找到问题的关键

  • 即找到服务对数据库的交叉使用

例子:打破外键关系

  • 把外键变成服务调用

例子:共享静态数据

  • 例如:国家代码等数据
  • 方法一:为每个包复制一份
  • 方法二:静态数据放入代码(属性文件、枚举)
  • 方法三:静态数据作为单独服务

例子:共享(可变)数据

  • 原因:领域概念在数据库中隐式建模
  • 示例中可以将这个对数据的交叉抽离成新的服务

例子:共享表

  • 如:Catalog 中的条目与电子记录的条目存放在通用条目表
  • 方法:拆成两个表

重构数据库

  • 推荐在分离服务前先分离数据库结构
  • 逐步进行:单块服务+单表 -> 单块服务 + 分离表 -> 分离服务+分离表

事务边界

  • Retry Later

    • 最终一致性
  • Abort

    • 补偿事务抵消之前的操作
    • 多个服务下如何处理?
  • 分布式事务

    • 2PC
  • 太复杂怎么办?

    • 业务是否需要一致? double check
    • 能否从业务上做处理?而不是技术

分离数据库后如何做报告

  • 报告生成需要跨数据库/表访问

  • 定期同步到报告数据库

    • 表结构修改怎么办
  • 通过服务调用获取数据

    • 只适用于少量数据,长时间同期的数据量太大
    • 批量 API 将结果写入文件
  • 推送数据到报告系统(ELK)

    • 直接对数据库操作
  • 导出事件数据

    • 低耦合
    • 比较难应对大数据量
  • 数据导出的备份

    • 持久化数据备份+Hadoop

修改的代价

  • 修改的量越大风险越大
  • 在影响最小的地方犯错误(白板)

Ch06: 部署

持续集成简介

  • 是否每天合并代码到主线?
  • 是否有测试来验证修改?
  • 构建失败后,修复 CI 是否是团队的头等大事?

CI 应用到微服务

  • 单仓库、单构建

    • 问题是一次性部署多个服务
    • 粒度太粗,影响 CI 周期,不易定位发生修改的服务
  • 单仓库、多构建

    • 缺点:代码粒度太粗,一个项目小修改 CI 的失败会卡其它项目的 CI
  • 多仓库、多构建

pipeline 与 CI

  • 评:pipeline 可以将测试粒度变细,fail fast

  • 例外:团队刚开始的时候应该先用单仓库、单构建

    • 开始时不容易分辨边界
    • 开始时容易有跨服务的修改
    • 但只能是过渡

Artifact

  • Platform specific Artifact

    • Ruby gem, Java jar/war, Python egg
    • 构建物可能不够,还需要中间件和其它配置
    • 考虑用 puppet, chef, ansible 管理
  • OS specific artifact

    • rpm, deb, MSI
    • 好处多多,写构建脚本比较困难,且平台支持不一
  • 定制化镜像

    • 构建镜像会花费大量时间
    • 镜像可能很大
  • 将镜像作为 artifact

  • Immutable Server

    • 配置漂移:如果有人登录服务器修改了配置呢?

环境

  • 耗时测试、UAT、性能测试、生产
  • 环境要保持一致,否则会有预期之外的情况

服务配置

  • 尽量各环境的配置保持一致

服务与主机的映射

  • “每台机器应该有多少服务”?

  • 单主机、多服务

    • 主机管理工作量小
    • 管理工作量不随服务增长而增长
    • 监控困难
    • 服务部署也会更复杂,难以保证服务不相互影响
    • 不利于团队自治性
    • 虚拟技术的发展,不太应该继续这样
  • 应用程序容器(tomcat, glassfish,…)

  • 每个主机一个服务

  • PaaS

    • 如果出错比较难排查
    • 一些特定的要求比较难满足

自动化

  • 自动化管理是微服务的必经之路

从物理机到虚拟机

  • 传统的虚拟化技术

    • 一般不做,虚拟化会有额外的开销
  • Vagrant

    • 开发机上资源消耗大
  • Linux 容器

    • 如 LXC
    • 更轻量
    • 并不是真正的隔离
  • Docker

    • 构建于容器之上的平台

一个部署接口

  • 最佳实践是有一个统一的接口来部署服务
  • 服务的名字、版本、哪个环境

Ch07: 测试

测试类型

  • 单元测试(是否正确实现功能)
  • 非功能性测试(响应、扩展、性能、安全)
  • 验收测试(是否实现了正确的功能)
  • 探索性测试(如何破坏系统)

测试范围(金字塔)

  • 用户界面

    • 范围最大、信心最足、最难定位
  • 服务测试

    • 绕开界面,直接对服务测试
    • 测试单独的服务可以提高测试的隔离性
    • 为了达到要求,需要 stub
  • 单元测试

    • TDD
    • 不会启动服务
    • 外部文件、网络连接访问有限
    • 对重构非常重要
  • 比例

    • 经验:下层比上层多一个数量级

实现服务测试

  • Mock vs Stub?

    • 这两个概念可能跟我们平时的用法不一样
    • 这里 stub 无副作用,mock 有
    • stub 用得多

端到端测试

  • 微秒的端到端测试

    • 界面依赖多个服务,当前服务依赖的其它服务也有新版本,测试时要用什么组合?
    • 不同服务的测试要重合,要重复测试吗?
    • 一种方法是让每个服务“扇入”到端到端测试
  • 脆弱的测试

    • 存在一些非功能错误无法识别(如底层服务未响应)

    • 谁来写测试?

    • 测试多长时间?

    • 测试量大大,可能产生大量堆积

    • 元版本

      • 会产生为所有服务给一个统一的版本号
      • 这样就回到了单块架构
  • 测试场景、而不是故事

    这个概念和之前的理解有出入

    • 核心端到端,其余在服务测试中覆盖
  • 消费者驱动的测试

    • 端到端测试解决问题:修改不破坏消费者
    • 另一种方式:CDC(Consumer-Driven Contract 消费者驱动的契约)
    • 即定义消费者的期望,并转化成测试代码,进入 CI pipeline
    • Pact: 一个 CDC 测试工具
    • CDC 与 story 一样有助于沟通
  • 还要做端到端测试吗?

    • 评:在业务还在强烈变化时,个人认为还是需要的,优化点在于有多少可以转移成 CDC

部署后再测试

  • 区分部署和上线

    • 部署后不一定立马切流量,切流量才是上线
  • Canary Releasing

    • 小流量测试
  • 平均修复时间 胜于 平均故障时间

    • 平均故障间隔时间 (Mean Time Between Failures, MTBF)
    • 平均修复时间 (Mean Time To Prepare, MTTP)
    • MTBF 是指两次故障之间正常工作时间的均值,需要靠更多的测试,而 MTTP 指出现故障平均花多长时间修复。这两者是需要平衡的

非功能性需求(性能、用户、稳定……)

  • 频率可能更小,但一定要做,不能拖到上生产

Ch08: 监控

小结:要做监控,要标准化做法手段:传递唯一标识最低限度:服务的响应时间最你限度:下游的健康状态

单一服务、单一服务器

  • 主机本身:CPU、内存等主机数据

    • 如 Nagios 工具或 New Relic 托管服务
  • 服务器本身的日志

    • rogrotate 移除旧日志

单一服务、多服务器

  • 应用场景:负载均衡
  • 除了查看所有主机的数据,也需要查看单个主机的数据
  • 如果只有几个主机,可以使用 ssh-multiplexers
  • 响应时间可以看负载均衡器中的聚合数据

多个服务、多个服务器

  • 是系统问题?服务问题?是哪个服务?
  • 回答:集中收集和聚合尽可能多的数据

日志、日志、更多的日志

  • 新需求:日志收集子系统
  • 例如 logstash、Kibana

多个服务的指标跟踪

  • 有了日志后,最好能生成指标方便追踪

  • Graphite 系统

    • 接收指标并展示
    • 通过有效配置,聚合数据减少存储容量
    • 跨样本做聚合

服务指标

  • collectd 生成操作系统的大量指标

  • Nginx, Varnish 支撑系统也会暴露有用的信息

  • 自己服务的指标呢?

    • Metrics 库

综合监控

  • Naive 的监控:设定一个标准值,超过报警
  • 示例:定期插入假事件,并判断是否被处理,更贴近最终的监控需求
  • 合成事务执行语义监控,比使用低层指标更能表明问题
  • 在生产系统上执行端到端测试(保证无副作用)

关联标识

  • 用 GUID 给请求打标识,并传递给后续所有系统
  • Zipkin
  • 使用包装的客户端确保信息不会丢失

级联

  • 上游需要确认下游服务的健康状态并打日志
  • 添加断路器更加优雅地处理

标准化

  • 以标准的方式暴露监控接口、落日志

Ch09: 安全

身份验证和授权

  • 人或事,抽象为“主体”(Principle)

  • 常见的单点登录(SSO)实现

    • SAML, OpenID Connect
    • OAuth 2.0
  • 单点登录网关

    • 网关接收请求,如果已授权,则将主体信息放在 HTTP Header 信息中,如果未授权,则由网关进行授权
    • 孤立地在微服务中定位问题会更难(包括生产环境的搭建)
    • 虚假的安全感:如果网关服务故障了……
    • 小心网关变成耦合点
  • 细粒度的授权

    • 细粒度的 Authorization 应该留给具体服务

服务间的身份验证和授权

  • 在边界内允许一切

    • 风险:当有人入侵网络后,系统对中间人攻击没有任何防备
    • 许多组织使用,但其实有许多风险,更糟糕的是,很多时候人们没有意识到风险
  • HTTP(s) Basic Authentication

    • 在 HEADER 中指定用户名密码
    • HTTP 会被中间人截取明文
    • HTTPS 的证书管理复杂(尤其是多台机器)
    • 流量无法被反向代理缓存,但可以在反向代理时转成 HTTP 流量
    • 如何和现有 SSO 集成?
  • SAML 或 OpenID Connect

    • 同一个网关来路由内部流量
    • 微服务的客户端有一组凭证,用于验证自身,服务获取所需信息,用于细粒度验证
    • 需要为每个微服务创建自己的凭证,可撤销
    • 缺点:需要客户端安全地存储凭证
  • 客户端证书

    • 每个客户端存储一个 X.509 证书,与服务端建立通信
    • 缺点:管理证书的工作很繁重
  • HTTP 上的 HMAC

    • 防止密码泄露
    • 即传输的是哈希后的内容,服务端独立算哈希
    • 好处 1 :如果哈希不匹配,则得知中间人篡改过
    • 好处 2: 密钥不泄露
    • 好处 2: 开销低于 HTTPS
    • 缺点 1: 需要共享密钥,不好撤销
    • 缺点 2: 它是一个模式,不是标准,实现不一
    • 缺点 3: 它只保证密码不泄露,其它信息依旧可能被嗅探
  • API 密钥 (token)

    • 具体如何使用取决于使用的具体技术
    • 常见方法:使用公钥私钥对,并集中管理
    • 该方法关注了程序的易用性
  • 代理问题

    • 服务提供方是允许代理方访问服务?

      • 混淆代理人问题:欺诈代理人去访问本不该是自己能访问的服务。服务提供方是否
    • 一种方法是让代理/路由做验证

    • 另一种方法:请求时传递主体凭证

    • 没有简单的答案

静态数据的安全

  • 防止网络被攻破,静态数据被窃取

  • 使用众所周知的加密方法

    • AES
    • 密码哈希加盐
  • 一切皆与密钥相关

    • 密码不应和密文存储在一起
    • 方案一:单独的安全设备来加密和解密
    • 方案二:单独的密钥库
  • 选择你的目标

    • 考虑哪些数据可以被放入日志,帮助识别哪些数据需要加密
  • 按需解密

  • 加密备份

    • 备份加密数据
    • 确保备份也被加密

深度防御

  • 防火墙

  • 日志

    • 可以检测异常、之后恢复
    • 要小心在日志中存储敏感信息
  • 入侵检测(和预防)系统

  • 网络隔离

  • 操作系统

保持节俭

  • 有些信息是不需要存储的

黄金法则

  • 不要自己实现加密算法

内建安全

  • 相应的安全意识、流程
  • 自动化工具探测系统漏洞

外部验证

  • 开发人员离问题太近导致可能发现不了问题

Ch10: 康威定律和系统设计

康威定律

  • 任何组织在设计一套系统(广义)时,所交付的设计方案在结构上都与该组织的沟通结构保持一致
  • “如果你有四个小组开发一个编译器,那你会得到一个四步编译器”

证据

  • 松耦合组织和紧耦合组织

    • 紧耦合组织:商业产品公司,所有员工一起工作
    • 松耦合组织:开源社区
    • 组织的耦合度越低,模块化越好,耦合度越低
  • Windows Vista

    • 组织结构相关的指标与软件质量的相关度最高
  • Netflix 和 Amazon

    • “两个比萨团队”,没有团队应该大到两个比萨不够吃

适应沟通途径

  • 当协调成本增加后,人们要么设法降低成本,要么停止更改数据

服务所有权

  • 所有权程度的增加会增加自治和交付速度

共享服务的原因

  • 难以分割

    • 拆分成本太高
  • 特性团队

    • 小团队负责一系列我需要的所有功能,即使功能跨组件(甚至服务)
    • 大范围采用特性团队后,所有服务都是共享的
    • 微服务根据业务领域划分而非技术,更容易进行以特性为导向的开发
  • 交付瓶颈

    • 如果某个服务突然出现大量的变量需求?

内部开源

  • 守护者角色

    • 核心团队+不受信任的提交者
  • 成熟

    • 往往 1.0 后才接受外部提交
  • 工具

    • 版本控制、CI、CD

限界上下文和团队结构

  • 可能的话,团队结构和限界上下文保持一致

孤儿服务

  • 长时间不需要更改的服务

反向的康威定律

  • 系统设计能改变组织结构吗?

  • “不管一开始看起来是什么样,它永远是人的问题”

Ch11: 规模化微服务

故障无处不在

  • 大规模后的故障是必然发生的
  • 即使买最好的工具、最昂贵的硬件也必然发生
  • 少花些精力在阻止故障发生,而想办法从故障中恢复

多少是太多

  • 系统的非功能性需求取决于用户

    • 如内部系统的自动扩容可能无意义
  • 理解以下需求

    在现场交付时,需要先量化这些指标

    • 响应时间/延迟

      • 如:200 并发下,90%的响应在 2s 内
    • 可用性

      • 能接受服务出现故障吗?
    • 数据持久性

      • 多大比较的数据丢失是可以接受的
      • 数据应该保存多久

功能降级

  • 依赖多个服务时,不能因为一个系统宕机导致整个调用链都失败(否则可用性和单块系统没区别)

  • 具体的决策依赖业务

    • 有些服务是致命的,有些则是可以绕过的

架构性安全措施

  • 目标:确保如果真的出错,不会引起严重的级联影响
  • 处理慢下游比处理宕机下游要难得多

反脆弱的组织

  • Netflix 通过引发故障来确保系统的容错

  • Chaos Monkey

    • 随机停掉服务器或机器(生产环境)
  • 拥抱故障

  • 如何应对?

    • 超时

      • 给所有跨进程的调用都加上超时
    • 断路器

      • 被动 & 手动
      • 被动断路器依赖于业务定义
    • 舱壁

      • 必要情况下可以“自闭”
      • 例如为每个下游服务的连接使用不同连接池
      • 例如将功能分离成独立的微服务
      • 可以把断路器作为密封舱壁的自动机制
    • 隔离

      • 在实现上游时,允许下游服务离线

幂等

  • 多次调用的效果与一次调用相同

  • 实际要完全实现幂等挺困难

    • 如需要保存已经处理的事件
    • 多个 worker 可能在窗口内会处理多个事件

扩展(scale?)

  • 防止单点故障,负载加强性能

  • 更强大的主机(垂直扩展)

  • 拆分负载

    • 之前在单机上的微服务拆分到多机上
  • 分散风险

    • “两地三机房”
  • 负载均衡

    • 避免单点故障
    • SSL 终止
  • 基于 worker 的系统

    • 即任务提交到队列,由 worker 运行
    • woker 本身不需要很高的可靠性,但任务队列需求
  • 重新设计

    • 最初的架构可能无法应对很大负载

扩展数据库

  • (单点)瓶颈到了数据库上

  • 区分“可用性”和“数据的持久性”

    • 例如对数据库备份,数据不丢,但服务不可用
  • 扩展读取

    • 只读复本(如 MySQL 主从)
    • 最终一致性
  • 扩展写操作

    • 分片

      • 分片如何处理跨分片查询?
      • 如何添加新节点?
      • 写入分片可能会扩展写容量,但不会提高弹性
      • 一致性哈希
  • 共享数据库基础设施

  • CQRS

    • 读写分离
    • Event Sourcing

缓存

  • HTTP 协议本身允许缓存(GET)

  • 客户端、代理、服务器缓存

  • HTTP 缓存(cache)

    • Cache-control + Expires

      • 一般适用于静态资源
    • ETag

  • 写缓存(buffer)

    • writebehind 缓存

      • 类比操作系统写入磁盘前缓存在内存
  • 为弹性使用缓存(buffer)

    • 下游失效时先缓存请求
  • 隐藏源服务

    • 缓存雪崩,缓存服务宕机,导致大量请求打到源服务
    • 一种方法:第一时间不要对源服务发起请求
    • 源服务定期填写缓存,客户端在 miss 时发布通知
  • 保持简单

    • 不需要不要用
  • 缓存可能中毒

自动伸缩

  • 响应型伸缩
  • 预测型伸缩
  • 事实上比起响应负载,更多应用于响应故障

CAP

  • CP 还是 AP?P 是一定需要的
  • 对于具体的服务,要求可能不同

服务发现

  • DNS

    • 标准
    • 更新很痛苦(TTL)
    • DNS 指向负载均衡
  • 动态服务注册

    • ZK
    • Consul
    • Eureka
  • 自己构建

  • 别忘了人的需求(查看、监控)

文档服务

  • API 文档
  • Swagger
  • HAL 和 HAL 浏览器
  • 自描述系统(UDDI)

Ch12: 总结

微服务原则

  • 围绕业务概念建模

  • 接受自动化文化

  • 隐藏内部实现细节

  • 一切去中心化

  • 可独立部署(不影响当前服务)

  • 隔离失败

    • 不要像使用本地调用那样处理远程调用
    • 反脆弱信条
  • 高度可观察

    • 日志、监控

什么时候不使用

  • 不了解领域,无法很好做拆分
  • 基础设施不完善前