【微服务设计】小而自治

微服务就是一些协同工作的小而自治的服务。


更新历史

  • 2022.12.17:完成初稿

读后感

微服务的理念和非常多的管理理念一脉相承,甚至让我联想起名创优品和阿米巴组织,这也说明很多东西都是相通的,只是在技术世界里,更容易推行罢了。微服务的精髓在于自治,而不在于具体的形式。

读书笔记

微服务

随着领域驱动设计、持续交付、按需虚拟化、基础设施自动化、小型自治团大型集群系统这些实践的流行,微服务也应运而生。塔并不是被发明出来的,而是从现实世界中总结出来的一种趋势或模式。很多组织发现细粒度的微服务架构可以帮助他们更快地交付软件,并且有更多机会尝试新技术。微服务在技术决策上给了我们极大的自由度,是我们能够更快地响应不可避免的变化。

什么是微服务

微服务就是一些协同工作的小而自治的服务

  • 很小,专注于做好一件事
    • 根据业务的边界来确定服务的边界
  • 自治性
    • 一个微服务就是一个独立的实体,可以独立部署在 PaaS 上
    • 服务之间通过网络调用进行通信,从而加强了服务之间的隔离性,避免紧耦合
    • 对于一个服务来说,考虑什么应该暴露,什么应该隐藏
    • 黄金法则:是否能够修改一个服务并对其进行部署,而不影响其他任何服务

主要好处

  • 技术异构性
    • 可以在不同的服务中使用最适合该服务的技术
    • 更快地采用新技术,并且理解这些新技术的好处,可以用风险最低的方式来尝试新技术,与时俱进
  • 弹性
    • 弹性工程学的一个关键概念是舱壁,服务边界就是一个很显然的舱壁
  • 扩展
    • 可以针对那些需要扩展的微服务进行扩展
  • 简化部署
    • 对特定部分的代码进行部署,也可以快速回滚,清除了单体软件发布过程中的种种障碍(时间长,复杂度高)
  • 与组织结构相匹配
    • 避免出现过大的代码库,从而获得理想的团队大小及生产力
  • 可组合型
  • 对可替代性的优化
    • 可以在需要时轻易地重写服务,或者删除不再使用的服务。代码量少,很多事情都好解决

面向服务的架构

SOA 是一种设计方法,其中包含多个服务,二服务之间通过配合最终会提供一系列功能。SOA 本身是一个很好的想法,但尽管做了很多尝试,人们还是无法在如何做好 SOA 这件事情上达成共识。现有的 SOA 知识并不能帮助你把很大的应用程序划小,我们可以认为为服务架构是 SOA 的一种特定方法。

没有银弹

微服务不是免费的午餐,为了得到微服务的好处,还需要在部署、测试和监控方面做很多的工作。还需要考虑如何扩展系统,并且保证它们的弹性,甚至还要处理分布式事务或者与 CAP 相关的问题!

演化式架构师

微服务给我们提供了很多选择,因此也需要我们做很多决定。比如应该使用多少种不同的技术,不同的团队是否应使用不同的编程规范,是应该合并多个服务还是把一个服务拆成多个?我们应该如何做决定呢?这些架构支持在频繁变换的环境下以更快地节奏进行变化,因此架构师这个角色也需要做相应的改变。

不准确的比较

架构师的一个重要职责是,确保团队有共同的技术愿景,以帮助我们向客户交付他们想要的系统。当我们把自己和工程师或者建筑师做比较时,很有可能会做出错误的决定。我们自称架构师实际上是为了更好地让社会理解我们,但实际上不是的,软件架构师的工作在精确性和记录性上和建筑师不可比。

建筑师的工作是做好详细的计划,然后让别人去理解和执行。进行这项工作需要对艺术性和工程性进行权衡,并且对整体进行监督,而其他所有视角都要屈从于建筑师的视角(其实反而更类似于部分 PMO 的角色)

架构师的演化视角

与建造建筑物相比,在软件中我们会面临大量的需求变更,使用的工具和技术也具有多样性。我们创造的东西并不是在某个时间点之后就不再变化了,甚至在发布到生产环境之后,软件还能继续演化。

和 IT 架构师相类比的一个更好的角色是城市规划师,而不是建筑师(类似 SimCity 游戏)。城市规划师的职责是优化城镇布局,使其更易于现有居民生活(对于平台来说就是用平台的人),同时也会考虑一些未来的因素(即未来技术方向)。城市规划师应该尽量去预期可能发生的变化,但是也需要明白一个事实:尝试直接对各个方面进行控制往往不会奏效。

