随着领域驱动设计、持续交付、按需虚拟化、基础设施自动化、小型自治团队、大型集群系统这些实践的流行,微服务也应运而生。微服务在技术决策上给了我们极大的自由度,使我们能够更快地响应不可避免的变化。
本文同步发表于 https://devlife.cn
1.1 什么是微服务
微服务就是一些协同工作的小而自治的服务。
1.1.1 很小、专注于做好一件事
现有问题:随着新功能的增加,代码库越来越大。尽管我们想在巨大的代码库中做到清晰地模块化,但事实上这些模块之间的界限很难维护。
在一个单块系统内,通常会创建一些抽象层或者模块来保证代码的内聚性,从而避免上述问题。在考虑使用微服务的时候,内聚性这一概念很重要。Robert C. Martin 的单一职责原则很好地强调了内聚性这一概念。
微服务将这个理念应用在独立的服务上。根据业务的边界来确定服务的边界,这样就很容易确定某个功能代码应该放在哪里。而且由于该服务专注于某个边界之内,因此可以很好地避免由于某个代码库过大衍生出的很多相关问题。
不能通过代码行数来衡量代码库的大小,这跟语言的表达力和领域本身的复杂度有关。作者给出的答案是:足够小即可,不要过小。换句话说,如果你不再感觉你的代码库过大,可能它就足够小了。另外一个关键因素是:该服务是否能够很好地与团队结构相匹配。
服务越小,微服务架构的优点和缺点也就越明显。使用的服务越小,独立性带来的好处就很多。但是管理大量服务也会越复杂。
1.1.2 自治性
一个微服务就是一个独立的实体。它可以独立地部署在 PAAS 上,也可以作为一个操作系统的进程存在。
我们要尽量避免把多个服务部署到一台机器上,尽管现如今机器的概念已经非常模糊了。尽管这种隔离性会引发一些代价,但它能大大简化分布式系统的构建。
服务之间均通过网络进行通信,从而加强了服务之间的隔离性,避免紧耦合。
这些服务应该可彼此间独立修改,并且某一个服务的部署不应该引起该服务消费方的变动。对于一个服务来说,我们需要考虑什么应该暴露,什么应该隐藏。如果暴露得过多,那么服务消费方会与该服务的内部实现产生耦合。这会使得服务和消费方之间产生额外的协调工作,从而降低服务的自治性。
API 的实现技术应该避免与消费方耦合,这意味着应该选用与具体技术不相关的 API 实现方式,以保证技术的选择不被限制。
对系统解耦进行判断的黄金法则:是否能够修改一个服务并对其进行部署,而不影响其他任何服务?
1.2 主要好处
微服务有很多不同的好处,其中很多好处也适用于任何一个分布式系统。但相对与分布式系统或者面向服务的架构而言,微服务要更胜一筹,它会把这些好处推向极致。
1.2.1 技术异构性
在一个由多个服务相互协作的系统中,可以在不同的服务中使用最适合该服务的技术。尝试使用一种适合所有场景的标准化技术,会使得所有的场景都无法得到很好的支持。
如果系统中的一部分需要做性能提升,可以使用性能更好的技术栈重新构建该部分。系统中的不同部分也可以使用不同的的数据存储技术。
微服务可以帮助我们更快地采用新技术,并且理解这些技术的好处。尝试新技术通常会伴随着风险,这使得很多人望而却步。尤其是对于单块系统而言,采用一种新的语言、数据库或者框架都会对整个系统产生巨大的影响。对于微服务系统而言,可以选择一个风险最小的服务来采用新技术,即便出现问题也容易处理。
不过为了同时使用多种技术,也需要付出一些代价。有些组织会限制语言的选择。
1.2.2 弹性
弹性工程学的一个关键概念是舱壁。如果系统中的一个组件不可用了,但并没有导致级联故障,那么系统中的其他部分还可以正常运行。服务边界就是一个明显的舱壁。
在单块系统中,如果服务不可用,那么所有的功能都会不可用。对于单块服务的系统而言,可用通过将同样的实例运行在不同的机器上来降低功能完全不可用的概论,然而微服务系统本身就能够很好的处理服务不可用的降级问题。
微服务系统可以改进弹性,但还是需要谨慎对待,因为一旦使用了分布式系统,网络就会是个问题,机器也如此。
1.2.3 拓展
庞大的单块服务系统只能作为一个整体进行拓展。即使系统中只有一小部分存在性能问题,也需要对整个服务进行拓展。
如果使用较小的多个服务,则可以只对需要拓展的服务进行拓展,这样就可以把那些不需要拓展的服务运行在更小的、性能稍差的硬件上。
1.2.4 简化部署
在有几百万行代码的单块应用程序中,即使只修改了一行代码,也需要重新部署整个应用程序才能够发布改更新。这种部署的影响很大、风险很高,因此相关干系人不敢轻易做部署。于是在实际操作中,部署的频率就会变得很低。这就会导致两次发布之间的差异很大,但两次发布之间的差异越大,出错的可能性就越大!
在微服务架构中,各个服务的部署是独立的,这样可以更快地对特定部分的代码进行部署。如果真的出了问题,也只会影响一个服务,并且容易快速回滚,这也意味着客户可以更快的使用我们开发的新功能。
1.2.5 与组织结构相匹配
团队或代码库过大容易引起问题。特别当团队是分布式的时候,问题更明显。在小型代码库上工作的小团队更加高效。
微服务架构可以很好地将架构与组织结构相匹配,避免出现过大的代码库,从而获得理想的团队大小和生产力。服务的所有权也可以在团队之间迁移,从而避免异地团队的出现。
1.2.6 可组合性
分布式系统和面向服务的架构声称主要的好处是易于重用已有功能。而在微服务架构中,根据不同的目的,人们可以通过不同的方式使用同一个功能,在考虑客户如何使用该软件时这一点尤为重要。
在微服务架构中,系统会开放很多接缝供外部使用。当情况发生改变时,可以使用不同的方式构建应用,而整体化应用程序只能提供一个非常粗粒度的接缝供外部使用。
1.2.7 对可替代性的优化
如果你在一个大中型组织工作,很可能接触过一些庞大而丑陋的遗留系统。这些系统无人敢碰,却对公司业务的运营至关重要。更糟糕的是,这些程序是使用某种奇怪的 Fortran 变体编写的,并且只能运行在 25 年前就应该被淘汰的硬件上。为什么这些系统直到现在还没有被取代?其实你很清楚答案:工作量很大,而且风险很高。
当使用多个小规模服务时,重新实现某一个服务或者是直接删除改服务都是相对可操作的。
使用微服务架构的团队可以在需要时轻易地重写服务,或者删除不再使用的服务。当一个代码库只有几百行时,人们也不会对它有太多感情上的依赖,所有很容易替换它。
1.3 面向服务的架构
SOA 是一种设计方法,其中包含多个服务,而服务之间通过配合最终会提供一系列功能。一个服务通常以独立的形式存在于操作系统进程中。服务之间通过网络调用,而非采用进程内调用的方式进行通信。
人们逐渐认识到 SOA 可以用来应对臃肿的单块应用程序,从而提高软件的可重用性,比如多个终端用户应用程序可以共享一个服务。它的目标是在不影响其他任何人的情况下透明的替换一个服务,只要替换之后的服务的外部接口没有太大变化即可。这种性质能够大大简化软件维护甚至是软件重写的过程。
SOA 本身是一个很好的想法,但尽管做了很多的尝试,人们还是无法在如何做好 SOA 这件事上达成共识。
实施 SOA 时会遇到这些问题:通信协议(例如 SOAP)如何选择、第三方中间件如何选择、服务粒度如何确定等,目前也会存在一些关于如何划分系统的指导性原则,但其中很多是错误的。
现有的 SOA 知识并不能帮助你把很大的应用程序划小。它没有提到多大算大,也没有讨论如何在现实世界中有效地防止服务之间的过度耦合。
在现实世界中,由于我们对项目的系统和架构有着更好的理解,所有能够更好地实施 SOA,而这事实上就是微服务架构。就像认为 XP 或者 Scrum 是敏捷软件开发的一种特定方法一样,你也可以认为微服务架构是 SOA 的一种特定方法。
1.4 其他分解技术
当你开始使用微服务时会发现,很多基于微服务的架构主要有两个优势:首先它具有较小的粒度,其次它能够在解决问题的方法上给予你更多的选择。那么其他的分解技术是否也有相应的好处呢?
1.4.1 共享库
基本上所有的语言都支持将整个代码库分解成多个库,这是一种非常标准的分解技术。这些库可以由第三方或者自己的组织提供。
不同的团队和服务可以通过库的形式共享功能。团队可以围绕库来进行组织,而库本身可以被重用。
但是这种方式存在一些缺点:首先你无法选择异构的技术。一般来讲,这些库只能在同一种语言中,或者至少在一个平台上使用。其次,你会失去独立地对系统某一部分进行拓展的能力。再次,除非你使用的是动态链接库,否则每次当库有更新的时候,都需要重新部署下整个进程,以至于无法独立地部署变更。而最糟糕的影响可能是你会缺乏一个比较明显的接缝来建立架构的安全性保护措施,从而无法确保系统的弹性。
共享库确实有其相应的应用场景,有时候你会编写代码来执行一些公共任务,这些代码并不属于任何一个业务领域,并且可以在整个组织中进行重用,很显然这些代码就应该成为一个可重用的库。但是你还是需要很小心,如果使用共享代码来做服务之间的通信的话,那么它会成为一个耦合点。
服务之间可以并且应该大量使用第三方库来重用公共代码,但有时候效果不大好。
1.4.2 模块
除了简单的库之外,有些语言提供了自己的模块分解技术。它们允许对模块进行生命周期管理。这样就可以把模块部署到运行的进程中,并且可以在不停止整个进程的前提下对某个模块进行修改。
例如 OSGI、Erlang的模块划分等。
除了把系统划分为不同的服务之外,可能会想要在一个进程内部使用模块进行划分,但是仅仅使用模块划分不能解决所有问题。
1.5 没有银弹
微服务不是免费的午餐,更不是银弹,如果你想要得到一条通用的准则,那么微服务是一个错误的选择。你需要面对所有分布式系统需要面对的复杂性。
1.6 小结
希望到目前为止,你已经了解了什么是微服务,微服务与其他组合技术有何不同,以及它能带来的主要好处是什么。