《微服务设计》 – [英]Sam Newman 著 / 崔力强 张骏 译

干货满满

读完时间:2021 年 02 月 14 日

出版时间:2016 年 4 月

前言

实现细节变化的速度总是比它们背后的理念要快得多。

相对于不断变化的新框架、新技术,更应该关注其背后的设计模式、数据结构及算法。

第1章 微服务

持续交付理论告诉我们如何更有效及更高效地发布软件产品,并指出保持每次提交均可发布的重要性。

1.1 什么是微服务

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

随着新功能的增加,代码库会越变越大。时间久了代码库会非常庞大,以至于想要知道该在什么地方做修改都很困难。尽管我们想在巨大的代码库中做到清晰地模块化,但事实上这些模块之间的界限很难维护。相似的功能代码开始在代码库中随处可见,使得修复 bug 或实现更加困难。

在一个单块系统内,通常会创建一些抽象层或者模块来保证代码的内聚性,从而避免上述问题。内聚性是指将相关代码放在一起,在考虑使用微服务的时候,内聚性这一概念很重要。

微服务将这个理念应用在独立的服务上。根据业务的边界来确定服务的边界,这样就很容易确定某个功能代码应该放在哪里。而且由于该服务专注于某个边界之内,因此可以很好地避免由于代码库过大衍生出的很多相关问题。

服务越小,微服务架构的优点和缺点也就越明显。使用的服务越小,独立性带来的好处就越多。但是管理大量服务也会越复杂,

服务之间均通过网络调用进行通信,从而加强了服务之间的隔离性,避免紧耦合。这些服务应该可以彼此间独立进行修改,并且某一个服务的部署不应该引起该服务消费方的变动。

1.2 主要好处

单纯考虑桌面网站或者移动应用程序的时代已经过去了。现在我们需要考虑的应用程序种类包括 Web、原生应用、移动端 Web、平板应用及可穿戴设备等,针对每一种都应该考虑如何对已有功能进行组合来实现这些应用。

1.4 其他分解技术

如果使用共享代码来做服务之间的通信的话,那么它会成为一个耦合点。

第2章 演化式架构师

2.1 不准确的比较

架构师的一个重要职责是,确保团队有共同的技术愿景,以帮助我们向客户交付他们想要的系统。

我们把自己称作软件“工程师”或者“建筑师”,但其实我们不是,对吧?建筑师和工程师所具备的精确性和纪律性是遥不可及的,而且他们在社会中的重要性也很容易理解。

2.2 架构师的演化视角

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

更好的类比是城市规划师,而不是建筑师。如果你玩过 SimCity,那么你应该很熟悉城市规划师这个角色。城市规划师的职责是优化城镇布局,使其更易于现有居民生活,同时也会考虑一些未来的因素。

2.4 一个原则性的方法

一般来讲,原则最好不要超过 10 个,或者能够写在一张海报上,不然大家会很难记住。而且原则越多,它们发生重叠和冲突的可能性就越大。

2.5 要求的标准

在某种上下文中是一个好公民不代表在其他上下文中也是,

返回码也应该遵守一定的规则。如果你的断路器依赖于 HTTP 返回码,并且一个服务决定使用 2XX 作为错误码,或者把 4XX 和 5XX 混用,那么这种安全措施就没什么意义了。即使你使用的不是 HTTP,也应该注意类似的问题。对以下几种请求做不同的处理可以帮助系统及时失败,并且也很容易追溯问题:(1)正常并且被正确处理的请求;(2)错误请求,并且服务识别出了它是错误的,但什么也没做;(3)被访问的服务宕机了,所以无法判断请求是否正常。

2.6 代码治理

聚在一起,就如何做事情达成共识是一个好主意。但是,花时间保证人们按照这个共识来做事情就没那么有趣了,因为在各个服务中使用这些标准做法会成为开发人员的负担。我坚信应该使用简单的方式把事情做对。我见过的比较奏效的两种方式是,提供范例和服务代码模板。

