• 作者:老汪软件技巧
  • 发表时间:2024-10-11 00:02
  • 浏览量:

关于微服务拆分这个话题,我经历过最离谱的是:一个表对应一个微服务,这导致了微服务数量急剧膨胀,作为新入职的菜鸟,当时的我吃尽了苦头~

大家好,我是五阳。

微服务拆分没有严格的标准,往往经验优先。

1. 为什么要拆分微服务1.1 单体架构的不足代码量会变得非常庞大和复杂,难以维护和扩展,例如 Idea 需要很久才能打开代码工程;代码合并冲突较多。某一部分出现性能问题,会影响其他系统;多个领域之间存在互相影响的问题,系统稳定性极差。每次更新和部署都需要重新构建和测试整个应用程序,服务启动时间、编译时间、测试时间太长,降低需求交付效率。1.2 微服务拆分过细的坏处微服务依赖关系复杂导致应用架构图复杂,理解成本大幅增加。代码梳理和开发成本过高。1 个人修改过多微服务,难以保证开发质量。增加过多的 RPC 调用,接口性能下降,稳定性下降。服务发版、全链路压测、服务扩缩容导致的运维成本高。基建jar 包升级成本高。2. 微服务拆分的原则2.1 团队隔离原则

不同团队的职责不同,负责领域不同,不能共享微服务。

不同团队之间必须严格实施微服务隔离。除了历史遗留问题之外,我实在难以找到其他导致团队间共享微服务的合理理由。

即使在某些情况下确实存在团队间共享服务的现象,那也仅仅是因为服务迁移的成本高昂且耗时较长。这种情形只能被视为一种短期的过渡状态,而不能作为长久之计。

2.2 领域隔离原则

以电商交易场景为例,订单域、商品域、营销域、结算域、支付域、履约配送域等领域划分方式,是电商业务的数十年发展总结的最佳实践,以上各个领域之间应该进行服务隔离,不能杂糅在一个服务中。

团队隔离原则依托于领域隔离原则,正是因为不同团队职责不同、领域不同,团队间才需要进行服务隔离。

然而领域拆分原则不能一劳永逸的解决所有场景。领域可以继续细分成多个子领域,子领域也有核心域、支撑域之分。如果每个细分的子领域均独占一个微服务,会导致微服务过多。

此外如果将多个子领域放到一个微服务也会有大单体架构的风险。如营销场景和玩法多样化,如优惠券系统、立减系统、优惠计算、抽奖、营销触达、资源位、权益等,如果将众多的营销系统塞到一个服务中,那么又将回到大单体架构的老路上。

按照领域拆分微服务时,如何界定领域的粒度呢? 没有严格的标准,但有业界经验。

如前所述,领域的复杂度决定了服务拆分的粒度。如果领域非常复杂,应拆分为多个微服务,如果领域不那么复杂,则应适当共享微服务。

2.2.1 支撑域不宜单独拆分微服务

核心域提供的能力是团队的核心能力,而支撑域之所以是支撑域而非核心域,往往不那么重要,复杂度不高。因此同一个领域下的多个支撑域可以共享微服务,待支撑域足够复杂以后,可以从共享服务中抽离出来,独占一个微服务。

以下将继续从人力角度、性能角度等分析微服务如何划分,这两点在一定程度上反映了领域的复杂度。

2.3 三个火枪手原则

三个火枪手原则是指由三个人共同负责一个微服务。

相比两个人,三个人的备份能力更强,例如1 人请假时,有另外两个人可以顶上。 4 人相比 3 人沟通的成本更高,三个人可以在工位就把事情沟通完了,4 个人沟通就很麻烦(可能需要抢会议室,4 个人开会容易影响其他人),取得共识的难度更高。

由于工作年龄、入职年龄、业务和代码熟悉程度的存在差异性,火枪手的水平和工作能力应该满足这 2 个条件

每个火枪手的工作职级可以参考 阿里P6甚至 P7,这个职级对工程师的要求是某领域的专家,无需其他人指导下,能独立解决较复杂的技术任务。每个火枪手应熟悉业务、熟悉代码,入职时间较久。一般认为至少在入职半年-1 年以上。对所负责系统已经很熟悉,不能是刚入职的新人(即便职级很高)。

一个微服务可以包含多个领域,3 个火枪手原则告诉我们,3 个火枪手能负责的多个子领域可以放置到一个微服务中。这 3 个人应该交叉负责多个子领域,要避免只有 1 个人熟悉某领域、只有 1 个人负责某领域的单点风险。

