0.1. 单体架构与微服务的区别 0.2. 单体拆分微服务前需要思考的点 0.2.1. 应用程序庞大得足以细分成微服务吗 0.3. 是否需要扩展应⽤程序的各个组件 0.3.1. 事务跨诸多服务吗 0.3.2. 服务之间是否需要经常联系吗 0.3.3. 另外的缺点 0.4. 微服务常见误区 0.4.1. 跨服务的关注点分离降低了复杂度 0.4.2. 微服务提高开发速度 0.4.3. 部署众多小型服务比部署整个应用更安全 0.4.4. 分开扩展服务通常是有利的 0.4.5. 微服务架构性能更高 0.4.6. 管理多个服务并不难 0.4.7. 如果从头开始精心设计微服务,它们将会起作用 0.1. 单体架构与微服务的区别 单体架构:由一台或多台服务器运行单个应用程序,其通常从单个存储库中部署。使用多台服务器时,这些服务器将运行相同的代码。 微服务:为了解决单体架构的局限,将其代码分割成在不同服务器上运行的多个组件。 任何技术都有其缺点,如果倾向于将微服务作为默认的架构,需要考虑以下问题。 0.2. 单体拆分微服务前需要思考的点 0.2.1. 应用程序庞大得足以细分成微服务吗 微服务是⼀⼤堆各司其职的⼩服务,在理想情况下,每个服务本⾝就是⼀个完整的应⽤程序。 由于微服务在⼈⼒和计算成本⽅⾯需要最少资源,所以它需要的成本较⾼,哪怕是轻量级微服务。⽽且,代码库在将来会越来越⼤,代码库本⾝可能会添加⼀个新的领域。 但应该永远记住:当接近阈值时,设计良好的代码库始终可以切换到微服务。 0.3. 是否需要扩展应⽤程序的各个组件 使⽤微服务架构的主要优点之⼀是易于扩展单个组件。你可能会找到组件需要单独扩展的⼤量应⽤程序,但应⽤程序果真需要这么做吗? 0.3.1. 事务跨诸多服务吗 现在,这可能是最难做出的战略性选择之⼀。跨多个服务的事务是整个架构的负担。 解决跨服务的事务意味着:服务之间的互锁会导致⼀系列难以追踪的僵局,以及会危及服务健康状况、有时甚⾄是⼯程师健康状况的竞态条件。 按照定义,REST 服务是⽆状态的。它们不应该参与边界不仅限于⼀项服务的事务。在⾼性能环境下: 两阶段提交 2PC 是不必要的⿇烦。 SAGA 模式只会增加你没有准备好的另⼀层复杂性。 由于微服务坚持采⽤分散式数据管理,它带来了最终⼀致性问题。如果是整体式应⽤程序,可以在单个事务中⼀起更新⼀堆东西。⽽微服务需要多个资源才能更新,且分布式事务不受欢迎。因此,开发⼈员需要意识到⼀致性问题。 0.3.2. 服务之间是否需要经常联系吗 在传统的整体式服务上,每个微服务实例由系统内的模块加以表⽰。模块之间的联系在内存中进⾏,延迟接近零。 微服务的引⼊意味着,联系由内存中事务完全变成了通过⽹络传达指。有众多久经实践考验的解决⽅案,但是它们都有代价:延迟。从内存中事务改为基于⽹络的联系会将延迟从纳秒变为微秒。 想象⼀下,三个不同的服务通过⽹络彼此联系。假设每个服务调⽤要花费 100 微秒(这在负载情况下并不少⻅),那么到头来单单在⽹络上就要花掉300 微秒。⽽⼀些应⽤程序本质上与组件和服务紧密集成。添加的通信层可能会给处理实时数据的应⽤程序造成灾难性的后果。 0.3.3. 另外的缺点 除了上述四个问题,在使⽤微服务之前还需要考虑它的另⼀些缺点,⽐如: 复杂性:虽然微服务原本旨在通过将应⽤程序分解成⼩组件来降低复杂性,但架构本⾝的部署和维护却很复杂。 分布成本:微服务是具有模块性的分布式系统,但是同样的分布确实要付出代价。整体式服务会部署在⼤型虚拟机或⾸选容器上。但如果是微服务,除了多个虚拟机或容器外,服务需要独⽴部署在理想的环境上。当然服务可能很⼩,但你可以计算⼀下总成本。 改编 DevOps:这可能有益也可能有害,如果你是⼩型企业的成员,成⽴⼀⽀ DevOps 团队会弊⼤于利。不过有⼀点倒可以肯定,要是没有专⻔的 DevOps 团队,就⽆法维护和监控微服务。 集成紧密:⼀些应⽤程序天⽣就紧密耦合。将它们分开来以“适应”架构将会带来灾难。 缺乏经验:这对任何新技术的采⽤来说都是重要的考虑因素。但是说到微服务,由于拥有抽象定义,造成的危害尤其⼤。 混乱的数据合约:在团队内部设定数据合约与团队之间共享数据合约⼤不相同。在接触微服务时,团队可能不在同⼀个地区,更不要说团队成员都采⽤同⼀种编程语⾔了。为特定要求制定数据合约会耗费你的时间和空间。 调试:每个服务都会有⾃⼰的⼀组⽇志⽂件要审查,更多服务意味着更多的⽇志⽂件。 0.4. 微服务常见误区 0.4.1. 跨服务的关注点分离降低了复杂度 关注点分离:在不相关的代码间应存在隔离墙。 当不相关的代码需要协同工作时,应该使用抽象良好的接口并尽量减少状态共享。 你的代码对其他代码的了解,越少越好。同样,一个函数执行的功能越多,你就越需要考虑运动部件之间的复杂关系(即复杂度)。而且,我们作为合格的工程师就应该努力降低复杂度。 从逻辑上讲,分离关注点的最佳方法是否就是让你的无关代码运行在不同的服务(服务之间以 API 沟通)中呢?不,并非如此。 经典的单进程关注点分离之所以有效,是因为它可以最小化并简化不相关代码之间的接口。在设计良好的程序中,此接口可以只有带 return 语句的单个函数调用。 不相关代码之间的边界本质上是复杂的,而简单的接口有助于管理这种复杂性。 相比之下,在微服务中,函数调用被替换为网络请求。这种新的服务间障碍严格来说更加复杂且更不可靠。 首先,每个网络调用都需要一定数量的样板。 其次,工程师现在需要默认任何服务随时会失效。 相反,在单体中,当代码失败时整个服务都会失败。尽管这听起来很糟糕,但由于现在只有一种故障情况,因此它更易于管理。 0.4.2. 微服务提高开发速度 当采用“关注点分离”并将其应用在开发人员时个团队之间各自独立。 从表面上看这似乎是有益的。如果团队只需要操心自己的服务,那将减轻他们的认知负担,并提高他们的生产力。 现在,工程师无需担心基础架构中其他部分的复杂性了。问题在于,大多数新功能都需要一些跨多个服务的补丁,因为,许多功能需要在两个或多个服务上开发。 从技术角度来看,实现多服务功能可能需要编辑多个存储库。至少,它需要一种方法来测试在多个服务上运行的代码。 如何手动测试多服务功能?在机器上启动多个容器,并仔细设置每个容器的状态。 如何进行单元测试呢?在哪里对多服务功能进行单元测试?是仓库 A 还是仓库 B?很容易想象,数据流中的一个小错误会破坏多个下游服务。我们应该期望工程师理解所有可能依赖其代码的下游服务吗? 如果没有投入大量的工程资源来构建多服务测试流程,那么除了最常见的功能之外,开发新功能的速度会像蜗牛般缓慢。如果没有质量测试框架,看似简单的任务(例如“添加分页”)也可能会变成历时数月、跨多个团队的工作。 0.4.3. 部署众多小型服务比部署整个应用更安全 “回滚”是现代软件工程需要面对的现实。当部署的代码会破坏某些功能时,必须回滚部署并还原提交。 微服务架构的一个观点是,部署多个独立服务比部署整个应用更安全。当一项服务中断时,其他服务还有回退可用。整个应用程序将继续运行,客户不会有什么感觉。 这种方法存在多个问题。 首先,这要假设服务可以容忍其他任何服务的随机消失。 其次,部署多服务功能时,服务的上线时间会有所不同。在一段时间里,那些服务将有不同的版本。对此有多种处理方法。你是否在半夜部署?你是否并行维护不同的 API 版本?你是否使用托管流?所有解决方案都需要额外的工程资源。如果部署意外破坏了(甚至不是部署的一部分)服务中的状态,会发生什么情况?你是否有针对任何意外情况的预案? 虽然单体部署也会出错,但是有多种方法可以缓解这种情况(蓝色 / 绿色、金丝雀等等)。虽然这些方法也可用于微服务,但是设置和管理安全部署并非易事,应对一项服务总比应对多个服务要容易些。 0.4.4. 分开扩展服务通常是有利的 在每个应用程序中,都有经常运行的部分和很少运行的部分。很少运行的部件比频繁运行的部件需要的资源要少一些。那么分开扩展这些部件是否有意义? 从根本上讲,扩展软件的原因是因为软件需要更多的核心资源。这些资源可能是 CPU 周期、内存、磁盘空间或网络。 例如,当 CPU 以100%运行时,可以启动另一个服务来减轻压力。 对于大多数应用,水平扩展(克隆单体)就足够了。水平扩展的复杂度较低,许多云服务都可以用很少的配置来做到这一点。 相比之下,选择分开扩展许多微服务有两个常见原因。 首先,如果代码具有实质上并行的部分,则在某些情况下将计算块分配给不同的“worker”可能会有些意义。 其次,如果资源需求在整个请求中出现变化,则单独扩展各个微服务可能是有意义的。 例如,如果一个请求在开始时是受内存限制的,而在结束时是 CPU 限制的,那么就可以将请求的开始部分放在高内存服务中,将结束部分放在高 CPU 服务中。即便如此,除非你是独角兽级别的企业,否则分开扩展服务带来的财务优势可能也无法抵消额外的复杂性。 0.4.5. 微服务架构性能更高 经验法则: 读取内存所需的时间是读取二级缓存的 10 倍; 读取硬盘驱动器所需时间是读取内存的 10 倍; 从网络读取所需的时间是从硬盘读取的 10 倍。 2019 年的内存:12.8GB/s 2019 年的数据中心的网络:5Gbit/s = 0.625GB/s 0.4.6. 管理多个服务并不难 软件工程师喜欢自欺欺人。使用微服务的决策也会有同样的乐观情绪。 0.4.7. 如果从头开始精心设计微服务,它们将会起作用 正常运作的复杂系统一定是从一个正常运作的简单系统演变而来的。从头开始设计的复杂系统永远无法正常工作,也无法靠打补丁来正常运作。你必须从一个简单系统起步。——Gall 定律