背景微服务是近几年流行起来的。很多公司的研发人员都在考虑微服务架构。同时,随着Docker容器技术、自动化运维等相关技术的发展,微服务变得更易于管理,这给了微服务架构良好的发展机遇。在微服务化的道路上,拆分服务是一个热门话题。按照什么原则拆分现有业务?是不是越细越好?接下来说一下服务拆分的策略和原则。拆分的目的是什么?在介绍如何拆分之前,我们需要了解拆分的目的是什么,以免在后续的拆分中忘记了原来的目的。拆分的本质是把复杂的问题简单化,那么单体架构阶段我们遇到了哪些复杂性问题呢?首先我们思考一下为什么我们选择单体架构。电商项目刚开始的时候,我们只是希望尽快把项目建起来,让产品早点上市,快速验证。在开发初期,这种架构确实给开发和运维带来了极大的便利,主要体现在:开发简单直接,代码和项目集中管理,排查问题时只需要排查这个应用,更有针对性只需维护一个项目,节省了维护系统运行的人力成本。但是随着功能越来越多,开发团队的规模也越来越大,架构单一的缺陷也逐渐体现出来,主要有以下几个方面。在技??术层面,数据库连接数成为应用服务器扩展的瓶颈,因为连接MySQL的客户端数量是有限的。此外,单体结构增加了研发成本,抑制了研发效率的提升。比如公司的垂直电商系统团队,会按照业务条线拆分成不同的组。当这么多小团队共同维护一套代码和一个系统时,在协调的过程中就会出现问题。不同团队之间几乎没有交流。如果一个团队需要一个发短信的功能,一些研发同学会认为最快的方法不是问其他团队有没有现成的,而是自己写一个,但这种想法是错误的。合适的话,会造成功能服务的重复开发。由于代码部署在一起,大家提交代码到同一个代码库,代码冲突在所难免;同时,功能之间的耦合严重,可能只改变一个小的逻辑就会导致其他功能不可用,所以需要对整体功能进行回归,延长交付时间。模块相互依赖。一个小团队的成员犯了一个错误,可能会影响其他团队维护的服务,对整个系统的稳定性影响很大。最后,单体架构也会对系统的运维产生很大的影响。想象一下,在项目开始的时候,你的代码可能只有几千行,构建一次只需要一分钟。然后就可以快速灵活的上线频繁修改和修复问题了。但是当你的系统扩展到几十万甚至上百万行代码时,一次构建的过程包括编译、单元测试、打包和上传到官方环境,可能需要十几分钟的时间,而且任何一个小的修改都需要整个项目的构建,以及启动变更的过程非常不灵活。而这些问题都可以通过微服务拆分来解决。为了方便大家更好的理解这一点,我在这里附上一张表格(内容来源:《持续演进的 Cloud Native:云原生架构下微服务最佳》书),可以帮助大家更直观的理解拆分的目的。分割时间应该如何决定?在产品的初始阶段,应该优先考虑单体架构。因为面对一个新的领域,初期很难对业务有清晰的认识,往往需要一段时间才能逐渐稳定下来。如果分割过早,边界分割会不合理或过于细化。会影响生产力。在很多情况下,从现有的单体架构中逐渐划分服务比从头开始构建微服务要容易得多。同时,公司的产品还没有经过市场验证,可能会失败,所以本次投资的风险会比较高。另外,在资源有限的情况下,使用微服务架构的很多优势无法体现,性能上的劣势会更加明显。如下所示。当业务复杂度达到一定程度时,微服务架构所消耗的成本就会体现出它的优势。并不是所有的场景都适合微服务架构,服务的划分应该逐步进行,不断演进。在产品早期,业务复杂度不高的时候,应该尽量采用单体架构。随着公司商业模式逐渐得到验证,产品被市场认可,为加快产品迭代效率,快速占领市场,公司开始引进更多的开发同学,系统的复杂度也会越来越高而更高,单体应用和团队规模之间就会出现矛盾,研发效率不升反降。上图中的交点表明业务已经到了一定的复杂度,单一的应用已经不能满足业务增长的需求,研发效率开始下降。这时候就需要考虑服务拆分的时机了。这一点需要架构师权衡。笔者所在的公司只有在团队规模达到100人时才考虑面向服务。当我们知道什么时候分裂时,我们可以直接去地面吗?不是的,微服务拆分的实现需要提前准备好配套的基础设施,比如服务描述、注册中心、服务框架、服务监控、服务跟踪、服务治理等几个基础组件。以上每一个组件都缺一不可,每一个组件的开发都包含了很多技术门槛,比如容器技术、持续部署、DevOps等相关概念,以及人才储备和概念转变。微服务不仅仅是技术上的升级,更是开发方式、组织架构、开发理念的一种转变。至此,什么时候拆分微服务,总体总结如下:业务规模:业务模式已经得到市场验证,需要进一步加快步伐快速占领市场。这个时候,业务的规模越来越大,按照产品的生命周期。划分(导入期、成长期、成熟期、衰退期)此时一般处于成长期阶段。如果是导入期,尽量使用单体架构。团队规模:一般在团队达到100人时。技术储备:领域驱动设计、注册中心、配置中心、日志系统、持续交付、监控系统、分布式定时任务、CAP理论、分布式调用链、API网关等人才储备:精通微服务的架构师落地经验和相应的开发同学。研发效率:研发效率大幅下降,具体问题在上面拆分的目的中提到。拆分时应遵循哪些指导原则?1.单个服务的内部功能是高内聚低耦合的,即每个服务只完成自己职责范围内的任务,将不属于自己职责范围内的功能交由其他服务完成。2.封闭原则(CCP)微服务的封闭原则就是当我们需要改变一个微服务时,所有的依赖都在这个微服务的组件中,不需要去修改其他的微服务。3、服务自治和接口隔离的原则,尽量消除对其他服务的强依赖,可以降低通信成本,提高服务稳定性。服务通过标准接口隔离,隐藏了内部实现细节。这使得服务可以独立开发、测试、部署和运行,并以服务为单位持续交付。4.不断进化的原则在服务拆分的初期,你其实很难确定服务会拆分成什么。从微服务这个词来看,服务的粒度好像够小了,但是服务太多了也会出问题。服务数量的快速增长将带来架构、开发、测试、运维等环节的复杂度急剧增加。难以快速适应,这将导致故障率显着增加和可用性降低。如果没有必要,应该逐步划分,不断演进,避免服务数量的爆发式增长。这相当于灰度释放的效果。首先,取出几个不重要的功能拆分出一个服务进行实验,如果出现故障,可以缩小故障的影响范围。5、拆分的过程中要尽量避免影响到产品日常的功能迭代,也就是说在做产品功能迭代的同时要完成服务的拆分。比如优先分离相对独立的边界服务(如短信服务等),从非核心服务入手,减少拆分对现有业务的影响,同时也给团队实践和尝试的机会并犯错误。同时,当两个服务之间存在依赖关系时,首先拆分依赖的服务。6.服务接口的定义必须是可扩展的。服务拆分后,由于服务部署为一个独立的进程,服务之间的通信不再是进程内的方法调用,而是跨进程的网络通信。在这种通信模型下,服务接口的定义必须是可扩展的,否则在更改服务时会出现意想不到的错误。比如一个微服务的接口,因为升级,把之前的三个参数改成了四个。上线后调用方会报大量错误。服务接口的参数类型建议是封装类,这样在添加参数的时候不需要改变接口签名。,并且只需要在类中添加字段即可7.避免循环依赖和双向依赖服务之间尽量不要有循环依赖或双向依赖。功能不下沉。8.阶段性合并随着你对业务领域的理解逐渐加深或者业务本身的逻辑发生了很大的变化,或者之前的拆分没有考虑清楚,拆分后的服务边界变得越来越混乱,这个时候,它有必要重新梳理领域边界,不断修正划分的合理性。拆分的粒度是不是越细越好?目前,很多传统的单体应用升级为微服务架构。拆分粒度太细会增加运维的复杂度,粒度太大则效果不佳。转换过程中如何平衡拆分粒度?弓箭平衡拆分粒度的原则可以从两个方面进行权衡,一是业务开发的复杂度,二是团队人数。如上图所示,就像一张弓箭。只有当业务复杂度和团队数量足够多时,服务拆分粒度这把剑才能飞得更远,发挥出最大的威力。比如电商的商品服务,当我们把商品从一个大的单体拆分出来的时候,商品服务本身的逻辑还没有复杂到2~3个人维护不了的地步。这个时候,我们没有必要继续把商品服务拆解成更细的细节,但是随着业务的发展,商品的业务逻辑越来越复杂,可能服务于公司的多个平台同时。这时候你会发现商品服务本身面临的问题与订单整体架构阶段面临的问题基本一致。在这个阶段,我们需要将商品拆分成更细粒度的服务,比如库存服务、价格服务、品类服务、商品基础信息服务等。虽然业务复杂度已经满足,但是如果此时公司人手不够(招聘不及时或者员工变动较多),最好不要拆分服务。拆分后会因为研发等人力不足而引发更多问题。效率急剧下降(一个开发人员负责许多它不匹配的服务)。这里提出了另一个问题。一个微服务需要开发和维护是不是更合理?我可以引用李云华先生在《从零开始学建筑》中的一段经典论述来解决这个问题。为什么三剑客原则说三个人分配一个服务更合理呢?而不是4或2?首先,在系统规模上,三个人负责开发一个系统,系统的复杂度刚好达到每个人都能充分理解整个系统并分工的粒度;如果两个人开发一个系统,系统的复杂度不够,开发人员可能会觉得无法体现自己的技术实力;如果四个人或更多人开发一个系统,系统的复杂性会让开发者无法深入了解系统的细节。其次,从团队管理的角度来说,3个人可以形成稳定的后备,即使1个人休假或者调到其他系统,剩下的2个人依然可以支撑;如果是2个人,转移1后剩下的1个人的压力;如果只有一个人,这是一个单点。球队没有后援,这在某些情况下是非常危险的。如果这个人正在休假并且系统出现故障怎么办?最后,从技术改进的角度来看,一个3人的技术小组可以形成有效的讨论并迅速达成共识;如果是2个人,他们可能会坚持彼此的意见,或者2个人的经验不足可能导致设计缺陷;如果是1个人,因为没有人和他进行技术讨论,很可能陷入思维盲区,造成重大问题;如果是4人以上,部分参与者可能不会认真参与,只是完成任务。“三剑客”原则主要应用在微服务设计和开发阶段。如果微服务经过一段时间的开发已经比较稳定,处于维护期,没有过多的开发,那么平均一个人维护一个微服务,甚至几个微服务就可以了。当然,考虑到人员备份的问题,每个微服务最好安排两个人维护,一个人可以维护多个微服务。综上所述,拆分的粒度不是越细越好,粒度需要符合弓箭原理和三剑客原理。什么是拆分策略?拆分策略可以从功能维度和非功能维度来考虑。功能维度主要是明确业务的边界。非功能维度主要考虑六点,包括可扩展性、可重用性、高性能、高可用性、安全性和异构性。.接下来,我们来详细介绍一下。功能维度功能维度主要是明确划分业务边界。采用的主要设计方法可以使用DDD(DDD的理论知识可以参考网上的其他资料)。DDD的战略设计会建立一个领域模型,通过领域模型可以指导微服务的拆解。主要分为四个步骤:第一步,找出领域实体、值对象等领域对象。第二步是找到聚合根,根据实体、值对象和聚合根之间的依赖关系建立一个聚合。第三步是根据业务和语义边界等因素定义有界上下文。第四步,可以将每个限界上下文拆分成对应的微服务,但也要考虑一些非功能性的因素。以电商场景为例,交易链路划分的限界上下文如下图左半部分所示。微服务可以根据有界上下文进行设计,拆解后的微服务如下图右侧所示。非功能维度当我们按照功能维度来拆分的时候,就不是万事大吉了。在大多数场景下,我们需要加入其他维度进行进一步拆分,最终解决架构单一带来的问题。可伸缩性:区分系统的变化部分和未变化部分。不变的部分一般是成熟的、通用的服务功能,变化的部分一般是变化比较大的、满足业务迭代扩展需求的功能。我们可以将未改变的服务部分拆分出来作为共享服务,将改变的部分分离出来,以满足个性化扩展的需求。同时,根据第28条原则,系统中经常变化的部分只占20%左右,而其余80%基本不变或很少变化。这种拆分也解决了发布频率过高影响成熟服务稳定性的问题。问题。可重用性:重复的功能经常出现在不同的业务或服务中。比如每个服务都有鉴权、限流、安全、日志监控等功能。这些传递的功能可以拆分成独立的服务,也就是微服务中的API网关。比如滴滴业务,有快车和顺风车业务,都涉及到订单支付的功能,那么订单支付可以单独作为一个通用服务,服务于上层业务。如下图所示:高性能:将性能要求高或性能压力大的模块拆分出来,防止性能压力大的服务影响其他服务。常见的拆分方法与特定的性能瓶颈有关。比如在电子商务的抢购中,性能压力最大的就是入口的排队功能。排队功能可以独立为一项服务。同时,我们也可以基于读写分离进行拆分。比如电商的商品信息,在app端有大量的读取操作,而在写入端访问商家中心的次数却很少。所以对于流量大或者核心多的服务可以做读写分离,拆分成两个服务发布,一个负责读,一个负责写。另外,数据的一致性也是在性能维度拆分时需要考虑的一点。对于强一致的数据,是强耦合,尽量放在同一个服务中(但有时因为各种原因需要拆分,这时需要Response机制来保证),弱一致性通常可以拆分到不同的服务中.高可用:将可靠性要求高的核心服务从可靠性要求低的非核心服务中分离出来,重点保障核心服务的高可用。拆分时,可以有一个或多个核心服务,只要最终的服务数量满足“三剑客”原则即可。例如,对于商家服务,您可以拆分核心服务和非核心服务。核心服务由交易服务接入,非核心服务提供给商户中心接入。安全性:不同的服务可能对信息安全有不同的要求。因此,将对安全性要求高的服务分离出来,区别部署,比如设置特定的DMZ区域,分区部署服务,可以更针对性地满足信息安全需求。安全需求还可以降低防火墙等安全设备的吞吐量和并发量要求,降低成本,提高效率。异构性:对于需要开发语言类型的业务场景,可以使用不同的语言将其功能分离,实现独立的服务。以上拆分方式并非多选,可根据实际情况自由安排组合。同时,拆分不仅仅是架构调整,还意味着在组织架构上进行相应的适应性优化,确保拆分后的业务由相对独立的团队维护。服务都拆了干嘛还要合并?古希腊哲学家赫拉克利特曾说过:“人不能两次踏入同一条河流”。随着时间的流逝,任何事物的状态都会发生变化。在线系统也是如此,即使一个系统在不同的时间处于不同的情况,也永远不会完全相同。现在拆分的服务粒度可能是合适的,但是谁能保证这个粒度永远是正确的。为什么服务拆了之后还要重新组装?就是要不断适应业务发展的新阶段。我在这里打个比方,看看大家是否清楚。拆卸相当于我们的开发代码,集成相当于重构代码。为什么我们需要重构?我相信你一定知道。微服务的组合也是如此,随着我们对应用领域的了解越来越多,它可能会随着时间的推移而改变。例如,你可能会发现某个特定的分解由于进程间通信过多而效率低下,导致你不得不将一些服务组合在一起。同时,由于人员数量与服务不匹配,维护成本增加也是服务合并的重要原因。比如今年疫情的影响,导致很多企业裁掉了大量员工。尽管人员流失,但服务数量没有改变,导致服务数量和人员之间的不平衡。一个开发者至少要同时维护5个服务的开发,效率大幅下降。那么,如果微服务数量过多,资源不匹配,可以考虑将多个微服务合并成服务包,部署到一台服务器上,这样可以节省服务运行时的基础资源消耗,降低维护成本。需要注意的是,虽然服务包是在一个进程中运行的,但是服务包中的服务还是要满足微服务的定义,以便将来哪天需要拆解的时候能够快速分离。服务合并成服务包示意图如下:拆分过程中需要注意的风险1、不要打无准备的仗。开发团队是否有足够的经验,能否把控微服务的技术栈,可能是首先要考虑的点。不要求团队必须拥有完美的体验才能开始服务拆分。团队中有这方面的专家当然是最好不过了。如果没有,可能需要提前进行充分的技术论证和演练,至少不要打无准备的仗。避免先拆哪个简单,哪个新业务需要上线,先开一个服务再说。否则,你可能会踩到一些常见的分布式问题,比如服务器资源不足、运维困难、服务间调用混乱、调用重试、超时机制、分布式事务等等。2、不断修正我们需要承认我们的认知是有限的,我们只能根据当前的业务状况和对未来有限的预测制定一个相对合适的拆分方案,而不是所谓的最优方案,任何方案都只能保证当前提供了相对合适的粒度和划分原则,要时刻做好过时的准备,在未来的最后一刻需要再次调整。因此,随着业务的演进,我们需要重新审视服务的划分是否合理。如果服务划分过于细化,导致人员效率下降,故障概率大大增加,则需要重新划定域边界。3.做一个行动主义者,而不是理论主义者。不要太执着于适不适合。不做怎么知道适不适合呢?如果拆开后发现确实不合适,就重新调整一下。您可能会争辩说调整成本相对较高。但其实这个问题的本质是服务架构是否有完整的能力体系,比如服务治理平台、数据迁移工具、数据双写等,如果有的话,再调整的成本不会太高。