换句话说,如果一个微服务包含的领域需要 3 个以上火枪手负责,说明微服务的复杂度过高。 如果不足 3 个火枪手,那么说明微服务拆分过细。

2.4 读写隔离原则

如果我的团队,只有 6 个人,即6 个火枪手,那么只能有 2 个微服务吗?这并非绝对。

业务的并发量较高的情况下,进行读写隔离是有必要的。

为什么呢?相比读请求,写请求耗时一般更高,当读请求量非常高时,会导致更频繁的 GC,机器的负载也会更高,这会导致同机器的写请求的性能下降。如电商提单(下单)写链路涉及的接口调用非常多,难以保障接口性能,与此同时,提单接口的稳定性要求非常高,如果不进行读写分离,当读请求大幅增长时,会导致提单(下单)链路性能下降,这是不能接受的。

此外读接口和写接口的降级、熔断、限流要求不同。读请求可以适当降级,而写请求降级可能导致数据不一致,如果读请求大幅增长导致机器性能恶化,读请求降级了,但是写请求不能降级,那将难以保障写接口的可靠性。

如优惠券系统是典型的读多写少的场景,优惠券分查发核退,可以将查询单独抽离出单独的服务,而优惠券的写入流程(发券、退券、核销券)可以单独拆分到另一个微服务。

如何评估并发量高与低呢?业界经验认为:集群的高峰期并发量在 5000/秒是比较高的并发,需要考虑缓存和分库分表。Java应用单机(8 核 16G)并发量在 500/秒以上,机器负载和请求耗时面临性能瓶颈,机器数量在 10-50 台以上,规模算较大。

一般情况下,集群的最大性能应预留 Buffer,如集群最大并发量可支撑目前最高峰的 2-3 倍以上,是稳妥的,能应对突发的流量尖刺。

各位架构师,可以按照以上经验,评估是否有必要进行微服务的读写隔离。需要强调读写隔离是出于对性能的考虑,如果并发量不高,不建议读写隔离,必要性不高。

2.5 BC端隔离原则

如电商场景,有多种用户角色,有多种系统入口,如运营后台、管理后台与 C端等入口。不同的系统服务于不同的用户角色,流程和模型也存在差异。

在业务初期,系统的B/C端往往由 1 个团队负责,随着业务的飞速发展,业务和系统复杂性增加,B C 端往往交由不同的团队负责,因此在早期 B/C端有必要进行微服务隔离,避免后续团队拆分导致服务迁移的成本。

在电商场景,业界一般会进行 B、C端隔离。包含微服务的隔离、模型的隔离、流程的隔离。

2.6 业务隔离原则

业务中台往往接入多个独立的业务,为了避免业务间存在互相干扰的风险,往往需要将重要的业务和非重要的业务进行微服务的隔离。

重要核心业务往往迭代频率较低,而新业务往往迭代较多,改动频率高,两者的稳定性要求也不同,进行微服务的隔离能极大程度上保障核心重要业务的稳定性。

多个非核心业务可以共享微服务,寻求其他方式实现业务间隔离。

依据业务身份进行流程编排,隔离处理流程;通过Maven子工程,进行代码隔离进行机器打标,实现部署架构上的隔离。2.7 外部 API 入口隔离

Http等外部API入口需要进行身份鉴权、IP 黑白名单和限流等安全性需求,如果随意在外部 API服务中添加仅内网可访问的接口,会导致严重的安全问题,因此将外网可访问的 Http接口单独放到一个微服务中是有必要的。

该服务不实现业务逻辑,仅进行协议转换等内容,通过RPC调用访问内部服务,业务逻辑由内部微服务完成。

这不就是 API网关吗?是的,如果公司内有 API 网关,可以省去此服务。如果没有微服务网关,那么最好有一个外部Http 接口服务。

2.8 最少改动原则

一个人同时能改动的微服务数量有限。

_公鸡吃蜘蛛的找茬图_蜘蛛蜘蛛吃女人

考虑这个场景,一个小需求交由一个火枪手独立开发,他评估改动后发现:虽然改动内容不同,但是改动内容包括 5 个微服务,需求上线时,他需要同时发布 5 个服务。

想起来就让人抓狂。

代码开发是非常细致的工作,在 1 个需求中,将太多的模块交给 1 个人负责,出问题的风险是很大的。一个人能负责的内容是有天花板的,为了保障需求交付的稳定性,不能让 1 个人负责过多的内容。

如果出现1 个人改动了 5 个微服务的情况,以下2个问题中,至少反映其中1 个问题。

微服务过度拆分,粒度过细负责内容过多,人力分配不合理。