架构师的职责之一就是保证该系统适合开发人员在其上工作。

分区

我们将架构师比作城市规划师,那么对应城市的区域,就是软件中的服务边界,或者是一组粗粒度的服务群组。作为架构师,不应该过多关注每个区域内发生的事情,而应该多关注区域之间的事情。这意味着我们应该考虑不同的服务之间如何交互,或者说保证我们能够对整个系统的健康状态进行监控。

一个原则性的方法

  • 战略目标
    • 架构师不需要定义战略目标,但需要花费更多时间和组织内非技术的部分进行交互,了解业务部门的愿景和可能如何发生改变
  • 原则
    • 为了和战略目标保持一致,我们会制定一些具体的规则,并称之为原则。原则最好不超过 10 个,能够写在一张海报上(如果还没有,则需要推动弄出来)
    • 如 Heroku 的 12 Factors
  • 实践
    • 实践应该巩固原则
  • 将原则和实践相结合
    • 有一些重要的原则来指导系统的演化
    • 有一些细节来指导如何实现这些原则

要求的标准

当我们思考需要做的取舍时,需要注意一个很重要的因素:系统允许多少可变性。我们需要识别出各个服务需要遵守的通用规则,一种方法是,给出一个好服务的例子来阐释好服务的特点。

在系统中什么是好服务呢?它需要有什么样的能力才能保证整个系统是可控的,并且一个有问题的服务不会导致整个系统瘫痪?

在优化单个服务自治性的同时,也要兼顾全局,清除地定义出一个好服务应有的属性。

  • 监控
    • 确保所有的服务使用同样的方式报告健康状态以及与监控相关的数据
    • 不要为了服务的具体实现而改变监控系统
    • 日志功能和监控类似,都需要集中式管理
  • 接口
    • 即使是 RESTful,也需要明确的指引,比如是名词还是动词?如何处理分页?如何处理不同版本 API?
  • 架构安全性
    • 必须保证每个服务都可以应对下游服务的错误请求
    • 返回码也应该遵循一定的规则,以下的请求要不同的处理:
      • 正常且被正确处理的请求
      • 错误请求,并且服务识别出了它是错误的,但什么也没做
      • 被访问的服务宕机了,所以无法判断请求是否正常

代码治理

应该使用简单的方式把事情做对,两种比较奏效的方式是:提供范例和代码模板。

创建服务代码模板不是某个中心化工具的职责,也不是指导(即使是通过代码)我们应怎样工作的架构团队的职责。应该通过合作的方式定义出这些实践,不断更新这个模板(内部开源的方式能够很好地完成这项工作)。

一个团队的士气和生产力是如何被强制使用的框架给毁掉的:基于代码重用的目的,越来越多的功能被加到一个中心化的框架中,直至把这个框架变成一个不堪重负的怪兽。团队应该可以选择是否使用服务代码模板,但如果是强制的,一定要确保它能够简化开发人员的工作,而不是复杂化。

对 DRY 的追求也很容易导致系统过度耦合,但这个需要艺术性判断和处理。

集中治理和领导

架构师的部分职责是治理。

治理通过评估干系人的需求、当前情况及下一步的可能性来确保企业目标的达成,通过排优先级和做决策来设定方向。对于已经达成一致的方向和目标进行监督。 —— COBIT 5

架构师的职责:

  1. 愿景:确保在系统级有一个经过充分沟通的技术愿景,有一组可以指导开发的原则,可以帮助你满足客户和组织的要求
  2. 同理心:理解你所做的决定对客户和同事带来的影响,确保以这些原则为指导衍生出来的实践不会给开发人员带来痛苦
  3. 适应性:确保在你的客户和组织需要的时候,调整技术愿景。需要了解新技术,需要指导在什么时候做怎样的取舍
  4. 合作:和尽量多的同事进行沟通,从而更好地对愿景进行定义、修订级执行。让同事理解这些决定和取舍,并执行下去
  5. 自治性:在标准化和团队自治之间寻找一个正确的平衡点
  6. 治理:确保系统按照技术愿景的要求实现。花时间和团队一起工作/编码,从而了解所做决定对团队造成了怎样的影响

