微服务架构优势及挑战
在前一节中,介绍了大致的软件服务架构的演变过程及原因,从单体架构、分布式架构、SOA架构,到如今流行的微服务架构。每一种架构模式,都有其问题背景,也存在一定的局限性,它可能较好地解决了当时软件领域存在的一些问题,但是放在今天却并不一定适用。
无需感叹“被淘汰”,实际上也并不是真的被淘汰了,哪种方案更好取决于带解决问题的规模。而一个好的架构师,他应该能够努力降低解决问题的复杂度,而非简单问题复杂化。所以说到架构演进,我们应该更关注“发展”,想想当今互联网如何颠覆、便利了我们的生活,这背后待解的问题规模也从简单到复杂,服务架构不断演进是必然趋势。
本节将重点介绍下微服务架构的优势,以及所面临的挑战。
微服务架构
微服务架构(Microservices Architecture),它是一种架构模式,指导我们如何将一个应用程序合理分解成一系列的服务,并通过服务之间的协作来实现期望的功能。
分解出的微服务应当满足:
- 高度的可维护性、可测试性
- 松耦合
- 独立部署
- 通过组合实现业务功能
- 可由不同团队负责
微服务架构,使得一个庞大复杂的应用也可以实现快速迭代、频繁和可靠的交付,它也赋予了开发团队可以选择、更新技术栈的能力。微服务架构,将敏捷(Agile)、模块化(Modular)推向了一个新的高度。
微服务模式语言
软件开发领域没有银弹,微服务架构也不是,它也存在很多的问题。而且,当选择使用微服务架构之后,会引入一些必须要面对、要解决的问题,否则,没什么收益反受其累。
软件领域令人着迷的一点就是,知识的沉淀分享。模式(Patterns)正是业界先驱、开发者们经历了实践检验后所沉淀下来的一些方法论,我们可以学习、实践这些模式以轻松应对不同的场景,没必要自己踩过一波雷再自己总结出血的教训。
微服务架构模式语言,其实指的是一系列微服务架构设计开发中常采用的一些方法论(模式)的集合。
模式语言主要想达到两个目标:
- 帮助决策目标场景是否适合采用微服务架构;
- 帮助在业务中成功地应用微服务架构;
传统的单体架构模式对不少应用程序来说,仍是一个不错的选择。当然对于规模很大、很复杂的应用程序或软件系统,单体架构确实存在一些问题,微服务架构会是一种更好的选择。而在使用、落地微服务架构的过程中,面向模式的软件架构设计(Patterns-Oriented Architecture Design),应该是每一位开发者所掌握的。
之所以强调软件架构要解决的问题场景,以及它带来的好处,而不是鼓吹微服务架构取代了谁,这也源于对真实线上项目的观察、思考。你能想象有很多知名游戏后台是采用的单体架构或者分布式(单体)架构吗?理解业务、技术架构的差异,加深对“解决问题复杂度”的理解,是我过去常思考的问题,共勉 :)
微服务的优势
服务端开发
从这个背景出发,假设我们现在要开发这样一个企业级应用程序,它的服务端要支持不同类型的客户端访问,包括桌面、移动端浏览器及移动端native程序。服务端程序要暴露一个API给3种不同的客户端来访问,也可能会通过RPC、Web Services或者消息broker来与其他程序进行集成。服务端收到客户端HTTP请求执行对应的业务逻辑、访问数据库、与其他系统交换消息、返回处理结果。服务端程序内部也可能集成了一些组件来完成特定功能。
那现在要解决的问题已经明确了,我们还需要考量下几个不同维度的指标。
“受力分析”
the forces:
物理学中对一个物体或系统进行受力分析,为了判断出合力的方向、大小,必须对过程中受到的各个分作用力进行分析,称之为受力分析。通过受力分析可以明确物体或系统的运动趋势。
在软件架构设计中,也有类似于物理学受力分析的过程。当我们明确了要解决的问题之后,就需要重新审视所关心的各个指标(称之为force),我们的架构是否能满足所有指标,还是要在不同指标之间做权衡。实际情况是,几乎所有的选择都是在权衡利弊,舍与得。
我们有哪些需要关心的指标呢?
服务端程序的开发,会由团队的诸多开发者共同维护;
新的团队成员,需要能够快速产出;
这意味着系统理解的复杂度、修改的风险要可控;
服务端程序本身,应该易于理解、修改;
必须能够分布式部署,满足可伸缩性、可用性要求;
允许方便地尝试新兴技术(如框架、编程语言等等);
其他,如对CI/CD更友好;
有些forces是跟团队技术栈、代码可维护性、研发效能相关的,有些是与运营质量相关的,也有些是与新兴技术相关的,这才是一个大公司中所面临的真实研发场景。几乎总是不可避免地要在上述forces中进行权衡、取舍。
传统架构
如果我们采用传统的单体架构的话,所有的程序逻辑都放在一个应用项目中,部署的时候打包成一个程序,并且支持多机分布式部署,并通过负载均衡来提高系统并发处理能力。
举个电商网站的例子,这个程序接受用户订单、检查库存、用户现金余额、运输货物,大致可以分成这么几个组件,StoreFrontUI是前端展示,AccountService是账户服务,InventoryService是库存服务,ShippingService是物流服务,这几个组件联同业务逻辑实现都在一个项目中实现,整个应用打包成一个WAR包,在Apache Tomcat中部署。
单体架构的好处是,易于开发、测试、部署。但是也存在明显的问题,参考我们上面列出的forces。
- 随着业务扩张业务场景也更复杂,程序规模、团队规模也将更大,了解系统整体逻辑会变得更困难,新人难以快速上手、老人也觉得难维护,如何保证交付质量难上加难。这样也难以切换、应用新技术,难以发挥不同技术栈开发人员的优势。最终会导致整体研发效率、研发质量下降。
- IDE负载过高,随着程序规模扩大,IDE建立索引、编译构建等等都会出现严重的效率下降的问题,最终也会拖慢研发效率。
- Web容器过载,假如我们是采用Java Web开发,最终构建成一个WAR包形式,程序规模变大势必会导致Web容器启动过慢,开发、测试效率也会下降,降低整体效率;
- 持续集成困难,可能修改了一个组件的代码,但是单体架构却要完整部署整个应用。慢只是其中一方面,如果修改后的组件有问题还会影响到整个应用本身,如导致应用无法启动,影响到团队其他成员开发、测试等。最终会导致大家不愿意去频繁地更新,但是对UI开发来说频繁更新应该是很常见的。
- 应用伸缩困难,这种单体架构只能朝一个维度来伸缩,就是你可以多部署几台机器多运行几个实例,来提供更高的并发处理能力。但是数据层面的伸缩却不理想,因为所有的程序实例都访问相同的、所有的数据,实例内部的cache不会很有效,可能增加了内存占用的同时还引发更多的IO操作。还有,就是程序内部不同组件不能更细粒度地去伸缩,比如有的模块是计算密集型的,有的模块是IO密集型的,这些组件对需要的资源类型、数量都不同的,单纯增加运行实例数量并不能实现很好地伸缩。
- 团队协作困难,当程序规模大了之后,自然而然地我们想对应用按照层次、功能进行分类,以交由不同的团队负责,比如UI展示层、逻辑层、数据层、各种组件适配等,但是因为是单体架构,怎么拆分让不同团队独立开发呢?很难实现。这样也会导致整体研发效率下降,而且要保证单体架构功能的完整性,需要协调多个团队的研发任务、进度,协作花费的时间也会大大增加。
- 被捆绑在了特定的技术栈上,难以发挥团队不同技术栈开发人员的优势,也难以将业界新兴技术引入进来。比如一开始选择了使用Java开发,但是后面发现go支持协程,同步编码异步高性能运行,就是发现go再香这个时候也难以在单体架构的程序中采用了,除非重写整个应用程序,但是工作中以业务为先,这几乎是不可能的。
微服务架构
微服务架构,就可以比较好地解决“受力分析”中存在的各种问题,相比单体架构具有明显的优势。
还是传统单体架构中的电商网站的例子,接受用户订单请求、检查库存、用户现金余额、运输货物,程序中包含了几个组件,界面展示StoreFrontUI,逻辑相关的账户管理AccountService、库存管理InventoryService、物流管理ShippingService。
在微服务架构下,可以将上述几个相对独立的组件微服务化,以RestAPI接口的方式对外提供服务,每个微服务也有自己的专用的数据库。通过API网关可以对外部请求做一些鉴权、频控、适配、转换或者业务逻辑组合等相关的操作。
通过微服务架构的方式,可以做到:
- 持续集成、持续部署更方便了,尽管系统规模很大、很复杂,也没有明显影响
- 改善了可维护性:每个服务粒度都比较小,非常容易理解和修改;
- 更好的可测试性:服务粒度小,方便快速测试;
- 更方便的部署:服务可以独立部署,相互之间不影响;
- 各个团队可以独立开发:可以更好地组织多个团队独立开发,也方便不同团队维护特定服务;
- 每个微服务本身粒度都是比较小的
- 方便开发者(或新成员)快速了解服务逻辑;
- IDE加载的代码量少了,响应更快,编码效率更高;
- 应用启动速度更快,开发人员开发、测试、部署更快速;
实现了故障隔离
如某个微服务存在内存泄露问题,那么现在也只会影响这一个服务本身,并不会影响到整个系统,这个微服务本身出现问题可以通过快速回滚该服务、重启该服务来解决。但是如果是单体架构,某个组件内存泄露会导致整体应用不可用。
避免了被绑死在一个特定的技术栈
当开发一个新服务的时候,开发人员可以选择一个合适的新技术栈来开发,即便是已经存在的微服务,也可以考虑通过新技术栈重写来实现,如将某个cpp服务用go重写。
通过这个简单的例子及与传统单体架构的对比,读者朋友们应该能体会到微服务架构的生命力。
微服务的挑战
当然了,微服务架构也存在一些问题,或者说,要正确地应用、落地微服务架构,我们还面临一些挑战。
引入额外复杂性
开发人员要额外处理构建一个分布式系统的复杂性的问题
- 必须实现服务之间的通信机制,并且要能够处理部分失败问题;
- 跨多个服务的请求处理,相比单体架构实现、测试、沟通、联调更困难;
- 开发工具、IDE支持是否友好,如是否支持workspace下的多个工程;
部署比较复杂
在生产环境中,部署、管理很多个服务构筑而成的系统,复杂性比单体应用高。
可观测性更复杂
微服务架构中,请求的处理尝尝跨越多个微服务、多台机器,定位线上问题,必须要有远程日志上报、检索系统的支撑。监控、告警,以及链路跟踪系统的建设,相对来说是必不可少的可观测性手段,需要有这方面的建设。
链路压测及容量评估
微服务架构中,对系统全链路的压测、容量评估,都会比单体架构要复杂些,需要有这方面的一些系统支撑。
系统健壮性
系统变的复杂之后,需要对上线后必然发生的一些失败具备妥善的处理措施,如机器挂掉后,是否存在单点问题,新实例是否能自动拉起;再如出现网络故障之后,包括延迟、丢包、网络分区后,系统是否仍能正常处理。
微服务架构中通常会考虑通过混沌系统(chaos engineering)来进一步测试并发现系统中的问题。
关键链路、非关键链路
对系统中的关键链路、非关键链路要有清晰的认识,对关键链路上的一些操作的合理性需要慎重审视,当然这对于单体架构也是一样的。只是说,在微服务架构下,相对来说逻辑散步在不同系统中,很可能单一处理环节的负责人并不具有架构视角下的全局认识,就要求要从更高层面对关键链路进行更合理地管控。
资源分配要更细致
各个微服务假如独立部署,如果资源管理粗放的花,相对来说会占用更多的资源,比如100个微服务默认给2c4g+320g磁盘,是不是存在浪费呢?使用k8s等调度框架的话可以实现灵活地升降配,对资源进行细致管理,同时容器化部署减少混部的问题。
这里列一下微服务架构好处的同时,也列一下其给服务治理带来的挑战。微服务架构不是银弹,建议始终秉持“降低解决问题的复杂度”这一理念来做决策。
确定何时用微服务
使用微服务架构的一个挑战是,决定何时应该采用微服务架构。
对于一些初创企业或者应用程序的第一个版本,通常关注的是如何快速迭代以实现功能逻辑,也不会碰到微服务架构要解决的那些问题。
如果开始之初就采用按照功能垂直分割,更甚至采用微服务架构的话,这些精心设计的分布式系统、服务之间的交互逻辑、分布式事务一致性的处理、分布式服务的部署运维等等,反而会影响到迭代的效率。
但是过一段时间之后,当系统面临的挑战变成如何实现伸缩性、团队独立协同开发、快速持续集成、持续交付等等,这个时候就需要考虑分解,但是单体架构程序中盘根错节的依赖关系,可能将其再拆分为一系列微服务的时候还是比较困难。
所以,这里项目管理人员、架构师、开发人员,还是要结合团队现状、未来情况来综合考虑,如果团队在微服务架构设计、开发、部署、运营等方面都很有经验,人力、时间也ok,直接使用微服务也不见得是坏事。
如何分解微服务
另一个挑战点,就是如何将应用合理分解为多个微服务,这里就是架构设计的艺术了。
有一些策略可以指导我们如何分解:
- 通过业务功能来进行分解,然后按照功能来定义服务;
- 通过领域驱动设计DDD的子领域来进行分解;
- 通过动词或者用例来进行分解,将服务定义成某类特定的操作,如ShippingService负责物流操作;
- 通过名词或者资源来进行分解,将服务定义成对某类资源的所有操作的集合,如AccountService是对账户信息的管理,包括增删改查;
理想情况下,每个微服务都应该只负责某一种职责,也就是Bob Martin提及的单一职责原则(SRP,Single Responsibility Principle)。SRP将一个类的职责定义为改变它的状态的原因,也可以将SRP用到服务分解的过程中。
也可以参考Unix系统下工具的设计,Unix提供了很多的工具比如grep、cat、find,每一个工具都只干一件事情,但是却可以通过shell脚本、管道来组合实现更强大的功能。
维护数据一致性
为了实现微服务之间的松耦合,每个微服务都有自己的数据库,维护不同服务之间的数据一致性就变成了一个比较大的挑战。
分布式数据库中一致性是强要求,对应的分布式事务也被成为刚性事务(ACID)。刚性事务中采用的分布式事务方案(2PC、3PC、X/Open定义的XA),对追求高可用、高性能的业务系统而言,倒尝尝成为被诟病的方案。
另一种维护数据一致性的方案就是柔性事务(BASE,Basically Available, Soft State, Eventually Consistent),这类业务系统追求高可用、高性能而非一致性。对数据一致性的要求其实也分不同层次,秒级、分钟级、小时级?是否允许观察到中间态等等?对应的也有不同的方案。
如长链条事务处理应用程序可以使用 Saga Pattern,一个服务的数据变更之后它需要生成一个事件,其他服务来消费这个事件并且更新自己的数据。有多种方式来实现数据的可靠更新、事件发布,比如:Event Sourcing、Transaction Log Tailing。
实现数据的查询
单体架构中可能几个DB表join一下就完成了,但是现在每个微服务都有独立的数据库,数据查询需要经过DB对应的微服务提供的接口进行查询,然后再完成数据组合,实现会更复杂一点。
微服务架构中,通常通过 API Composition 或者 CQRS (Command Query Responsibility Segregation) 来实现。
微服务架构模式
微服务架构下还面临其他的一些问题,如何保证功能符合预期、保证数据一致性、保证系统性能、如何提高运维质量、运维效率等,对开发人员的要求还是比较高的。
这里就建议多参考些已经经过实践检验的微服务架构下的常见设计模式。
据我了解,在很多年前前国内很多大厂就意识到了“微”服务架构的价值,并进行了探索,那个时候很多微服务相关的中间件、平台还不够成熟,但也已经进行了很多的实践。
今天微服务架构大行其道,微服务场景下依赖的服务治理、日志监控、容器及容器编排技术等都日趋完备,微服务架构会越来越受欢迎。
参考文献
- What are microservices, https://microservices.io/
- The pattern language is your guide, https://microservices.io/
- F.Buschmann, R.Meunier, H.Rohnert, Pattern-Oriented Software Architecture
- Forces on Architecture decisions, http://web.mit.edu/richh/www/writings/forces-wicsa-2012.pdf
- Domain-Driven Design, https://en.wikipedia.org/wiki/Domain-driven_design
- Pattern: Microservice Architecture, https://microservices.io/patterns/microservices.html