最理想的情况:一个人在1 个需求迭代时,最多改动两个微服务,发布 2 个微服务。

微服务的划分粒度不可过细。同一个领域下的多个子领域适当共享微服务。

2.9 单向依赖原则

如果两个微服务之间存在互相调用,那么说明微服务划分不合理。

我首先以方法调用的例子来说明。如果 A 方法调用了 B方法,B 方法内部也调用了 A方法,这会导致出现什么问题呢?

死循环后,出现栈溢出。A和B方法内部职责不清晰。很难有一个标准,能指导业务逻辑放到 A 方法还是 B 方法。

现在来考虑服务之间的互相依赖的风险。

去年国内最大的打车公司出现重大事故,传说该公司的K8s 集群被错误的全部删掉掉。当系统重建时,A、B 两个微服务互相依赖,这将导致谁也无法率先启动。(除非先降级互相的调用)无法满足高内聚、低耦合的原则。两个服务的职责和领域存在交叉,存在职责不明、边界不清的情况。每个人都有自己的理解,人来人去,久而久之,会导致两个微服务的依赖关系更加失控。

如何解决或避免服务之间互相依赖的情况呢? 将互相依赖的部分放到 1 个微服务即可,或者干脆将两个微服务合并为 1 个微服务。

2.10 重要性隔离

团队中总是会存在一些重要程度比较低的、稳定性要求比较低的场景。

例如我负责的电商场景,在排查问题、修复问题时需要一些运维工具,需要提供运维接口。这些运维接口只给研发使用,不对外开放。接口的重要程度低、稳定性要求低,于是我们将运维接口放到了一个运维接口服务。

这个接口运维服务没有C 端用户访问,我们甚至可以在业务高峰期上线发版,非常灵活。

2.11 代码复用并非一定共享服务

“因为 B 服务有发送短信的工具代码,所以我将售后短信通知放到了 B 服务。” 我见过太多这种情况。

将某块业务逻辑放到 A服务还是 B 服务?很多人在考虑时,并非是按照领域划分原则,而是考虑实现成本。

因为售后需求中要发送短信,按照领域划分,本应该放到 A 售后服务更合适,但是因为A 服务没有现成的短信发送方法,所以将毫不相干的逻辑放到了 有短信发送工具方法的B 服务中。

代码复用有很多种方式,并非只有共享微服务一种方式。

2.11.1 通用工具方法应该抽到 单独的 Jar 中

发短信、发 push、发 email、生成 excel、读取 excel、发 IM 消息、下载文件、上传文件等 通用的工具类应该被抽离到单独的 maven工程中,通过common jar包的方式共享。

如果将以上工具类放到一个微服务工程中,势必导致某些人贪图省事,增加很多无关的领域逻辑。久而久之,这个微服务势必变成一个大泥球,大单体,难以维护。

2.12 人均服务数原则

太多的微服务给团队的负担很大。 尤其是服务运维成本、基础架构升级成本尤为严重。

举个例子大促期间需要扩缩容服务,全链路压测需要评估机器负载,是否需要扩缩容,微服务数量越多,这部分工作量越大。

基础架构提供了很多 jar 包,例如 rpc、mq、监控、缓存等等通用的 Client,需要升级的时候,所有的服务都需要升级。太多的微服务增加的基建升级成本更高。

控制微服务数量是非常有必要的!

团队成员数量不同,理想的微服务数量也不同。

成员数量理想数量

5 人及以下

3 个服务及以下

10 人及以下

5 个服务及以下

15 人及以下

7-8 个服务及以下

三个火枪手原则是指:一个微服务包含所有子领域的复杂度由3 个火枪手负责刚刚好。然而考虑到读写隔离、业务隔离、API 入口服务隔离、领域隔离等等情况,很难做到 团队成员数/微服务数=3。

2.13 适当前瞻性设计

业务是不断向前发展的,发展并非是线性的,业务的突然爆发、高层的战略调整对业务的改动,会导致业务的规模和复杂度出现翻天覆地的变化。

在业务发展初期,由于业务复杂度低,多个领域共享一个微服务是合理的,业务飞速迭代后,复杂度急剧增加,就有迫切的拆分微服务的必要,适当前瞻性设计有必要。

进行业界调研尤其是参考友商的建设经验是非常必要的! 可以避免在重大的问题上犯低级错误。

3. 一定会有热点微服务

经验告诉我们,团队中一定会存在大泥球服务,几乎所有人都会修改这个服务。

如何解决呢?不要试图解决 ~ 拆分这个微服务导致的风险往往比获得收益大。

向前看,尽量避免更多的微服务变成大泥球,这件事更重要。