《管理的常识》一书里说,管理的核心是不断的解决(推进工作过程中出现的各种)问题。同样地,我认为架构的核心则是不断的解决(系统设计实现与演化过程中的各种)问题。
Fred Brooks 在《人月神话》里说,“没有银弹”,现在依然成立,微服务也并不是只有优点,没有副作用,把系统拆分了了很多部分,每一个部分简单了,但是整体的关系变复杂了。前面介绍了那么多微服务的特点和优势,以及相关技术,我们再来分析一下微服务存在的问题,进而讨论什么场景适合使用微服务,什么场景不适合,以及最佳的实践方式。
微服务架构首先是一个分布式系统架构。分布式系统的发展由来已久,但是近年来产生了理论和实现的大爆发。
究其原因:分布式系统的发展得益于廉价 pc 硬件使得堆机器成为可能,而单机的成本/容量是非线性的,所以分布式的核心是线性的水平扩展整个集群的功能,以及带来的协调机制,管理复杂度,数据和状态一致性,容错和故障恢复,容量与弹性伸缩,这些通用性的基础能力建设。
简单的翻译一下:单个机器堆资源,升级 CPU 内存加大一倍,想要增加一倍的处理能力且成本不超过一倍,不仅很难、而且不现实了。这样,我们就需要思考怎么通过进一步对于系统里不同功能的部分,拆解开,单独扩展这些能水平扩展的部分,从而在控制成本的前提下,提升整个系统的处理能力。
另外,我们现在都知道,设计系统如果对可靠性,可用性有非常高的要求,那么需要先假设上下游都是不可靠的,依赖的基础设施和网络也是不可靠的,这样就考虑分布式后的分片、复制、故障转移、灾难恢复等,分布式系统下,我们可以对系统的不同性质的节点、不同可靠性可用性要求的组件,做针对性的单独处理。
很多系统看起来是分布式的,其实是一个大单机。 很多系统看起来是微服务的,其实是一个大单体。
就像人月神话里说的,往已经延期的项目,加人手,可能会导致更延期。问题不是出现在应不应该加人,或者应不应该使用分布式上。而是实施的人,没搞清楚关键。比如系统拆成分布式或微服务,然后某个关键地方依然卡住了业务流程,可能整体还不如单机的,扩展性失效,多加机器还不如单机。
为什么?
因为如果希望用多个机器来扩展业务,最后发现各个机器上运行的程序没有划清楚边界职责,多个机器合起来并不比单个机器有更大处理能力,这就是一个大单机。
同样的,如果我们拆分了很多更小独立的服务,但是一个业务请求进来,还是在一些地方被卡住,导致有一些瓶颈使得整个拆分后并不比之前有整体的改进,那么其实是费劲的做了一个大单体系统。
所以合理的拆分微服务,使得我们能够更好的扩展系统是关键,同时如果业务简单,流量不大,不扩展也可以很好的应对,系统对于可用性和一致性要求也不是特别高,那么分布式和微服务,也不是必须的。
微服务系统适合的场景
以下几类系统,比较适合使用微服务架构,或者使用微服务架构改造:
大型的前后分离的复杂业务系统后端,业务越复杂,越需要我们合理的设计和划分,长期的维护成本会非常高,历史包袱也很重,这个问题后面会详谈。
变化发展特别快的创新业务系统,业务快,就意味着天天要“拥抱变化”,每一块的业务研发都要被业务方压的喘不过气,不做拆分、自动化,一方面研发资源永远不够用,活永远干不完,另一方面不停的在给系统打补丁,越来质量越低,出错的可能性也越来越大。
规划中的新大型业务系统,如果有能力一开始就应该考虑做微服务,而不是先做单体,还是发展到一定的阶段再做改造,改造一定是伤筋动骨的大手术,虽然我们可以采取一些策略做得更平滑,但是成本还是比较高的。
敏捷自驱的小研发团队,拥抱新技术,可以直接用微服务做系统,不但的通过快速迭代、持续交付,经过一定时间的尝试和调整,形成自己的微服务实践经验,这样在团队扩大时可以把好的经验复制到更大的团队。
反过来,以下几类系统,不太适合一开始就做微服务:
小团队,技术基础较薄弱,创业初期或者团队新做的快速原型,这个时候做微服务的收益明显比单体要低,快速把原型做出来怎么方便怎么来,用团队最熟悉的技术栈。
流量不高,压力小,业务变化也不大,单体能简单搞定的,就可以先不考虑微服务,不考虑分布式,因为分布式和微服务带来的好处,可能还不足以抵消复杂性增加带来的成本。
对延迟很敏感的低延迟高并发系统,低延迟的秘诀就是离 io 能多远就多远,离 cpu 能多近就多近,分布式和微服务,导致增加了网络跳数,延迟就没法降低。
技术导向性的系统,技术产品,这类产品跟业务系统不同,常规的业务系统研发方法不是太适合。
Chris Richardson 在文章(http://blog.daocloud.io/microservices-1/)里提到了微服务的几个不足:
服务大小:『微服务』强调了服务大小,有人强调服务要大一点,也有人愿意采用小服务。需要注意这只是一种选择,微服务的目的是有效的拆分应用,实现敏捷开发和部署。
进程通讯:微服务应用是分布式系统,由此会带来分布式固有的复杂性。开发者需要在 RPC 或者消息传递之间选择并完成进程间通讯机制。相对于单体式应用中通过语言层级的方法或者进程调用,微服务下这种技术显得更复杂一些,需要考虑 RPC 的超时、重试、失效策略,或者消息服务不可用,堆积堵塞等问题。
数据库拆分:数据库事务对于单体式应用来说很容易,因为只有一个数据库。在微服务架构应用中,需要更新不同服务所使用的不同的数据库。使用分布式事务并不一定是好的选择,不仅仅是因为 CAP 理论,还因为很多中间件并不支持这一需求。最终你不得不使用一个最终一致性的方法,从而对开发者提出了更高的要求和挑战。
测试复杂度:比如采用流行的 Spring Boot 架构,对一个单体式 web 应用,测试它的 REST API,是很容易的事情。反过来,同样的一个业务场景,需要测试启动和它有关的所有服务,这些服务在不同的进程,所以变得尤为复杂。
服务依赖关系:微服务架构模式应用的改变将会波及多个服务。比如,假设你在完成一个案例,需要修改服务 A、B、C,而 A 依赖 B,B 依赖 C。在单体式应用中,你只需要改变相关模块,整合变化,部署就好了。对比之下,微服务架构模式就需要考虑相关改变对不同服务的影响。比如,你需要更新服务 C,然后是 B,最后才是 A,幸运的是,许多改变一般只影响一个服务,而需要协调多服务的改变很少。特别是,如果要处理的服务间依赖是一个网状关系,那么很可能导致,我们修改 A 时,考虑到了会影响 BCDEF 这几个服务,但是漏掉了影响系统 K,导致上线以后发现 K 的部分功能无法使用了。
部署复杂度:部署一个微服务应用也很复杂,一个分布式应用只需要简单在负载均衡服务后面部署各自的服务器就好了。每个应用实例是需要配置诸如数据库和消息中间件等基础服务。而一个微服务应用一般由大批服务构成。例如,根据 Adrian Cockcroft,Hailo 有 160 个不同服务构成,NetFlix 有大约 600 个服务。每个服务都有多个实例。这就造成许多需要配置、部署、扩展和监控的部分,除此之外,你还需要完成一个服务发现机制,以用来发现与它通讯服务的地址(包括服务器地址和端口)。传统的解决问题办法不能用于解决这么复杂的问题。接续而来,成功部署一个微服务应用需要开发者有足够的成熟部署方法,并高度自动化。
Peter Lawyer 有在一篇文章里(https://vanilla-java.github.io/2016/03/22/Micro-services-for-performance.html) 中也提出,微服务的几个问题:
服务形成信息障碍。
引入了额外的复杂性和新问题,例如网络等待时间,消息格式,负载平衡和容错。忽略其中之一属于“分布式计算的谬误”。
测试和部署更加复杂。
整体应用程序的复杂性仅转移到网络中,但仍然存在。
细粒度的微服务已被批评为一种反模式。
其实可以看到,大家的想法和分析都比较一致,微服务架构带来的固有复杂性和人为复杂性如何管理:
如何合理拆分微服务,粒度如何控制:对业务按粒度和边界拆解的问题,这决定了我们的服务是不是合理,开发和维护是不是方便。核心思路是深入了解业务。
遗留系统应该如何改造,从哪儿下手,如何推动:改造遗留系统一般来说比重新做一个新系统更复杂,如何平滑的、最大代价的将老系统改造成微服务,也是一个巨大挑战。
拆分后的性能应该如何保障:性能有两个指标,关键是区分出来如何处理不同特性的数据,关注不同的指标,做好优化和选型,针对具体细节具体对待。
怎么考虑拆分后的数据一致性,分布式事务的选取和取舍:多个服务间的业务数据一致性的问题,事务是个需要重点考虑的问题,主要是考虑强一致性的分布式事务还是可以使用最终一致的弱一致性。对于一般的业务,可能使用补偿冲正类的做法,在业务允许的一定时间内数据达到一致状态即可,比如 30s,或者 10 分钟。
系统和服务的高可用可伸缩如何实现:高可用意味着系统稳定健壮,可伸缩意味着弹性,资源有效使用,如何做到无状态、不共享,是实现可高用可伸缩的关键。
拆分过程的测试和部署如何处理,怎么提升管理能力,降低风险:跨系统的协作问题、测试的问题,这对于技术能力和研发成熟度较低的团队,会带来很大麻烦。核心思路定义好业务边界、系统间接口与数据标准,提升自动化测试水平。微服务架构由于拆分粒度较细,测试是个大问题。在保持每块职责单一的同时,关键是保证接口稳定,做好自动化测试(特别是 UT 和接口的自动化)和持续集成,尽量少全流程的人工回归测试。部署单元太多导致维护成本上升的问题,要管理的点多了,问题自然复杂了,核心思路是自动化部署与运维。
拆分后的运维和监控如何处理:怎么应对系统的故障,关键是保障核心技术的指标,以及业务指标,做好预警报警,积累问题、寻找根因,控制和杜绝低级失误。