微服务与DevOps实践技术架构与组织架构.docx
《微服务与DevOps实践技术架构与组织架构.docx》由会员分享,可在线阅读,更多相关《微服务与DevOps实践技术架构与组织架构.docx(22页珍藏版)》请在冰豆网上搜索。
微服务与DevOps实践技术架构与组织架构
微服务与DevOps实践
技术架构与组织架构
9.1 概述
首先,我们来看看微服务的定义:
微服务是一个界限明确、高度封装、松耦合、可以独立部署和独立扩展的服务应用组件,如图9.1所示。
微服务架构基于SOA和领域驱动设计(DDD)构建,其主要目的包含以下三个方面:
开发的敏捷性、部署的便利性及明确的可扩展性。
图9.1
其次,微服务和传统的SOA有什么异同:
从设计原则上来讲,微服务架构遵循SOA的设计原则。
小的、可重用的服务并不一定是微服务,微服务架构强调敏捷、独立开发、独立部署、独立扩展,重用在某种程度上会影响敏捷性。
微服务架构为了实现其敏捷特性,在SOA架构约束的基础之上又添加了新的约束,微服务之间不能互相依赖,因此要求微服务能够独立部署、独立扩展,微服务之间的依赖越少越好。
微服务中的一个服务只实现一个独立的特性。
对于微服务而言,尽量不要为外部应用发布代码级API,可以通过服务调用或者事件解决依赖问题。
微服务中的服务之间最好通过异步事件交互。
微服务中的每个服务都拥有自己独立的数据。
另外,微服务架构的优点有很多:
·敏捷性:
微服务不仅可以提高开发的敏捷性,还可以加速持续部署(CD),从而使开发团队能够尽快部署新的功能。
·减少风险:
由于每个微服务都是比较小巧并且独立部署的,因此可以减少每次部署的风险。
·适合分布式开发:
微服务之间依赖程度低,因此可以更加灵活地独立开发。
·技术灵活性:
微服务之间的耦合程度低,因此技术团队可以根据不同的特点选择最合适的技术栈,例如利用不同的编程语言解决不同的领域问题。
·可扩展性:
一个应用有许多微服务组成,每个微服务可以根据需要独立扩展,而无须对整个应用做整体扩展。
鉴于以上优点,我们来看一下微服务的本质。
微服务在本质上是一种服务实现模式,微服务可以实现一个应用中独立的业务功能,而不是整个应用或者模块,因此,微服务其实是将单体应用的复杂性从程序内部转移到了服务组件之间,这就对微服务抽象的粒度有一个比较高的权衡要求。
对于微服务的粒度,大家讨论的最多,这也是实践过程中经常遇到的令人纠结的问题。
实际上,如果抽象粒度太细,就需要大量的服务编排来满足业务场景,而大量使用服务编排就可能会导致微服务退回到SOA模式(服务编排是类似BPM或者ESB的系统);而如果服务粒度太粗,则不利于开发的敏捷性和部署的便利性。
微服务的主要目的是实现敏捷,微服务架构的主要构建方式是采用领域驱动设计,而领域驱动设计主要包括如下五个概念:
·BoundedContext
·ContextMap
·EventSourcing
·CQRS
·BASE
微服务的粒度并没有一个明确的界限,也就是说“尺寸”是相对的。
通常,微服务的粒度和对敏捷性的要求密切相关,往往粒度越细其敏捷度也越高,但是并不是所有的应用对敏捷的要求都那么高,也就是说在微服务的设计和实现过程中,对于粒度的大小可以做适当的调整。
对于想要采用微服务架构的团队,首先要考虑如下几个前提条件:
现有软件架构是否已经服务化或者按照系统功能做了模块化切分。
团队的敏捷成熟度如何,是否有足够的DevOps经验。
开发团队是否有足够的架构设计能力来适应采用微服务所带来的设计方法、模式以及技术架构上的巨大差异。
DBA团队是否有能力和意愿制定新的数据管理模式——将数据管理的方式由原来的集中管理转变为去中心化管理。
运维团队是否有足够的能力为微服务提供全新的环境管理工具、部署工具和监控工具。
综上所述,我们建议微服务的演进线路如图9.2所示。
图9.2
从单体应用或者传统分层架构的应用向服务化过渡,通过封装和组合等方式提供对外发布接口的能力,从而提升应用的可访问性。
通过重构将Domain-Level(领域层面)的功能模块转变为可以独立部署的服务,从而提升整个应用的敏捷性。
我们将这种可独立部署的服务称为MiniService,其粒度比微服务粗,因而抽象难度比较低,但是也能在一定程度上获得微服务所带来的敏捷性提升的好处,但是对DevOps的基础设施等要求没有微服务高,因此建议没有微服务经验的团队可以从MiniService开始尝试。
通过Feature-Level(特性层面)的抽象,根据单一职责原则将MiniService拆分成微服务,从而获得更高的扩展性和敏捷性。
9.2 融数数据微服务的架构选型
我们在构建微服务体系的过程中,经历了技术选型、技术验证、引入开源实现及完全自研等一系列的过程,其中也走过不少弯路,现在回想起来,感触良多,在这里跟大家分享一下,希望能够有所帮助。
对于技术选型,我们不希望重复发明轮子,也不希望完全受制于开源的实现,所以在技术选型时遵循如下原则:
图9.3
考察社区热度、架构成熟度、学习曲线、可维护性,如图9.3所示。
尽量照顾现有服务的开发方式和框架的使用习惯,不对目前的研发团队造成太大的冲击。
能够给程序员提供何种能力?
我们的期望是:
·方便开发
·方便迁移
·多协议支持
·多语言支持
·方便监控
·方便运维
我们比较了可能用到的一些开源服务框架,并对它们的功能点进行了评估,如图9.4所示。
图9.4
由于之前团队采用RESTEasy+SpringBoot的方式实现服务,不希望对现有的系统和产品产生太大的影响,因此决定服务框架首先需要支持REST服务,又由于Netflix提供了比较全面的解决方案,并且SpringCloud在遵循cloud-native原则的基础上对Netflix进行了比较友好的封装,因此初步的结论是可以基于SpringCloud进行二次开发,封装我们自己的微服务框架。
经过半年的实践,我们发现Netflix技术栈的思想不错,但是很多实现并不是特别完美,例如:
Zuul提供的EdgeService及API网关是完全基于HTTP协议的过滤器实现的,基于HTTP协议的同步调用方式会带来性能的损失,并且缺乏长连接的推送及多路复用的能力。
Zuul不能满足RPC调用的需求,而在大规模团队协作的过程中,契约优先的开发方式优于代码优先的开发方式,因此对于应用内部交互来讲,RPC框架从管理和协作的角度要优于REST服务。
基于Eureka的服务注册依然是中心化治理的方式,与传统基于ZooKeeper或者etctd/consul的治理方式一样,中心化的治理会给服务的部署带来非常大的阻碍,需要对不同的环境配置不同的中心注册服务器,服务的版本管理及服务注册信息的同步也会带来问题,更加糟糕的是,在某些服务不正常的情况下,客户端需要进行大量的判断,防止出现治理风暴等问题。
因此,我们后来果断转换思路,对之前的微服务框架进行了彻底的改造:
1、基于对gRPC的封装,构建ServiceProvider的全新实现,替代REST服务。
2、通过Proxy方式,实现gRPC与REST服务的相互转换,保持系统兼容性。
3、进行去中心化治理,通过Proxy来实现端点治理,将客户端治理变为服务端治理。
4、通过扩展并集成Zipkin实现服务链路监控和运行时拓扑收集。
5、构建Endpointinventory服务,以semanticversioning的方式管理服务版本。
6、将Proxy作为服务部署的执行端点,通过VIP绑定,透明化客户端寻址工作。
利用Proxy提供轻量级的负载均衡、流量控制及灰度发布的功能。
9.3 设计思想
融数数据微服务架构的整体设计思想如图9.5所示。
图9.5
该设计思想的具体说明如下。
面向运维的设计,配合DevOps平台,提供服务生命周期管理及易于被监控的能力。
面向开发者,合理封装。
开发者无须了解gRPC的具体实现。
基于IDL的契约优先的开发方式。
提供完善的测试框架和Mock工具。
提供完善的易于监控的能力。
9.4 总体架构
融数数据微服务总体架构(Graeae)如图9.6所示。
图9.6
9.4.1 总体架构的特性
融数数据微服务总体架构有如下特性。
Graeae架构与协议无关。
该架构可以基于Netty4、线程模型及bufferpool进行调整,以减少GC压力并通过线程切换提升性能协议。
遵循protocolbuffer协议,可以做到通用性强、序列化性能好、压缩效率高。
语言中立,目前整个架构支持Java、Python和Go三种语言的开发。
引入了熔断器机制、流量控制、服务治理。
基于Proxy和PaaS平台进行分布式治理监控。
使用集成Zipkin的调用链监控,以及基于Pinpoint的APM监控。
对于该架构而言,直接调用的性能好于反射调用,且使用Netty4线程模型优化。
9.4.2 具体实现
服务提供者Endpoint基于责任链模式(如图9.7所示)对gRPC进行封装,对屏蔽了gRPC框架的事件驱动采用同步调用方式,方便业务迁移。
图9.7
Endpoint封装了脚手架工具,提供基于ProtoBuf的IDL接口定义语言,使用契约优先的方式定义服务,并可以自动生产服务端和客户端的代码框架。
代码优先意味着实现简单,能够快速执行。
问题也很明显,可能和某个具体语言绑定,面对多语言环境,其打通成本较高。
契约优先的中立性提供了一个中间桥梁,让面向多语言成为了可能,基于契约的元信息为后续治理和演进提供了入口点。
缺点是需要引入契约语言的学习,并与多语言进行适配。
Endpoint采用生命周期自管理的方式,提供容器化的生命周期管理API和相应的SPI,方便扩展及与DevOps工具结合。
外部管理(如Tomcat)让用户不用关注自身的起始、消亡,但带来的不足是对生命周期的管理相对减弱、部署的依赖管理扩散。
进程自治可以加强其对自身生命周期的管理,高内聚,不将依赖扩散。
在一定程度上能够带来部署的便利及不同部署环境的适应性(如云环境)。
为优雅关闭提供切入点,进一步增强对系统的可控性。
从对环境适应性和对生命周期的管理能力考虑,进程自治有着不可忽略的优势。
Endpoint将配置与代码分离,提供多种方式的配置覆盖能力,使得改变配置无须重新
部署。
配置和代码一起进行的优点是使开发变得简单,但不足也很明显,即面对不同的环境需要部署多套代码,复杂度增加。
配置和代码分离后的优点是真正做到了只部署一套代码。
配置信息按环境独立配置,不受环境制约,可随时调整。
集成SpringBoot,提供自定义注解方式,能够快速启动服务,方便开发。
//服务提供方
publicclassSmsTemplateApplication{
publicstaticvoidmain(String[]args){
newSpringApplicationBuilder().sources(SmsTemplateApplication.class)
.web(false).showBanner(false).run(args);
}
}
//服务实现
publicclassSmsTempletServiceImplimplementsSmsTemplateService{@Autowired
privateSmsTempletDaosmsTempletDao;
@Transactional
publicDeleteReplydeleteById(IdRequestreq){returnSmsTemplateReply.newBuilder().build();}
}
利用Proxy部署和设置服务治理端点,进行分层治理,如图9.8和图9.9所示。
图 9.8
图9.9
用Proxy配合部署方式来兼容semanticversioning的版本管理,版本格式有主版本号、次版本号、修订号、版本号递增同时需要备注,规则如下:
主版本号:
当做了不兼容的API修改时,需要递增主版本号。
次版本号:
当做了向下兼容的功能性新增时,需要递增次版本号。
修订号:
当做了向下兼容的问题修正时,需要递增修订号。
先行版本号及版本编译信息可以加到“主版本号.次版本号.修订号”的后面,作为延伸。
9.5 对微服务的支撑
融数数据DevOps体系主要解决的问题是有效地识别和管理元数据,以便高效、自动化、高质量地将系统动态组装并运行起来,该体系架构如图9.10所示。
下面结合图10.10对DevOps体系做几点说明。
抽象了部署的最小单元——包。
每个微服务都是由包及其配置组成的,前面提到,服务框架做了代码和配置分离,因此这里可以将包和包的配置分离,提供运维便利性。
逻辑环境包括了微服务、微服务配置及运行微服务所依赖的Host或者HostGroup。
版本化逻辑环境,方便回滚。
该体系中的服务组件=(可运行代码+配置)&(依赖+配置)&(基础设施+配置),如图9.11所示。
图9.10
图9.11
从部署的角度讲,无论包还是包的集合(往往是组成一个独立业务域的独立功能)都需要依靠版本才能进行整体部署,可以部署在不同的逻辑环境上,也可以部署在同一个逻辑环境上,如图9.12和图9.13所示。
9.6 DevOps平台总体架构
DevOps平台通过构建平台将代码编译成物理二进制包,再使用元数据对这个二进制物理包进行描述,形成逻辑包,且将部署、依赖和二进制包本身的元数据统一存储到元数据服务中,最终通过统一环境管理平台读取元数据,按需拉取相应的逻辑包对应的物理包,放置到目标环境的相应目录下。
之后通过进程管理调用相应的服务启动相关微服务,在部署的过程中,由逻辑环境管理绑定VIP到微服务的Proxy上,将信息注册到serviceinventory服务上,这样Proxy和服务Endpoint等的运行时信息(例如IP、端口、版本等)就可以被收集到serviceinventory服务上。
在真正调用服务时通过内建的Zipkin可以收集服务调用链的情况,并同inventory服务的元数据信息进行匹配,便可以准确地知道服务调用的关系,从而达到真正的分布式治理;而客户端调用只需要知道服务所在的逻辑环境信息就可以自动完成服务寻址,这个服务地址就是Proxy绑定的VIP地址,从而简化客户端调用。
DevOps平台要做的就是保证Proxy的健壮性,由于Proxy只是简单的反向代理,不存储服务状态,因此只需要做故障漂移就可以了,如图9.14所示。