2.4 微服务架构实施的先决条件
不提倡从一开始就建立微服务架构的原因之一是没有做好准备,下面我们来看一下建立微服务架构前,需要从哪些方面做准备。
2.4.1 研发环境和流程上的转变
在实施微服务架构之前,我们要准备相关的环境和流程,可以简单地通过以下几个方面建立基本的条件。
自动化工具链
微服务架构的一大优势是快速交付,快速交付不止体现在服务的粒度更小,可以独立交付,还体现在整个流程更快速。微服务架构基于自动化的工具链,以流水线交付的方式串联整个 DevOps 流程。小团队可以基于服务独立开发、测试、部署、运维。传统的交付周期以月为单位,而微服务架构的交付周期能做到以天为单位,按照传统的开发模式是无法满足这样的交付周期要求的。
微服务框架
微服务框架可以封装、抽象分布式场景下的一些常用能力,例如负载均衡、服务注册发现、容错、远程通信等能力,可以让开发人员快速开发出高质量的服务,在采用微服务架构之前,应该先进行微服务框架的选型和试用。
快速申请资源
如果以天为单位进行交付,就必须能够快速申请资源。基础设施即代码可通过编程的方式管理虚拟机或容器,免去了手动配置、更新各个硬件的环节,这就使得基础设施极具弹性,能够快速、高效、准确地进行重复性操作。开发人员使用同一套配置或代码,就可部署并管理成千上万台物理机。基础设施即代码能够得到更快的速度、更低的成本和更可靠的环境。用代码定义服务器配置意味着在众多服务器之间有绝对的一致性,容易形成标准化。手动调整配置往往会有一些微妙的差异,难以追溯和调试,并且会导致许多诡异的问题。
故障发现反馈机制
当服务数量增多、交付频繁的时候,故障次数可能会大幅上升,我们需要通过全面的监控发现故障,及时处理并发出报警。当生产环境出现问题的时候,需要将故障进行分级,评估影响面,并分配给相应的架构师或者开发人员。开发人员需要不断更新故障的状态,以便管理者、客服、销售人员等问题相关人了解进度,来提供更好的用户体验。
研发流程上的转变
需要重新组建团队,以服务为核心,按照业务领域划分全功能团队,改变原有的研发流程、决策机制。例如,倡导敏捷文化、快速迭代,做更多的自动化测试,加强Code Review,给团队更多的自主决策权等,具体内容可以参考本书第9章和第10章。
2.4.2 拆分前先做好解耦
解耦这个词来源于数学,是指使含有多个变量的数学方程变成能够用单个变量表示的方程组,即变量不再同时直接影响一个方程的结果,从而简化计算。
在软件世界里,解耦强调的是每个单元可以独立变化,尽量减少外界对系统内部的影响。说白了也就是,如果把Memcache换成Redis,那么需要多少工作量,涉及的修改面有多大。解耦也会带来工作量的增加、架构或者代码变得复杂等问题。例如很多人会假设把Oracle换成MySQL,Memcache换成Redis,但是在实际工作中,并不是所有的业务发展速度都有这么快,如果能预料到短期将发生变化,为什么不直接使用 MySQL 呢?通常这是一个伪命题。如果在未来几年后才发生变化,那么现在去做相应的适配,这不符合敏捷开发的哲学思想,也不是一个高效率的思路。
在转向微服务架构之前,业务服务存在状态、数据库中存在触发器和存储过程、服务之间绕过接口调用等问题,是我们首先要解决的。
状态外置
无状态(Statelessness)指的是服务内部变量值的存储。有状态的服务伸缩起来非常复杂,可以通过将服务的状态外置到数据库、分布式缓存中,使服务变成无状态。通常业界用牲畜来比喻无状态,用宠物来比喻有状态。宠物是需要呵护的,是有名字的,不能被轻易替代的,而牲畜是没有名字的,只生产肉和奶,死掉一个用新的来替代即可。所以,我们期望服务可以做到无状态,可以被轻易地替代。
但是,无状态不代表状态消失,只是把状态转移到分布式缓存和数据库中了。业务服务伸缩的时候,还是要考虑分布式缓存和数据库所能承受的压力限制。那为什么还要外置呢?因为一方面即使不外置到数据库,数据库也存在状态,另一方面,这样可以把复杂度抽象到统一的位置,便于集中处理。例如,服务端的Session信息可以放到分布式缓存中,这一设计方法既可以让业务服务在一定范围内(分布式缓存的上限)伸缩时不受状态的限制,又可以把复杂度抽象到特定的位置,让专业领域开发人员统一做有状态的伸缩。虽然绝大多数服务都可以状态外置,但是并不是所有的业务服务都能设计成无状态,例如客户端与服务端的长连接,这种状态很难外置。
以下三种常见的状态需要和业务服务拆分开来,否则扩展性将受到很大限制。
定时任务:因为大多数任务不能重复触发,否则轻则重复做无用功(幂等的情况下),重则会导致不一致。例如从A表中把数据迁移到B表中,如果在两个服务中同时处理,没有一个协调器的话,会导致重复拉取。所以,需要把定时任务从业务服务中提取出来,通过分布式任务调度统一协调。
本地存储:在本地存储文件也是比较常见的。当有多个实例的时候,要么全部同步一遍,要么需要根据用户路由到同一个实例,并且在伸缩的过程中需要迁移。
本地缓存:某些业务会将数据存放在本地做缓存,例如 Session 数据。如果要去掉本地缓存,则可以通过分布式缓存和Cookie解决业务服务带状态的问题。当然,本地缓存也有适用的业务场景,不能一概而论。
去触发器、存储过程
触发器、存储过程在系统规模比较小的时候,的确非常简单实用。随着业务的发展,业务服务比较容易扩展,数据库通常变成了伸缩的瓶颈,许多方案都是为了减轻数据库的压力而提出的。触发器、存储过程可能会带来如下问题。
整体的伸缩受到数据库的限制,因为触发器、存储过程难以扩展。
当存在水平分表的时候,可能无法满足需求。
如果触发器、存储过程过多,则会导致运维复杂度升高。
解决方案通常是通过外部的业务服务或者定时任务替换触发器及存储过程。
通过接口隔离
直接访问其他服务的数据库,如图2-3所示。CRM直接调用OA的数据库,没有通过接口调用。对CRM进行微服务架构拆分之前,需要先理清系统的外部依赖关系,如果存在多个系统共享一个数据库,就会导致耦合,影响可用性和扩展性,进而可能出现如下问题。
当CRM中的数据结构发生变化的时候,OA也要跟着变化,导致开发的过程互相依赖。
有可能在CRM进行的限流是没用的,因为OA没有通过CRM提供的接口进行调用。
假设随着业务的发展,需要在 CRM 的数据库上做缓存,可能存在多个地方要考虑缓存的问题。
图2-3 直接访问其他服务的数据库
总之,接口应该作为唯一对外提供的访问方式,这代表的是控制力。解决方法就是通过接口调用,逐步去除数据库的直接访问。