编写文档是有用的。但是开发人员更喜欢可以查看和运行的代码。

理想情况下,你提供的优秀范例应该来自真实项目,而不是专门实现的一个完美的例子。因为如果你的范例来自真正运行的代码,那么就可以保证其中所体现的那些原则都是合理的。

针对自己的开发实践裁剪出一个服务代码模板,不但可以提高开发速度,还可以保证服务的质量。

2.7 技术债务

技术愿景有其本身的道理,所以偏离了这个愿景短期可能会带来利益,但是长期来看是要付出代价的。

维护一个债务列表,并且定期回顾。

2.9 集中治理和领导

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

即使你很清楚什么是对的,然后尝试去控制团队,也可能会破坏和团队的关系,并且会使团队感觉他们没有话语权。

2.10 建设团队

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

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

2.11 小结

一个演进式架构师应该承担的职责。

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

第3章 如何建模服务

3.2 什么样的服务是好服务

如果做到了服务之间的松耦合,那么修改一个服务就不需要修改另一个服务。使用微服务最重要的一点是,能够独立修改及部署单个服务而不需要修改系统的其他部分,这真的非常重要。

第4章 集成

4.3 共享数据库

隐藏实现细节非常重要,因为它让我们的服务拥有一定的自治性,从而可以轻易地修改其内部实现。

4.4 同步与异步

对于使用基于事件的协作方式来说,情况会颠倒过来。客户端不是发起请求,而是发布一个事件,然后期待其他的协作者接收到该消息,并且知道该怎么做。我们从来不会告知任何人去做任何事情。基于事件的系统天生就是异步的。整个系统都很聪明,也就是说,业务逻辑并非集中存在于某个核心大脑,而是平均地分布在不同的协作者中。基于事件的协作方式耦合性很低。客户端发布一个事件,但并不需要知道谁或者什么会对此做出响应,这也意味着,你可以在不影响客户端的情况下对该事件添加新的订阅者。

4.5 编排与协同

当考虑具体实现时,有两种架构风格可以采用。使用编排(orchestration)的话,我们会依赖于某个中心大脑来指导并驱动整个流程,就像管弦乐队中的指挥一样。使用协同(choreography)的话,我们仅仅会告知系统中各个部分各自的职责,而把具体怎么做的细节留给它们自己,就像芭蕾舞中每个舞者都有自己的方式,同时也会响应周围其他人。

编排方式的缺点是,客户服务作为中心控制点承担了太多职责,它会成为网状结构的中心枢纽及很多逻辑的起点。

4.7 REST

REST 架构风格声明了一组对所有资源的标准方法,而 HTTP 恰好也定义了一组方法可供使用。GET 使用幂等的方式获取资源,POST 创建一个新资源。这就意味着,我们可以避免很多不同版本的 createCustomer 及 editCustomer 方法。

比如 WebSockets,虽然名字中有 Web,但其实它基本上跟 Web 没什么关系。在初始的 HTTP 握手之后,客户端和服务端之间就仅仅通过 TCP 连接了。

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

尽量让中间件保持简单,而把业务逻辑放在自己的服务中。

4.16 小结

最大程度地保证微服务之间的低耦合:

  • 无论如何避免数据库集成
  • 理解 REST 和 RPC 之间的取舍,但总是使用 REST 作为请求/响应模式的起点
  • 相比编排,优先选择协同
  • 避免破坏性修改、理解 Postel 法则、使用容错性读取器
  • 将用户界面视为一个组合层

第5章 分解单块系统

5.8 例子:共享静态数据

大部分场景下,都可以通过把这些数据放入配置文件或者代码中来解决问题,而且它对于大部分场景来说都很容易实现。

5.12 事务边界

分布式事务很容易出错,而且不利于扩展。这种通过重试和补偿达成最终一致性的方式,会使得定位问题更加困难,而且有可能需要其他的补偿措施来修复潜在数据的不一致。

5.22 小结

