随着软件开发复杂度的不断提高,团队开发成员间如何更好地协同工作以确保软件开发的质量已经慢慢成为开发过程中不可回避的问题。尤其是近些年来,敏捷软件开发逐渐成为显学,如何能在不断变化的需求中快速适应和保证软件质量显得尤其重要。
持续集成(Continuous Integration)作为敏捷开发重要的一步,其目的在于让产品快速迭代的同时,尽可能保持高质量。如果要导入持续集成,又该如何开始呢?现在业界普遍采用的实践是将架构微服务化,系统容器化。
微服务(Microservices)把系统所做的事情拆解成小且可管理的条目并且分而治之,可以让大型项目在团队工作管理、代码变更以及发布周期方面更具优势。更小的代码库可以让程序员更加专注,并且与产品客户有更投入的关系,这样程序员就能够对工作有更明晰的认识和更积极的动力。与用户联系紧密后会得到更快的反馈,程序员可以更及时的发现产品所暴露的缺陷以及应当去实现的新功能。
容器化(Containerization)的用意则是把微服务中配置、依赖、与运行环境封装成为一个镜像,透过 Dockerfile 让运行环境代码化(Infrastructure as Code),方便版本控管,让运行环境不再是个黑箱。以标准化后的开发/测试/部署的运行环境,让持续集成中的各个环节更容易地自动化。
传统的系统架构:
传统的应用通常采用所谓的三层架构,例如:界面层 – 业务逻辑层 – 数据层,通常我们会把所有实现业务逻辑层的代码编译构建后部署到中间件,再通过负载均衡、集群等解决分流、灾备等问题。但是这种架构设计带来的问题是:
开发效率低:随着应用复杂度的增加,越来越少开发人员对应用能有全局性的深度理解。新功能开发和缺陷修复难度呈几何性增加。代码修改的正确性无法保障。而庞大的代码库需要更庞大的开发团队来维护,无形中又增添了管理、沟通和协调的成本。另外,新加入的团队成员需要花费大量的时间和精力来熟悉一个复杂的代码库。
交付周期长:在单一进程的单块架构下,任何微小的改动都需要重新编译、集成、测试和部署整个应用。随着应用体积的增大,交付流程和反馈周期都会相应变长,应用发布的代价也随之增加。于是应用交付周期变缓,交付间隙积累的代码变动增加,从而对于下次交付产生更大的压力,形成恶性循环。
技术转型难:单一进程、单块架构意味着中心化的技术选型。比如,应用的不同逻辑组建通常需要采用相对统一的编程语言、框架和技术栈。这些在项目初始阶段便已定型。之后,即便是应用中全新的逻辑组件,也很难采用不同的技术栈。而当应用达到一定规模后,全局化的技术栈更新会面临很高的风险。所以,单块架构应用一旦定型,就很难再享受行业技术变更、发展所带来的红利。
微服务的优势:灵活可控
在微服务架构下,我们将原本单一的应用按照功能边界(Bounded Context)或者 DDD (Domain Driven Design) 精神设计/分解成一系列独立、专注的微服务。每个微服务对应传统应用中的一个组件,但是可以独立编译、部署和扩展。相对单模块的架构,微服务具备以下优势:
复杂度可控:在将应用分解的同时,规避了原本复杂度无止境的积累。每一个微服务专注于单一功能,并通过定义良好的接口清晰表述服务边界。由于体积小、复杂度低,每个微服务可由一个小规模开发团队完全掌控,易于保持高可维护性和开发效率。
独立部署:由于微服务具备独立的运行进程,所以每个微服务也可以独立部署。当某个微服务发生变更时无需编译、部署整个应用。由微服务组成的应用相当于具备一系列可并行的发布流程,使得发布更加高效,同时降低对生产环境所造成的风险,最终缩短应用交付周期。
技术选型灵活:微服务架构下,技术选型是去中心化的。每个团队可以根据自身服务的需求和行业发展的现状,自由选择最适合的技术栈。由于每个微服务相对简单,当需要对技术栈进行升级时所面临的风险较低。因为服务间是以接口沟通,耦合度低,甚至完全重构一个微服务也是可行的。微服务架构中,可为每个服务选择一个新的适合业务逻辑的资料库系统,比如 MongoDB、PostgreSQL。这样做的好处:首先我们可以根据业务类型来选择适合的资料库,同时也可以减小单个资料库的负载。
易于横向扩展:单块架构应用也可以实现横向扩展,就是将整个应用完整的复制到不同的节点。当应用的不同组件在扩展需求上存在差异时,微服务架构便体现出其灵活性,因为每个服务可以根据实际需求独立进行扩展。
容错机制易实现:当某一组件发生故障时,在单一进程的传统架构下,故障很有可能在进程内扩散,形成应用全局性的不可用。在微服务架构下,故障会被隔离在单个服务中。若设计良好,其他服务可通过重试、平稳退化等机制实现应用层面的容错。
(服务拆分为微服务之后不同模块之间的联系和依赖会更加复杂,势必对于运维和部署有更高的要求,如果没有工具和系统能够提供这样的能力,或者没有更好的方法去做的话,微服务就是空中楼阁)
微服务带来的问题:更高的技术门槛
微服务从字面上来看就是把大服务拆分为多个微服务,看似简单。实际上采用微服务架构对整个团队要求的门槛是更高的:
DevOps 技术栈:使用微服务架构后,团队需要有 DevOps 精神,善用工具和自动化技术来解决问题。这要求了大家得懂更多不同以往类型的技术栈。
运维成本:拆分成微服务后,部署变得复杂,监控的复杂度也随之提高,要理解在微服务之间产生的复杂交互,需要优秀的诊断与监控工具,同时对运维技能的要求也提高了(如:必须了解多种技术栈)。而更多的服务也就意味着更多的运维工作,需要保证所有的相关服务都有完善的监控等基础设施,传统的架构开发者只需要保证一个应用正常运行,而现在却需要保证几十甚至上百道工序高效运转,这是一个艰巨的任务。
接口相依与版本控管:因为服务和服务之间通过接口沟通,当某一个服务更改接口格式时,可能涉及到此接口的所有服务都需要做调整,这要求了大家要对系统中各服务间的调用关系有一定的了解,并且开发接口必须遵循团队的纪律,不能随心所欲地变更接口。
分布式系统的复杂性:分布式系统意味着开发者需要考虑不可靠的网络/网络延迟、容错、消息序列化、同步/异步、版本控制、负载均衡等,而面对如此多的微服务都需要分布式时,整个产品需要有一整套完整的机制来保证各个服务可以正常运转。
实现微服务架构的方式
微服务是一种观念,没有制式的标准,可以按照组织架构或者产品特性来定制,也可以利用开源工具与框架以节省技术劳动工作。如果不采用任何框架,各个团队的服务提供方就需要各自实现一套序列化/反序列化、网络框架、连接池、收发线程、超时处理、和状态机等业务之外的重复技术劳动,造成整体的低效。所以,统一处理上述非关业务的技术劳动,是服务化首要解决的问题。
模块间的内部沟通:RPC 框架
RPC 框架是架构微服务化的首要基础组件,它能大大降低架构微服务化的成本,提高调用方与服务提供方的研发效率,屏蔽跨进程调用的各类复杂细节。
1. 客户端职责:序列化/反序列化、超时管理、连接池管理、负载均衡、故障转移、队列管理、异步管理等等。
2. 服务端职责:序列化/反序列化、超时管理、服务端收发包队列、I/O 线程、工作线程、上下文管理器、异步回调等等。
整体解决方案:微服务框架
Netflix 是微服务的先驱之一,在开源也做了许多贡献。如果不满足于 RPC 框架,可以参考 Netflix OSS 或者 Spring Cloud 这两套目前比较盛行的微服务框架。比较可惜的是目前比较成熟的框架多数是基于 JAVA 实现的。(更多工具/框架可以参考 Github 上面的微服务推荐清单)
一套完整的微服务框架,可能包括这些与业务不直接相关的组件:
分布式信息(Distributed messaging):透过轻量化的信息分发器连接分布式系统中的各个节点。
监控数据流与日志处理(Metrics/Trace/Logging):服务分析需要处理海量来自实时租户应用的通信追踪,进一步发现应用程序拓扑结构,跟踪当服务通过网络微服务时的单个请求等。
可插拔的序列化组件(Pluggable Serialization): 支持 JSON, XML, Plain text, Compact Binary 等格式的序列化/反序列化。
健康检查(Health Check):提供对应的健康检查接口供监控系统调用,便于第一时间发现异常。
HTTP RESTful API 与 RPC 接口:实现服务间接口调用的框架。
分布式配置管理(Distributed/Versioned Configuration):可在分布式系统中实现动态变更、配置同步,并以版本控管。
服务网关(Service Gateway):除了具备服务路由、负载均衡外,在外部访问最前端的地方产生前门保护作用,避免权限控制机制贯穿并污染整个开放服务的业务逻辑,破坏了微服务无状态的特点。服务网管包含:
1.权限控管机制
2.微代理/路由
3.服务注册/发现
4.负载均衡
断路器(Circuit Breakers):避免在微服务架构中个别服务出现异常时引起的故障蔓延。通常包含了以下两种机制:
1.限速(Rate Limiting)
2.容错(Fault Tolerance)
集群状态管理(Cluster State Management):除了状态查看之外,同时包含了服务故障发生后的领导选举机制(Leadership Election)。