职责很多,可以由架构师领导这个小组,每个交付团队都有人参加。架构师负责确保该组织的正常运作,整个小组都要对治理负责。

类比一下教小孩儿行走的过程。你没法替代他们行走。你会看着他们摇摇晃晃前行,但是,如果每次你看到他们要跌倒就上去扶一把,他们永远都学不会。而且无论如何,他们真正跌倒的次数会比你想象的要少!但是如果他们要走到马路中间或者水边,你就必须站出来了。

建设团队

对于技术领导人来说,更重要的事情是帮助你的队友成长,帮助他们理解这个愿景,并保证他们可以积极地参与到愿景的实现和调整中来。

帮助别人成长的形式有很多种,微服务架构本身能够提供一种很好的形式,给人提供了对单个服务负责的机会,而当这些人在单个服务上面得到足够锻炼之后,就可以给他们更多的责任,从而帮助他们逐步达成自己的职业目标,同事通过分担职责也可以防止某一个人负担过重。

伟大的软件来自于伟大的人,如果只担心技术问题,那么恐怕你看到的问题远远不及一半。

如何建模服务

什么样的服务是好服务

  • 松耦合
    • 能够独立修改级部署单个服务,而不需要修改系统的其他部分
  • 高内聚
    • 把相关的行为聚集在一起,把不相关的行为放在别处

限界上下文(bounded context,来自 DDD)

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

细胞之所以会存在,是因为细胞膜定义了什么在细胞内,什么在细胞外,并且确定了什么物质可以通过细胞膜

业务功能

当你在思考组织内的限界上下文时,不应该从共享数据的角度来考虑,而应该从这些上下文能够提供的功能来考虑。首先要问自己“这个上下文是做什么用的”,然后再考虑“它需要什么样的数据”。

集成

集成是微服务相关技术中最重要的一个。做得好的话,微服务可以保持自治性,也可以独立地修改和发布他们;但做得不好会带来灾难

寻找理想的集成技术

微服务之间的通信方式的选择非常多样化,选择之前先要弄明白,我们到底希望从这些技术中得到什么。

  • 避免破坏性修改
    • 一个微服务在一个响应中添加了一个字段,那么已有的消费方不应该受到影响
  • 保证 API 的技术无关性
    • 可以随时切换到更新的技术上
  • 使你的服务易于消费方使用
  • 隐藏内部实现细节

远程过程调用

有些问题通常一开始不明显,但慢慢就会暴露出来,并且其代价远大于一开始快速启动的好处

  • 技术的耦合,如 Java RMI 不好,而 thrift 和 proto buffer 则支持的很好
  • 本地调用和远程调用并不相同
    • 网络不可靠,RPC 需要对 payload 封装和解封装
  • 脆弱性,依赖代码生成,难以同步修改服务端和客户端代码,实际上耦合起来

使用 RPC 需要注意的问题:

  • 不要对远程调用过度抽象,以至于网络因素完全被隐藏起来
  • 确保可以独立升级服务端的接口而不用强迫客户端升级
  • 在客户端中一定不要隐藏我们是在做网络调用这个事实

REST

  • 留心过多的约定,可能是为了短期利益而牺牲长期利益
  • 需要留意性能问题,尤其在低延迟通信场景

响应式扩展

提供了一种机制,在此之上,可以把多个调用的结果组长起来并在此基础上执行操作,可以让代码变得简单很多!

版本管理

  • 尽可能推迟破坏性修改
  • 及早发现破坏性修改
  • 使用语义化的版本管理:Major.Minor.Patch
  • 不同接口共存
  • 同时使用多个版本的服务

用户界面

将用户界面视为一个组合层

  • API 组合:通过 gateway 进行,但也有一定的局限性
  • UI 片段组合:每个服务提供 UI 组件,然后直接在页面上组合起来(这个可以基于微件操作一波)

分解单块系统

  • 第一步:识别出代码中的边界(接缝)
  • 第二步:考虑哪部分抽取出去得到的收益最大
    • 改变的速度
    • 团队结构
    • 安全
    • 技术

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

部署

持续集成 CI

知否真正理解 CI 的三个问题:

  1. 你是否每天签入代码到主线
  2. 你是否有一组测试来验证修改
  3. 当构建失败后,团队是否把修复 CI 当作第一优先级的事情来做

每个微服务应该有一个源代码库和 CI 构建。