我们通过寻找服务边界把系统分解开来,且这可以是一个增量的过程。在最开始就要养成及时寻找接缝的好习惯,从而减少分割服务的代价,这样才能够在未来遇到新需求时继续演化我们的系统。如你所见,有些工作是很艰难的。但由于可以增量进行,所以也没什么好怕的。

第6章 部署

6.2 把持续集成映射到微服务

在同步发布(lock-step release)中,你需要一次性部署多个服务。如果你认为这不是个问题的话,那么上述模式就可以工作得很好。一般来讲,我们绝对应该避免这个模式,但在项目初期是个例外。当仅有一个团队在所有的服务上工作时,这种模式在短时间内是可接受的。

如果这一行的修改导致构建失败,那么在构建得到修复之前,与其他服务相关的代码也无法提交。想象一下,如果有很多团队在共享一个巨大的构建,那么谁会对此负责?

6.11 从物理机到虚拟机

把机器划分成大量的 VM 并不是免费的。把物理机想象成一个装袜子的抽屉,如果你在抽屉里放置了很多木隔板,那么可存放袜子的总量是多还是少了?答案很明显是少了,因为隔板本身也占空间!管理抽屉是比较简单的,不仅仅是放袜子,你也可以把T恤放在某个隔间里面,但是更多的隔板意味着更少的总空间。

第7章 测试

7.2 测试范围

单元测试对于代码重构非常重要,因为我们知道,如果不小心犯了错误,这些小范围的测试能很快做出提醒,这样我们就可以放心地随时调整代码。

7.6 脆弱的测试

当发现脆弱的测试时,我们应该竭尽全力去解决这个问题。否则,人们就会开始对测试套件失去信心,因为它们“总是这样失败”。一个包含脆弱测试的测试套件往往会成为 Diane Vaughn 所说的异常正常化(the normalization of deviance)的受害者,也就是说,随着时间的推移,我们对事情出错变得习以为常,并开始接受它们是正常的。因为人类有这种倾向,所以在开始接受失败测试是正常的之前,应该尽快找到这些脆弱的测试并消除它们。

7.9 还应该使用端到端测试吗

大家更愿意在部署到生产环境之前,尽可能努力地消除所有缺陷,即使这意味着发布软件需要更长的时间。你要知道,再怎么测试也不可能消除所有的缺陷,所以生产环境中有效的监控和修复还是有必要的。理解了这一点,你就能够理解从生产环境中学习是一个明智的决定。

7.10 部署后再测试

如果你正试图了解是否有人会真正使用你的软件,那需要尽快发布软件,这比构建健壮的软件更有意义,因为可以验证之前的想法或业务模型是否工作。在上面例子的情况下,测试可能都是多余的,因为不知道你的想法是否工作,其影响要远远大于生产环境上的一个缺陷。在这种情况下,在生产之前完全不测试是非常明智的。

7.11 跨功能的测试

将系统拆分为较小的微服务后,跨网络边界调用的次数明显增加了。之前操作可能只涉及一次的数据库调用,现在可能涉及三四次跨网络边界来调用其他服务,还有匹配数量的数据库调用。所有这些调用都可能减缓系统操作的速度。因此,追踪延迟的根源显得尤为重要。当有多个同步的调用链时,链的任何部分变得缓慢,整个链都会受影响,最终会对整体速度有明显的影响。这使得用一些方法对微服务系统进行性能测试,比对单块系统更重要。

第8章 监控

将系统拆分成更小的、细粒度的微服务会带来很多好处。然而,它也增加了生产系统的监控复杂性。

我们现在有多个服务需要监控,有多个日志需要筛选,多个地方有可能因为网络延迟而出现问题。该如何应对呢?我们得好好梳理一下,否则很可能导致混乱,成为一团乱麻,而这是周五下午(或在任何时间!)我们最不想面对的情况。这里的答案很简单:监控小的服务,然后聚合起来看整体。

8.5 多个服务的指标跟踪

