近日,CODING 平台技术总监吴海黎参加了由 ECUG 社区举办的技术大会,与听众一同分享了 CODING 微服务架构的演进历程。让我们一起来欣赏精彩的演讲内容吧。
大家好!我是来自 CODING 的吴海黎,今天我给大家分享的内容是微服务拆分的实践,微服务几乎可以说是当下的一个主流架构,希望今天的分享能给大家落地微服务带来帮助。整个分享分为三个部分: 第一是单体架构的简介,第二是微服务架构落地方案,第三是 DevOps 之于微服务的重要性。
虽然第一部分是介绍单体架构的主要痛点,但是还是应该跟大家分享一下,单体应用对于我们在业务早期,业务处理单一,团队规模较小,单体应用的中心化处理方案是能够降低团队的沟通成本,降低架构的复杂度,从而提高研发效率的这么一个架构。所以是否升级微服务架构,取决于我们所处的业务阶段和组织架构。介绍完单体架构的优势,我们一起来看看 CODING 微服务改造前的 Backend Service 是什么样的。
首先这个架构应该是 0.5 版本的微服务架构,可以看出这个架构有一部分服务已经是拆分成独立的微服务了,并且服务之间的注册和发现是依赖于 ETCD 去做服务发现与注册的事情。服务之间的通讯方式是 GRPC,我们选 GRPC 的原因是部分的服务是架在 GO 上面的,部分的服务是放在 Java 上面,所以我们需要跨语言的通讯方式。
我们可以清楚地看到这个架构存在的主要问题是服务拆分了,但是并没有治理,服务的耦合性依然非常严重,比如说我们现在整个入口依然是 CODING 的主站,然后通过主站的流量分发走 GRPC 接口去调用新的服务,没有完全的做微服务的拆分事情。服务之间的耦合性依然非常严重。
看完技术架构,再看一下基于架构的迭代问题,可以看出 CODING 每天的迭代工作量是很大的,我们每天大概是会新增 200 多个需求,100 多名工程师进行研发,合并 50 多次 MR,但是由于我们的业务是分成多个部门进行研发,如果一个部门的业务 Staging 验收失败,可能会导致我们在整个产品迭代部署失败。
回顾一下架构和迭代的主要痛点,第一个是迭代慢,二是编译构建慢,三是难以扩展,四是稳定性慢。
带着问题来看一下微服务架构的主要定义,我把马丁的关于微服务的定义重点圈出来,我对它的理解是我们需要有一种方法论去切分我们单体的服务,使每个微服务的数据独立不共享,可独立构建和部署,并且构建和部署应当有一套自动化的工具来加速整个构建和部署的过程,也就是 CI/CD 和 DevOps。介绍完微服务的定义,大家不难看出微服务架构是用以业务变得复杂的手段,团队规模扩大后,我们将复杂的业务做出合理的拆分,从而方便各个业务独立部署,独立技术架构演进的这么一个技术架构解决方案,也是我们解决之前问题的架构解决方案。
介绍完微服务,可以清晰地看出 CODING 的微服务改造整体方案出来了,我们的整个改造是开源方案为主,避免自己造轮子去蹚一些没有必要的业务上的雷。第二是我们会聚焦核心目标做必要拆分,我们不会一来把所有的单体业务全部拆分为微服务,我们还是按照一个节奏、步骤去增量改进。第三点是兼容原有架构,技术选型对原有技术架构我们是无侵入、可逐步地将所有的应用拆分成微服务,并且线上用户没有感知。第四点是 DevOps 的部分,我重点把 ops 做了颜色上的区分,就是在强调我们在做微服务拆分的时候有 50% 的架构都放在了可运维架构上面。
微服务架构的落地前期主要关注两个方面,第一方面是如何拆,第二方面是微服务的技术架构选型,主要是两方面的技术架构选型,一是基础设施层,另外一个是应用层选型。
首先看一下微服务拆分,微服务拆分是一个业务和组织架构强关联的问题,同一个公司不同的业务阶段不同组织架构拆分的方案也不相同,微服务的拆分是否合理,直接决定服务之间的通讯次数、分布式事物,以及后期业务演进的便利性。
简单通过一张图来看一下,如果说拆分粒度过小,服务的 RPC 和分布式事务都可能导致性能严重下降,这也是我在 2017 年在北京第一次做创业的时候,我当时犯的一个错误,第一次我们把一个电商的微服务拆分得非常细,几乎是可以理解成按照表的维度去做了微服务的拆分,带来的结果就是服务与服务之间的通讯非常消耗性能,分布式事务非常之多。
如果是拆分粒度过大,就起不到业务的独立演进的目的。
在介绍微服务拆分之前,我觉得有两个大的方法论或者说结论需要跟大家分享,第一个是 DDD,由于时间关系,我们在会上理解到 DDD 是一种架构设计的方法,是我们划分业务便捷有效保证微服务高内聚低耦合的方法论就可以了。
我们在具体落地的过程中,是通过一个事件风暴的会议来实现聚合和限界上下文,这是我们需要在 DDD 里面理解的两个概念。其中聚合是我们在业务领域的组成部分,在同一个微服务中,而限界上下文是服务边界和微服务的边界。
第二个是康威定律,康威定律严格来说不是方法论,只是一个结论,在讲我们的业务模型往往和我们的组织架构匹配,也和我们的系统结构匹配,而每一个业务部门的业务顶域、技术栈、同对文化的差异,必然导致迭代的频率、发版频次的不同,也使得单体应用无法满足公司组织架构的升级,同时也意味着微服务拆分的必然性。
这张图是 CODING 最后的单体应用拆分方案,我分成了横向和纵向两个部分,可以看出我们首先是由于部门之间业务不同,对于独立部署是刚需,所以我们先进行了部门级别的微服务拆分。在部门内部,我们是根据 DDD 的设计方法划分业务与业务之间的边界,形成最终的拆分方案。右边的图是我们拆分之后的微服务概要的分布图,可以看出拆分之后我们是每一个微服务技术架构可以独立演进,业务高度聚合,可独立构建和部署。
看完拆分方案,我们一起来看看技术架构,主要是两个部分,第一个是基础设施层,一个是应用层。首先是基础设施层,我们最后选用的方案是 Service mesh+K8s,我把 springcloud 放在这张图上面,不是说 springcloud 本身是一个基础设施层的解决方案,是因为我们在做技术选型的时候,实际上是用 springcloud+K8s 和 Service mesh+K8s 做了一个对比。其实严格来说,两者重点的区分都是对架构的侵入性和具体实现语言的无关,以及今后我们架构演变的便利性,所以我们最终选择的方案是 Service mesh。
其实 springcloud 这几年的发展可以分为三个阶段,第一个是大量整合 Netflix 的一些开源组件;第二个阶段是由于 Netflix 迭代速度可能满足不了 springcloud 社区对于组件升级的需要,springcloud 自己造了一些轮子,比如 spring gateway 这样的东西。第三个阶段是随着国内对微服务应用技术越来越多,可以看到一些开源的大厂,比如说阿里,他们做了一些开源的贡献,有很多组件相继出来。但是对我们而言,对于架构的无形入侵是我们做技术选型最重要的一点,所以我们最终选择是在基础设施层解决微服务的网络治理的问题,所以我们选择的是 Service mesh,具体来讲我们选择的是 LINKERD2。
接着看一下应用层,可以看出我们应用层的技术选型基本上是围绕两件事情来做的,一个是流量的管理,第二个是服务的治理。第一个主要是通过 spring cloud gateway 和 Apollo 的配置中心和 Hystrix 来完成。可以看到由于我们网络的管理,下沉到了基础设施层,所以我们这一层没有服务和网络相关的中间件。
首先看一下流量管理是怎么做的,首先讲讲我们流量是怎么从外部进入内部服务器的,我们这边先是在用云服务器的负载均衡功能将流量打到,云厂商提供的负载均衡的功能,将流量打到了云服务器的 Nginx,再有 Nginx 做反向代理到多个实例的网关做 HA。我们这边对网关组件定义的功能主要是鉴权、认证、动态、路由,跟安全相关的东西,比如说IP白名单功能,比如说限流。
还有一个比较重要的特性是如何监控和报警我们的 API 服务,我们选用的方案是 Hystrix+Promethus,由于时间关系,如果大家对这个本身的技术选型或者说有架构上的疑问,我们可以放在会后讲完之后提问环节一起聊一聊。
再看一下服务治理,目前的注册和发现是依靠 K8s 的 EDCD 来完成,每一个 K8s 的节点都会注册上去,K8s 的网络会复制到集群中间的节点到节点的网络群。另外我们调用的方式是走的 GRPC,目前只支持重设和负载均衡,具体是依靠 LINKERD 去做了网络流量的劫持去完成这件事情。目前 GRPC 的调用还不支持熔断和降级,我们目前在做整合的过程中。我列了应用部分我们关注的微服务架构问题集,以及我们的选型方案给大家,有兴趣的朋友可以问我要这张截图。
我想补充一点,微服务的拆分不可能是一次做完,架构改造不可能一蹴而就,我们的经验是先确定最紧急、最重要的业务拆分需求,以此为根,将业务依赖的技术服务和应用组件先剥离出来,作为微服务第一期的上线内容,重点是验证微服务的基础设施、应用层架构拆分方案的可行性。当然各种灰度方案是有必要做的,只有第一步的象限走稳,后期的逐步拆分才不会出太大的问题。前期我们做微服务架构改造的时候,也犯了一些错误,我们是将产品迭代和微服务架构改造分为两件事情在做,我们两个方向分别去推进,会犯的一个比较大的错误或者是问题是,我们需要不停地从主代码里面去合并代码到拆分的工程里面去。其实这个直接会导致我们的拆分工作量越来越大,问题会越来越复杂。后来直接导致我们拆分的项目改造的信心带来巨大的打击。
说完微服务的技术架构,我们再一起看看如何去做本地开发环境。目前我们的业务是由 100 个左右的微服务组成,很明显开发服务不可能全部形成微服务,因为就算服务 0.5G 的内存占用也需要 50 个净内存,我们需要一个 K8s 集群环境部署全量 Master 版本的微服务,本地开发使用 Telepresence 提供的网络代理和转发机制,将本地的微服务新增或者替换到对应的 K8s 集群环境中去,就可以方便地完成本地的开发和测试工作。
另外,我们也可以通过这个机制去做部门级别的微服务测试工作。 Telepresence 干的事情是它会将自己以一个 pod 节点的方式注入 K8s 的集群里面去,这个 pod 节点具体干的事情就是网络代理和转发的事情,会将集群的流量达到开发者本身的流量,开发者可以通过流量的获取去做本地的功能调试这么一件事情,其实这个对于一个微服务的本地开发或者是 K8s 的运行环境的开发来说,是非常重要的一件事情。
刚才我们介绍了如何拆分微服务以及技术架构的基础设施和应用层的一些技术选型,也介绍了本地开发环境的搭建方式,最后我们一起来看看 DevOps 为什么对微服务如此重要。
首先分享一下什么是 DevOps,其实 DevOps 的核心概念是打通开发和运维的信息边界,使它们的认知和目标一致,最后使得它们的工具一致、环境一致,从而保障我们的迭代速度和修复问题的能力,从定义上看,DevOps 包含文化的建设和工具的建设两个部分,文化的建设比较抽象,我们就不展开说。我们今天重点介绍一下工具的重要性,也就是自动化的重要性。
首先看一下自动化构建和自动部署,在 2019 年微服务改造过半之后,CODING 每天需要构建 20 多次完成 50 多次部署,这跟最开始可能一天我们连一次版本都不能发布的数量变化是非常巨大的。假如这些事情都是没有自动化构建和部署的支持,光是这么一个事就是难以想象的工作量需要极大的团队来维护整个 CI/CD 这件事情。
我们来看一个运维方面的问题,分布式追踪,可以想象一下如果线上出了问题,我们需要在几百个服务实例中找到出错的服务,可能你还要找到服务里面,还有 10 个、20 个实例,你要具体定位到哪个实例出了问题,如果没有分布式,这是无法想象的事情。但是如果我们有分布式追踪的技术,我们现在用的是 opencensus 这件事情,这个排查可能分钟级别就能做完。
最后不得不提起,CODING 做的就是 DevOps 全流程管理的事情,将我们的代码从管理到需求管理、自动构建、自动部署全流程打通,极大加速了我们微服务项目迭代的过程。