服务拆分的设计和思考(B2B 技术共享第九篇)

5,728 阅读10分钟

本文主要想给大家分享一下,宋小菜这三年来,是如何从单点巨石系统演变成领域驱动的服务化设计的。这个演变现在还在继续,我们在实践过程中遇到了很多坑,也收获了经验和思考。

一、早期的系统

1.1 唯快不破

我是在宋小菜处于筹备阶段就加入的第一个程序员,见证了宋小菜系统从0到1的全过程。那时候的技术团队只有一个产品经理,一个程序员(就是我),还有五个外包同学。在宋小菜第一天工作的日子我还记得非常清楚,是2015年1月18日,我们的第一个任务我也记得非常清楚,在3月份前产出一个可以交易的平台。交易需要什么,用户、账户、商品、资金、订单……这些模块组成了宋小菜最早的交易系统。那时候为了快,牺牲了太多的东西,比如缺乏了整体的架构设计、缺乏了充足的业务理解、缺乏了功能的抽象、缺乏了性能的分析。这些行为在现在看起来,非常不可思议也不可接受。我时常在想一个问题,如果以我们现在的经验和理解,穿越回到三年前去开发最早期的宋小菜系统,又会是一番怎么样的场景呢?我会一开始就规划出用户中心、商品中心等几大中心吗?我会一开始就设计无状态系统和水平扩展能力吗?我会一开始就开发网关平台收拢和规范所有API的调用吗?答案一定是不会的,而且这样做结果往往也不一定会比当初更好。 早期系统并不需要一些类似核武器一样的架构设计和中间件,真正需要的是最快速度为公司的业务提供战斗的能力。

1.2 面临的问题

那时候主要的系统有两个,分别为内部的ERP系统和供客户端调用的API服务系统,这两个系统完成了我们的交易的闭环。随后的两年时间里,疯狂的业务代码堆积和不受控制的重复代码,使得这个系统越来越笨重。那时候的系统结构非常简单,公共的模块以jar包的形式抽离出来,其中ERP系统的依赖关系可以参见下图:

image1.png | center | 1338x1274

这时候的系统改造就变得非常痛苦,每次发布都会变得异常紧张,生怕有哪个模块的jar包改动了没有更新,就导致系统无法顺利启动。曾经有那么一段时间,我会很惧怕系统的发布,如果顺利发布,我也会长舒一口气。
再发展下去,就已经不是害怕系统发布这么简单了,系统逐渐进入一个可怕的死循环了:
在系统的各个地方散落了一些异常刺眼的注释,比如“这段代码太牛逼了,我不敢改,所以就复制了一份”、“这部分的逻辑是xxx写的,不要轻易改动”等等。以下六点是我们系统存在的主要问题:
  1. jar包管理混乱,经常导致系统无法构建;
  2. 笨拙、耦合严重,发布影响点混乱,底层jar的变更,导致每个应用系统都需要发布;
  3. 开发过程中协作困难;
  4. 新人很难上手,理解困难;
  5. 不容易管理,出现很多不洁代码;
  6. 技术债务越背越多; 当公司规模还小的时候,业务复杂度和系统复杂度都还处于一个比较好接受的阶段,巨石系统往往是一个比较高效的架构设计。但是随着业务的不断扩展,复杂度和访问量进一步的增加,如果突破了某一个临界点了,系统架构的升级和服务的拆分,就变成不得不做的一件事了。

二、服务拆分上遇到的坑

2.1 缺乏规划

当时决定进行服务拆分的时候,技术团队的规模也并不是特别庞大,也并没有哪位同学对于服务的拆分有较多经验。现在回想起来,那时候的起步确实草率了点。我们没有做太多的规划,直接到了“说干就干”的环节了。可想而知服务会出现的多么凌乱,一会一个“价格中心”,一会一个“权限中心”,哪个简单就先拆哪个,哪个新业务要上了,先起一个服务再说。随后就暴露出了服务器资源不够、运维困难、服务之间调用混乱等问题。 大家意识到这个问题的时候,还不算病入膏肓。我们立刻进行了宋小菜整体服务大盘的设计和划分,将服务分为上下两层。上层为业务层,下层为能力层,尽量使得服务之间的调用变成树状,而不是环状。

image3.png | center | 2198x1154

2.2 服务的粒度