了解趋势另一个重要的好处是帮助我们做容量规划。我们的系统到达极限了吗?多久之后需要更多的主机?在过去,当我们还在使用物理主机时,通常一年才会考虑一次这个问题。在供应商提供按需计算的IaaS(Infrastructure as a Service,基础设施即服务)的新时代,我们可以在几分钟内(如果不是秒级的话)实现扩容和缩容。这意味着,如果了解我们的使用模式,就可以确保恰好有足够的基础设施来满足我们的需求。在跟踪趋势和理解应该如何使用这些数据方面,使用的方式越智能,我们的系统就越省钱,而且响应性也就越好。

8.6 服务指标

我们永远无法知道什么数据是有用的!很多次,直到机会已经错过很久后,我才发现如果当时记录了数据,事情就容易理解得多。所以我倾向于暴露一切数据,然后依靠指标系统对它们进行处理。

第9章 安全

9.2 服务间的身份验证和授权

SSL 之上的流量不能被反向代理服务器(比如 Varnish 或 Squid)所缓存,这是使用 HTTPS 的另一个缺点。这意味着,如果你需要缓存信息,就不得不在服务端或客户端内部实现。你可以在负载均衡中把 Https 的请求转成 Http 的请求,然后在负载均衡之后就可以使用缓存了。

使用 TLS(Transport Layer Security,安全传输层协议), TLS 是 SSL 在客户端证书方面的继任者。在这里,每个客户端都安装了一个 X.509 证书,用于客户端和服务器端之间建立通信链路。服务器可以验证客户端证书的真实性,为客户端的有效性提供强有力的保证。

9.3 静态数据的安全

对于静态数据的加密,除非你有一个很好的理由选择别的,否则选择你的开发平台上的 AES-128 或 AES-256 的一个广为人知的实现即可。Java 和 .NET 运行时都包含 AES 的实现,它们很可能都是经过充分测试的(和打好补丁的),但是对于大多数平台,也存在单独的库,比如支持 Java 和 C# 的 Bouncy Castle 库(http://www.bouncycastle.org/)。关于密码,你应该考虑使用一种叫作加盐密码哈希(salted password hashing, https://crackstation.net/hashing-security.htm#properhashing)的技术。

9.6 保持节俭

然而,如果让我们的生活更轻松一点呢?为什么不尽快删改尽可能多的、可以作为个人身份的数据?当用户请求登录时,我们需要永远存储完整的 IP 地址吗?或者我们是否可以用x替换最后几位数字?我们需要存储用户的姓名、年龄、性别和出生日期,以便为他提供产品建议,还是其年龄范围和邮编这样的信息就已经足够了?这样做的好处是多方面的。首先,如果你不存储它,就没有人能偷走它。第二,如果你不存储它,就没有人(例如,政府机构)可以要它!

9.8 黄金法则

不要实现自己的加密算法。不要发明自己的安全协议。除非你是一个有多年经验的密码专家,如果你尝试发明自己的编码或精密的加密算法,你会出错。即使你是一个密码专家,仍然可能会出错。许多之前提到的工具,比如 AES,都在行业中身经百战,其底层算法一直被同行审查,软件实现多年来也一直被严格测试和打补丁。它们已经足够好了!重新发明轮子在很多情况下通常只是浪费时间,但在安全领域,它会带来直接的危害。

重新发明轮子在很多情况下通常只是浪费时间,但在安全领域,它会带来直接的危害。

很多人对造轮子有执念,如果没有颠覆性的改变,还是使用或维护已有的吧。

9.11 小结

