一、背景
上周小组有个需求上线牵扯9个应用(小组目前维护了26个服务,由于团队系统业务属性特征基于高可用、高性能原则拆分,有些是合理的,有些不是很合理的),同时上周OpsReview的一个微服务滥用典范案例(Promise服务A调用服务B,服务B只是读个配置数据返回,无具体业务逻辑),OpsReview会上也看到过其他微服务泛滥等案例,这些事情引发了我对服务泛滥和服务粒度的深入思考,接下来在稳定性的前提下,基于成本收益维度思考,小组哪些应用(先从2级开始)应该合并治理。
在当今软件开发的舞台上,微服务架构因其所灵活性以及独立部署等优势而广受推崇。然而,随着服务的过度细分,我们面临着服务数量激增所带来的管理复杂性和分布式通用问题挑战性等。
本文通过Promise后端现有服务探讨服务粒度的合理划分与有效合并的关键因素。本文的观点源自我在学习与实践过程中的思考,尚处于不断探索和验证的阶段。希望能“抛砖引玉”,引发大家的思考与讨论,让我们共同进步,从而优化我们的系统设计。
目前团队服务泛滥的影响如下:
1、维护成本增加:服务数量的增加直接导致了维护工作量的增加。这不仅包括服务器维护,还包括代码维护依赖管理,比如Promise时效内核升级、中间件JSF等版本升级、安全漏洞工单log4j版本改造等都需求部署上线N个(最多26)应用。当服务数量过多时,即使是小的变更也可能需要协调多个服务,从而增加了工作量。
2、团队协作难度:在服务泛滥的环境中,团队成员可能需要跨多个服务进行协作,比如本次需求上线牵扯9个应用。这不仅需要更多的沟通和协调,而且还需要确保所有相关人员对服务的改动和更新都有清晰的认识。这种跨服务的依赖关系可能会导致团队间的沟通混乱和效率低下。
3、资源利用效率低:服务泛滥可能导致资源分配不均,一些服务可能过度使用资源,而其他服务则可能闲置。这种不平衡的资源分配会导致整体成本的上升。
名词解释:
模块化:是指将一个复杂的系统分解成若干个相互独立且可集成的部分,这样可以简化系统的设计、开发和管理。模块化的目的是通过创建高内聚、低耦合的模块来提高软件的可维护性、可复用性和可扩展性。在模块化的过程中,每个模块都是一个独立的单元,可以单独开发、测试和优化,同时通过定义良好的接口与其他模块通信。
粒度:在软件架构中,粒度通常用来描述组件或模块的大小和层次。较小的粒度意味着系统更加灵活,因为它允许更精细的控制,但可能会带来性能问题,如增加硬件资源和通信开销。较大的粒度则可能提高效率,但会牺牲一定的灵活性。
二、Promise服务粒度问题
在微服务架构的设计理念中,最大陷阱之一在于,并非所有构成程序的组成部分都必须被设计为微服务。
系统交互图
简单描述下各应用作用:
- A:订单履约控制中心
- B:promise时效服务,主要提供订单下传库房时效、订单妥投时效
- C:promise产能服务,主要用于仓库产能查询、产能占用
- D:promise消息服务,主要用于订单时效异步处理,其中包括发送全程跟踪,订单时效缓存等。
- E:promise订单时效持久化服务
对应用户在京东APP购买商品流程如下:
- 北京用户4月1号22点购买了一件衣服,库存中台定位的是北京的仓。
- A服务通过JSF调用Promise计算这个订单下传库房时间是4月2号0点01分(对应B应用)。
- JSF调用占用4月2号的仓库产能(对应C应用)。
- 然后通过MQ方式给用户发送全程跟踪话术,告知用户订单时效环节(对应D应用)。
- 最后把订单时效数据进行保存持久化,用于业务进行数据分析和履约绩效考核(对应E-mysql持久化应用)
- 同时为了方便排查历史订单数据持久化同时存在es(对应E-es持久化应用)。
那么问题来了
- 上面这些应用划分合理吗?尤其持久化包含2个应用E-mysql和E-es。 这应该是1个服务还是2个服务更合理呢?
接下来针对这个问题,进行分析。
三、服务粒度
模块化关心系统分解成单独的部分。而粒度则处理这些单独部分的大小。粒度才是分布式各种挑战的问题关键。那如何来度量粒度大小呢?看里面java文件数、class类数量、代码行数?其实并不是的,而是看服务的职责,服务范围包含哪些,另外一个指标是看服务对外提供的公共API接口或者MQ的数量。虽然这2个指标也是存在变化和主观因素,但这应该是目前最接近客观度量和评估服务粒度的方法。
如下图:当业务复杂度达到一定程度后,微服务架构消耗的成本才会体现优势,并不是所有的场景都适合采用微服务架构,服务的划分应逐步进行,持续演进。
现在很多拆分粗粒度可能是主观意识或者直觉,缺乏理论。我们需要使用【拆分因素】和【合并因素】来客观综合分析利弊,从而形成拆还是不拆服务的充分合理解释。
1、拆分因素
粒度拆分因素解决的是什么时候应该将服务拆分为更小部分。为服务拆分为更小的部分提供了指导和依据。其中粒度拆分因素的主要因素如下:
1.1、服务职责&业务边界
服务职责是拆分服务考虑的第一因素,确保服务与业务能力对应,领域治理,职责单一,避免单个服务承担过多不相关的功能。需要从两个维度来考虑。
- 第一个维度是内聚性,即服务内部的行为关联的紧密度,每个服务应该只负责一个业务能力或业务领域。
- 第二个维度是组件的整体大小,服务的大小和职责范围需要平衡,既不能过于庞大,也不能过于细碎。一般根据服务对外提供的入口数来度量。
比如上面说的Promise时效B应用、C产能应用、D消息全程跟踪、持久化应用 这4个应用内聚性紧密型不大,产能域和时效域拆分。同理订单时效E-mysql持久化服务和E-es持久化服务,这2个应用都是数据持久化,功能是一样,即把订单时效数据保存起来。内聚性相对较强,入口也都是消费对应MQ。强内聚意味着数据持久化这个服务是单一目的性的,这2个应用不应该拆分。
1.2、容错性&稳定性&高可用
- 容错性和服务可用性也是很好的分解因素之一,分析如何通过服务拆分提高系统的容错性和高可用性。
- 将系统中的业务模块按照业务优先级排序,可靠性要求高(0/1级应用)的核心服务和可靠性要求低(2级应用)的非核心服务拆分开来,重点保证核心服务的高可用。这样可以避免非核心服务故障影响核心服务。
- 服务的粒度越大,故障发生时带来的影响面也越大,识别出系统中脆弱的部分将其拆分出去,可以有效的减少故障时带来的连带影响。拆分为更小的服务,以便于在不同的节点上实现容错和故障转移。
比如案例中的物流配送时效和产能服务通常是关键服务,它们的可用性直接影响用户体验。拆分这些服务时,要特别考虑如何设计系统的高可用性和容错机制。这也是为什么上面为什么要通过MQ异步方式解耦把时效核心服务和 全程跟踪数据持久化应用拆分,优先保障订单库存生产(物流配送),再来处理全程跟踪话术(用户体验)和数据持久化数据分析。因为数据持久化mysql耗时并且可能故障概率大。
如果mysql服务和es服务容错性不一样,比如es老是崩溃,不稳定,进而影响了MYSQL服务,导致业务影响,则可拆分为2个服务。
1.3、性能&吞吐量
- 基于性能拆分和基于可靠性拆分类似,将性能要求高或者性能压力大的模块拆分出来,避免性能压力大的服务影响其他服务。
比如持久化mysql服务100W/M 和持久化es服务80W/M 是一样的吞吐量。大促期间es消费相对较慢,但性能在可接受范围。比如自营订单1W/S TP99位20MS,外单100/S TP99 为3000MS可能更适合拆分
1.4、可扩展性
- 服务拆分需要考虑可扩展性。其实很多设计方案都会多度设计扩展性。其实通常很难去猜测未来上下文功能是否可能会扩展(比如额外的数据持久化方式hbase、jdq等)。
- 做过太多需求,说某种业务场景未来可能会用,但根据历史经验,业务基本不会有这种场景。
- 建议是等待,直到持续可扩展性被确认,然后让可扩展性这个因素成为判断粒度分解的主要因素。
1.5、团队大小和稳定性
- 考虑开发、维护团队的能力和组织结构。服务拆分应便于团队协作和沟通。
从拆分因素来看上面的promise服务粒度应用的划分是否妥当? A: 对于B时效服务、C产能服务以及D消息服务,其应用拆分是根据业务的独特需求来定制的。特别是在大型促销活动期间,订单的实时生产、仓库产能实时管理情况是至关重要的。因此,我们基于时效性和产能管理的需求进行了服务划分。此外,通过MQ(消息队列)机制,我们实现了业务特征的解耦,使D消息服务能够消费相应的MQ,并处理相关的业务逻辑,例如提供用户的全程跟踪体验和订单时效性的缓存。为了确保服务的稳定性和高可用性,我们特意将持久化应用独立出来,从而定义了清晰的职责边界。例如,在进行MySQL数据库的主从切换时,我们只需暂停持久化应用,这样就不会影响到订单的时效处理和用户的全程跟踪逻辑。
2、合并因素
上面的拆分因素为何时将服务分解为更小的部分提供指导和依据,那粒度合并因素则相反,为服务重新重合在一起提供指导和参考依据。粒度集成的几个主要因素如下:
2.1、服务原子性
服务的原子性是指服务操作应该是一个不可分割的整体,要么全部成功,要么全部失败。这有助于保证数据的一致性和系统的可靠性。
- 考虑不同服务之间是否需要事务?需要原子操作或事务性的业务流程可能不适合拆分成多个服务,因为分布式事务管理通常是复杂且成本较高的。
- 具有原子操作的单独服务具有更好的安全访问控制
- 如果服务之间需要频繁同步数据以保持一致性,这可能表明它们应该被集成合并为单个服务。
- 具有组合操作的单独服务间不支持数据库事务,微服务应该追求数据自治,每个服务拥有自己的数据库,以避免分布式事务的复杂性。
2.2、成本效益
拆分服务可能会增加运维成本,因此需要进行成本效益分析,确保服务的粒度调整能够带来正面的经济效益。
- 硬件成本:如果拆分后,硬件成本更高,可能更倾向更粗粒度,减少资源消耗
- 变更成本:如果服务经常一起变更(开发、测试、部署、上线),可能更倾向更粗粒度。
- 维护成本:维护多个服务,比如服务通用治理版本升级等,成本较高
2.3、网络开销
- 考虑服务之间是否有交互?服务间的通信会带来额外的延迟。如果服务拆分导致大量的远程调用,这可能会对性能造成影响。在某些情况下,集成合并服务以减少网络通信可能更有利
2.4、共享代码
- 是否有共享代码,在分布式服务架构中处理共享代码,事情会变得复杂,有时候会影响服务粗粒度。如果共享代码太多,合并则可能更合适
2.5、数据关系
- 数据库表的关系影响服务粗粒度,服务之间的数据是否也可以拆分?
- 假设服务底层数据并不共享,而是在每个服务内形成紧密的限界上下文,则适合拆解
- 如果底层数据共享相同表,可能更适合一个服务
2.6、代码变更&部署频率
- 代码变更的频率是服务分解的另一个考虑因素,如果服务之间变更频率是一样的,可能不适合拆分。
- 如果一组服务总是一起更新,这可能表明它们是高度耦合的,应该作为一个单独的服务部署。
比如上面的【订单时效E-mysql持久化】服务和【E-es持久化】服务变更频率是一样的,时效增加一个字段,用于下游考核业绩,es同样也需要用于日常排查。
2.7、测试和监控
- 考虑服务划分对测试策略和监控体系的影响。更细的服务粒度会带来更复杂的链路监控和日志管理需求。
- 微服务的监控和日志管理可能会变得复杂。如果服务拆分导致难以追踪问题和性能监控,这可能是一个合并服务的理由。
从集成因素来看上面的promise服务粒度问题
Q: 关于持久化,目前包括两个应用:E-mysql和E-es。是将它们设计为一个服务更合理,还是作为两个独立的服务? A: 综合前述的分析,将这两个应用设计成一个服务显得更为合理。这样的设计可以充分利用两者之间的协同效应,同时简化系统架构,提高维护效率。通过统一的服务接口,我们可以更加灵活地管理数据持久化的过程,无论是对MySQL还是Elasticsearch的操作,都可以确保一致性和高效性。这种一体化的服务设计,既可以减少系统复杂性,也便于在需要时对服务进行扩展和优化,但需要做好es容错性,即es有问题不能影响mysql业务。
四、总结
在微服务架构的世界里,服务粒度的艺术不仅仅是技术上的划分,更是对业务理解的体现,对系统复杂性的把控,对团队协作和效率的考量。我们不能忽视服务粒度选择对系统性能、可维护性和可扩展性的深远影响。正如我们通过Promise系统的探索所见(Promise业务属性是下单前商详结算黄金链路、下单后订单控制节奏,一切的拆分原则优先考虑高可用、高并发出发),恰当的服务粒度能够带来清晰的职责边界,提升系统的响应性和可靠性,同时降低维护的复杂性和成本。
在决定服务如何拆分或合并时,我们不仅要考虑技术因素,还要考虑团队结构、业务需求和未来的可扩展性。这需要我们深入理解业务逻辑,预见潜在的变化,并且勇于对现有架构进行调整。服务粒度的选择不是一成不变的,而是随着业务的发展和技术的进步而不断演化的。
我们不要简单地追求服务数量的增加,而是追求每个服务能够在正确的时间做正确的事情。通过精心设计服务粒度,构建出既强大又灵活的系统,这些系统能够支持我们的业务成长,应对未来的挑战。
如果您的团队面临服务泛滥,您会如何权衡服务的粒度呢?欢迎大家指正和完善,谢谢!
本文的观点源自我在学习与实践过程中的思考,尚处于不断探索和验证的阶段。希望能“抛砖引玉”,引发大家的思考与讨论,让我们共同进步,从而优化我们的系统设计。
附:
1、软件架构第一定律:软件架构中的一切都是在权衡