一、前言
微服务(MicroServices)是一种架构风格,一个大型复杂软件应用由多个微服务和前端展示层组成。系统中的各个微服务可被独立部署,各个微服务之间是松耦合的。每个微服务仅关注于完成一件任务并很好地完成该任务。在所有情况下,每个任务代表着一个小的业务能力。 以往我们开发应用程序都是单体应用(可以理解为一个部署包包含了项目的所有功能),虽然开发和部署比较方便,但后期随着业务的不断增加为了能够达到响应业务需求,单体应用的开发迭代和性能瓶颈等问题愈发明显,微服务就是解决此问题的有效手段。 想要回答为什么要使用微服务架构的问题,首先应该了解一体化架构。
二、什么是一体化架构
一体化架构顾名思义,将应用各层打成一个包来部署。为了让代码正常工作,一体化应用的所有组件缺一不可。
以典型的3层传统web应用为例,该应用由用户界面、数据库、服务器端应用组成。这里的服务器端应用被称为monolith(一体化或者单体),包含表现、业务层、数据层。所有代码都存在于同一个代码库中。为了让代码工作起来,它被部署成为一个单元。任何一个小的改动变化,都需要重新构建和部署整个应用。
单体应用架构
二、什么是微服务架构
微服务架构是一种架构风格,整个应用被划分并设计为以业务域
为模型的松散耦合的独立服务。微服务中的“微”非常具有欺骗性,事实上它没有规定服务的规模有多小或多大。
这里的重点是每个独立服务都有一个业务边界,可以独立开发、测试、部署、监控和扩展,甚至可以用不同的编程语言开发它们。
微服务架构
在基于微服务的架构中,理想情况下
每个组件或服务都有自己的数据库。没有集中式数据库,我们可以根据需要为每个单独的微服务使用NoSQL、RDBMS或任何其他数据库,这也是让微服务真正独立的原因之一。
三、一体化架构的问题
或者说是微服务架构所解决的问题。
3.1难以扩展
一体化架构应用只能通过在负载均衡器后面放置整个应用程序的多个实例来进行水平扩展。如果应用中的特定服务需要扩展,则没有简单的选项。我们需要完整地扩展应用程序,这显然会造成不必要的资源浪费。
相比之下,基于微服务的应用程序允许我们根据需要独立扩展单个服务。在上图中,如果需要缩放服务B,则可以有10个实例,同时保持其他实例,并可以根据需要随时更改。
3.2交付时间长
一体化架构在单个应用的任何部分/层中进行的任何更改都需要构建和部署整个应用程序。个人开发人员还需要下载整个应用程序代码来修复和测试,而不仅仅是受影响的模块,这就影响到了持续部署的效率。
而在微服务架构中,如果仅在一百个微服务中的一个中需要改变,则仅构建和部署改变的微服务,没有必要部署一切。我们甚至可以在短时间内多次部署。
3.3应用复杂性
过去,随着应用规模的增长(功能、功能等),团队也会相应扩张,应用很快就就会变得复杂和交织在一起。随着不同的团队不断修改代码,维护模块化结构慢慢变得越来越困难,并慢慢导致像意大利面一样交织的代码。这不仅会影响代码质量,还会影响整个组织。
在基于微服务的应用中,每个团队都在单独的微服务上工作,代码会有序很多。
3.4没有明确的所有权
在一体化应用中,看起来独立的团队实际上并不是独立的。它们同时在相同的代码库上工作,严重依赖于彼此。
在基于微服务的应用中,独立团队处理单独的微服务。一个团队将拥有一个完整的微服务。工作的明确所有权明确控制服务的一切,包括开发、部署和监控。
3.5故障级联
如果没有正确设计,一体化应用的一部分失败可能会级联并导致整个系统崩溃。
在基于微服务的架构的情况下,我们可以使用断路器
来避免这种故障。
3.6Dev和Ops之间的墙
开发团队通常会进行开发、测试,一旦部署,就会将维护和支持的所有权交给运维团队,应用此时与开发团队无关了,而运维团队需要努力在生产环境中支持一体化架构应用。
在基于微服务的应用中,团队的组织理解为“构建它、运行它”,开发团队继续在生产中拥有该应用。
3.7陷入某种技术/语言
使用一体化架构,意味着被某种已实现的技术/语言锁定。如果需要更改技术/语言,则必须重写整个应用程序。
使用微服务,每个服务可以根据需求和业务使用不同的技术或语言实现。任何改变服务技术/语言的决定都只需要重写该特定服务,因为所有微服务都是相互独立的。
3.8支持微服务的正确工具/技术的可用性
几年前,我们还没有适当的工具和技术来支持微服务。但自从Docker容器和云基础设施(特别是PaaS)向大众提供服务以来,微服务正在大规模采用,因为它们提供了我们所需的“自由”,而无需进行传统的配置程序。
四、认识微服务小结
4.1 微服务架构优点
- 每个服务足够内聚,足够小,代码容易理解,开发效率高
- 服务直接可以独立部署,让持续部署成为可能
- 每个服务可以各自进行水平和垂直扩展,而且每个服务可以根 据需要部署到合适的硬件和软件上
- 容易扩大开发团队,可以根据每个组件组织开发团队
- 提高容错性,一个服务的问题不会让整个系统瘫痪
- 系统不会长期限制在某个技术栈上
- 降低成本。可尽量复用已有功能,避免重复造轮子。可以大大减少项目建设过程的调研、设计、开发、测试、运维的成本。
- 易于开发与维护:一个微服务只关心一个特定的业务功能,所以他业务清晰,代码量较少。独立开发部署服务。
- 局部修改容易,所以开发上线速度和灵活性
- 更高的代码质量
- 获得围绕业务功能创建/组织的代码
- 提高生产力。开发人员专职自己的微服务开发,对业务和代码都熟悉。
- 更容易扩展
- 技术栈不受限:每个服务可用不同的开发语言
- 按需伸缩:可根据不同的需求,实现细粒度的拓展。
- 为多端化服务(多种客户端,例如:浏览器,车载终端,安卓,IOS等等)打下坚实的基础。
- 持续集成和持续交付的应用大大提高生产力。提高开发人员生产力, 开发人员只需要将代码推送到代码仓库即可。该代码将被集成,测试,部署,再次测试,与基础功能(Maven依赖)合并,经过质量审查,并准备以极高的信心进行部署。减少了手动操作的环节,极大的提高了重复工作的效率。
4.2 微服务的缺点
- 运维的技术复杂度和相应的运维成本(测试、变更、部署)
- 必须建立开发运维一体化机制Devops
- 必须有完备的监控手段和自动化恢复手段
- 分区数据库可能带来的业务数据同步与一致性问题
- 微服务的接口将成为变更的敏感位(尽量保持接口的稳定性,不能经常变化入参和出参)
- 运维要求高:服务更多意味着运维的投入,单体应用只需保证一个应用的正常运行,而在微服务中,需要保证几十上百的服务正常运行与协作。服务越小,独立性更好,但是相应的服务数量就越多。每个服务都需要独立的配置、部署、监控、日志收集等,成本呈指数级增长。需要更高的自动化运维策略。
- 微服务应用作为分布式系统带来了复杂性。当应用是整体应用程序时,模块之间调用都在应用之内,即使进行分布式部署,依然在应用内调用。可是微服务是多个独立的服务,当进行模块调用的时候,分布式将会麻烦。
- 多个独立数据库,事务的实现更具挑战性。
- 依赖管理复杂,
- 测试微服务变得复杂,当一个服务依赖另外一个服务时,测试时候需要另外一个服务的支持。
五、直观体验一下微服务
5.1 微服务功能
该微服务实现如下功能:为vue开发的小程序及h5页面提供数据接口(就是HTTP的访问地址),可以查询编程语言的服务。 实现了如下的微服务接口:
- 通过筛选条件查询:https://localhost:8888/v2/vue/api/programLanguage/getByName?language_name=P
返回:
["PHP","Python"]
- 无条件的全量查询:https://localhost:8888/v2/vue/api/programLanguage/getAll
返回:
["C","vue","java","PHP","Python","C "]
- 另外一种条件查询方式:https://localhost:8888/v2/vue/api/programLanguage/getdetail/C
返回:
{"laguage_name":"C","star_number":10,"desc":"万物之源C语言。C语言于1969年至1973年之间由AT&T公司旗下贝尔实验室创建完成,用于构建Unix操作系统。C语言是一种通用型命令式计算机编程语言,其支持结构化编程、词汇变量范围与递归,同时亦是套能够预防各类未预期操作的静态类型系统。其最初构建目标在于编写系统软件。"}
5.2 代码示例
以第一个接口https://localhost:8888/v2/vue/api/programLanguage/getByName
代码为例。
搜索公众号后端架构师后台回复“架构整洁”,获取一份惊喜礼包。
代码语言:javascript复制 @ApiVersion(2) // 加入接口url的版本控制,http://localhost:8888/v2/vue/api/programLanguage/getByName?language_name=C
@RequestMapping(value = "/programLanguage/getByName")
public List<String> getByName(@RequestParam String languageName) {
List<String> filterList = languageList.stream().
filter(s -> s.toLowerCase().contains(languageName.toLowerCase())).
collect(Collectors.toList());
return filterList;
}
在IDEA启动该项目后,浏览器访问地址查看效果。
访问效果
在Postman中调用微服务接口,返回的Json数据被格式美化了,更易读些。
Postman
六、微服务拆分的策略
6.0 微服务拆分原则
1、单一职责、高内聚低耦合:微服务边界内的业务能力应单一,微服务之间低耦合度; 2、微服务粒度适中:以完成一个完整的业务需求来拆分微服务,不是越细越好; (1)以业务模型切入:以业务模型中的业务活动为基本单元进行拆分,即微服务边界最大不能超过业务活动; (2)演进式拆分:在实际应用过程中根据负载情况进行微服务拆分的细化,实现性能升级; (3)读写分离:把负责产生与维护数据的业务功能和负责查询搜索数据的业务功能分开。 3、避免环形依赖与双向依赖:可应用服务上移、服务下移等手段避免生成环形依赖,应用回调等手段避免生成双向依赖; 4、考虑团队结构:以完成一个完整的业务逻辑所需求的前、中、后端来拆分,便于开发团队分别实现。
6.1 错误策略
如果彻底颠覆原有系统,投入巨资启动一个“更大规模”的微服务系统来替换会导致延期(几年才可能带来价值)、风险(超支,或被取消)。
6.2 正确策略
尝试改变,而非颠覆。试拆一个小系统--->培养能力,积累经验--->更大规模的推广的循序渐进方式进行。
微服务拆分的核心需求:高内聚,低耦合,尽可能的减少微服务间的调用,尽可能的减少分布式事务; 微服务应该足够小,能够分配一个团队(5人左右)去实现,但也不能过细。
6.2.1 整体思路
先业务分解,再领域建模。
6.2.1.1 围绕限界上下文边界
微服务应该按照业务能力而不是技术能力进行边界划分。微服务应优先以限界上下文边界进行划分。需注意:
- 理想情况下限界上下文与微服务为1:1
- 考虑到其他原则和现实约束,实际微服务的划分有可能在限界上下文图的基础上进行合并。
微服务拆分的底线是不能打破聚合
,打破聚合会破坏事务一致性和业务约束。
6.2.1.2 对齐不同业务变化速率/相关度
微服务架构的目标之一是实现独立的部署和发布,从而更灵活地响应业务和需求变化。需要在限界上下文的基础上,考虑不同业务的变化速率,将业务速率相近的放入一个微服务中。 需注意:
- 绝对速率:有些业务只是建设阶段变更频率较高,维护期相对稳定;有些业务在可预见的将来有持续需求,变化频率较高。需要将二者区分放入不同的微服务。
- 相对速率:软件系统的响应力最终体现在需求的端到端交付周期。如果两个业务的相关度比较高,变化速率接近,一个业务需求到来总是需要同时修改这两个上下文。需要将二者放入同一个微服务。
6.2.1.3 考虑系统非功能性需求
微服务架构的优势之一是能适配系统的弹性伸缩要求,更灵活地适应业务量的增长。在设计微服务边界时需要考虑非功能性需求:
- 伸缩性:有些业务在容量上有极大的弹性伸缩需求(如秒杀),需要设计为独立微服务
- 可用性:有些业务在可用性上有极高要求(如交易撮合),需要设计为独立微服务
- 安全性:有些业务在(数据)安全性上有独立的要求,需要设计为独立微服务
- 其他非功能性需求:微服务视角主要关注伸缩性、可用性和安全性,但其他非功能性需求也是设计一个系统需要考虑的
6.2.1.4 其他设计约束
- 复杂度 微服务在调测、部署、运维等方面会带来额外的复杂度和开销。将微服务粒度拆分过细反而是反模式。需要考虑需要解决问题的复杂度,将相对简单的服务合并在一起。
- 团队结构 建议一个微服务由一个独立的2 pizza team(7 -2个人)进行端到端的开发和维护。团队规模过大或过小有时也是服务拆分粒度不合理的迹象。此外需要考虑团队成员的能力搭配。
- 技术异构 有些遗留系统,业务软件包(如ERP)或技术组件(如搜索引擎)形成了天然的服务边界,是很难被打破成微服务。
6.2.1.0 战略设计阶段详解
电梯演讲的模板中,明确了七个方面,用以引导团队去思考产品本身核心价值及愿景所在,最后可以汇成一句流畅精干的描述语句。
6.2.1.1 业务目标梳理
从用户视角出发,了解用户需求,确定如何提供响应切实到每一类用户群的产品与服务的筹划。
价值定位画布 价值定位画布是价值定位设计里的经典实践,描述了产品提供的价值如何与顾客需求之间建立联系,以及为什么顾客要去买你的产品。 通过分析用户工作中的成就和痛点来找到需要提供的服务,明确产品存在的价值和意义。
价值定位画布
以内部员工的培训系统为例,进行业务目标梳理。
6.2.1.2 场景梳理方法
是针对核心用户及顶层服务的一种定性分析。同时也是从用户视角对问题域的探索,产出问题域中存在的需要支撑的场景分类及典型场景,用以支撑领域建模阶段。
搜索公众号Linux中文社区后台回复“命令行”,获取一份惊喜礼包。
场景梳理步骤 步骤1: 选定由价值定位画布中提炼出的一块问题域/业务; 步骤2: 参与者针对所选定的问题域,发散思考其中存在的各个服务场景,可以采用贴便签纸的方式; 步骤3: 对发散所得的服务场景进行收敛整理,得到场景分类清单。
场景梳理方法
6.2.1.3 流程梳理和领域建模示例
通过对业务和问题域进行分析,建立领域模型,向上通过限界上下文指导微服务的边界设计,向下通过聚合指导实体的对象设计。
领域建模
6.2.1.4 限界上下文设计示例
6.2.1.5 确定微服务拆分
围绕限界上下文调整。
- 设计因素1:围绕限界上下文边界, 理想情况下限界上下文与微服务为1:1, 微服务拆分的底线是不能打破聚合,打破聚合会破坏事务一致性和业务约束。
- 设计因素2:考虑系统非功能性需求:安全性、伸缩性等
- 设计因素3:其他设计约束:复杂度、团队结构、技术异构
6.2.1.6 分层模型
分层模型是领域驱动设计里分层技术架构的体现。结合微服务特点,根据实际业务和技术情况定义整体架构的分层情况,从而降低设计复杂性。需要考虑每个具体的微服务内部的分层结构。
6.2.1.7 需求变更
七、微服务拆分模式
7.1 绞杀模式 (Strangler)
绞杀者模式类似建筑拆迁,在新建筑分阶段建设完成入住后,分步拆除旧建筑物。
“绞杀者模式”是在遗留系统外围,将新功能用新的方式构建为新的服务 。通过在新的应⽤中实现新特性,保持和现有系统的松耦合,随着时间的推移,新的服务逐渐“绞杀”老的系统。以此逐步地替换原有系统。适合于那些老旧庞大难以更改的遗留系统。
“绞杀模式”,有一个别名,叫做“停止挖坑”,意思就是不要在当前的系统里继续增加功能,而是采用松耦合的方式增加新的功能,使得老的功能慢慢被绞杀掉。这种模式的前提就是要确认遗留系统不再进行功能新增
,只做 Bug 修复和例行维护。这样带来的变更风险最小,但演进时间较长。
绞杀模式逐步替换老系统
7.2 修缮者模式(Rehab)
修缮者模式类似文物修复,将存在问题的部分建筑重建或者修复后,重新加入到原有的建筑中,保持建筑原貌。
“修缮者模式”是在既有系统的基础上,通过剥离新业务和功能,逐步“释放”现有系统耦合度,解决遗留系统质量不稳定和 Bug 多的问题。就如修房或修路一样,将老旧待修缮的部分进行隔离,用新的方式对其进行单独修复。修复的同时,需保证与其他部分仍能协同功能。修缮模式适用于需求变更频率不高的存量系统。
一运行时间超过5年的遗留系统,其中某个模块需要升级,我们单独把这个模块微服务化到新系统,然后老系统开始使用新开发的微服务。
另外一个注意点就是:老的应用和数据库一起迁移到新的微服务当中去!
修善者模式
八、微服务改造的切入点
微服务接口开发难易程度由低到高: 数据源维度 第三方接口-->全新业务全新数据库-->老业务简单数据库结构-->老业务复杂数据库结构-->老的第三方封闭系统。 接口类别维度 单一查询接口-->全新业务涉及增删改查接口-->老业务数据写入接口。