如果你想要一个基于浏览器的应用程序安全的基本概述,优秀的非营利的 OWASP(Open Web Application Security Project,开放式Web应用程序安全项目,https://www.owasp.org/)是一个很好的起点,其定期更新的十大安全风险文档,应被视为所有开发人员的必备读物。

第10章 康威定律和系统设计

梅尔 ·康威于 1968 年 4 月在 Datamation 杂志上发表了一篇名为“How Do Committees Invent”的论文,文中指出:
任何组织在设计一套系统(广义概念上的系统)时,所交付的设计方案在结构上都与该组织的沟通结构保持一致。这句话被称为康威定律,经常以各种形式被引述。埃里克 · S · 雷蒙德在《新黑客字典》中总结这一现象时指出:“如果你有四个小组开发一个编译器,那你会得到一个四步编译器。”

10.1 证据

组织的耦合度越低,其创建的系统的模块化就越好,耦合也越低;组织的耦合度越高,其创建的系统的模块化也越差。

10.4 适应沟通途径

当协调变化的成本增加后,有一件事情会发生:人们要么想方设法降低协调/沟通成本,要么停止更改。而后者正是导致我们最终产生庞大的、难以维护的代码库的原因。

10.6 共享服务的原因

服务会根据业务领域,而不是技术进行建模。

使用越标准化的技术栈和编程范式,就越容易让其他人更改你的服务。当然,另一方面,如前文所述,标准化会导致团队降低采取正确的解决方案来解决问题的能力,并可能会降低效率。

10.12 人

如果没有把人们拉到一条船上,你想要的任何变化从一开始就注定会失败。

第11章 规模化微服务

11.1 故障无处不在

可以在试图阻止不可避免的故障上少花一点时间,而花更多时间去优雅地处理它。

11.5 反脆弱的组织

服务间加强隔离还有另一个好处。当服务间彼此隔离时,服务的拥有者之间需要更少的协调。团队间的协调越少,这些团队就更自治,这样他们可以更自由地管理和演化服务。

11.7 扩展

单服务单主机模型肯定要优于多服务单主机模型。

弹性扩展的一种方式是,确保不要把所有鸡蛋放在一个篮子里。一个简单的例子是,确保你不要把多个服务放到一台主机上,因为主机的宕机会影响多个服务。但让我们考虑一下主机指的是什么。在大多数情况下,现在的主机实际上是一个虚拟的概念。如果所有的服务都在不同的主机上,但这些主机实际上都是运行在一台物理机上的虚拟主机呢?如果物理机宕掉,同样也会失去多个服务。一些虚拟化平台能够确保你的主机分布在多个不同的物理机上,以减小发生上述情况的可能性。

另一种常见的减少故障的方法是,确保不要让所有的服务都运行在同一个数据中心的同一个机架上,而是分布在多个数据中心。

值得注意的是,供应商给你的 SLA 保证肯定会减轻他们的责任!如果供应商错失担保目标,给他们的客户也就是你带来大量金钱上的损失,你会发现即使翻遍整个合同,也很难找到可以从他们那里追回任何损失的条款。因此,我强烈建议你,了解供应商如果没有履行义务的影响,并看看你是否需要准备一个 B(或 C)计划。例如,我的很多客户都将一个灾难恢复托管平台放到一个不同的供应商那里,以确保他们不至于脆弱得因为一家公司出错而受影响。

负载均衡器各种各样,从大型昂贵的硬件设备,到像 mod proxy 这样基于软件的负载均衡器。它们都有一些共同的关键功能。它们都是基于一些算法,将调用分发到一个或多个实例中,当实例不再健康时移除它们,并当它们恢复健康后再添加进来。一些负载均衡器提供了其他有用的功能。常见的一个是 SSL 终止,通过 HTTPS 连接入站负载均衡器后,当到实例本身时转换成 HTTP 连接。从经验上看,管理 SSL 的开销非常大,拥有一个负载均衡器来处理这个过程是相当有用的。如今,这在很大程度上也简化了单个主机运行实例的配置。

负载均衡器允许我们以对服务的所有消费者透明的方式,增加更多的微服务实例。这提高了我们应对负载的能力,并减少了单个主机故障的影响。然而,很多(如果不是大多数的话)微服务会有某种形式的持久化数据存储,很有可能是在另一台机器上的数据库。如果多个微服务实例运行在多台机器上,但只有一台主机在运行数据库实例,那么数据库依然是一个单点故障源。

系统最初的架构,可能和能够应对很大负载容量的架构是不同的。正如 Jeff Dean 在他的演 讲“Challenges in Building Large-Scale Information Retrieval Systems”(2009 年 WSDM 会议)中所说的,你的设计应该“考虑 10 倍容量的增长,但超过 100 倍容量时就要重写了”。在某些时刻,你需要做一些相当激进的事情,以支持负载容量增加到下一个级别。

当达到特定伸缩阈值时,必须重新设计架构。有人以此为理由,主张从一开始就构建大规模系统。这是很危险的,甚至可能是灾难性的。在开始一个新项目时,我们往往不知道真正想要构建的是什么,也不知道它是否会成功。我们需要快速实验,并以此了解需要构建哪些功能。如果在前期为准备大量的负载而构建系统,将在前期做大量的工作,以准备应对也许永远不会到来的负载,同时耗费了本可以花在更重要的事情上的精力,例如,理解是否真有人会使用我们的产品。

11.8 扩展数据库

服务的可用性和数据的持久性这两个概念。

CQRS(Command-Query Responsibility Segregation,命令查询职责分离)模式,是一个存储和查询信息的替代模型。传统的管理系统中,数据的修改和查询使用的是同一个系统。使用 CQRS 后,系统的一部分负责获取修改状态的请求命令并处理它,而另一部分则负责处理查询。

11.9 缓存

HTTP 提供了一些非常有用的控制手段,帮助我们在客户端或服务器端缓存,即使你不使用 HTTP 也值得了解一下。

首先,使用 HTTP,我们可以在对客户端的响应中使用 cache-control 指令。这些指令告诉客户他们是否应该缓存资源,以及应该缓存几秒。我们还可以设置 Expires 头部,它不再指定一段内容应该缓存多长时间,而是指定一个日期和时间,资源在该日期和时间后被认为失效,需要再次获取。你共享的资源本质,决定了哪一种方法最为合适。标准的静态网站内容,像 CSS 和图片,通常很适合使用简单的 cache-control TTL(Time To Live,生存时间值)。另一方面,如果你事先知道什么时候会更新一个新版本的资源,设置 Expires 头部将更有意义。以上两种方法都非常有用,客户端甚至无需发请求给服务器。

除了 cache-control 和 Expires,我们在 HTTP 的兵器库里还有另一种选择:实体标签(Entity Tags)或称为 Etag。ETag 用于标示资源的值是否已改变。如果我更新了客户记录,虽然访问资源的 URI 相同,但值已经不同,所以我会改变 ETag。有一种非常强大的请求方式叫作条件 GET。当发送一个 GET 请求时,我们可以指定附加的头告诉服务器,只有满足某些条件时才会返回资源。
例如,假如我们想要获取一个客户的记录,其返回的 ETag 是 o5t6fkd2sa。稍后,也许因为 cache-control 指令告诉我们这个资源可能已经失效,所以我们想确保得到最新的版本。当发出后续的 GET 请求,我们可以发送一个 If-None-Match:o5t6fkd2sa。这个条件判断请求告诉服务器,如果 ETag 值不匹配则返回特定 URI 的资源。如果我们的已经是最新版本,服务器会直接返回响应 304(未修改),告诉客户端缓存的已经是最新版本。如果有可用的新版本,我们会得到响应 200 OK、更新后的资源以及新的 ETag。


你会发现尽管自己经常在读取时使用缓存,但在一些用例中,为写使用缓存也是有意义的。例如,如果你使用后写式(writebehind)缓存,可以先写入本地缓存中,并在之后的某个时刻将缓存中的数据写入下游的、可能更规范化的数据源中。当你有爆发式的写操作,或同样的数据可能会被写入多次时,这是很有用的。后写式缓存是在缓冲可能的批处理写操作时,进一步优化性能的很有用的方法。

使用后写式缓存,如果对写操作的缓冲做了适当的持久化,那么即使下游服务不可用,我们也可以将写操作放到队列里,然后当下游服务可用时再将它们发送过去。

避免在太多地方使用缓存!在你和数据源之间的缓存越多,数据就越可能失效,就越难确定客户端最终看到的是否是最新的数据。这在一个涉及多个服务的微服务架构调用链中,很有可能产生问题。再强调一次,缓存越多,就越难评估任何数据的新鲜程度。所以如果你认为缓存是一个好主意,请保持简单,先在一处使用缓存,在添加更多的缓存前慎重考虑!

你可以在多个地方进行缓存。当考虑在一个面向公众的 Web 应用程序中提供内容服务时,在你和客户间可能存在多个缓存。可能不仅你在网站上使用 CDN,有些 ISP 也会使用缓存。你可以控制这些缓存吗?即使你可以,还有一个缓存是你无法控制的:用户浏览器中的缓存。这些使用 Expires: Never 的页面,停留在很多用户的缓存里,永远不会失效,直到缓存已满或者用户手动清理它们。显然,我们无法让上述任何事情发生。我们唯一的选择就是,改变这些网页的 URL,以便能够重新获取它们。缓存可以很强大,但是你需要了解数据从数据源到终点的完整缓存路径,从而真正理解它的复杂性以及使它出错的原因。

11.11 CAP 定理

在分布式系统中有三方面需要彼此权衡:一致性(consistency)、可用性(availability)和分区容忍性(partition tolerance)。

11.14 文档服务

Swagger 让你描述 API,产生一个很友好的 Web 用户界面,使你可以查看文档并通过 Web 浏览器与 API 交互。能够直接执行请求是一个非常棒的特性。例如,你可以定义 POST 模板,明确微服务期望的内容是什么样的。

第12章 总结

12.1 微服务的原则

微服务引入了很多复杂性,其中的关键部分是,我们不得不管理大量的服务。解决这个问题的一个关键方法是,拥抱自动化文化。前期花费一定的成本,构建支持微服务的工具是很有意义的。自动化测试必不可少,因为相比单块系统,确保我们大量的服务能正常工作是一个更复杂的过程。调用一个统一的命令行,以相同的方式把系统部署到各个环境是一个很有用的实践,这也是采用持续交付对每次提交后的产品质量进行快速反馈的一个关键部分。请考虑使用环境定义来帮助你明确不同环境间的差异,但同时保持使用统一的方式进行部署的能力。考虑创建自定义镜像来加快部署,并且创建全自动化不可变服务器,这会更容易定位系统本身的问题。

区分部署和发布,降低发布出错的风险。

12.2 什么时候你不应该使用微服务

这个问题我被问过很多次了。我的第一条建议是,你越不了解一个领域,为服务找到合适的限界上下文就越难。正如我们前面所讨论的,服务的界限划分错误,可能会导致不得不频繁地更改服务间的协作,而这种更改成本很高。所以,如果你不了解一个单块系统领域的话,在划分服务之前,第一件事情是花一些时间了解系统是做什么的,然后尝试识别出清晰的模块边界。

考虑首先构建单块系统,当稳定以后再进行拆分。

12.3 临别赠言

微服务架构会给你带来更多的选择,也需要你做更多的决策。相比简单的单块系统,在微服务的世界里,做决策是一个更为常见的活动。我可以保证,你总会在一些决策上出错。既然知道了我们难免要做一些错事,那该怎么办呢?嗯,我会建议你,尽量缩小每个决策的影响范围。这样一来,如果做错了,只会影响系统的一小部分。学会拥抱演进式架构的概念,在这种概念下,系统会在你学到一些新东西之后扩展和变化。不要去想大爆炸式的重写,取而代之的是随着时间的推移,逐步对系统进行一系列更改,这样做可以保持系统的灵活性。

习惯这一点:从很多方面来说,持续地改变和演进系统,这条规则比我在本书中分享给你的任何一个知识都要重要。变化是无法避免的,所以,拥抱它吧!

发表评论

电子邮件地址不会被公开。 必填项已用*标注