微服务架构深度解析与最佳实践
时间:2022-08-11 14:42:01 | 来源:网站运营
时间:2022-08-11 14:42:01 来源:网站运营
微服务架构的概念,现在对于大家应该都不陌生,无论使用 Apache Dubbo、还是 Spring Cloud,都可以去尝试微服务,把复杂而庞大的业务系统拆分成一些更小粒度且独立部署的 Rest 服务。但是这个过程,具体应该怎么做?现有的条件下到底要不要做微服务?服务拆分成什么粒度才是合适的?遗留的老系统需要如何考虑重构改造?有哪些坑需要我们注意?系统怎么在分布式服务下实现数据的一致性和服务的高可用可伸缩?拆分的过程中系统数量增多,测试、部署、运维、监控,又应该如何处理?
(个人微信号:Robynn-D , 欢迎交流)
本文将从这些问题的深度分析出发,阐述微服务架构落地的一些设计原则和利弊取舍,结合微服务架构过程的很多最佳实践经验,希望给读者带来一定的启发和思考,避免在实际应用过程中走弯路,能够多快好省的落地实现微服务架构。内容涉及:
- 微服务架构的发展过程简介
- 微服务架构的特点与常见特性
- 微服务架构的常见技术与简单示例
- 微服务架构存在的一些问题
- 如何合理拆分微服务
- 遗留系统应该如何改造
- 怎么考虑拆分后的数据一致性
- 系统和服务的高可用可伸缩如何实现
- 拆分过程的测试和部署如何处理
- 拆分后的运维和监控如何处理
第一部分:微服务深度解析
微服务架构的发展过程简介
微服务架构发展的五个关键时间节点
- “微服务”这个概念最早是在 2011 年 5 月威尼斯的一个软件架构会议上讨论并提出的,用于描述一些作为通用架构风格的设计原则。
- 2012 年 3 月在波兰克拉科夫举行的 33rd Degree Conference 大会上,Thoughtworks 首席咨询师 James Lewis 做了题为《Microservices - Java, the Unix Way》的演讲(http://2012.33degree.org/talk/show/67),这次演讲里 James 讨论了微服务的一些原则和特征,例如单一服务职责、康威定律、自动扩展、DDD 等等。
- “微服务架构”则是由 Fred George 在 2012 年的一次技术大会上所提出(http://oredev.org/oredev2012/2012/sessions/micro-service-architecture.html),在大会的演讲中他讲解了如何分拆服务以及如何利用 MQ 来进行服务间的解耦,这就是最早的微服务架构雏形。
- 而后由 Martin Fowler 发扬光大并且在 2014 年发表了一篇著名的微服务文章(https://martinfowler.com/articles/microservices.html),这篇文章深入全面的讲解了什么是微服务架构。随后,微服务架构逐渐成为一种非常流行的架构模式,一大批的技术框架和文章涌现出来,越来越多的公司借鉴和使用微服务架构相关的技术。
- 2016 年 4 月,Lightbend 公司的创始人兼 CTO、Akka 的作者 Jonas Bonér,发布了一本小册子《响应式微服务架构》,讨论了基于响应式原理的微服务架构,以及如何将其用于构建可扩展,可应对故障的隔离服务,并与其他服务结合以形成一个紧密的整体。
特别值得一提的是,在微服务架构发展的过程中,还有两位技术大牛的影响不可忽视:
- Chris Richardson:《POJOS IN ACTION》与《微服务架构的设计模式》的作者,也是著名开源项目 cloudfoundry 和 eventuate 的创始人,他做了大量的微服务架构相关的方法和实践的探索,这里有其大量的演讲材料和视频:https://chrisrichardson.cn/resource/。
- Sam Newman,Martin Fowler 和 James Lewis 的 Thoughtworks 前同事,《Building Microservices》和《Monolith To Microservices》这两本的作者,前一本中文版叫《微服务设计》,同时也是 2014 年著名的推特论战《单体应用 vs 微服务应用》的主角之一(另外两人分别是 Netflix 的 Adrian Cockcroft 和 Etsy 的 John Allspaw ,具体参见 https://www.csdn.net/article/2014-08-06/2821078)。
微服务架构的发展趋势
软件架构的发展趋势,简单的说可以分为这几个阶段(详细介绍可以参见我的上一个文章《软件架构发展历程分享》或者《高可用可伸缩微服务架构:基于 Dubbo、Spring Cloud 和 ServiceMesh》一书):
- 单体架构:最简单的架构风格,所有的代码在一起,部署到单个进程,例如打包成一个 war 或者 jar,就是通常说的“大泥球”。
- 垂直架构:随着业务的发展,系统变得复杂,通过结构化思考,大家发现对于大规模协同开发,有效的控制手段就是对系统进行抽象和分层,例如场景的 MVC 方式。
- 面向服务架构(SOA):面向服务架构从建设企业 IT 生态系统的角度思考架构问题,关注点是各个系统提供可以集成的业务服务能力,而且通常由 ESB 之类的集中化技术实现。
- 微服务架构(MSA):微服务架构风格,将系统设计为一组低耦合的微服务,每个服务独立的部署运行,服务间一般采用 REST 等方式通信,同时采用自动化的测试运维等技术降低服务拆分后的复杂度。
我们可以从 Google 的趋势图看到,从 2014 年起,微服务架构架构的热度就直线上升,成为最热门的技术之一。从国内的情况来看,一方面;另一方面,一直到现在,每年的 QCon 大会都会有微服务架构版本,跟大家分享最新的微服务架构知识和实践经验。
什么是微服务架构
说了这么多,那么到底什么是微服务和微服务架构呢?
按 Martin Fowler 和 James Lewis 的定义,微服务架构风格是通过实现一系列微小的服务的方式,开发一个独立应用系统的方法,每个服务运行在自己的进程内,通过轻量级的机制与其他服务通信,通常是 HTTP 资源 API 的方式。这些服务都是围绕业务能力来构建,通过全自动部署工具来实现独立部署。这些服务,其可以使用不同的编程语言和不同的数据存储技术,并保持最小化集中管理。
具体包含如下特征:
- 组件以服务形式来提供:微服务也是面向服务的,提倡用可以独立部署的服务来作为组装业务能力的组件,而不是类库的形式。这样需要明确定义组件间的接口和通信协议。微服务架构的一个目的是通过合理的边界划分和演化机制来减少变更带来的影响。
- 围绕业务功能进行组织:根据康威定律,“设计系统的组织,其产生的设计等同于组织之内、组织之间的沟通结构”。微服务更倾向于围绕业务功能对服务结构进行划分、拆解。这样的服务,是针对业务领域有着关完整实现的软件,它包含使用接口、持久存储、以及对象的交互。因此团队应该是跨职能的,包含完整的开发技术:用户体验、数据库、以及项目管理。
- 产品不是项目:传统的开发模式,我们一般称为项目心态,就是以乙方心态给甲方做项目。一旦项目开发完成,软件将移交给维护/实施部门,或者是乙方交给甲方,然后开发团队就可以解散掉了。而微服务要求开发团队对软件产品的整个生命周期负责。这要求开发者每天都关注软件产品的运行情况,并与用户联系的更紧密,这也就是我们说的产品心态。产品心态下的软件质量要明显好于项目心态。Amazon 的理念就是“You build, you run it”,这也正是 DevOps 的文化理念。
- 强化终端及弱化通道:微服务的应用致力松耦合和高内聚,所以更倾向于 REST,而不是复杂的协议(如 WS 或者 BPEL 或者集中式框架),或者采用轻量级消息总线(如 RabbitMQ 或 ZeroMQ 等)来发布消息。
- 分散治理:这是跟传统的集中式管理很大区别的地方。微服务把单体式框架中的组件,拆分成不同的服务,在构建它们时将会有更多的选择。分散治理也意味着责任的下放,每个团队对自己的业务服务负责,如果你维护一个 7x24 小时的不间断服务,那么你就必须 24 小时随时 OnCall,哪怕晚上 3 点起来处理问题,就是 AWS 的模式。
- 分散数据管理:当单体式的应用使用单一逻辑数据库对数据持久化时,企业通常选择在应用的范围内使用一个数据库。微服务让每个服务管理自己的数据库:无论是相同数据库的不同实例,或者是不同的数据库系统。这一点,我们后面会详细说明。
- 基础设施自动化:云计算,特别是 AWS、Azure、Aliyun 等的发展,减少了构建、发布、运维微服务的复杂性。微服务的团队更加依赖于基础设施的自动化。其实不管是运维,测试也是一样。
- 容错性设计:任务服务都可能因为供应商或者底层硬件或网络的不可靠而故障。微服务应为每个应用的服务及数据中心提供日常故障检测和恢复,收集各项系统状态指标和业务指标、日志信息进行监控,并提供预警报警能力。面向失败的设计,后面也会再讨论。
- 改进设计:组件的关键特性是可替代性和可升级性,由于设计会不断更改,微服务所提供的服务应该能够替换或者报废,而不是要长久的发展的,每种设计也一样都有自己的阶段使命和生命周期。这样如果客户需要兼容性,版本控制是一种好的手段。
Chris Richardson 则简化了这种说法,认为微服务是一种架构风格,通过一组服务的方式构造系统,同时需要满足如下条件:
- 高可维护性和测试性
- 松耦合性
- 可独立部署
- 围绕业务能力进行组织
- 一个小团队对其完全负责 微服务架构能够快速、频繁和可靠地发布大型的复杂应用系统,也能够使得组织可以进化出自己的技术栈。
Peter Lawyer 说,微服务很多地方特别像是 Mike Gancarz 总结的 Unix 哲学:
- 小即是美(Small is beautiful)
- 让程序只做好一件事(Make each program do one thing well)
- 尽可能早地建立原型(Build a prototype as soon as possible)
- 可移植性比效率更重要(Choose portability over efficiency)
- 尽可能地榨取软件的全部价值 Use software leverage to your advantage.
微服务架构就是 Unix 哲学在分布式系统里的应用。微服务架构的哲学本质上等于 Unix 哲学里的“让程序只做好一件事”:
- 服务都很小,细粒度地执行单个功能。
- 组织文化应拥抱自动化部署和测试。这减轻了管理和运维的负担。
- 文化和设计原则应拥抱失败和错误,类似于抗脆弱系统。
- 每个服务都是弹性的,回弹性的,可组合的,最小化的和完整的(弹性和回弹性参见响应式微服务)。
从这里我们可以得出一个结论,一个微服务架构的系统需要满足一系列的条件和原则,而不仅仅是说使用了某个微服务的技术框架就是微服务架构。微服务这个词目前也过于流行以致于有些泛滥了。很多技术组件或者框架,例如可以用来暴露服务,可以用来自动化部署,可以用来管理配置等等,它们都可以用来作为微服务架构设计时的某个组成部分。但是单独用一个,并不代表我们实现了微服务设计,而只能说明我们借鉴了一些微服务的思想。另一方面,我们在做微服务的时候,也不用把市面上所有的微服务组件都拿来用。就像是我们写个业务系统不会用到 JDK 的所有 API,画一幅画之前我们买了一盒 24 支的水彩笔,实际上我们可能做一幅作品最终只用到了 5-6 个颜色的水彩笔。
微服务架构的特点、优势和常见技术
微服务的四个特点和六个能力
现在让我们分析一下上一节里的各个技术大牛们阐述的技术观点,从设计开发、系统部署、测试运维和服务治理四个主要方面来考虑微服务架构的特点,那么这四个方面就可以总结为下图:
微服务架构首先是一个分布式的架构,其次我们要暴露和提供业务服务能力,然后我们需要考虑围绕这些业务能力的各种非功能性的能力。这些分散在各处的服务本身需要被管理起来,并且对服务的调用方透明,这样就有了服务的注册发现的功能需求。
同样地,每个服务可能部署了多台机器多个实例,所以,我们需要有路由和寻址的能力,做负载均衡,提升系统的扩展能力。有了这么多对外提供的不同服务接口,我们一样需要有一种机制对他们进行统一的接入控制,并把一些非业务的策略做到这个接入层,比如权限相关的,这就是服务网关。同时我们发现随着业务的发展和一些特定的运营活动,比如秒杀大促,流量会出现十倍以上的激增,这时候我们就需要考虑系统容量,服务间的强弱依赖关系,做服务降级、熔断,系统过载保护等措施。
以上这些由于微服务带来的复杂性,导致了应用配置、业务配置,都被散落到各处,所以分布式配置中心的需求也出现了。最后,系统分散部署以后,所有的调用都跨了进程,我们还需要有能在线上做链路跟踪,性能监控的一套技术,来协助我们时刻了解系统内部的状态和指标,让我们能够随时对系统进行分析和干预。这六种能力总结如下图:
微服务的优势
为什么我们要从单体系统走向微服务架构呢?我们先来看一个图。
这个图 x 轴是系统复杂度,y 轴是开发的生产力,我们可以看到:
- 在系统复杂度很低的时候,单体的生产力要高一点,这是因为拆分微服务,管理这些服务的成本增加了
- 当复杂度开始增加,单体的生产力快速的降低,而微服务则不太受影响,这是因为复杂度大了,单体牵一发而动全身,各种耦合和相互影响太多
- 随着复杂度越来越高,微服务的低耦合开始减低了生产力的衰变,而单体架构的生产力则会降到一个非常低的水平
也就是说微服务应用在复杂度低的情况下,生产力反而比单体系统低。在复杂度高的地方,情况恰恰相反。
随着复杂度升高,单体架构的生产力快速下降,而微服务相对平稳,所以,复杂度越高的业务系统,越适合使用微服务架构。
搞清楚了微服务架构与单体架构的生产力的区别以后,我们再来看看微服务有哪些优势,我总结了一下有以下几点:
- 服务简单:因为微小,所以简单,从一个大泥球,变成了很多个小而美的颗粒,每个小颗粒职责单一,边界明确,可以通过简单组装完成大的功能,自然就比之前的大泥球好处理得多。
- 灵活扩展:单体的情况下,只能通过增加机器,再部署多套单体系统做成集群,前面加负载均衡来扩展;微服务以后,我们会发现用户服务压力不大不用扩展,订单服务压力大的时候多部署两台就可以了,这样我们就把扩展的方式从全部优化到局部。
- 便于维护:如果一个系统有 100 个业务功能,依赖关系相互耦合到一起,那么这就是维护的恶梦,这样的系统没有任何免疫力,修改任何一个功能,都可能会导致整个系统崩溃。微服务就简单得多了,每个服务自己出现问题,其他服务不会直接受到影响。同时维护具体某个服务的人员,也不需要一上来就学习全部的业务知识,比如用户服务模块的维护人员,只需要先学习用户服务的业务就可以上手工作了。
- 独立演进:变成微服务以后,各个独立系统的内部设计和实现细节都被隔离开,相互之间不受影响,只要服务间的接口不变,大家就可以各自独立的发展自己的系统,完成升级、重构、功能增强等等。
- 混合开发:各服务独立开发的另外一个好处就是,各个独立系统可以使用自己的技术栈和研发模式,包括开发语言和工具、数据库存储和中间件等技术,这样有助于试验和引入更先进和创新的技术,从一些个边缘服务开始尝试,技术、工具、中间件、研发模式,孵化成熟以后,可以大范围推广,实现技术和研发能力的持续更新换代,让研发组织保持长期的优势和活力,充分获得技术发展的红利。
- 持续交付:微服务比单体系统简单明确,这样就便于我们利用自动化测试和自动化部署来加速功能的迭代,配合 CI/CD 等基础设施,实现业务功能的持续交付,保障研发能够紧跟业务发展变化的节奏。
常见的微服务技术框架
具体怎么做微服务呢?我们先看看大家最常简单见到的微服务的图:
(个人微信号:Robynn-D , 欢迎交流)
在互联网出行业务中,用户通过 API 网关访问系统的乘客、司机、出行三个 REST 服务,这三个服务再通过 REST 访问计费、支付和通知三个服务。看起来很简单,也好理解,但是实际的业务系统里,设计和实现一般会是这样:
- 服务框架:我们可以选择用 Spring Cloud 或者 Apache Dubbo,包括新兴的 Spring Cloud Alibaba,还有华为贡献的 Apache ServiceComb,蚂蚁金服的 SOFAStack ,Oracle 的 Helidon,Redhat 的 Quarkus,基于 Scala 语言和 Akka 的 Lagom,基于 Grails 语言的 Micronaut,基于 Python 语言的 Nameko,基于 Golang 语言的 go-micro,支持多语言混编的响应式微服务框架 Vert.X,腾讯开源的 Tars,百度开源的 Apache BRPC(孵化中),微博的简化版 Dubbo 框架 Motan 等等。
- 配置中心:Apollo,Nacos,disconf,Spring Cloud Config,或者 Etcd、ZK、Redis 自己封装
- 服务注册发现:Eureka,Consul,或者 Etcd、ZK、Redis 自己封装
- 服务网关:Zuul/Zuul2,Spring Cloud Gateway,nginx 系列的 Open Resty 和 Kong,基于 Golang 的 fagongzi Gateway 等
- 容错限流:Hystrix,Sentinel,Resilience4j,或者直接用 Kong 自带的插件
- 消息处理:Kafka、RabbitMQ、RocketMQ,以及 Pulsar,可以使用 Sping Messaging 或者 Spring Cloud Stream 来简化处理
- 链路监控与日志:开源的链路技术有 CAT、Pinpoint、Skywalking、Zipkin、Jaeger 等,也可以考虑用商业的 APM(比如听云 APM、OneAPM、App Dynamic 等),日志可以用 ELK
- 认证与授权:比如要支持 OAuth2、LDAP,传统的自定义 BRAC 等,可以选择 Spring Security 或者 Apache Shiro 等
Sping Cloud 与 Apache Dubbo、Spring Cloud Alibaba
Spring Cloud 是一个较为成熟的微服务框架,定位为开发人员提供工具,以快速构建分布式系统中的某些常见模式(例如,配置管理,服务发现,断路器,智能路由,微代理,控制总线,一次性令牌,全局锁,Leader 选举,分布式会话,群集状态)。其技术栈大致如下:
可以看到很多组件都是由 Netflix 贡献的。
而 Apache Dubbo 定位是一款高性能、轻量级的开源 Java RPC 框架,它提供了三大核心能力:面向接口的远程方法调用,智能容错和负载均衡,以及服务自动注册和发现。所以,我们可以看到 Dubbo 提供的能力只是 Spring Cloud 的一部分子集。
同时 Dubbo 项目重新维护以后,捐献给了 Apache,项目的导师就是 Spring Cloud 的核心人员。自这时候起 Dubbo 项目就开始在适合 Spring Cloud 体系,结果就是 Alibaba 也基于自己的一系列开源组件和技术,实现了 Spring Cloud Alibaba,并顺利从 Spring Cloud 孵化器毕业。详见:
https://spring.io/projects/spring-cloud-alibabaSpring Cloud Alibaba 致力于提供微服务开发的一站式解决方案。此项目包含开发分布式应用微服务的必需组件,方便开发者通过 Spring Cloud 编程模型轻松使用这些组件来开发分布式应用服务。主要功能和开源技术栈:
- 服务限流降级(Sentinel):默认支持 WebServlet、WebFlux, OpenFeign、RestTemplate、Spring Cloud Gateway, Zuul, Dubbo 和 RocketMQ 限流降级功能的接入,可以在运行时通过控制台实时修改限流降级规则,还支持查看限流降级 Metrics 监控。
- 服务注册与发现(Nacos):适配 Spring Cloud 服务注册与发现标准,默认集成了 Ribbon 的支持。
- 分布式配置管理(Nacos):支持分布式系统中的外部化配置,配置更改时自动刷新。
- 消息驱动能力(Apache RocketMQ):基于 Spring Cloud Stream 为微服务应用构建消息驱动能力。
- 分布式事务(Seata):使用 @GlobalTransactional 注解, 高效并且对业务零侵入地解决分布式事务问题。。
可以看到,基本上功能都比较完备了。
第二部分:微服务最佳实践
微服务架构不是银弹
《管理的常识》一书里说,管理的核心是不断的解决(推进工作过程中出现的各种)问题。同样地,我认为架构的核心则是不断的解决(系统设计实现与演化过程中的各种)问题。
Fred Brooks 在《人月神话》里说,“没有银弹”,现在依然成立,微服务也并不是只有优点,没有副作用,把系统拆分了了很多部分,每一个部分简单了,但是整体的关系变复杂了。前面介绍了那么多微服务的特点和优势,以及相关技术,我们再来分析一下微服务存在的问题,进而讨论什么场景适合使用微服务,什么场景不适合,以及最佳的实践方式。
微服务架构首先是一个分布式系统架构。分布式系统的发展由来已久,但是近年来产生了理论和实现的大爆发。
究其原因:分布式系统的发展得益于廉价 pc 硬件使得堆机器成为可能,而单机的成本/容量是非线性的,所以分布式的核心是线性的水平扩展整个集群的功能,以及带来的协调机制,管理复杂度,数据和状态一致性,容错和故障恢复,容量与弹性伸缩,这些通用性的基础能力建设。
简单的翻译一下:单个机器堆资源,升级 CPU 内存加大一倍,想要增加一倍的处理能力且成本不超过一倍,不仅很难、而且不现实了。这样,我们就需要思考怎么通过进一步对于系统里不同功能的部分,拆解开,单独扩展这些能水平扩展的部分,从而在控制成本的前提下,提升整个系统的处理能力。
另外,我们现在都知道,设计系统如果对可靠性,可用性有非常高的要求,那么需要先假设上下游都是不可靠的,依赖的基础设施和网络也是不可靠的,这样就考虑分布式后的分片、复制、故障转移、灾难恢复等,分布式系统下,我们可以对系统的不同性质的节点、不同可靠性可用性要求的组件,做针对性的单独处理。
很多系统看起来是分布式的,其实是一个大单机。 很多系统看起来是微服务的,其实是一个大单体。
就像人月神话里说的,往已经延期的项目,加人手,可能会导致更延期。问题不是出现在应不应该加人,或者应不应该使用分布式上。而是实施的人,没搞清楚关键。比如系统拆成分布式或微服务,然后某个关键地方依然卡住了业务流程,可能整体还不如单机的,扩展性失效,多加机器还不如单机。
为什么?
因为如果希望用多个机器来扩展业务,最后发现各个机器上运行的程序没有划清楚边界职责,多个机器合起来并不比单个机器有更大处理能力,这就是一个大单机。
同样的,如果我们拆分了很多更小独立的服务,但是一个业务请求进来,还是在一些地方被卡住,导致有一些瓶颈使得整个拆分后并不比之前有整体的改进,那么其实是费劲的做了一个大单体系统。
所以合理的拆分微服务,使得我们能够更好的扩展系统是关键,同时如果业务简单,流量不大,不扩展也可以很好的应对,系统对于可用性和一致性要求也不是特别高,那么分布式和微服务,也不是必须的。
微服务系统适合的场景
以下几类系统,比较适合使用微服务架构,或者使用微服务架构改造:
- 大型的前后分离的复杂业务系统后端,业务越复杂,越需要我们合理的设计和划分,长期的维护成本会非常高,历史包袱也很重,这个问题后面会详谈。
- 变化发展特别快的创新业务系统,业务快,就意味着天天要“拥抱变化”,每一块的业务研发都要被业务方压的喘不过气,不做拆分、自动化,一方面研发资源永远不够用,活永远干不完,另一方面不停的在给系统打补丁,越来质量越低,出错的可能性也越来越大。
- 规划中的新大型业务系统,如果有能力一开始就应该考虑做微服务,而不是先做单体,还是发展到一定的阶段再做改造,改造一定是伤筋动骨的大手术,虽然我们可以采取一些策略做得更平滑,但是成本还是比较高的。
- 敏捷自驱的小研发团队,拥抱新技术,可以直接用微服务做系统,不但的通过快速迭代、持续交付,经过一定时间的尝试和调整,形成自己的微服务实践经验,这样在团队扩大时可以把好的经验复制到更大的团队。
反过来,以下几类系统,不太适合一开始就做微服务,
- 小团队,技术基础较薄弱,创业初期或者团队新做的快速原型,这个时候做微服务的收益明显比单体要低,快速把原型做出来怎么方便怎么来,用团队最熟悉的技术栈。
- 流量不高,压力小,业务变化也不大,单体能简单搞定的,就可以先不考虑微服务,不考虑分布式,因为分布式和微服务带来的好处,可能还不足以抵消复杂性增加带来的成本。
- 对延迟很敏感的低延迟高并发系统,低延迟的秘诀就是离 io 能多远就多远,离 cpu 能多近就多近,分布式和微服务,导致增加了网络跳数,延迟就没法降低。
- 技术导向性的系统,技术产品,这类产品跟业务系统不同,常规的业务系统研发方法不是太适合。
微服务带来的一些问题
Chris Richardson 在
http://blog.daocloud.io/microservices-1/提到了微服务的几个不足:
- 服务大小:『微服务』强调了服务大小,有人强调服务要大一点,也有人愿意采用小服务。需要注意这只是一种选择,微服务的目的是有效的拆分应用,实现敏捷开发和部署。
- 进程通讯:微服务应用是分布式系统,由此会带来分布式固有的复杂性。开发者需要在 RPC 或者消息传递之间选择并完成进程间通讯机制。相对于单体式应用中通过语言层级的方法或者进程调用,微服务下这种技术显得更复杂一些,需要考虑 RPC 的超时、重试、失效策略,或者消息服务不可用,堆积堵塞等问题。
- 数据库拆分:数据库事务对于单体式应用来说很容易,因为只有一个数据库。在微服务架构应用中,需要更新不同服务所使用的不同的数据库。使用分布式事务并不一定是好的选择,不仅仅是因为 CAP 理论,还因为很多中间件并不支持这一需求。最终你不得不使用一个最终一致性的方法,从而对开发者提出了更高的要求和挑战。
- 测试复杂度:比如采用流行的 Spring Boot 架构,对一个单体式 web 应用,测试它的 REST API,是很容易的事情。反过来,同样的一个业务场景,需要测试启动和它有关的所有服务,这些服务在不同的进程,所以变得尤为复杂。
- 服务依赖关系:微服务架构模式应用的改变将会波及多个服务。比如,假设你在完成一个案例,需要修改服务 A、B、C,而 A 依赖 B,B 依赖 C。在单体式应用中,你只需要改变相关模块,整合变化,部署就好了。对比之下,微服务架构模式就需要考虑相关改变对不同服务的影响。比如,你需要更新服务 C,然后是 B,最后才是 A,幸运的是,许多改变一般只影响一个服务,而需要协调多服务的改变很少。特别是,如果要处理的服务间依赖是一个网状关系,那么很可能导致,我们修改 A 时,考虑到了会影响 BCDEF 这几个服务,但是漏掉了影响系统 K,导致上线以后发现 K 的部分功能无法使用了。
- 部署复杂度:部署一个微服务应用也很复杂,一个分布式应用只需要简单在负载均衡服务后面部署各自的服务器就好了。每个应用实例是需要配置诸如数据库和消息中间件等基础服务。而一个微服务应用一般由大批服务构成。例如,根据 Adrian Cockcroft,Hailo 有 160 个不同服务构成,NetFlix 有大约 600 个服务。每个服务都有多个实例。这就造成许多需要配置、部署、扩展和监控的部分,除此之外,你还需要完成一个服务发现机制,以用来发现与它通讯服务的地址(包括服务器地址和端口)。传统的解决问题办法不能用于解决这么复杂的问题。接续而来,成功部署一个微服务应用需要开发者有足够的成熟部署方法,并高度自动化。
Peter Lawyer 有在
https://vanilla-java.github.io/2016/03/22/Micro-services-for-performance.html 中提出,微服务的几个问题:
- 服务形成信息障碍。
- 引入了额外的复杂性和新问题,例如网络等待时间,消息格式,负载平衡和容错。忽略其中之一属于“分布式计算的谬误”。
- 测试和部署更加复杂。
- 整体应用程序的复杂性仅转移到网络中,但仍然存在。
- 细粒度的微服务已被批评为一种反模式。
其实可以看到,大家的想法和分析都比较一致,微服务架构带来的固有复杂性和人为复杂性如何管理:
- 如何合理拆分微服务,粒度如何控制:对业务按粒度和边界拆解的问题,这决定了我们的服务是不是合理,开发和维护是不是方便。核心思路是深入了解业务。
- 遗留系统应该如何改造,从哪儿下手,如何推动:改造遗留系统一般来说比重新做一个新系统更复杂,如何平滑的、最大代价的将老系统改造成微服务,也是一个巨大挑战。
- 拆分后的性能应该如何保障:性能有两个指标,关键是区分出来如何处理不同特性的数据,关注不同的指标,做好优化和选型,针对具体细节具体对待。
- 怎么考虑拆分后的数据一致性,分布式事务的选取和取舍:多个服务间的业务数据一致性的问题,事务是个需要重点考虑的问题,主要是考虑强一致性的分布式事务还是可以使用最终一致的弱一致性。对于一般的业务,可能使用补偿冲正类的做法,在业务允许的一定时间内数据达到一致状态即可,比如 30s,或者 10 分钟。
- 系统和服务的高可用可伸缩如何实现:高可用意味着系统稳定健壮,可伸缩意味着弹性,资源有效使用,如何做到无状态、不共享,是实现可高用可伸缩的关键。
- 拆分过程的测试和部署如何处理,怎么提升管理能力,降低风险:跨系统的协作问题、测试的问题,这对于技术能力和研发成熟度较低的团队,会带来很大麻烦。核心思路定义好业务边界、系统间接口与数据标准,提升自动化测试水平。微服务架构由于拆分粒度较细,测试是个大问题。在保持每块职责单一的同时,关键是保证接口稳定,做好自动化测试(特别是 UT 和接口的自动化)和持续集成,尽量少全流程的人工回归测试。部署单元太多导致维护成本上升的问题,要管理的点多了,问题自然复杂了,核心思路是自动化部署与运维。
- 拆分后的运维和监控如何处理:怎么应对系统的故障,关键是保障核心技术的指标,以及业务指标,做好预警报警,积累问题、寻找根因,控制和杜绝低级失误。
七个关键问题的应对策略
如何合理拆分微服务
当一个系统服务化的时候,就会面临一个问题:如何进行服务的划分?怎么确定服务的粒度?有没有一些可以参考的业界通用规则?
实际上服务划分的本质是对系统进行架构设计,服务的划分粒度没有绝对的过大或过小之说,不同阶段的侧重点和思考的角度也不尽相同。创业初期的团队,过分的追求微服务,为了“微”而微,反而会导致业务逻辑过于分散,技术架构过于复杂,团队基础设施搭建能力弱,进而导致忽略了快速迭代交付产品的重要性,可能错失了市场机会。所以,关于服务的划分不是对错的选择题,而是需要综合考虑各种外界的因素,所作出的一个最适合的决策,这些外界因素通常包括业务、技术债、开发、运维、测试这
五个方面:
- 业务所处领域的市场性质:对市场比较敏感的项目,创业初期粒度应该尽量划分的粗一些,先提供充足的弹药去占领市场,然后再去考虑对系统进行重构和优化;
- 与原有系统之间的关系:对于历史遗留的系统,需要做好新旧系统之间的边界划分,避免过于激进、过大幅度的改造,应该采取小步快跑的方式,有节奏的对老系统进行服务化改造;
- 开发团队的成熟度:服务化带来的技术风险应该提前进行评估,要考虑团队的承受度,用合适的人做适合的事,考虑团队需要有包括敏捷,包括 Devops,包括基础设施,运维和测试的自动化等基础能力;
- 基础设施的搭建能力:在进行细粒度的服务划分时,要考虑团队是否有足够的能力来支撑大量服务实例运行的运维复杂度,是否可以做好分布式的日志追踪和服务的监控;
- 测试团队的测试执行效率:过于细粒度的服务划分,如果测试团队不能通过自动化测试、自动回归、压力测试、极限测试等手段来提高测试执行效率,必然会带来测试工作量的大幅度上升,进而影响整个项目的上线周期;
如果没有特别强烈的大规模水平扩展需求,拆分就没有必要,反而把问题搞复杂了。进行服务化的拆分时,通常会先按照业务子系统先进行一次划分,根据业务逻辑和数据的关系划分为若干个子系统,然后再考虑子系统内部是否可以再次进行拆分。至于拆分的基本原则,我推荐:
- 高内聚低耦合:这个已经提了很多了,简单说一下,就是要把强相关的部分,总是会一起改动的部分,聚合到一起,相关性不大的部分拆开,可以参考 DDD 中的一些办法。
- 粗粒度服务:服务的粒度要稍微的抽象和粗粒度一些,因为服务是基于业务场景的抽象和设计,不能做成是直接把数据库的增删改查暴露出来成接口和方法,而是应该隐藏这些细节,考虑清楚从业务和客户角度来看,哪些步骤和过程,是必须封装起来的,细节隐藏掉,然后对外提供的就是粗粒度的服务,而在单体系统的时候,我们可以直接调用这些细节,无需过多考虑。
遗留系统应该如何改造
对旧系统进行改造,可以分为几个步骤:拆分前准备阶段,设计拆分改造方案,实施拆分计划。下面是我的一些经验之谈。
1)拆分之前先梳理系统关系和接口
其中准备阶段主要是梳理清楚了依赖关系和接口,就可以思考如何来拆,第一刀切在哪儿里,即能达到快速把一个复杂单体系统变成两个更小系统的目标,又能对系统的现有业务影响最小。要尽量避免构建出一个分布式的单体应用,一个包含了一大堆互相之间紧耦合的服务,却又必须部署在一起的所谓分布式系统。没分析清楚就强行拆,可能就一不小心剪断了大动脉,立马搞出来一个 A 类大故障,后患无穷。
2)不同阶段拆分要点不同,每个阶段的关注点要聚焦
拆分本身可以分成三个阶段,核心业务和非业务部分的拆分、核心业务的调整设计、核心业务内部的拆分。这三个阶段,第一阶段将核心业务瘦身,把非核心的部分切开,减少需要处理的系统大小;第二阶段。重新按照微服务设计核心业务部分;第三阶段把核心业务部分重构设计落地。拆分的方式也有三个:代码拆分、部署拆分、数据拆分。代码直接体现了依赖关系,拆完就可以单独打包部署。但是有时候,我们可以通过控制一些提供服务的开关,使用同一份代码和打包的程序,部署多组进程,每组提供不同的服务,这就是部署拆分,比如同一份代码,我们部署了 3 组机器,A 组 5 台提供订单服务,B 组 2 台提供用户服务,C 组 2 台提供任务调度处理任务。数据拆分最复杂,涉及到代码的调整,SQL 和事务的分析和重构,数据库表的拆分甚至数据迁移,数据结构的调整和数据迁移则一般意味着需要停机维护。这三个方式,可以在适当的条件下选择先做哪个操作合适。 另外,每个阶段需要聚焦到一两个具体的目标,否则目标太多反而很难把一件事儿做通透。例如某个系统的微服务拆分,制定了如下的几个目标:
- 性能指标(吞吐和延迟):核心交易吞吐提升一倍以上(TPS:1000->10000),A 业务延迟降低一半(Latency:250ms->125ms),B 业务延迟降低一半(Latency:70ms->35ms)。
- 稳定性指标(可用性,故障恢复时间):可用性>=99.99%,A 类故障恢复时间<=15 分钟,季度次数<=1 次。
- 质量指标:编写完善的产品需求文档、设计文档、部署运维文档,核心交易部分代码 90%以上单测覆盖率和 100%的自动化测试用例和场景覆盖,实现可持续的性能测试基准环境和长期持续性能优化机制。
- 扩展性指标:完成代码、部署、运行时和数据多个维度的合理拆分,对于核心系统重构后的各块业务和交易模块、以及对应的各个数据存储,都可以随时通过增加机器资源实现伸缩扩展。
- 可维护性指标:建立全面完善的监控指标、特别是全链路的实时性能指标数据,覆盖所有关键业务和状态,缩短监控报警响应处置时间,配合运维团队实现容量规划和管理,出现问题时可以在一分钟内拉起系统或者回滚到上一个可用版本(启动时间<=1 分钟)。
- 易用性指标,通过重构实现新的 API 接口既合理又简单,极大的满足各个层面用户的使用和需要,客户满意度持续上升。
- 业务支持指标:对于新的业务需求功能开发,在保障质量的前提下,开发效率提升一倍,开发资源和周期降低一半。
- 核心人员指标:培养 10 名以上熟悉核心交易业务和新系统的一线技术人员,形成结构合理的核心研发人才梯队。
结果可想而知了,目前太多了,反而没有目标。最后第一阶段只选择了稳定性作为最重要的指标,先稳住系统,然后再在后面的阶段里选择其他指标,逐步实现各个目标。
3)快速迭代,找到突破口,持续产出
大家都知道,敏捷开发之所以流行,就是因为小步快跑,快速迭代,实现对业务变化和新需求的第一时间响应,这对快速发展变化的外部市场,以及 KPI 压力非常大的业务部门非常重要。研发团队在系统改造过程也可以通过快速的阶段性产出,来证明团队的技术能力和推进水平,增进互相的背靠背信任关系,为长期的顺畅合作打下坚实基础。这方面,很多研发团队都想试图憋大招,搞个大项目,反而慢慢失去各个利益方的耐心,最终把合作关系搞僵,吃了大亏。
4)大胆假设,小心求证,稳步上线
凡事不破不立,拆分改造过程,我们每一次改动的地方,可能有多个不同的方案和路径,具体选择哪一个最合适,这需要我们放开思路,大胆假设,充分吸收各方面的意见和想法,然后小心谨慎的去测试,甚至在线上做验证,保障万无一失后,最后上线。
5)保障质量,不断重构和改善现有设计和代码
所有的事物都有产生,发展,衰退和消亡的过程。长期来看,软件系统的代码质量肯定是会一直下降的,就像是人的身体健康,到了一定的程度,就会难以为继,需要重构或者重做。而不断的重构,改善现有的设计集合代码,就像是一直在保养身体,可以减缓衰老,保证健康,增加寿命。
6)取得领导和业务方的支持,过程和决策透明化
拆分改造看起来,没有给系统带来明确的可见收益,比如没有明显改进了用户体验,也没有给系统新增了一个业务功能,但是却涉及到多方参与,付出劳动,这就必然会带来很大的阻力,怎么办呢?还是从《管理的常识》一书里,我看到了一个很有道理的话:”如果无法推动问题背后的人解决问题,那说明对问题挖掘的还不够深“。现代化的工作教会我们,双赢/多赢是协作的唯一办法,也是可以持续的办法。搞清楚怎么才能推动各个合作方的支持,怎么才能让领导同意,如果我们现在提的意见,他们不同意,那么他们关心的点是什么,怎么把他们关心的点,纳入到这个工作范围里来,从而实现大家可以达成一致来合作。同时需要注意的是,信息一定要透明,决策要公开,让大家都直接参与到这个过程,从而明确目标,一致前行。
总结成 48 字箴言的“微服务拆分核心价值观”:
- 功能剥离、数据解耦
- 自然演进、逐步拆分
- 小步快跑、快速迭代
- 灰度发布、谨慎试错
- 提质量线、还技术债
- 各方一致,过程透明
理想中的系统拆分改造效果(实际上一般最后都鸡飞狗跳):
关于微服务对性能的影响
大家可以先思考 2 个问题:延迟(latency)和吞吐量(throughout)有什么关系? 延迟是响应时间么?
先说一下延迟和响应时间,延迟是对于服务本身来说的,响应时间是相当于调用者来说的(更多的内容可以参考《数据密集型应用系统设计》一书):
- 延迟(latency) = 请求响应出入系统的时间
- 响应时间(ResponseTime)= 客户端请求开始,一直到收到响应的时间 = 延迟 + 网络耗时
理想状态下,延迟越低,吞吐越高,当然这是对单机单线程而言的,在分布式下就不成立了,举个反例:
比如从密云水库,拉一个水管到国贸,水流到国贸,需要 1 小时;如果再拉一个水管到顺义,20 分钟就可以。如果你在国贸用水龙头接水,你可以单位时间接到非常多的水,这个数量跟你在过国贸还是顺义,没有关系,只跟水库单位时间输入的水量/水压有关系。但是如果你在水管里放一个小球,它从密云到国贸的时间是到顺义的时间的三倍,这样对于到国贸的这个水管系统,延迟很高,但是系统的吞吐量跟到顺义的是一样的。
同理,如果一个单体系统,被拆分成了 10 个服务,假如一个业务处理流程要经过 5 个服务,这 5 个服务只要是每个吞吐量(TPS/QPS)不低于原先的单体,那么整个微服务系统的吞吐量是不变的。
相反地,我们通过服务变小,关系变简单,数据库简化,事务变小等等,如果 5 个系统的吞吐都比原来的系统打,那么改造后的系统,整体的吞吐也比之前要高。那么这个过程的副作用是什么呢?
简单的说,就是延迟变高了,原来都是本地调用,现在变成了 5 次远程调用,假设每次调用的网络延迟在 1-10 毫秒(物理机房+万兆网卡可以很低,云环境下比较高),那么延迟就会比之前增加增加 5-50 毫秒,而且前提是分布式下的请求,使用异步非阻塞的流式或消息处理方式,同步阻塞会更高,而且影响吞吐量。好在低延迟的系统要求比较少见,对于一般的业务系统来说,可以水平扩展的能力比延迟增加几毫秒要重要的多。
比如我们在淘宝或者京东,买个衣服,交易步骤的处理,在秒级都是可以接受的,如果是机票、酒店、电影票之类的,分钟级以上都是可以接受的。
再举一个现实的例子,某个公司从 2016 年起,就在做微服务改造,研发团队规模不大,业务发展很快,基础设施没有跟上,自动化测试、部署都没有。同时这个公司的主要核心业务是一个低延迟高并发的交易系统,微服务拆分导致系统的延迟进一步增大,客户满意度下降。很快研发团队就发现了拆分成了多个小系统以后,比单体更难以维护,继而采取了措施,把部分微服务进行合并,提高可维护性和控制延迟水平。
对于分布式微服务系统的低延迟设计,更多信息可以参考 Peter Lawyer 的博客:
https://vanilla-java.github.io/。
怎么考虑拆分后的数据一致性
微服务提倡每一个服务都使用自己的数据库存储,这就涉及到老系统的数据库拆分改造。一般的数据库拆分,我们可以把不同业务服务涉及的表拆分到不同的库中去,这样如果之前的事务中操作的两个表如果被拆分到了不同的数据库,就会涉及到分布式事务。下面先解释一下分布式事务。
我们知道 ACID(原子性 Atomicity、一致性 Consistency、隔离性 Isolation、持久性 Durability)定义了单个数据库操作的事务性,这样我们就能放心的使用数据库,而不用担心数据的一致性,操作的原子性等等。由于数据库同时可以并发的给多个应用、多个会话线程使用,这样就涉及到了锁,隔离级别和数据可见性等一系列工作,好在关系数据库都已经帮我们解决了这些问题。
但是在 SOA、分布式服务化和微服务架构的大背景下,数据拆分到多个不同的库已经是常态,这种改造或者设计中,同一个业务处理涉及到的关联数据生命周期可能要贯穿到多个不同的数据库,如果没有事务保证,那么数据的一致性或者正确性就会收到破坏,账就可能会错乱了,平台或者客户就会产生损失了。如何保证数据的事务性,则是一个非常有意思的话题。传统的数据库和消息系统一般都是支持 XA 分布式事务,通过一个 TM 事务管理器协调各个 RM 资管管理器,每个 RM 管理自己的本地事务,通过两阶段提交 2PC 来保障一致。
由于 CAP 的不可能三角约束下,我们大部分时候选择了从 ACID 到 BASE(Basically Available 基本可用, Soft-state 柔性状态, Eventually consistent 最终一致),这样分布式事务我们一般也从 XA 变成了 TCC(Try-Commit-Cancel),把分布式事务的控制权从底层资源层(比如数据库)挪到了业务实现层,从而通过释放数据库层的锁,来提升性能和灵活性。具体情况可以参阅下面的两个文章。
- 分布式事务的原理综述,讲的非常详细: https://mp.weixin.qq.com/s/syPKHckm6uZ3TgJYfRnw
- 分布式事务解决方案与适用场景分析,结合实际,详细说明了 TCC 的原理和用法: https://mp.weixin.qq.com/s/Okvgn5beGy5aJypfu6mKcg
我的好朋友长源和张亮,也分别写过一个分布式事务的系列:
- 长源-漫谈分布式事务:https://zhuanlan.zhihu.com/distributedDatabase
- 张亮-分布式事务:https://shardingsphere.apache.org/document/current/cn/features/transaction/
如果直接操作数据库或者支持 XA/JTA 的 MQ,可以使用 XA 事务。其他情况,可以使用开源的分布式事务中间件。开源的分布式事务中间件有 Apache ServiceComb Pack,Seata/Fescar,Apache ShardingSphere 等。
(个人微信号:Robynn-D , 欢迎交流)
但是实际上,对大部分业务来说,性能远大于分布式事务带来的强一致性要求。更多时候,我们可以先牺牲掉强一致性,甚至是准实时一致性,先让多个不同节点的服务,都自己单独把业务执行掉。然后再通过定时任务去检查是否一致的状态,如果不一致,保证可以从某个存储拿到原来的数据,重新执行即可。这时候其实是做了补偿的操作,补偿会带来数据重复处理的情况,就是检查的时候没有执行,但是去补偿操作的时候,可能已经在执行了。
特别是我们使用异步的 MQ 之类的方式做业务处理和补偿,消息也可能由于 MQ 的机制而重复。这时候我们只需要加上业务处理的幂等操作即可,比如订单处理,我们可以使用 Redis 或者 RoaringBitmap,把最近的 10000 个订单 id 或者 1 小时以内的订单 id,都放进去,每次订单处理之前,先看一下 id 是不是已经在里面了,如果是说明重复了,就不处理。
总之,最好的处理办法就是直接牺牲掉强一致性和准实时一致性,不用事务,既简单,又快速。
系统和服务的高可用可伸缩如何实现
1) 扩展立方体
既然分布式的核心是水平扩展系统,那么我们先来看看“扩展立方体”,如上图所示,扩展有三个维度:
- x 轴-水平复制:复制系统,简单说就是集群,把整个系统作为一个整体,重新部署几套,前面加上集群管理器或者负载均衡器;
- y 轴-功能解耦:拆分业务,按业务把不同的部分切割开,这样可以使用服务化的方式,把不同服务独立部署,然后各个服务可以自己多实例部署来扩展;
- z 轴-数据分区:切分数据,把相同的数据按照不同的属性切割开来扩展系统,比如都是用户,VIP 用户比普通用户对平台交易的价值高,我们就可以把 VIP 用户相关的数据单独切分出来,单独提供资源处理,这样可以在 VIP 用户访问量很大(比如 VIP 用户都是高频的量化交易程序)时,单独给 VIP 用户增加机器资源,扩展 VIP 用户的交易订单处理能力。
我们可以通过上面三个维度的扩展性,灵活的应用到自己的场景里去。高水平扩展性带来我们随时给系统增加机器资源的能力。假设 A 系统的可用性为 99%,忽略掉负载均衡器的可用性,那么每多一个 A 服务节点,就意味着我们的可用性再增加 2 个 9,2 个节点是 99.99%,3 个节点就是 99.9999%的可用性。(这也说明了为什么要拆分数据库,数据库跟系统是串行的,只使用一个数据库,就意味着数据库宕机导致所有的服务节点都不可用了。)
2) 无状态系统
同时为了做到尽量高的扩展性,我们需要尽量让每个服务是无状态的、不共享状态和数据,这样的话每个服务才能随时增加机器。
3) 版本机制与特性开关
当我们新上线一个功能,或者升级一个现有功能的时候,可能会产生不兼容,或者对客户有未预期的影响,这个时候考虑提供版本化的接口,或者使用特型开关,在一定的时间窗口内实现对客户的兼容和友好用户体验。
4) 容错机制
分布式环境下,我们默认上下游和基础设施都是不可靠的,那么怎么在不可靠的假设基础上实现可靠性?这就要求我们考虑容错性,实现面向负载和面向失败的设计,考虑系统的限流、服务熔断和降级、系统过载保护等。
5)尽早发现问题
程墨 Morgan 在
https://www.zhihu.com/question/21325941/answer/173370966 里提到的 case 一样,不要画蛇添足,做一些不必要的操作,“要让服务稳定而高可用,靠的可不是一台服务器,应该用多服务的方式来应对”,如果系统可能会发生问题,就让问题早点暴露,而不是让系统带病运行,最终问题大爆发,可能错过了最佳的处理时机。我也深有体会,特别是交易类的系统,如果带着一些不一致的 bug 运行一段时间,可能会让大量的交易账目出错,再来修补问题,更正数据,代价非常大,甚至要赔偿给用户上百万美元。
6)根因分析、积累故障处理经验
历史告诉我们,错误如果不彻底分析原因解决,一定会再次发生。所以,把所有犯过的错,出过的故障,积累起来,每次都做根因分析,一层层的复盘,找到本质原因,制定改进行动项,才能解决这一类问题。经过一段时间的积累,再分析这一段时间的故障情况,就知道我们的研发团队哪一块的能力有缺失,应该去增强和补充这一块的能力。通过积累处理经验,提升系统的可用性和稳定性。
上面总结了一些高可用可伸缩的要点,下面的小节会讲到自动伸缩扩容。
拆分过程的测试和部署如何处理
通过前面的分析,我们了解到测试、部署和运维,在微服务环境下会变得复杂。试想,原来只需要测试一个系统,现在要测试一堆系统,原来要发布一个应用,现在要发布一堆应用。原来线上排查问题,只需要从一个日志文件看日志信息,一个数据库找数据,现在都不知道去哪儿找数据,因为第一时间不知道业务处理在哪个环节出错了,需要先搞清楚一个跨多个系统的调用处理过程,在哪个环节出了错,如果是显式的错误,有日志还好点,要是没有报错,而是数据错了,那简直就是排查问题的噩梦。
特别是如果两个不同的服务系统,分别是两个小组维护,一有问题就可能会产生相互推诿扯皮,A 让 B 先去排查是不是 B 的问题,B 让 A 去排查是不是 A 的问题,出现两个和尚没水吃的尴尬境地。一个解决问题的办法就是,自动化,降低人为因素的影响,也消灭服务拆分带来的这种重复劳动的复杂性,提升测试、部署、运维效率。
1) 自动化测试
建立全功能覆盖的测试 case,并实现自动化,变更时全量自动回归。集成 Sonar 等工具,检查代码风格、单测覆盖率和成功率等,控制代码质量。我们一般要求核心业务代码,覆盖率 100%;重要业务代码,覆盖率 90%;一般的后端业务代码,覆盖率 80%;其他代码覆盖率 60%。遗留代码,维护时把本次修改设计到的代码,覆盖率提升到 60%。代码风格可以参考阿里巴巴或是 Google 的 Code Style 编码规范定制适合自己团队的标准。
2)自动化部署
借助与 Jenkins、Nexus、Ansible,Docker、K8S 等工具,实现多个应用的自动打包,编排,以及自动化部署,构建微服务项目的部署流水线。特别是基于 K8S,我们可以实现微服务的服务自愈和自动弹性伸缩,在服务失败后重新拉起,在负载高或者低时动态控制容器数量。
3)自动化运维
通过标准规范,配置管理工具,资源交付工具等手段的配合,逐步实现基础架构、应用、IT 服务和业务运营的自动化,实现日常运维处理和运维流程的自动化,降低风险、提高效率,促进组织能力和成熟度提升。
拆分后的运维和监控如何处理
监控与运维是生产环境运行系统的日常工作,就像是人体的免疫细胞一样,保障着整个系统的健康运行,业务的正常运转,下面我们从 5 个方面说明一些微服务下的健监控和运维工作要点。
1) 系统监控
系统监控是最基础的监控指标,是我们了解系统内部运行情况的直接手段。我们要对所有重要的状态进行度量和监控,全面实时的掌握系统健康状态。
2) 业务监控
业务监控意味着我们要从用户的角度看来待系统的监控指标,而不仅仅是技术角度,这样我们就能发现和分析业务指标的突然变化是什么原因造成的,跟系统本身有没有关系,有没有需要我们改进提升的地方,可以更好的支撑业务增长,影响和稳定业务指标。时常可能会出现,业务方说客户都在抱怨系统不稳定,卡了,延迟高了,但是我们从系统监控上看,系统的指标没有大的变化,那么一定是我们设定的监控指标不够,没有覆盖到业务的全流程。反过来,也说我们和业务方、和客户思考问题的角度有差异,为了更好的服务客户,我们需要调整自己的视角,从用户角度出发思考问题。
3) 容量规划
只了解系统的过去和现状是不够的,因为随时可能会有突发的流量袭来,导致系统被冲击,可能会超出系统处理能力导致延迟飙升,直至系统宕机崩溃。所以我们需要在平时做好容量规划,通过持续的压测,了解到系统的极限处理能力,针对瓶颈资源持续做优化,提升处理能力,做好容量预案,随时有激增流量的扩容方案,超出处理能力的限流和熔断、系统过载保护方案,保障系统的稳定运行。
4) 报警预警
做好了业务和系统指标监控,也做了容量规划,那么我们还需要通过这些指标和容量策略,在合适的时机对系统进行干预,把系统风险提前消灭掉。所以,我们需要根据经验定义预警报警的阈值,根据容量水位进行扩容缩容的后续处理动作。特别报警预警的实时性,是我们应对线上突发异常的一个重要指标,例如对于 web 和 app 用户,如果我们在系统突发异常的 10 秒内收到预警,然后又花了 20 秒把系统重启,恢复了服务能力,那么用户可能会觉得刚才 30 秒是不是网络卡了一下,不会产生大规模的客诉。相反地,假如我们 2 分钟收到报警,又花了 10 分钟才处理完,这时候基本上大批客户都会感知到了这次故障,称为一个有大量客诉的事故了。
5) 故障处理
从我们的实际经验来看,导致系统出现非预期的不可用性故障,主要有三类原因:
- 人为的操作失误导致的宕机类不可用,没有标准的操作流程或者操作者没遵守流程;
- 遗漏的功能或性能相关的 bug 问题引起的不可用,我们的测试覆盖不足,或者对系统间的影响关系判断不准确,导致 Corner-Case 有遗漏;
- 不可预知的突发条件或状况引起故障的不可用,比如我们使用了 AWS,突然某个时间段 AWS 日本某个可用区的网络突然发生了大规模超时,某个 RDS 的底层硬盘突然损坏等;
这三类问题的应对策略是分别如下
- 操作失误的应对策略:制定标准操作流程,并根据实际情况不断更新和调整,贯彻培训,严格执行,用流程来防止人为的不规范;
- 功能问题的应对策略:建立全功能覆盖的测试 case,并不断扩充 Corner-Case,逐步实现自动化,跟 CI/CD 集成,每次修改后都能及时的回归所有已知的 case,不留死角。完成系统和服务依赖关系分析,梳理和合理改造影响范围。建立可跟踪的性能测试基线标准和环境,每次重构或者设计调整,都通过基线环境进行性能验证,不把性能问题带到线上。
- 突发故障的应对策略:突发问题是我们真正面临的问题,一般来说不可控,超出预期,难以通过我们的努力直接解决。
所以,如何在不可靠的基础上实现应对策略,除了上面提到的容错和面向失败设计以外,我的经验就是需要在基础设施和业务服务之间,考虑再加一些中间层,特别是使用一些业内知名的成熟中间件,利用它们的主从、多副本、分片等机制,通过软件的高可用实现对硬件和网络底层问题的隔离,进而给上层的业务服务层提供高可用的基座。
简单说,就是把系统的稳定性风险,从我们的基础设施或者原来在业务代码实现里写的各种策略,转移到了中间件上。另一方面,需要考虑突发故障后的系统快速恢复策略,如果我们能在用户可容忍的感知时间内把系统恢复到故障前的状态,则大部分情况下这次突发故障的影响就会非常小。
所以,系统的启动时间,数据预热和配置加载时间,我们需要考虑减低平均故障处理时间(MTTR),缩减到一个可控的很小范围内,比如系统在 10 秒内启动,30 秒内完成预热加载,这样系统发生问题时,我们不在线排查问题,迅速无脑重启即可恢复业务。
故障处理的第一原则是,先解决问题,然后再去考虑分析原因,复盘过程,总结经验教训,最后才是考虑要不要追责。特别强调的是,如果一个线上的发布或者变更操作,有可能造成客户的感知事件,最好就先跟客户进行一个可以预期造成业务影响的沟通,给客户同步一下操作的时间,目的,持续时间,可能造成的影响,让客户可以从容的安排和调整自己的业务,保证不受影响或者降低损失(如果停机会给客户造成损失的话)。如果技术团队对这个操作没有十足的把握,最好考虑在一个可接受的时间窗口内停机处理。对于发布造成的故障,我们一直有个说法:
如果发布可能导致宕机这件事是提前告知了客户,那么真的发生了宕机就是一个故事。相反,如果可能导致宕机这事儿没有提前告知客户,那么操作过程导致宕机,就是一个事故。
最佳实践的总结
林林总总说了这么多的微服务架构相关的知识也好,经验也罢,不一定适合每个希望做微服务系统的技术人员的实际需求。“道无常道,法无常法,君子审时度势,自可得而法”。实际项目里需要做哪些工作,采取哪些策略,先后运用哪些步骤,都需要因地制宜,借鉴各种“他山之石”,综合考虑。
微服务架构的最佳实践,其实就是把微服务架构的条条框框都思考一遍,这一条到底解决了什么问题,适用于什么场景,对我现在的工作有没有帮助,考虑清楚了以后彻底的忘掉这些“有为法”。然后用自己的思考成果去解决工作中遇到的各类问题,就算是真正学会了微服务。问题永远存在,解决掉遇到的问题,是推动技术发展,促进组织进步,提升个人技术能力的不二法门。
过去的几十年里,技术发展的越来越丰富,体系越来越庞大,业务系统越来越复杂,正像是《人月神话》中形容的那样:
“史前史中,没有别的场景比巨兽们在焦油坑中垂死挣扎的场面更令人震撼。上帝见证着恐龙、猛犸象、剑齿虎在焦油中挣扎。它们挣扎得越猛烈,焦油纠缠得就越紧,没有任何猛兽足够强壮或具有足够的技巧,能够挣脱束缚,它们最后都沉到了坑底。”
如何才能逃离“焦油坑”,目前看来使用“微服务架构”的指导思想,去解决复杂业务系统研发中的各类问题,是一个明确的解决办法。没有什么比去解决实际问题、让系统变好更重要。系统变好了,我们的技术也变好了,公司的业务也支持的更好了,业务好了则行业也发展的更好了,行业好了就会让整个国家和民族都更好了。所以,解决问题是关键。
万维钢老师在《精英日课》里说“这个世界上的问题,分为三类”:
第一类是目标明确、路径明确的,比如你上高中的时候,你知道把几门功课都学好,就能上个好大学,这就是一个单纯问题,但是可以看到,单纯问题也可能是非常难的。钱能直接解决的问题,一般也是这类问题。
第二类是两难问题,选择就意味着放弃,比如两个女孩喜欢你,一个有钱,一个漂亮,选了有钱的就不漂亮,选了漂亮的就没有钱。路径很明确,但你面临的痛苦不是因为问题很难,而是不管你怎么处理,都需要付出失去的代价,放弃潜在的另一些可能,会在将来造成实际的损失。你跟小张结了婚,10 年后天天吵架,后悔没有取晓丽,红玫瑰与白玫瑰。
第三类问题,我们叫棘手问题,这类问题,即可能没有特别明确的目标,而且也没有任何人能告诉你一个明确的路径,你现在做了什么努力,可能也短时间看不到效果,做好了不一定有很大成果,做不好肯定要背锅。大家可能都知道这个问题在,就是没有人去解决或者能去解决。最关键是,从外部的一些人看来,这些问题,很像是一个单纯问题。这特么就扯淡了。
而现在看来,微服务架构的这些知识和实践,包括最近几年大家在响应式微服务架构(Reactive MicroServices Architecture)、服务网格(Service Mesh)做得一些工作,恰恰就是给怎么去处理“复杂业务系统研发”这个棘手问题,带来了一个比较明确的路径,指引着大家逐渐的把这个棘手问题变成一个单纯问题,然后用各种新的思想和武器去解决它。
最后给大家分享一个今天看到的微服务和中台的段子:
Q:大师大师,微服务拆多了怎么办?
A:那就再合起来啊。
Q:那太没面子了啊。
A:你就说你已经跨越了微服务初级阶段,在做中台了。
(个人微信号:Robynn-D , 欢迎交流)