服务的拆分是一件非常容易的事情,关键在于粒度的把控。那时候对于微服务有一种近乎偏执的理解,认为只要某个功能模块独立,就得变成一个服务独立出来,而且为了保证可用性还必须起两个节点。按照这个理论我们进行了实践,也确实出现了不少极端的操作。逐渐我们明白这个思路是有一些问题的,而要解决这个问题,我们必须对于服务拆分的理念达成统一。 我们尝试使用DDD(领域驱动设计)来进行服务的规划,基于DDD的设计方法,从当前业务出发,抽象业务找到宋小菜业务的核心能力,并以此作为服务进行拆分。但DDD是方法论,并不是教条和金科玉律,在使用的过程中也千万不要走火入魔。 使用了DDD进行分析之后,我们发现了一个更加头疼的问题,那就是以前的服务拆的太细了,把很多不该拆分出去的服务拆成了两个甚至更多。所以之后我们内部启动了“方舟项目”,该项的目的主要有这些:

  1. 对于服务如何拆分,在团队中构建共同的认知,并推进使用DDD;
  2. 定义微服务工程结构和规范;
  3. 将以往粒度过细的服务进行合并; 通过这次趟坑,我们也明白服务的拆分是一件非常慎重的事情,拆分一时爽,要是再想合并和统一,就不是那么容易的一件事情了。

2.3 可能出现的风险

2.3.1 经验

开发团队是否具备足够的经验,能否驾驭微服务的技术栈,可能是第一个需要考虑的点。这里并不是要求团队必须具备完善的经验才能启动服务拆分,如果团队中有这方面的专家固然是最好的。如果没有,那可能就需要事先进行充分的技术论证和预演,至少不打无准备之仗。 上文也提到了,我们的启动略微草率了一些,团队中也并没有这方面非常专业的同学坐镇,所以在一些分布式常见的问题上,都踩了坑,比如调用重试、超时机制、分布式事务等,这些问题一出现,很多没有经验的同学会非常抓狂,甚至无从下手。

2.3.2 稳定的测试

服务的拆分,必然会出现的问题就是加大了同学们的开发自测难度。以前的系统无论是在本地启动,或是发布在测试环境,所有的调用都是确定性的。但是一旦某一块服务拆分出来了,可能会面临很多问题:

  1. 本地针对远程服务的调用,可能是被禁止的,那就需要使用mock的方式来解决了,在本机单元测试的时候,主要测试的是业务逻辑和流程,并不能很完美的测到远程调用的返回数据。
  2. 调用的测试环境可能很不稳定,当你想要调用的时候,服务可能早就挂了。
  3. 多个项目组开发新功能的时候,调用了同一个服务,这时候有可能会因为服务器资源有限,导致大家资源竞争。 不同公司情况不同,为开发同学营造的测试环境也不尽相同。但有一点是一定的,如果测试复杂,会给开发同学造成很大的压力。明明一个很简单的功能,改一改可能只需要5分钟,但是想走完一次完整的测试,却花了他1个小时的时间。如果在服务化拆分后不能很好的解决,会导致开发同学有越来越多的侥幸心理和偷懒的情况,不进行测试就提交了代码或进行了发布。

2.3.3 日志

服务的拆分必然会导致日志散落在各地,无论是定位问题需要查看日志,还是一些事件需要基于日志去实现,都会变得比以往复杂很多。一开始的时候,我们并没有意识到这个问题,所以很多开发同学出现bug或是故障之后,不知道如何去定位了。因为一个方法的调用,可能会因为服务化的调用被放大几次甚至十几次,最后的错误到底出现在哪里,如何去找去看,都是一个问题。

三、结束语

服务拆分可能出现的问题还有很多,在这里也只是将一些最容易出现的场景分享给大家。希望通过本文,可以给那些准备进行服务拆分,或在正处于服务拆分起步阶段的团队一些建议和经验。

关于如何搭建高效率的生鲜B2B平台,因为包含的内容较多,也很复杂,无法再一篇文章中给大家讲清楚,本篇文章只是抛砖引玉,下面将分为多篇文章从行业现状、业务现状、产品概述、技术团队搭建、服务端技术平台搭建、前端开发等多个维度来讲述,我们将三年多在B2B领域沉淀的核心产品和技术平台公开,希望更多行业的人能深入了解,少走一些弯路,希望对大家有帮助,本系列文章分布如下(会继续更新):

1、《如何搭建高效率的生鲜 B2B 平台(B2B 技术共享第一篇)》

2、《宋小菜如何切入生鲜 B2B 市场(B2B 技术共享第二篇)》

3、《生鲜 B2B 平台的产品体系如何迭代(B2B 技术共享第三篇)》

4、《生鲜 B2B 如何搭建高效的技术团队(B2B 技术共享第四篇)》

5、《如何从 0 到 1 搭建生鲜 B2B 的技术体系(B2B 技术共享第五篇)》

6、《宋小菜技术如何应对生鲜 B2B 业务的快速变化(B2B 技术共享第六篇)》

7、《生鲜 B2B 技术平台的前端团队该如何搭建(B2B 技术共享第七篇)》

8、《宋小菜有关“能力”的设计和思考(B2B 技术共享第八篇)》

9、《服务拆分的设计和思考(B2B 技术共享第九篇)》