简评:这本书是很“宽”的一本书,描述了许多构建微服务中将面临的挑战,不过大多数知识点还是点到为止。个人觉得适合有一定基础的读者,能扩展知识的宽度,之后再针对其中的知识点深造。
本文是读书笔记,由 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: 总结
微服务原则
-
围绕业务概念建模
-
接受自动化文化
-
隐藏内部实现细节
-
一切去中心化
-
可独立部署(不影响当前服务)
-
隔离失败
- 不要像使用本地调用那样处理远程调用
- 反脆弱信条
-
高度可观察
- 日志、监控
什么时候不使用
- 不了解领域,无法很好做拆分
- 基础设施不完善前