构建流水线和持续交付 CD

编译及快速测试 -> 耗时测试 -> 用户验收测试 -> 性能测试 -> 生产环境

简化管理唯一的钥匙就是自动化,不支持自动化的技术不要选择!

测试

测试的分类

端到端测试,任何一个服务的构建都会进行一次端到端测试

但端到端测试很脆弱,需要避免陷入异常正常化(the normalization of deviance)。谁来写端到端测试也是一个问题,并且运行时间可能很长。

端到端测试应该放到少量核心场景来,比如用户最高频使用的主流程(应该是非常低的 2 位数),或者直接进行 CDC(Consumer-Driven Contract) 测试。

MTTR 要比 MTBF 更重要,不出现问题很难,但修得快事可以不断优化的。

监控

监控小的服务,然后聚合起来看整体!

  • 实现语义监控
  • 通过关联标识来确定调用链

对于每个服务而言:

  • 最低限度要跟踪请响应时间。做好之后,可以开始跟踪错误率及应用程序级的指标
  • 最低限度要跟踪所有下游服务的健康状态,包括下游调用的响应时间,最好能够跟踪错误率
  • 标准化如何收集指标以及存储指标
  • 以标准的格式将日志记录到一个标准的位置
  • 监控底层操作系统,就可以跟踪流氓进程和进行容量规划

对系统而言:

  • 聚合 CPU 之类的主机层级的指标及应用程序级指标
  • 确保你选用的指标存储工具可以在系统和服务级别做聚合,同时也允许你查看单台主机的情况
  • 确保指标存储工具允许你维护数据足够长的时间,以了解你的系统的趋势。
  • 使用单个可查询工具来对日志进行聚合和存储
  • 强烈考虑标准化关联标识的使用
  • 了解什么样的情况需要行动,并根据这些信息构造响应的警报和仪表盘

安全

  • 身份认证和授权:单点登录
  • 服务间的身份验证和授权
    • 在边界内允许一切
    • https 基本身份验证
    • 使用 OpenID Connect
    • 客户端证书 TLS
    • HMAC
    • API 密钥

康威定律和系统设计

康威定律:任何组织在设计一套系统(广义概念上的系统)时,所交付的设计方案在结构上都与该组织的沟通结构保持一致。

但是反向康威定律目前没有证据证明可行

规模化微服务

  • 故障无数不在
    • 假设一切都会失败,快速恢复能力更重要。因为即使最好的工具和最昂贵的硬件也会发生失败
  • 多少是太多:不同的需求决定了要做到什么程度
    • 响应时间/延迟
    • 可用性
    • 数据持久性
  • 功能降级
    • 通过思考每项跨功能需求的重要性,我们对自己能做什么有了更好的定位。现在,让我们考虑从技术方面可以做的事情,以确保当故障发生时可以优雅地处理
  • 反脆弱的组织
    • 超时
    • 断路器
    • 舱壁
    • 隔离
  • 幂等
  • 扩展
    • 更强大的主机
    • 拆分负载
    • 分散风险
    • 负载均衡
    • 基于 worker 的系统
  • 扩展数据库
  • 缓存
  • 自动伸缩
  • CAP 定理
  • 服务发现
  • 动态服务注册
  • 文档服务
  • 自描述系统

总结

微服务的原则

  • 围绕业务概念建模
    • 限界上下文来定义领域边界
    • 比围绕技术概念定义接口更加稳定
  • 接受自动化文化
    • 自动化测试必不可少
    • 使用环境定义来明确不同环境的差异,但同时保持统一的方式
  • 隐藏内部实现细节
    • 隐藏数据库
    • 选择与技术无关的 API
  • 让一切都去中心化
    • 确保团队保持对服务的所有权
    • 使用内部开源模式
    • 尝试共同治理模型
    • 避免企业服务总线,会导致业务逻辑的中心化和哑服务
    • 系统来代替编排或哑中间件
    • 使用智能端点(smart endpoint)确保相关的逻辑和数据
  • 可独立部署
    • 单服务单主机
    • 蓝绿部署或金丝雀部署
    • 消费者驱动的七月测试
    • 消费者应该自己决定何时更新
  • 隔离失败
    • 反脆弱的信条
    • 舱壁和断路器
    • 特定情况牺牲可用性或一致性
  • 高度可观察
    • 语义监控
    • 聚合日志和数据
    • 关联标识