116-架构实战案例解析

开篇词 | 想吃透架构？你得看看真实、接地气的架构案例

你好，我是王庆友，前 1 号店首席架构师，很高兴能在这里和你一起聊聊架构。

架构的内容，相信你也看过很多了，博客、书籍、技术大会等等，都会有大量架构方面的分享。但一方面，这些内容比较碎片化，比如这一次讲的是技术的高并发处理，下一次讲的是老业务的改造。表面上看，你脑子里塞得满满的，但实际上，你很难循序渐进、系统地去学习架构。

另一方面，这些内容大多比较含糊，看似说的要点很全，但缺乏关键的实现细节，这样的话，说的和做的联系不起来，结果就是你可能理解起来都有困难，更不用说在实际工作中落地了。因此，我想了想，要能为你提供全新的架构学习体验，那就是让理论和实战结合起来，理论讲透彻，实战提供大量接地气的案例，让你能够知行合一地学架构。

我先简单介绍下自己吧，我从事软件开发已有二十年，架构设计也超过十年了，所以也经历了软件发展的各个阶段。

一开始呢，我从事的是企业级软件开发，后来转到了大型互联网电商平台，最近我奋战在新零售领域，帮助传统的大型连锁餐饮企业搭建 O2O 平台，打通他们的线上和线下系统。

从 1 号店开始，我负责了许多大项目的架构设计，积累了很多架构方面的经验。我是一个爱琢磨的人，喜欢总结做过的东西，形成一个体系化的认知。因此，在架构这个话题上，我有很多东西想和你分享。我希望，这些总结过的经验，可以帮你在成为架构师的道路上，少走一些弯路，也希望通过这个专栏能够抛砖引玉，和你一起讨论，一起成长。

为什么开发人员要学习架构？

这其实可以从系统和个人发展这两方面来看。

一、无架构，不系统

现在的软件系统规模越来越大，业务上和技术上都非常地复杂，大一点的互联网公司，技术人员都有几千号人。那么，如何开发这么复杂的系统？如何有效地组织他们的工作呢？

在这里，一个好的架构设计无疑是至关重要的，无论你是有一定经验的开发人员，还是已经开始从事系统设计的架构师，深入学习和理解架构都是必不可少的，掌握好架构设计，可以让你轻松应对技术和业务的挑战。但是很多技术人员，由于个人项目经验有限，又缺乏很好的学习途径，对架构设计一知半解。在实际工作中，不能把握好架构设计的度，要么设计不足，要么过度设计，导致系统变来变去，严重影响开发效率和质量。

二、拓展你的职业发展空间

此外，对于技术人员来说，公司通常会提供两个职业发展通道供你选择：管理路线和技术路线。

现实中，大部分同学应该都是走技术路线的，很多程序员的职业发展目标，也都是想要成为一名优秀的架构师。这不仅仅意味着更优渥的薪水和更持久的职业生涯，更因为在架构师这个舞台上，你可以凭借个人出色的架构能力，为项目的落地发挥巨大的作用，你会有更大的成就感。

所以说，无论从软件发展的趋势，还是从个人职业发展方向上考虑，你都应该拥抱架构，主动学习，尽快成长为一个能力全面的架构师。

如何学习架构？

但是，成为一名优秀的架构师，却不是一件容易的事情，你需要克服很多挑战。

首先，你需要跳出当前的小模块，站在系统整体的角度来考虑问题。

其次，你不仅要从技术的角度考虑问题，也要学会从业务的角度来考虑问题，深入理解系统的挑战在哪里，不要在错误的地方发力。

最后，你需要做好各方面的平衡，能在现有的各项资源约束下，寻求一个最优解。

而这些，无疑都是非常考验人，也非常锻炼人的，需要你能够快速成长起来。如果你在走向架构师这条路上，完全靠自己摸索，找不到正确的方向，不断地犯错，你很可能会半途而废。

那么，如何找到一个好的学习方式呢？

我们知道，一门新的语言，有很多书籍教你如何从入门到精通；一个新的开源框架，也有全面的资料教你如何深入掌握。但在架构学习这方面，并没有十分系统的理论指导，教你如何一步步进阶。

有时候，一些架构的术语和概念甚至都没统一，需要靠你自己去分辨。有时候，你可以从各种渠道找到一些项目的架构介绍，但不知是作者出于保密的目的，还是自己也没有深度参与项目，内容往往避实就虚，缺乏具体的东西。这样的架构介绍，参考性就很有限，你看过了，也就忘了，就像是中国的山水画，写意不写实，内行可以看门道，外行只能看热闹，无助于你实际能力的提升。

此外，架构设计的实践性很强，如果公司的业务比较简单，对架构设计的要求不高，你就未必有很好的实践机会；如果你只是抽象地了解了一些架构原则，不去具体地实践它，那么你很难去学透架构，让它变成自己的能力。

而在设计这个专栏的时候，我就有针对性地考虑到了这些问题，并给出了解决方案：

首先，我不会在这里罗列所有架构相关的知识点，而是选择实践中最有用的部分，形成一个精简的体系，让你可以快速掌握架构的核心内容，抓住学习的重点。

此外，本专栏的核心内容全部来自于本人实际项目的总结。我会基于这些实战后的思考，和你深入分享我的经验和看法，让你能够透过现象看本质，对架构的认知快速到位。我想说的是，这将会是一个有态度的专栏，而不是架构知识的搬运工。

更为重要的是，每讲课程，我会根据不同的架构原则，给你提供很多完整且非常接地气的实际案例。比如说，通过 1 号店 App 服务端架构改造的案例，你将会深入地理解系统从单体架构到分布式，再到服务化演变的背后原因；通过 1 号店订单水平分库的案例，你将会了解大型互联网公司是如何解决单个数据库的性能和容量瓶颈的。这些案例都很典型，不但让你能够理论结合实践，深入理解和运用各种架构原则，而且在很多情况下，你甚至可以直接参考它们，在公司落地类似的架构改造。

这个专栏，它有深度、有细节、实战性强。希望通过这个专栏，能让你对架构形成一个清晰的认知框架，并对各个架构要点形成自己的理解，同时，通过结合着大量实际案例的学习，希望最终你可以把这些知识变成实际的能力。

我是如何规划这个课程的？

首先，我会和你分享架构的本质，让你对架构形成一个体系化的认知。

接下来，我将整体内容划分为业务架构和技术架构两大部分，先理论后案例，带你深入理解每种架构要解决的问题，以及如何解决。

业务架构篇：重点针对系统的扩展性和复用性两大目标展开。首先，我会介绍这两大目标的内涵和实现手段，让你明白如何通过业务的拆分和组合，搭建一个柔性可变的系统；然后，每部分都有三个实际案例，让你理解如何以服务化和平台化的方式升级改造系统，最终实现扩展性和复用性的目标。

技术架构篇：重点针对系统高可用和高性能 / 可伸缩的目标展开。首先，我会介绍实现这些目标的策略和架构原则，比如说，高可用的策略有避免事故、降低影响、快速恢复等，高可用的架构原则有无单点、可监控、水平扩展等，这样你可以总体了解，实现这些目标都有哪些手段；然后，每个目标都有 3 个实际案例，让你理解如何在项目中，灵活应用这些策略和原则。

最后，我还会通过一篇架构总结，汇总架构原则，以及它们的适用场景，并就如何成长为优秀的架构师，为你提供一个可行的方向指导。

写在最后

那么在最后，我想说的是，架构对我们的工作是如此的重要，如果你想深入学习架构，除了有好的学习方法，你一定还要多思考，多交流，多实践。

在课程设计中，我在每一节课后都留了思考题，希望你能够结合每篇文章的知识点，给出自己的思考。

这次当然也不例外，你可以给我留言，说说你对学习架构都有哪些困惑。

另外，你也要大胆地抛出问题，我一定会尽我所能及时给你反馈。很多问题都是共性的，我们一起讨论和解决，成长一定会更快。

书上学来终觉浅，梅花香自苦寒来。好的指导方法加上你自身的努力实践，相信你的架构之路会越走越顺，越走越快。

希望这个专栏，能够帮你开启架构师的进阶之路，在接下来的两个月里，让我们在架构之路上一起成长吧。

我是王庆友，欢迎你把这篇文章分享给你的朋友，我们一起学架构。感谢收听，我们第一讲再见。

01 | 架构的本质：如何打造一个有序的系统？

你好，我是王庆友，今天是专栏的第一讲，我想先和你聊聊架构的本质。

我们知道，现在的软件系统越来越复杂，当然相应地，架构的作用也越来越明显。作为开发人员，我们每天都在和架构打交道，在这个过程中，对于架构也经常会产生各种各样的问题：

什么是架构？架构都有哪些分类，分别解决什么问题呢？

怎样才是一个好的架构设计？我怎么才能成长为一名优秀的架构师呢？

这些问题涉及我们对架构的认识，也是学习和运用架构的开始。所以，今天，我们就来深入地分析架构的实质，让你能够透彻地理解它。

作为专栏的第一讲，我希望先和你讨论架构中理念性的部分，就是所谓架构的道，这样可以指导你学习后续的实操层面的内容，也就是架构的术。

接下来，我们就正式开始吧，先说下我对架构本质的理解。

架构的本质

物理学中有个很著名的“熵增定律”：一个封闭系统，都是从有序到无序，也就是它的熵（即混乱程度）会不断地增加，最终系统会彻底变得无序。

这个理论放在软件系统的演化上，也是非常适用的。

一方面，随着业务需求的增加，我们会往系统里不停地添加业务功能；另一方面，随着访问量的不断增加，我们会不断通过技术手段来加强系统非业务性功能。如果事先不做良好的设计，随着时间的推进，整个系统野蛮生长，就会逐渐碎片化，越来越无序，最终被推倒重来。

不过，自然界中的生物可以通过和外界交互，主动进行新陈代谢，制造“负熵”，也就是降低混乱程度，来保证自身的有序性，继续生存。比如，植物通过光合作用，把光能、二氧化碳和水合成有机物，以此滋养自己，延续生命。对于软件系统，我们也可以主动地调整系统各个部分的关系，保证系统整体的有序性，来更好地适应不断增长的业务和技术变化。这种系统内部关系的调整就是通过架构实现的，所以，架构的本质就是：

通过合理的内部编排，保证系统高度有序，能够不断扩展，满足业务和技术的变化。

这里包含两层意思，我们具体展开说下：

首先，架构的出发点是业务和技术在不断复杂化，引起系统混乱，需要通过架构来保证有序。我们知道架构这个词来源于建筑行业，那为什么建筑行业需要“架构”呢？

搭一个草房子很简单，可以直接上手；盖一个 2 层楼房，稍微复杂一些，但在工匠的经验指导下，问题也不大；而盖一座高楼，复杂性就大不一样了，我们需要考虑内部结构、承重、采光、排水、防雷抗震等，这就需要专业人员事先做好整体的架构设计，并严格地按照设计来施工。

这里，你可以看到，建筑里的架构不是天然就有的，而是因为建筑越来越复杂，我们需要通过架构来管理这种复杂性，避免建造过程的失控。

软件系统也是如此，从简单的桌面应用发展到现在的大型互联网平台，这个过程中，系统规模越来越大，业务和技术也越来越复杂。我们同样需要通过架构设计，消化复杂性带来的混乱，使系统始终处于一个有序状态，能够应对现有和将来的需求变化。

其次，架构实现从无序到有序，是通过合理的内部编排实现的，基本的手段，就是“分”与“合”，先把系统打散，然后将它们重新组合，形成更合理的关系。

具体地说，“分”就是把系统拆分为各个子系统、模块、组件。拆分的时候，首先要解决每个部分的定位问题，然后根据定位，划分彼此的边界，最后实现合理的拆分，我们比较熟悉的微服务架构，就是一种典型的拆分做法。

“合”就是基于业务流程和技术手段，把各个组件有机整合在一起。比如说在微服务架构中，拆分为具体微服务后，我们需要对这些服务进行归类和分层，有些属于底层基础服务，有些属于上层聚合服务，还要尽可能地实现服务的平台化，比如我们最近说的中台，这些都是合的思想体现。

这个分与合的过程将系统的复杂性分解为两个层次：

首先，各个子系统承担独立的职责，内部包含了自身的复杂性。子系统的复杂性对外部是透明的，外部不用关心。

其次，子系统通过封装后，简化为职责明确的一个点，因此，我们只需要在合的过程中，解决各个点之间的依赖关系，这样就可以定义出系统整体。

举个例子，我们都知道 GoF 的 23 个设计模式，在 Builder 模式中，它的主逻辑只需要给出各个部件的组装关系即可，它不关心创建某个具体部件的内部逻辑，这个可以交给工厂模式去实现。这里，Builder 模式负责粗粒度的组装逻辑，它承担的是合的部分；工厂模式负责细粒度的构造逻辑，承担的是分的部分，大家各自管理自己的复杂性。

通过合理的“分”与“合”，系统不是回到了原点，而是把原先铁板一块的系统变成一个富有弹性的结构化系统。这样，系统的复杂性有效地分解了，系统的有序度大幅度地提升了。

当然，系统的复杂性是多方面的，有技术上和业务上的，架构也是一个体系，会有多种架构一起来应对这些复杂性挑战。那么接下来，我们就来具体看下。

架构的分类

按照不同的角度，架构可以有很多分类，但一般来说，主要分为业务架构、应用架构和技术架构。那么，这些架构分别为谁服务，解决什么问题，相互之间是什么关系呢？

回答这些问题前，我们先来看下系统的落地过程。

系统首先由人来开发，然后由机器来运行，人和机器共同参与一个系统的落地。

对于负责开发的人来说，比较头疼的是，业务太复杂，脑子想不清楚，即使当前勉强把业务逻辑转化为代码，系统后续的维护也是问题。因此，开发人员的要求是系统概念清晰，业务逻辑容易理解，可以直观地进行代码开发。

对于负责运行的机器来说，比较头疼的是，外部请求并发量太大，导致机器扛不住，有的时候，硬件还会出问题。因此，它的要求是系统能够水平扩展，支持硬件容错，保证系统的高性能和高可用。

这里，开发的痛点主要由业务架构和应用架构来解决，机器的痛点主要由技术架构来解决。

为什么这么说呢？我们看下，这些架构具体都是做什么用的。

简单来说，业务架构就是讲清楚核心业务的处理过程，定义各个业务模块的相互关系，它从概念层面帮助我们理解系统面临哪些问题以及如何处理；而应用架构就是讲清楚系统内部是怎么组织的，有哪些应用，相互间是怎么调用的，它从逻辑层面帮助我们理解系统内部是如何分工与协作的。

技术架构就是讲清楚系统由哪些硬件、操作系统和中间件组成，它们是如何和我们开发的应用一起配合，应对各种异常情况，保持系统的稳定可用。所以，技术架构从物理层面帮助我们理解系统是如何构造的，以及如何解决稳定性的问题。

这里你可以看到，业务架构、应用架构和技术架构，分别从概念、逻辑和物理层面定义一个系统。业务架构给出了业务模块的划分和依赖关系，这也大致决定了应用系统如何分工和协作，当然这不需要严格地一一对应，比如一个商品业务，可能对应 3 个应用，一个前台商品展示应用、一个后台商品管理应用，以及一个商品基础服务，但这不影响我们从逻辑上理解，一个业务场景，有哪些应用参与，并且它们是如何协作的。

而技术架构呢，通过保障应用的稳定运行，最终保证业务不出问题。比如在大促的时候，多个应用可能会受大流量冲击，技术架构就要考虑怎么通过技术手段，保障相关的应用能够处理高并发，从而保证大促顺利进行。

这里，我举个拍电影的例子，来帮助你更直观地理解这三种架构的关系：业务架构定义了这个电影的故事情节和场景安排；应用架构进一步定义有哪些角色，每个角色有哪些职责，并且在每个场景中，这些角色是如何互动的；技术架构最后确定这些角色由谁来表演，物理场景上是怎么布置的，以此保证整个拍摄能够顺利完成。

最后，我想强调一下：系统是人的系统，架构首先是为人服务的。因此，业务概念清晰、应用分工合理、人好理解是第一位的。然后，我们再考虑技术选型的问题，保证系统非功能性目标的实现。所以做架构设计时，一般是先考虑业务架构，再应用架构，最后是技术架构。

什么是好的架构？

从上面的内容，我们不难看出，一个好的架构必须满足两方面挑战：业务复杂性和技术复杂性。

1. 业务复杂性

系统首先要满足当前的业务需求，在此基础上，还要满足将来的业务需求，因此系统要能不断地扩展变化，包括调整现有功能，以及增加新功能。

而且，系统的功能变化不能影响现有业务，不要一修改，就牵一发动全身，到处出问题。因此，在架构设计上，要做到系统的柔性可扩展，能够根据业务变化做灵活的调整。

此外，市场不等人，上新业务要快，之前花了半年上了个业务，这回再上个类似的新业务，需要短时间就能落地。因此，架构设计上，还要做到系统功能的可重用，这样才能通过快速复用，实现业务敏捷和创新。

2. 技术复杂性

要保证一个业务能正常运行，除了满足业务功能之外，还要保证这个系统稳定可用。

一个复杂系统是由很多部分组成的，如应用程序、服务器、数据库、网络、中间件等，都可能会出问题。那怎么在出问题时，能够快速恢复系统或者让备用系统顶上去呢？

还有流量问题，平时流量不大，少量机器就可以处理，但在大促的时候，大量流量进来，系统是不是能够通过简单地加机器方式就能支持呢？

此外还有低成本的问题，系统能否做到，使用廉价设备而不是高大上的 IOE 设备，使用免费的开源组件而不是昂贵的商业套件，使用虚拟化技术而不是物理机，并且在流量低谷和高峰的不同时期，让系统能够弹性缩容和扩容呢？

这些都属于技术性的挑战，解决的是系统的非业务性功能，也都是架构设计要支持的。

因此，一个好的架构设计既要满足业务的可扩展、可复用；也要满足系统的高可用、高性能和可伸缩，并尽量采用低成本的方式落地。所以，对架构设计来说，技术和业务两手都要抓，两手都要硬。

那么，一个优秀的架构师需要具备什么样的能力，才能设计一个好的架构呢？

什么是好的架构师？

一个优秀的架构师，应具备很强的综合能力，要内外兼修，“下得厨房，上得厅堂”，下面我来通过典型的架构方式，来介绍一名优秀架构师应该具备的能力：

一个驾校教练，必定开车技术好；一个游泳教练，必定游泳水平好，因为这些都是实践性很强的工作。架构师也是一样，TA 必定是一个出色的程序员，写的一手好代码。

在此基础上，架构师要有技术的广度（多领域知识）和深度（技术前瞻）。对主流公司的系统设计非常了解，知道优劣长短，碰到实际问题，很快就能提供多种方案供评估。

此外，架构师还需要有思维的高度，具备抽象思维能力。抽象思维是架构师最重要的能力，架构师要善于把实物概念化并归类。比如，面对一个大型的 B2C 网站，能够迅速抽象为采购 -> 运营 -> 前台搜索 -> 下单 -> 履单这几大模块，对系统分而治之。

架构师还需要有思维的深度，能够透过问题看本质。透过问题看本质是由事物的表象到实质，往深层次挖掘。比如，看到一段 Java 代码，知道它在 JVM（Java Virtual Machine，Java 虚拟机）中如何执行；一个跨网络调用，知道数据是如何通过各种介质（比如网卡端口）到达目标位置。透过问题看本质，可以使架构师能够敏锐地发现底层的真实情况，以端到端闭环的方式去思考问题，能够识别系统的短板并解决它。

还有很重要的一点，能落地的架构才是好架构，所以架构师还需要具备良好的沟通能力（感性），能确保各方对架构达成共识，愿意采取一致的行动；而良好的平衡取舍能力（理性），可以确保架构在现有资源约束下是最合理的，能让理想最终照进现实。

总结

我今天和你分享了架构的本质，架构的终极目标是保证系统的有序，通过拆分和整合，使系统具有柔性，能够进化，从而可以满足现有的和将来的各种变化。

如果你能深入地理解架构的这些本质和手段，就可以不用照搬某某大厂的方案了，而是能够根据实际情况，以最合理的方式来解决系统面临的问题。

这里呢，我也分享了架构的三种典型分类，包括它们各自的定位和相互关系，相信你现在对架构整体有了一个简明的框架，知道架构设计都要做哪些事情了。

最后，我还为你提供了高标准的架构师能力模型，这样，你能比较清楚自己的努力方向是什么，这些要求很高，但你也不要有任何的畏难情绪，你可以在架构实践中，逐步地往这个目标上靠近，通过本专栏后续的学习，相信你也可以更快地达到这个目标。

最后，给你留一道思考题：除了本文提到的三种架构，你还知道有哪些架构分类，它们分别做什么用？

我是王庆友，欢迎在留言区和我互动，我会第一时间给你反馈。如果觉得有收获，也欢迎你把这篇文章分享给你的朋友。感谢收听，我们下期再见。

02 | 业务架构：作为开发，你真的了解业务吗？

你好，我是王庆友，今天我们一起聊聊业务架构。

作为开发人员，我们平常讨论比较多的是技术层面的东西，比如 Spring 框架、Redis 缓存、MySQL 数据库等等，我们喜欢讨论这些，是因为纯技术的东西比较通用，和业务相关性不大，沟通起来比较方便。

但你要知道，一个项目能否成功落地，首先需要的是把业务分析做到位，至于选用什么技术来实现，这是我们第二位才去考虑的因素。从架构角度看，业务架构是源头，然后才是技术架构。所以，作为专栏的第二讲，今天我们就从业务架构开始说起。

在软件开发的过程中，你肯定知道需求分析是怎么回事，但不一定知道业务架构设计是怎么回事；你也肯定清楚需要产品经理这个角色，但不一定清楚有时还需要业务架构师这个角色。关于需求分析和业务架构设计，相信你经常会有以下几个疑问：

业务架构师和产品经理有什么区别？

需求分析和业务架构设计有什么区别，业务架构到底有什么用？

我们知道，项目的开发都是从收集业务需求开始的，原始的需求一般来自于最终用户。但是，每个用户其实只清楚自己所负责的那部分，因此这些原始需求往往是零散和碎片化的，特别是当一个业务流程跨多个部门的时候，更没有一个人能够说清楚这个业务的全貌。

所以说，仅仅基于这些原始的需求来指导开发是远远不够的，这时，就需要产品经理和架构师介入进来，填补这段空白。

接下来，我们就一起看下，产品经理和架构师在这个过程中都会做些什么，他们是如何帮助业务落地的。

产品经理的职责

简单来说，产品经理的职责就是：告诉用户，系统长什么样子；告诉开发，他要实现什么功能。

产品经理首先会收集用户的原始需求，然后，将它们梳理成一个个业务流程，每个业务流程由多个业务步骤组成。一个业务步骤包含三部分的内容：输入、输出和业务功能。

比方说，一个典型的交易流程，它包含商品浏览、商品加购物车、下单、支付等步骤。其中，下单步骤的输入，就是订单的各种信息，下单的功能，就是整合这些信息，创建一个具体的订单，而下单的输出结果，就是新创建的订单。

需求梳理好后，产品经理会把每个步骤具体化为页面原型。在原型中，会以直观的方式给出各个步骤的输入或输出，以及用户的操作过程，最后再把这些页面串起来，形成一个业务流程。

你可以看到，经过产品经理的工作，大量零散的原始需求经过梳理和关联，变成一系列有序的业务流程，以及流程里面的业务步骤（业务步骤也称之为业务节点），然后产品经理把这一系列的业务流程和业务节点以用户界面的方式定义出来，总的来说，产品经理定义了系统的外表。

这些产出对于用户了解系统长什么样子，应该如何使用这个系统，以及系统是否满足他们的需求来说，是足够的，但对于开发者来说还远远不够，因为他们需要能进一步看到系统的内部结构。

而这一步，就是业务架构师要做的事情。

业务架构师的职责

在这之前，我们不妨先思考下，如果是按照产品的输出，直接以业务流程的角度来构建系统，会是什么样子呢？

如果按照这个思路，我们将为每个业务流程搭建一个对应的系统模块，然后业务流程中的每个业务步骤，将对应系统模块中的一个接口，包括它的功能、输入和输出。

就拿前面的购物流程来说，我们设计一个购物流程模块，里面包含商品查询、添加购物车、下单和支付接口，来分别对应流程里的 4 个业务步骤。

以这样的方式构建系统，表面上看起来，业务和系统的映射好像非常简单，但在实际中，落地的难度非常很大。因为只是这样一个小小的购物流程模块，就要同时涉及商品、购物车、下单和支付四个业务，模块的开发者要同时非常清楚这四部分的数据模型和业务逻辑。

同样的道理，系统里的其他模块也是包含多个业务领域的内容，如果一个业务领域的需求发生了变化，比如说，订单要增加一个新的状态，那么所有涉及该订单的模块都要知道这个变化，并要做出相应的调整。这就要求，每个开发者都是全知全能的，对所有业务都了如指掌，我们知道，这是不可能的。

每个业务都有其本身的专业性，比如订单业务、商品业务、支付业务，它们的数据模型和业务逻辑都相当复杂，构成了一个个相对独立的业务领域。如果我们是按照业务流程来划分系统模块，结果是把不同业务混在了一个模块里，所以，这种模块划分的方式并没有降低总的业务复杂度。

我们可以换一种做法，先把所有的业务流程拆散，这样得到了一堆业务节点；然后把业务节点进行归类，相关的业务节点放在同一个系统模块里。判断节点是否相关，主要看它们是否属于同一个业务领域，比如一个订单查询的节点，和订单创建的节点，它们都属于订单域，那么这些节点都可以归属到同一个订单模块里。

下图就清楚地表示出了系统模块按业务流程拆分，和按业务域拆分的不同。

如果按照业务流程来拆分系统模块，那么，有多少业务流程，就有多少个系统模块，这个对应关系比较直接，但实现起来很困难。

如果按照业务域来拆分，有多少业务领域，就有多个系统模块，流程中的业务节点按照业务域的不同，可以划分到不同的系统模块。

在实际业务场景中，一个业务节点可能会涉及不同业务领域的功能。比如说，一个下单节点，会涉及到获取商品信息、获取用户信息、扣库存、下订单等多个业务功能，那么你就可以进一步分解这个节点的功能，把不同的功能分到对应的业务域和系统模块。

基于业务域，构建了系统模块后，我们就可以按照这样的方式还原整个业务流程，比如上面的购物流程例子，我们就可以这样还原它：

购物流程 = 商品模块. 商品搜索 + 购物车模块. 添加商品 + 订单模块. 创建订单 + 支付模块. 支付

如果你把这个定义画成序列图，就很直观和容易理解，也比较符合开发人员思维，系统实现起来非常容易。通过这种系统模块之间的不同功能组合，我们很容易给出各个业务流程的定义。

所以，对业务架构师来说，TA 的工作，就是把业务流程和节点打散，按照业务域的维度来划分系统模块，并定义这些模块之间的关系，最终形成一个高度结构化的模块体系。这样，开发人员既容易理解业务，又方便落地系统。

现在，我们就可以回答文章开头的问题了，产品经理和业务架构师的工作既有区别又有联系，简单地说，产品经理定义了系统的外观，满足了用户；业务架构师在此基础上，进一步定义了系统的内部模块结构，满足了开发人员。

当然，满足现有的业务需求，保证系统顺利落地，这只是业务架构的最基本目标，业务架构的意义远不止于此，它有一系列更高的目标，下面，我就逐一为你展开介绍。

架构目标之一：业务的可扩展

第一个目标是业务的可扩展，我们都知道，业务需求是不断变化的，不断创新是业务的内在要求。而对于系统来说，它的要求却是相对稳定，尽量避免大的调整。

那么，我们如何才能实现业务的快速变化和系统的相对稳定呢？

这也是业务架构要重点解决的问题，具体地讲，业务架构设计要能支持打造一个柔性系统，通过提供良好的业务扩展性，允许业务不断调整和快速生长。

可以看到下图中，左边部分就比较形象地展示了业务和系统的不同特点：业务的主题是变化和创新，系统的主题是稳定和可靠。

在右边图中，我们通过通过巧妙的业务架构设计，很好地解决了业务和系统之间的矛盾。

这里，我们把业务平台和业务线剥离开，让业务平台封装基础通用的功能，这样，它就变得相当地稳定；让各个业务线包含自己的个性化需求，业务线只依赖业务平台，业务线彼此之间互相独立，可以自由变化。这样的业务架构设计，就同时保证了系统的相对稳定和业务的快速创新。

为了帮助你更好地理解业务架构的扩展性，这里，我给出了支付宝的业务架构变化过程。

在支付宝一代的业务架构中，前台的业务和后台的业务直接耦合，形成了多对多的网状结构，如果修改一个后台业务线，就会影响到很多前台业务线；如果增加一条新的前台业务线，需要同时和很多后台业务线对接，这样的架构无疑是对业务的扩展非常不利的。

而在支付宝二代业务架构中，你会发现，他们在前后台业务线之间，构建了独立的支付清算平台，从而实现了前台业务和后台业务的解耦。

在这里，不管前台业务，还是后台业务，都只需要对接中间的支付清算平台，把系统的变化收敛到一个点，而业务线之间相互不影响，这样的方式，自然可以很好地支持业务扩展。

好了，这里我们说完了业务架构的可扩展目标，接着再说说业务架构的另一个目标：可复用。

架构目标之二：业务的可复用

你肯定会有这样的体验：一个项目过来，你和伙伴们一起加班加点、紧赶慢赶，总算把它成功落地了。结果这时候又有另一个类似的项目过来，你们又要按照同样的方式，重新吃一遍苦，结果就是开发不满意，项目经理不满意，老板也不满意。

对于类似的业务需求，如果原来做的工作可以大量复用的话，这是非常理想的结果，无论对于开发效率和开发质量的提升都很有帮助。

当然，能不能复用，能在多大程度上复用，这和业务架构设计很有关系，也是业务架构设计的重要目标之一。

那么，业务架构设计如何实现业务的可复用呢？

你可以试想一下，在业务架构设计中，如果只是简单地基于业务流程来定义系统模块，这个系统模块就要和业务流程严格对应。我们知道，业务流程对应业务场景，而业务场景是经常变化或是定制的，这就导致系统模块也是经常变化和定制的，那么，这样的系统模块就很难在不同业务场景中复用。

如果我们按照业务域来划分业务，把业务流程中的节点拆分到各个业务域，按照业务域构造系统模块，这样的复用效果会如何呢？

我们都知道，业务域是相对固定的，它有明确的数据模型和业务规则，这样一来，系统模块也就比较固定和通用，也就具备比较好的复用基础。

但要想实现高复用，业务架构对系统模块的定义，还有更多的要求。

首先，模块的职责定位要非常清晰。对于模块来说，在定位范围内的职责要全部涵盖到，而不在这个范围的职责全部不要。

其次，模块的数据模型和接口设计要保证通用。架构师需要归纳业务场景，通过抽象提炼，形成通用化的设计，以此来满足多个类似场景的需求。

小提示：清晰的模块定位和通用化设计，是模块能够复用的内在要求。

最后，实现模块的高复用，还需要做好业务的层次划分。我们知道，越是底层的业务，它就相对更固定。举个例子，同样是订单业务域，对于底层订单的增删改查功能，不同类型的订单都是一样的，但对于上层的订单生命周期管理，外卖订单和堂食订单可能就不一样。

所以，在做高复用设计时，我们可以尝试把一个业务域按照层次拆分得更细，比如，把订单模块拆分为多个上层订单模块和一个基础订单模块，这样，基础订单模块对于所有类型的订单，都能够提供复用。

就拿当前非常流行的微服务架构来说，很多公司在微服务的基础上，通过服务分层，进一步落地了共享服务体系和中台架构，这些都是业务架构复用能力的体现。

下面是一个三方支付平台的业务架构图，你可以看下，在一个实际的业务架构中，模块是怎么划分的，架构的可扩展和高复用是如何体现的。

总结

今天，我带你了解了产品经理和业务架构师的不同职责，产品经理是站在用户的角度进行需求分析，而业务架构师是站在开发者的角度定义系统内部结构。通过今天的讲解，你应该对业务架构也有了更清楚的认识。

除了满足当前的业务需求外，业务架构师还需要面向未来，实现业务的可扩展和高复用两大目标，我也大致介绍了架构师实现这些目标的思路。在接下来的文章里，我还会针对这两大目标，结合实际案例，具体讲解如何实现它们，让你能更加深入地理解业务架构设计，并可以在工作中学会去运用这些手段。

最后，给你留一道思考题：产品经理和业务架构师都是分析业务，产品经理为什么不能兼业务架构师的角色？他们的能力模型有什么区别？

欢迎在留言区和我互动，我会第一时间给你反馈。如果觉得有收获，也欢迎你把这篇文章分享给你的朋友。感谢收听，我们下期再见。

03 | 可扩展架构：如何打造一个善变的柔性系统？

你好，我是王庆友，今天我和你聊一聊如何打造可扩展的架构。

在实际工作中，业务需求总在不断变化，因此，你经常会面临以下这些问题：

如何快速地上线新业务？老板很可能明天就想看到效果。

对某个功能进行修改，如何不影响到系统其它的功能？

对于新的需求变化，我们一方面要快快搞定，另一方面要稳稳接住。但问题是软件虽然姓“软”，但也不是想变就能变，如果事先没有经过良好的设计，调整起来，往往牵一发动全身，导致系统到处出问题。

那如何设计一个具有良好扩展性的系统，能够快速支持业务变化落地呢？

接下来，我们围绕系统的可扩展，先来了解下什么是系统，什么样的系统才能具备良好的扩展能力。然后通过一个实际的例子，说明如何通过架构手段打造一个可扩展的系统。

系统的构成：模块 + 关系

我们天天和系统打交道，但你有没想过系统到底是什么？在我看来，系统内部是有明确结构的，它可以简化表达为：系统 = 模块 + 关系。

在这里，模块是系统的基本组成部分，它泛指子系统、应用、服务或功能模块。关系指模块之间的依赖关系，简单地讲，就是模块之间有调用，我们知道，调用区分发起方和服务方，因此，依赖关系是有方向性的。

这个模型虽然简单，但它给我们提供了一个深入分析系统的工具。接下来，我们就从业务扩展性出发，讨论什么样的模块是容易修改的，什么样的依赖关系是容易调整的。

模块

我们先看模块，模块定义系统都有哪些基本的“玩家”，分别承担什么职责。从业务的角度看，每个模块都代表了某个业务概念，或者说业务领域。

模块内部由数据和业务逻辑组成，其中数据是核心，业务逻辑围绕着数据，对数据做进一步加工，方便外部使用。

从扩展性的角度出发，首先，我们对模块的要求是：定位明确，概念完整。

每个模块要有明确的定位，模块有了定位，说明我们已经想清楚了它的核心职责是什么，这样，每个人对它的期望和理解就会一致。在实践中，我们经常会争论一个功能应该放到 A 模块还是 B 模块，表面上看，各有各的道理，谁也说不服谁，但如果对照模块的定位，回到模块设计的初心，我们往往很快就能有答案。

定位比较抽象，在具体划分模块职责的时候，要保证模块业务概念的完整性。数据上，模块需要覆盖对应业务领域的全部数据，比如一个订单模块，它要覆盖所有渠道的订单，包括三方平台的订单、自有商城的订单、线下门店的订单等，这些不同类型订单的数据模型和实际数据，都由订单模块负责。

功能上，模块要包含业务领域的全部功能，比如订单模块包含所有订单相关的功能，包括订单数据的增删改查、订单业务规则校验、订单的状态和生命周期管理等。

其次，模块还要：自成体系，粒度适中。

模块的业务逻辑尽量围绕自身内部数据进行处理，对外部依赖越小，模块的封装性越好，稳定性也越强，不会随着外部模块的调整而调整。

模块的粒度要保持适中，不能为了追求定位清晰，把粒度划分得很小，导致系统的碎片化。比如系统早期的时候，一般我们把积分功能放到用户模块里面，不单独构建积分模块，如果后续积分的概念越来越突出，承载的业务越来越复杂，到时候可以把积分功能分离出来，单独成模块。

这里，为帮助你更好的理解，我举一个模块划分的反面例子。在实际工作中，很多老系统都有体量很大的模块，我们称之为“肿瘤”，它的特点就是定位模糊，职责泛滥，功能无所不包，这样，模块的可维护性很差，没人敢轻易对它动刀子。

好了，说完了模块，我们再继续看下模块的依赖关系。

依赖关系

依赖关系定义了模块如何协作，一起完成业务流程，依赖关系实质上体现的是模块的组织结构。

如果不对模块的依赖关系做针对性设计的话，依赖关系就是一个多对多的网状结构，一个有 N 个模块的系统，理论上有 N×N 个依赖关系，如果考虑依赖具有方向性，这个数字还要加倍。

所以，要简化模块的依赖关系，我们就要同时简化依赖的方向和减少依赖的数量。

首先，我们希望模块之间的依赖是单向的，尽量避免相互调用，为什么单向更好呢？我们知道业务流程是有顺序的，如果模块依赖关系越直观地体现业务流程的顺序，越能帮助人理解，否则，我们会被双向的依赖箭头绕的晕头转向，很难通过模块之间的依赖关系还原实际业务的处理过程。

接下来，我们看下模块的组织结构。我们知道，网状结构是一种松散的结构，节点之间的依赖关系比较复杂，一般用于表示非正式的关系，比如人群的社交关系；而层次结构是一种更有序的结构，一般用于表示正式的关系，比如公司内部的人员关系。

在模块的组织结构设计上也是如此，我们要尽量把网状结构转化为层次结构，模块结构层次化是简化模块依赖关系的有力手段。

具体做法就是，我们按照模块定位的不同，把模块划分为不同层次，比如划分为上面的应用层和下面的资源层。这样，一个层通过把多个模块组织在一起，就形成了概念上更大粒度的模块。有了层以后，我们理解业务时，因为模块定位相同，往往关注这个更大粒度的层就可以，依赖关系只要指向这个层，而不是层里面的各个模块。这样，从人理解业务的角度，依赖的数量大幅度地减少了。

另外，我们知道，层与层之间的依赖关系都是层与层之间自上而下的依赖，相对于多对多的网状依赖，层次依赖的方向更清晰，特别符合人的理解习惯。

举个具体例子，作为开发，我们都比较了解 MVC 架构，系统模块按照定位，分为表示层、应用层、聚合服务层、基础服务层。

表示层，对应前端的模块，如 App、小程序、公众号等，属于 View 层。

应用层，对应和前端表示层直接关联的服务端，属于 Control 层。

聚合服务层，如果系统业务比较复杂，经常需要单独的聚合服务层负责业务流程的编排组合，这个属于 Model 层的加强。

基础服务层，代表最基础的业务模块管理，如订单、商品、用户等，属于实际的 Model 层。

我在这里贴了一张 MVC 分层结构图，你可以看到，模块总体上是非常清晰的层次结构。

现在，我们清楚了一个可扩展系统对模块和依赖关系的要求，接下来，我们再回到系统扩展性目标，做个深入总结。

扩展性的本质

在文章开头，我们说因为业务总在变化，所以需要架构设计给系统提供良好的扩展性。

这只是表象，深层的原因是，一个新的需求进来，系统不只是为它增加一个新功能这么简单，系统的调整会引起一系列的连锁反应，从而大面积地影响系统的现有功能。架构设计时，如果模块划分的不好，一个 N 个模块的系统，它的复杂度就是 N×N（这个在上一讲介绍的支付宝一代架构中，体现得很明显）。如果再加一个新的模块，复杂度就变成 (N+1)×(N+1)，系统的复杂度随着功能的数量指数级地上升，这样一来，当系统的规模到一定程度，复杂度就会失控，导致系统彻底无序。

所以，要支持系统的扩展，架构设计上必须能够控制系统的复杂度，面对新需求，要让系统复杂度做加法而不是乘法，从而保证系统的调整是局部化和最小化的，所以，业务架构扩展性的本质是：通过构建合理的模块体系，有效地控制系统复杂度，最小化业务变化引起的系统调整。

那如何打造一个合理的模块体系呢？具体的架构手段就是按照业务对系统进行拆分和整合：通过拆分，实现模块划分；通过整合，优化模块依赖关系。

接下来，我们以一个在线出行公司为例，它有出租车、快车和顺风车 3 条业务线，来具体看下如何为它打造合理的模块体系。

打造可扩展的模块体系：模块拆分

我们先对系统进行模块化拆分，拆分有两种方式：水平拆分和垂直拆分。

水平方向拆分

水平拆分是指从上到下把系统分为多层，按照系统处理的先后顺序，把业务拆分为几个步骤。

比如，整个叫车过程，我们可以分为 UI 展现、地图搜索、运力调度和订单支付等几个环节，这是根据系统的处理过程进行划分的。

这样一来，我们就把一个复杂流程，分解为几个相对独立的环节，分别进行处理，这么做带来了很多好处。

首先，UI 展现部分独立成为一个模块，实现了前后端的分离。我们知道，前端的用户体验和界面样式会经常变化，而后端的数据和业务逻辑相对稳定，通过水平拆分，我们实现了稳定部分和不稳定部分的分开，避免相互影响。

这里的后端包含三个模块，其中地图搜索负责路径规划，运力调度负责人车匹配，订单支付负责交易管理。

可以看到，通过水平拆分，可以使每一块职责都比较明确，功能内聚，每个模块管理自己内部的复杂性。同时，模块之间相互松耦合，一个模块的修改不影响另一个模块，比如地图搜索模块中改变了优先路径的推荐，不会影响运力调度模块中的人车匹配算法。

水平分层可以很好地满足现有业务做深度扩展，当业务有变化时，系统在特定层做调整，对其他层影响有限，这样把变化局限在一个小范围。

垂直方向拆分

垂直拆分指的是按照不同的业务线拆分，比如，将整个出行业务分为出租车业务、快车业务和顺风车业务，按照不同的业务场景，自上而下进行竖切，让每个业务都自成体系，形成自己的业务闭环。

通过垂直拆分，一个复杂的出行场景就拆分为几个具体的场景，我们可以根据各个业务线的特点去设计系统，从而降低了整个系统的复杂性。

垂直拆分可以很好地满足业务广度上的扩展，比如说增加一条新的业务线，可以按照这个思路落地系统。

一般做业务架构时，我们先考虑垂直拆分，从大方向上，把不同业务给区分清楚，然后再针对具体业务，按照业务处理流程进行水平拆分。

如果同时进行垂直拆分和水平拆分，一个大系统被拆分为了一个二维的模块矩阵，每个模块既属于某个业务线，也属于业务流程的某个环节。这样一来，每个模块的职责都很清晰，当业务变化了，我们可以清楚地知道，这个变化涉及哪些模块，然后，对这些模块进行相应的调整就可以。

为了帮你更好地理解这两种拆分方式的好处，我这里举个搭积木的例子。经过拆分，每个业务模块都成为一个积木，然后，我们以搭积木的方式来构造系统。当业务发生变化，我们就调整对应的积木，如果系统拆分得合理，拆分后的模块就具有良好的封装性，也就意味着我们主要是调整积木的内部，而它的外观基本不变。这样一来，相邻的积木不会受到影响，系统整体也不需要大的调整。结果是，系统的变化是局部和可控的，保证了灵活的应对变化能力。

打造可扩展的模块体系：模块整合

系统拆完后，接下来就是模块整合的工作，整合也有两种好的手段：通用化和平台化。

通用化整合

通用化指的是通过抽象设计，让一个模块具备通用的能力，能够替代多个类似功能的模块。

回到刚才的出行平台，我们发现 3 条业务线都有地图搜索、运力调度、订单支付这些模块，不同的业务线之间，这些同名的模块逻辑高度类似，只是细节方面有差别。

那么，我们能不能对这些类似的模块进行抽象化处理，整合成一个通用的模块呢？答案是可以的，我们可以在模块接口中，通过输入参数标识调用来自哪个业务，是出租车、快车还是顺风车，然后在模块内部，针对不同业务线的差异化部分做针对性处理。结果可能是这个通用模块增加 5% 的逻辑，但避免了 95% 的重复逻辑，这样，经过通用化整合，新的模块以很低的代价，就为多个业务线提供了复用。而且，当新的业务线进来，很可能这个通用化的模块，就已经提供了现成的支持。

通过模块通用化，模块的数量减少了，模块的定位更清晰，概念更完整，职责更聚焦。在实践中，当不同业务线对某个功能需求比较类似时，我们经常会使用这个手段。

平台化整合

平台化是把定位相同的模块组织在一起，以组团的方式对外提供服务。对于外部系统来说，我们可以把这些模块看成是一个整体，一起对业务场景提供全面的支撑。

如下图所示，我们可以看到，地图搜索、运力调度、订单支付，都是各个业务线都需要的基础和通用的业务能力，当我们增加新的业务线时，还是离不开这些基础能力。

所以，我们可以把这些基础模块放在同一层，构成一个基础业务平台。之前，它们是一个个离散的服务，独立地输出能力，现在变成一个大的业务平台，可以提供整体的能力输出。

通过打造业务平台，一方面，我们对多个业务模块进行包装，形成更大粒度的抽象，相当于减少了模块的数量；另一方面，作为平台，它的定位更明确，系统依赖关系也更清晰；而且，如果新的业务线进来，它可以基于业务平台快速落地。

业务平台化是模块依赖关系层次化的一个特例，只是它偏向于基础能力，在实践中，当业务线很多，业务规则很复杂时，我们经常把底层业务能力抽取出来，进行平台化处理。

总结

好了，下面我来总结一下今天所讲的内容。

首先，我们对系统进行建模，系统 = 模块 + 关系，这样会简化你对系统的认识。基于这个模型，我们对模块划分和关系定义提出具体的要求，你可以在实际设计时参考这些要求。

另外，我们深入地分析了扩展性的本质。系统的扩展能力来自于内部模块体系的有序，这样才能低成本地应对业务变化，认识到了这一点，有助于你从根本上理解和重视架构的扩展性设计。

然后，我提供了一个出行平台的例子，来帮助你理解，如何通过模块拆分和整合的手段，具体地设计一个可扩展的架构，希望你能在工作中灵活运用。

最后，给你留一道思考题：你所在公司里有没有类似的肿瘤系统，它包含了太多职责，导致系统内部结构混乱，大家都不敢对它进行调整？

欢迎在留言区和我互动，我会第一时间给你反馈。如果觉得有收获，也欢迎你把这篇文章分享给你的朋友。感谢收听，我们下期再见。

04 | 可扩展架构案例（一）：电商平台架构是如何演变的？

你好，我是王庆友。

上一讲中，我们介绍了如何打造一个可扩展的架构。今天，我就针对最近十几年电商平台的架构变化过程，来具体说明下，为了支持业务的快速发展，架构是如何一步步演进的。

从 2003 年淘宝上线开始，国内电商平台经历了高速的发展，在这个过程中，系统遇到了很多的挑战，比如说:

如何针对当前的业务现状，选择合适的架构呢？

如何在业务发展过程中，升级改造架构，并保证系统的平滑过渡呢？

接下来，我会结合自己的工作实践，和你一起探讨架构的演变历程，你可以从中了解到各种架构的优劣点和适用性，然后在实际工作中选择合适的架构。

这里，我总结了国内电商平台架构发展的大致过程，你可以结合图片参考下。

我们可以看到，从最初的单体架构到最新的中台架构，架构的可扩展性越来越强，这些都是系统不断适应业务复杂化的结果。下面，我就结合电商业务的变化，按照顺序和你介绍下各个架构。因为篇幅的原因，对于中台架构，我会放在后面的文章里重点介绍。

单体架构

在单体架构中，只有一个应用，所有代码跑在一个进程，所有的表放在一个 DB 里。第一代电商平台都是单体架构，比如说淘宝，在最初的 3 年，它的系统就是一个巨大的单体应用。

单体应用内部一般采用分层结构，从上到下，一般分为表示层、业务层、数据访问层、DB 层。表示层负责用户体验，业务层负责业务逻辑，数据访问层负责 DB 的数据存取。

我们可以看到，各个层的职责，正好对应业务处理的不同阶段，所以，单体架构在水平方向上，通过层次化的划分，降低了业务的深度复杂性（所谓的业务深度，指的是业务流程从开始到结束的长度）。

不过在垂直方向上，单体应用缺乏清晰的边界，上下层模块之间是多对多的网状依赖关系，比如业务层的某个模块（上图中 BO1），可能调用数据访问层的所有模块（DAO1~3）， 同样的道理，数据访问层的某个模块，也可能被业务层的所有业务模块给调用。

所以，单体架构中的模块只是在逻辑上独立，并没有在物理上严格分开，导致系统在落地时，模块的职责和边界划分比较随意，相应地，模块之间的依赖关系也比较模糊。所以，在单体架构中，模块结构是否合理，很大程度上依赖于开发者的个人水平。

在电商发展的初期，业务并不复杂，比如前台的首页、搜索页、详情页、结算页等，页面的功能都比较简单，可以放在一个应用里处理，这样，使用单体架构就可以快速落地系统。但当业务开始变得复杂时，每个页面都发展为一个独立的业务体系，比如说首页，它原先展示相对固定的内容，现在发展为一个动态的千人千面系统。

这样一来，业务的复杂度急剧上升，模块的数量也大幅度增加了，我们就很难在单体架构里，通过构建一个清晰的模块体系来支持系统的扩展。而且，所有代码放在一个代码库里管理，如果多团队并行开发的话，很容易发生代码冲突，这样也难以满足系统的快速扩展。

举个例子，07 年的时候，eBay 网站总体上也是一个单体应用，它的核心工程有数百万行代码，由于代码合并和编译非常复杂，他们甚至有专门的团队负责代码合并，有专门的团队负责编译脚本开发，另外还有一套复杂的火车模型，来协调不同团队之间的并行开发和上线。

所以，当业务系统的体量变大时，单体架构的弊端就充分暴露出来了，我们就需要对系统进行有效的拆分，比如把首页、搜索页、详情页、结算页拆成一个个独立的应用，分别进行管理。于是，分布式架构就应运而生了。

分布式架构

分布式架构，简单来说就是系统由多个独立的应用组成，它们互相协作，成为一个整体。

分布式架构包括了多个应用，每个应用分别负责不同的业务线，当一个应用需要另一个应用的功能时，会通过 API 接口进行调用。在分布式架构中，API 接口属于应用的一部分，它和表示层共享底层的业务逻辑，你可以认为，API 相当于应用在实现本身业务的基础上，开了个小窗口，给外部使用。

关于分布式的具体架构，你可以参考下图：

你可以看到，分布式架构在单体应用的基础上，进一步对系统按照业务线，进行了业务广度上的切分（所谓业务广度，指的是不同业务线的数量），这样就把一个大系统的业务复杂度，分割成多个小业务的复杂度，从而降低了整体的复杂度。通过拆分后，各个应用之间的耦合度低，就可以很好地支持多团队的并行开发。

但分布式架构也有局限性，作为应用的开发者，除了要满足自身业务的需求之外，同时还需要考虑外部业务的需求，这两部分经常会打架。比如，由于自身业务的需求，引起底层的业务逻辑修改，这时会同时影响 API 接口功能，导致其他业务受影响；同样的道理，外部业务需求过来，需要 API 接口做调整，即使不影响底层业务逻辑，也会导致整个应用重新部署，影响自身业务的稳定性。

另外，在分布式架构下，每个应用都是从头到尾，自搭一套完整的体系，导致业务之间重复造轮子，造成资源浪费。举个例子，在 2008 年，淘宝还没有开始服务化改造之前，不同业务线的用户、商品、订单逻辑非常类似，导致了整个系统有超过 1/3 的核心代码重复。

所以，你可以发现，分布式架构适用于业务相关性低、耦合少的业务系统。举个例子，企业内部的管理系统，分别服务于不同的职能部门，比如财务系统和 HR 系统，就比较适合按照分布式架构去落地。

但在电商场景下，业务都是围绕交易展开的，各个页面（应用）都需要和商品、用户、订单、库存打交道，对于这样业务相互依赖、应用之间需要紧密协作的场景，在系统架构方面，是否有更好的手段，可以更高效地集成这些应用呢？

答案是有的，SOA 架构就可以有效地解决这个问题。 接下来，我们就具体了解下。

SOA 架构

SOA 架构（Service Oriented Architecture）是一种面向服务的架构，它的发展经历了两个阶段：传统的 SOA 架构，它解决的是企业内部大量异构系统集成的问题；新的 SOA 架构，它解决的是系统重复建设的问题。下面我就来和你详细介绍一下。

从 2000 年开始，很多传统企业进入了信息化建设高潮，先后采购了很多系统，比如 ERP、OA、CRM 等等。这些系统都是由不同的供应商提供的，落地后，就形成了很多的信息孤岛。随着业务的发展，企业需要打通这些不同的系统，那么问题来了，这些系统使用不同的技术，事先也没有提供开放接口给外部使用，那我们如何才能有效地集成这些系统呢？

解决的办法是，每个系统首先把外部需要的能力，封装为一个个粗粒度的接口，打包成一个独立的服务；然后，外部系统通过这个服务访问系统内部，解决不同系统相互集成的问题。经过这样的改造，系统最后就变成了一个面向服务的 SOA 架构。

这就是一个传统的 SOA 架构，如下图所示：

你可以看到，在 SOA 架构中，每个服务都对应一个现有的系统，所有这些服务都部署在一个中心化的平台上，我们称之为企业服务总线 ESB（Enterprise Service Bus），ESB 负责管理所有调用过程的技术复杂性，包括服务的注册和路由、各种通信协议的支持等等。

比如说，09 年的时候，eBay 就基于 Axis 2，开发了自己的 SOA 框架，让各个系统通过提供标准的服务，来满足外部调用需求。 比如后台搜索系统，本身是 C++ 开发的，但是它通过提供 Java 服务，封装常见的搜索功能，就方便了其他系统（大多是 Java）和搜索系统进行集成。

以上讲的是传统 SOA 架构，它主要用于解决遗留系统的集成问题。而新的 SOA 架构，它利用服务共享的思想，解决系统的重复开发问题。

举个淘宝的例子，淘宝的系统基本是自建的，系统相互打通的问题不大。但经过一段时间的自然生长，系统重复建设的问题很突出，前面也提到，有超过 1/3 的核心代码重复。针对这种情况，我们就可以通过服务化手段，把通用的逻辑和数据从各个业务系统里抽取出来，封装成独立的服务，提供给所有业务进行共享。

基于这个思路，淘宝花了 2~3 年时间，先后落地了用户、商品、订单、库存、店铺、营销等服务，搭建了共享服务体系。通过共享，淘宝不仅提升了开发效率和质量，也加强了系统的扩展能力。

新的 SOA 架构如下图所示：

所以我们可以看到，相对于分布式架构，SOA 架构给系统的扩展带来了一系列的好处：

首先，它通过服务化思想，提供更好的业务封装性，并通过标准技术，能更友好地对外输出业务能力；

其次，SOA 服务不依附于某个具体应用，它可以独立地部署和扩展，这样避免了直接影响现有的系统；

最后，服务通过封装通用的业务逻辑，可以供所有应用共享，解决了重复造轮子的问题。

不过，虽然 SOA 服务化的思想很好，但在系统实现上比较重，落地比较困难。那有没有更轻量级的架构，使得系统各个部分更容易构建和相互协作呢？

这时候，微服务架构便悄悄地登场了。

微服务架构

关于微服务，大家都不陌生，但究竟什么是微服务，每个人的理解可能都不一样。接下来，我就基于自己的服务化实践，和你分享我的看法。

微服务概念的提出，一开始是用来和单体架构做区分的。我们知道，单体架构和分布式架构，实际上都是围绕一个大的业务线来构建应用，当业务变得复杂时，就无法做到模块边界和依赖关系的清晰划分，模块局部的调整往往会导致系统整体的调整，使得系统很难扩展。

而微服务围绕更小的业务单元构建独立的应用。

比如说，一个飞机航班预订系统，我们可以把它划分为预订航班、时间表查询、计算票价、分配座位等几个小应用（微服务）来落地。那么经过划分后，每个小应用都比较简单，只关注于一个业务功能即可。

这里要注意的是，每个微服务，都是负责端到端的业务，包括前端的 UI 展现部分和后端业务逻辑。微服务的团队成员可能包括产品、开发、测试、运维等人员，由这个小团队负责应用的整个生命周期管理。

因此，从一定程度上说，微服务叫做微应用，或者说微产品，更合适一点，你也可以认为微服务架构是拆分得更细的分布式架构。

另外，微服务强调围绕业务，进行清晰的业务和数据边界划分，并通过良好定义的接口输出业务能力，这和 SOA 架构里的服务有点类似。但两者不同的地方在于，微服务是去中心化的，不需要 SOA 架构中 ESB 的集中管理方式。

一方面，微服务强调所谓的哑管道，即客户端可以通过 HTTP 等简单的技术手段，访问微服务，避免重的通信协议和数据编码支持。另一方面，微服务强调智能终端，所有的业务逻辑包含在微服务内部，不需要额外的中间层提供业务规则处理。

这样子，微服务提供方可以自由地选择语言和工具，来落地微服务，服务的部署和维护上也更灵活，从这个意义上来说，你也可以认为微服务是轻量级的 SOA 服务。

所以说，微服务兼有应用和服务的特征，你可以把微服务理解为：

微服务 = 小应用 + 小服务。

以上就是微服务架构设计的初衷，但在实践中，我们更多地把微服务当做一个小服务，而不是一个端到端的小应用，那么为什么会这样呢？这里有几个原因。

首先，我们很难把一个大系统，按照端到端业务的方式，拆分为一个个应用；而拆分为服务是比较灵活的，我们可以把系统核心的业务逻辑和数据封装成服务，其它部分还是以应用的方式落地。另一方面，微服务要求团队人员跨多个职能，构建独立的小团队，来负责服务完整的生命周期，这就需要把现有的职能团队打散后重组，这种人员组织的调整实际上也很难落地。

我们可以看到，微服务强调围绕端到端的小业务功能，通过组建跨职能的团队，来进行落地，这只是一种理想化的做法。所以，在实践中，我们往往弱化微服务的小应用定位，然后扩大化微服务小服务的定位，我们不再强调端到端的业务封装，而是可以有各种类型的微服务。

比如说，封装底层基础业务的是共享微服务，封装流程的是聚合微服务，封装具体业务场景的服务端是应用微服务，封装基础中间件（如 Redis 缓存、消息推送）的是系统微服务。当然，这些服务在具体落地时，我们还是采取去中心化的机制，使用轻量级的通讯框架，最后把它们打造成一个个技术上轻量级的、功能职责上细分的微服务。

所以，基于这样的思路，微服务就很容易构建，同时，也像水电煤一样，容易被我们使用。然后，我们在这个基础上组装微服务，像搭积木一样搭建系统，这样的系统更具弹性，更容易扩展。

值得注意的是，我们需要对服务依赖关系进行有效的管理，打造一个有序的微服务体系。否则的话，东一个服务，西一个服务，这样会让系统变得碎片化，难以维护和扩展。

所以我这里也放了一张图，来帮助你理解，一个有序的层次化微服务体系大致是什么样子的。

总结

今天，我与你分享了电商平台架构的发展过程，从单体架构到分布式架构，再到 SOA 架构和微服务架构，每种架构都针对前一种架构的缺点做了改进，架构的扩展性也变得越来越好，可以满足更高的业务复杂性要求。

但值得注意的是，每种架构都有两面性，既有优点，又有缺点，在实际系统中，这些架构也都是并存的。架构没有最好，只有最合适的。我们做架构设计时，一定要根据当前业务的特点，选择合适的架构。

通过今天的分享，相信你对架构的扩展性有了更深入的理解，也能够根据公司的业务现状，进行更合理的架构选型了。

最后，给你留个思考题：现在人人都在落地微服务，你在这方面有什么经验和教训吗？

欢迎你在留言区与大家分享你的答案，如果你在学习和实践的过程中，有什么问题或者思考，也欢迎给我留言，我们一起讨论。感谢收听，我们下期再见。

05 | 可扩展架构案例（二）：App服务端架构是如何升级的？

你好，我是王庆友。

上一讲，我与你介绍了电商平台从单体架构到微服务架构的演变过程。那么今天，我会通过一个 1 号店 App 服务端架构改造的例子，来具体说明架构的演变过程，让你能更深入地理解架构演变背后的原因。

好，先让时间拨回到 2012 年，当时随着智能设备的普及和移动互联网的发展，移动端逐渐成为用户的新入口，各个电商平台都开始聚焦移动端 App。这个时候，1 号店也开始试水移动端购物，从那时起，1 号店 App 的服务端架构一共经历了三个版本的变化。

接下来，我就为你具体介绍 App 服务端架构变化的过程以及原因。

V1.0 架构

我先说说最开始的 1.0 版本。当时的情况是，App 前端的 iOS 和 Android 开发团队是外包出去的，而 App 的服务端是由 1 号店内部一个小型的移动团队负责的，这个团队主要负责提供 App 前端需要的各个接口，接口使用的通信协议是 HTTP+JSON。

具体的架构如下图所示：

这个架构比较简单，App 的服务端整体上就一个应用，由移动团队来维护所有对外接口，服务端内部有很多 Jar 包，比如商品搜索、商品详情、购物车等等，这些 Jar 包包含了各个业务线的业务逻辑及数据库访问，它们由各个业务线的开发者负责提供。

你可以看到，这个 1.0 版本的服务端，实际上就是一个单体应用，只是对外的接口和内部 Jar 包分别由不同的团队来提供，这个架构的优点和缺点同样都非常明显。

它的优点是简单方便。App 前端的外包团队只需要对接后端的一个移动团队就可以了，然后移动团队通过现成的 Jar 包，封装各个业务线的功能。至于这些 Jar 包，业务线团队也无需额外去开发。

为什么呢？我们知道，早期的电商平台都是先有 PC 端应用，再推 App，App 最开始的功能，大多是从已有的 PC 端平移过来的。因此，这些 Jar 包直接从 PC 端应用里拿过来就可以了，如果 Jar 包版本有更新，由业务线团队直接同步给移动团队即可。

那这个架构设计是不是很完美啊？当然不是，不知道你发现了没有，其实这里也存在了很多问题。

第一个问题：移动服务端对 Jar 包的紧密依赖

移动团队负责对外接口，但他们非常依赖业务团队提供的 Jar 包来实现业务逻辑，这是一种物理上的紧耦合依赖关系。

如果业务团队根据 PC 端的需求，修改了应用代码后，Jar 包也会随之修改。那么在实践中，经常会出现这样的情况：业务团队很多时候，要么忘了同步新的 Jar 包给移动团队，要么是新的 Jar 包调整了类的接口，导致了 App 服务端的功能有问题，或者直接不可用。

第二个问题：移动团队的职责过分复杂

服务端为 App 提供的是粗粒度接口，而业务团队的 Jar 包提供的是细粒度的接口。

因此，移动团队在 Jar 包的基础上，还需要做很多的业务逻辑聚合，很多时候，这些逻辑还跨多个业务线，导致移动团队对所有业务逻辑都要深入了解。相信你也知道，这是很难做到的。

第三个问题：团队并行开发困难

由于移动团队和业务团队是通过物理 Jar 包进行集成的，移动团队直接受业务团队的代码影响，就导致了团队之间并行开发困难，一次大的 App 升级经常需要 2~3 个月的时间。

而当时的 1 号店，需要能尽快地推出 App 端，我们所有的做法都是围绕这个目的来的，包括把前端团队外包出去，后端采用单体架构，移动端功能从 PC 端直接移植过来。所以，从当时的情况来说，这种简单的服务端架构和团队合作模式是非常合适的。

而过了一段时间，当移动端的功能已经初步具备，我们就需要针对移动自身的特点去组织功能，并能够快速上线这些新功能。那么，这种单体架构加物理 Jar 包耦合的方式，就成为 App 进一步发展的瓶颈。

接下来，我们就看下系统是如何通过架构升级，来解决这个问题的。

V2.0 架构

到了 2013 年，1 号店 App 服务端架构升级到了 V2.0。在这个时候，1 号店自己接手了 App 前端的开发工作，同时，服务端接口也由各个业务线团队直接负责，这样，App 前端直接对接多个后端应用提供的 HTTP 接口。

整体架构如下图所示：

对于各个业务团队来说，他们现在走向了前台，每个团队负责各个业务线的 App 接口。他们一般采取这样的做法，一方面，他们以 Web 应用的方式，为 PC 端浏览器提供访问；另一方面，针对移动端的访问需求，他们在 Web 应用里面，增加了一些 REST 接口，直接供 App 访问。在这里，移动接口和 Web 应用在同一个工程里开发，作为同一个应用进行部署和运行。

这里你可以看到，这实际上就是一种分布式的系统架构，每块业务由不同的团队负责，可以很好地支持团队之间的并行开发；同时，移动接口和 PC 端共享底层业务逻辑，有助于快速把 PC 端的功能完整地复制到 App 端。

这样，通过 V2.0 架构的升级，业务线团队的生产力就被完全释放了，App 的功能也就快速丰富起来了。

但这种方式也带来了一系列的问题，我们具体说下。

首先是移动端和 PC 端互相干扰的问题。

你可以看到，在同一个业务线内部，移动接口和 Web 应用，物理上是绑定在一起的。很多时候，PC 端的代码修改会影响到移动接口，而 Web 应用的发布，也会导致移动接口被动地被发布，如果 PC 端出现功能问题，也会影响到移动接口的可用性。反过来也是一样的，移动接口的需求变化，会影响到 PC 端的功能。

我们知道，当移动端发展到了一定程度，它需要和 PC 端有不同的功能和用户体验，但这种紧耦合的方式，导致了相互之间产生很多不必要的干扰，对系统的功能和稳定性都带来了负面影响。

其次是重复开发的问题。

移动接口除了要给 App 端提供业务数据，还需要考虑一系列系统级的功能，比如说，安全验证、日志记录、性能监控等等，每个移动接口都需要这些通用功能。

那现在，由于 App 前端是和后端直连的，这就意味着，每个后端系统都需要独自去支持这些系统级的功能，导致了各个后端系统重复开发。一旦这些通用需求发生了变化，比如说，我们要对传输数据进行压缩，那么，所有的后端系统都需要同步调整，这样不但工作量很大，而且也给项目管理也带来了很大的挑战。

最后是稳定性的问题。

在这里，基于这种直连方式，只要一个后端系统出问题，就会直接影响到 App 的可用性，使得 App 整体上非常的脆弱。

之所以会出现以上这些问题，它的根本原因在于，我们在 App 端，直接照搬了 PC 端的做法，没有针对移动端自身的特点，去做架构设计。

我们知道，当 App 发展到一个成熟阶段时，无论是业务功能，还是非业务性功能，和 PC 端都是不同的。所以，在架构设计上，我们必须能够支持它们各自不同的特点，根据这个思路，我们的 App 服务端架构也演变到了 V3.0 版本。

V3.0 架构

在 V3.0 版本中，服务端架构包含了两个大的升级。

首先，我们对每个业务线的服务端进行拆分，让 App 接口和 PC 端接口各自在物理上独立，但它们共享核心的业务逻辑。

拆分后的架构如下图所示：

这样拆分的结果是，原来大的服务端变成了 3 个应用，包括一个 App 端接口应用，一个 PC 端 Web 应用，还有一个核心业务逻辑服务，3 个部分都是独立维护和部署的。

除此之外，架构改造还考虑了移动端自身的特点。

一方面，每个移动端接口需要调用对应的后台服务，进行业务逻辑处理，这个是个性化的，每个接口的处理逻辑都不一样；另一方面，每个移动端接口都需要进行系统级的功能处理，比如前面所说的安全验证、接口监控等，这个是共性的，每个接口的处理方式都是一样的。

那么，在架构上，我们就需要把共性的系统级功能进行集中处理，把个性化的业务功能进行分散处理。

最后，我们结合服务端的应用拆分，以及对移动接口本身的改造，落地了服务端 V3.0 架构。

如下图所示：

在这里，App 前端会通过移动网关来访问服务端接口。这里的网关主要就是负责处理通用的系统级功能，包括通信协议适配、安全、监控、日志等等；网关处理完之后，会通过接口路由模块，转发请求到内部的各个业务服务，比如搜索服务、详情页服务、购物车服务等等。

对于 PC 端浏览器来说，它直接访问对应的 Web 应用，如搜索应用、详情页应用等，然后这些应用也是访问同样的内部服务。

这里说明下，当时还没有流行前后端分离，所以 PC 端有对应的 Web 应用，同时负责业务逻辑和 UI 展现。

现在，你已经了解了 V3.0 版本的整体架构设计，接下来，我们就深入移动网关，去具体了解下它的内部实现机制。

移动网关的内部实现

在图中，你可以看到，整个移动网关分为三层，自上而下分别是通用层、接口路由层、适配层，接下来我们逐一分析。

通用层

首先是通用层，它负责所有系统级功能的处理，比如通讯协议适配、安全、监控、日志等等，这些功能统一由网关的通用层进行预处理，避免了各个业务线的重复开发。

在具体实现时，每个通用功能的处理逻辑都会封装成一个拦截器，这些拦截器遵循统一的接口定义，并且拦截器都是可配置的。当有外部请求过来，网关会依次调用这些拦截器，完成各个系统级功能的处理。

这个拦截器接口的定义如下：

Object filter(Object input)throws Exception

接口路由层

接下来是接口路由层。移动端请求经过通用层的预处理之后，将会进一步分发给后端的业务适配器进行处理。

我们在配置文件里，对接口请求的 URL 和业务适配器进行映射，接口路由层的分发逻辑就是根据请求中的 URL，在配置文件里找到对应的适配器，然后把请求交给适配器进行后续的处理。

配置文件的具体内容如下所示：

www.website.com/search    SearchAdapter
www.website.com/detail    DetailAdapter

服务适配层

最后是服务适配层。我们知道，外部接口的请求格式，往往和内部服务接口的格式是不一样的。具体到 1 号店当时的情况，外部接口是 HTTP+JSON 格式，内部服务是 Hessian+ 二进制格式。

适配器首先用来解决内外部接口的适配，除此之外，适配器还可以根据需要，对多个内部服务做业务聚合，这样可以对 App 前端提供粗粒度的接口服务，减少远程网络的调用次数。

这些适配器遵循统一的接口定义：

Object adapter(Object input)throws Exception

这些适配器物理上是 Jar 包的形式，由各个业务线研发团队提供，所有的适配器会集中部署在网关，而网关本身可以支持多实例的部署，通过水平扩展的方式提升服务端的处理能力。

现在，你已经很清楚了 V3.0 架构的实现细节，接下来，我们就深入看下，这次架构升级达到了什么样的实际效果。

架构的实际效果

首先，App 端和 PC 端彻底独立了。在上面的图中，我们可以看到，App 前端和 PC 端浏览器是完全对等的，PC 端浏览器有自己的服务端，App 前端也有自己的服务端，在这里，移动网关就充当 App 服务端的角色。

在这个架构下，两个服务端都可以针对自身的特点，独立开发，独立部署，无论在逻辑层面还是物理层面都实现了彻底解耦。我们知道，一开始，App 是依附于 PC 端，而现在，它终于可以独立地发展了。

其次，通过架构改造，实现了核心业务的复用。这里，我们把核心的业务逻辑从 Web 应用中剥离出来，变成了共享的服务。在服务设计时，我们不再区分 PC 端还是移动端，而是从业务本身出发，提供一套通用的接口，同时供 PC 端和移动端调用，从而实现了底层业务逻辑的复用。

还有，这个架构强化了系统级功能。原来通用的系统级功能，由各个团队各自去提供，很多团队要么不提供，要么实现的方式不一样；现在的系统级功能，是由集中式的移动网关统一来提供，我们就可以很方便地强化这些系统级功能。

举个例子，我们可以把通信协议由 HTTP 升级为更安全的 HTTPS，当后端服务有问题时，也可以通过网关进行事先的数据缓存，直接返回给 App 前端。比如说商品的详情数据，就很适合这样的处理。

所以，有了移动网关，整个 App 的可用性、稳定性和安全性都得到了大幅度的提升。

最后，团队分工也更明确了。在这里，移动团队主要负责移动网关，包括网关本身和各种过滤器的维护，他们可以针对移动端的特点，做各种系统级功能的优化；而业务团队，主要负责各自的业务逻辑，包括适配器和底层服务。移动团队和业务团队通过明确的适配接口进行协作，相互不影响。

我们可以看到，V3.0 在 V2.0 分布式架构的基础上，通过服务化改造，实现了基础业务的复用；同时，通过移动网关落地系统级功能，实现了系统的平台化改造。

总的改造结果就是，解放了业务线，提升了系统的稳定性，使得移动端可以做大做强。

总结

今天，我与你分享了 1 号店 App 服务端架构改造的实际例子。在这个例子中，架构经历了单体架构到分布式架构，再到 SOA 架构的变化过程，并且通过移动网关的方式，一定程度上实现了平台化。

在这里，你可以清晰地看到，公司每个阶段的业务，都有它不同的特点，我们选择的架构必须能够适配它，过度设计和设计不足，同样都是有害的。

通过今天的分享，相信你对各种架构的优缺点，以及业务上的适用性有了更进一步的了解。他山之石，可以攻玉。架构的策略和原则是通用的，希望你能够通过实战不断去领会和运用。

最后，给你留一道思考题：你都做过哪些系统改造，改造前是什么架构，改造后又是什么架构，过程中有哪些挑战呢？

欢迎在留言区和我互动，我会第一时间给你反馈。如果觉得有收获，也欢迎你把这篇文章分享给你的朋友。感谢收听，我们下期再见。

06 | 可扩展架构案例（三）：你真的需要一个中台吗？

你好，我是王庆友。前面的课程，我们从单体架构开始，讲到了微服务，今天我们就接着讲最新的中台架构。

关于中台，最近比较火，你可能也听到过不少关于它的讨论，但中台究竟是什么？它能解决什么问题？相信你不一定非常清楚。今天，我就为你解决这些困惑。

讲中台之前，我们先来理解下前台和后台，这样，你才能更清楚中台的定位。

前台比较好理解，指的是面向 C 端的应用，比如像微信、淘宝这样的应用。不过，你要注意，前台不仅仅是指前端，它还包含和前端配套的服务端。

后台指的是企业内部系统，比如 ERP、CRM、仓库管理系统等等，主要是面向企业内部人员使用。对于传统企业来说，之前只有线下场景，通过内部的后台就能完成所有业务流程；而对于互联网企业，或者逐步开展线上业务的传统企业来说，同时需要前台和后台，一起协作，完成业务的闭环。

但问题是，前台和后台的特性是不一样的。前台对外，我们知道，消费者的需求快速多变，所以前台需要能快速响应，做到低成本试错；而后台对内，企业内部的业务流程不能经常变，所以后台需要稳定，不能随意调整，一旦改动，影响面广，成本很高。

简单地说，前台要快，后台要稳，因此在业务扩展时，我们经常会遇到以下两类挑战：

这个营销思路很棒，老板希望能马上验证，前台好改，但后台调整起来需要好几个月；

后台系统技术旧，性能差，接口不开放，前台对接起来很麻烦，而且一有促销活动，后台立马就挂。

第一类挑战，在互联网企业比较普遍，前台经常玩各种花样，要求快；第二类挑战，在传统企业很典型，大量的后台都是早期采购的商业套件，新的线上应用很难直接对接内部老系统。

你可以发现，前台和后台是企业 IT 系统的一体两面，它们需要紧密协作，共同服务于企业的业务战略。但两者对业务稳定性的要求不一样，在技术上也普遍存在脱节现象。

所以，如何实现前后台的平滑对接，这是一个巨大的挑战，中台架构因此而生。

接下来，我会结合自己在中台方面的实践，和你深入聊下中台的定位，以及具体的中台架构，让你可以轻松应对这个挑战。

中台的定位

讲中台前，我先举一个你比较熟悉的 Windows 系统的例子：

在 Windows 系统里，最上面是各种桌面应用，比如 Office 套件等，这些是用户能够直接看到的部分；最底下是各种硬件设备，比如磁盘、内存、CPU 等；中间是操作系统，它处于软硬件之间。

我们知道，理论上，桌面应用可以直接操作底层硬件，完成所需要的功能。比如，我们用低级的汇编语言去开发应用，就可以通过端口来直接操作硬件。但很显然，这种开发方式的效率很低，代码的可读性和可维护性也很差。

但是，如果我们在中间加上一层操作系统，通过操作系统向下管理硬件，屏蔽各种硬件的差异和复杂性，向上提供简洁的 API 接口，我们就可以使用各种高级语言，通过调用 API，很方便地操作硬件了。

在这个里面，操作系统在底层硬件和上层应用之间，起到了很好的衔接作用。

我们就对照 Windows 操作系统的例子，来看下传统企业的 IT 系统。比如说麦当劳，它经过多年的信息化建设，购买了大量的商业套件，如总部使用的 ERP、门店使用的收银系统等等，这些系统都属于后台的范畴，面向企业内部管理，针对的是传统的线下业务。

现在，随着麦当劳的业务发展，要往新零售转型，比如说，他们要提供线上小程序点餐服务，为消费者创造更好的用户体验。

但是，这个小程序点餐服务不是孤立的，它离不开内部系统的支撑。比如，小程序展示的菜品来自于后台 ERP；小程序下的订单，会进入门店的收银系统和厨房作业系统。

那么问题来了，这些 C 端应用，与内部后台系统要如何打通呢？

理论上，C 端的应用也是可以直接调用后台老系统来实现打通的，比如在麦当劳的例子中，小程序服务端可以直接调用 ERP 获取菜品信息，提供给小程序前端进行展示。但这个和 Windows 系统里的桌面应用直接控制硬件设备类似，这里前后台的直接对接是非常低效的。

我们知道，小程序服务于 C 端，ERP 服务于 B 端，ERP 建设在前，小程序建设在后。ERP 系统在实施的时候，完全没有考虑小程序点餐场景，两者在业务流程、数据模型、技术栈、性能要求等方面，差异都很大，导致直接的对接非常困难。

而且，如果有新的 C 端场景进来，又要从头到尾对接一遍，重新吃一遍苦。这是一种硬着陆的方式，如果新业务上线采取这种方式，那至少需要好几个月时间，根本无法满足业务快速创新的要求。

这时，如果有个中间层来负责 C 端应用与内部后台系统的平滑衔接，帮助新的 C 端应用软着陆，这样就会非常高效。这里我对比了操作系统和新零售中台，如下图所示：

以麦当劳为例，如果我们对内部老系统进行包装，对外提供标准的 API，这样就能把旧的 IT 基础设施，转换成面向互联网的业务平台。然后，新的 C 端应用可以快速基于这个业务平台来构建，而不用关心底层老系统的实现细节。这个中间层就是中台。

你可以看到，中台相当于企业的商业操作系统，通过对后台的包装，为前台提供全方位的支持。这里，需要注意的是，中台不仅仅是前后台之间简单的适配器，中台本身也会落业务数据，有完整的业务规则，就像 Windows 操作系统一样，它在适配硬件的基础上，进一步提供内存管理、进程调度等功能，为上层应用提供体系化的支持。

对于互联网企业来说，前后台虽然是同时建设的，它们在功能上能够衔接起来，但前台求快，后台求稳。所以在这里，中台可以先承接前台的业务和数据，和前台构成 C 端业务的小闭环，支持业务的快速创新，等业务模式验证后，中台和后台再进一步彻底打通，构成业务的大闭环。

现在你已经了解了中台的定位，可能会想，企业处于什么样的发展阶段，需要落地中台呢？

接下来，我就结合一个出行平台的发展过程，来说明中台的适用性，让你能够在合适的时机选择落地中台。

中台的适用性

一个出行平台，当公司发展从 0 到 1 的阶段时，往往只有一条业务线，比如说出租车业务，我们直接根据它的需求落地系统即可。随着公司发展到从 1 到 n 的阶段时，业务线会逐渐增加，比如增加了快车、顺风车等业务。

这时，从系统落地的角度，我们有两种做法。

第一种是独立地建设新业务线，这样，各个业务线并列，系统整体上是一个“川”字型的结构。

如下图左边部分所示：

但是，如果各个业务线的业务逻辑非常类似，子系统之间会有大量的代码复制，这就会导致重复建设以及多头维护的问题。显然，这是非常低效的，本来我们想能尽快上线新的业务线，但结果是欲速而不达。

第二种做法是，把各业务线中相同的核心逻辑抽取出来，通过抽象设计，实现通用化，共同服务于所有业务线的需求，系统结构整体上是一个“山”字型。

“山”字型的上面三竖，代表各个业务线定制的应用；最底下一横，代表通用层，它把各个业务线有机粘合在一起，实现了业务逻辑和业务规则的统一，如上图中的右边所示。

这样，我们就能一处建设，多处复用，一处修改，多处变化，从而实现最大程度的复用。

那我们什么时候，需要从“川”字型转为“山”字形呢？

一方面，这和公司业务线的数量有关，业务线越多，意味着重复建设的成本会更大，当我们开始上第 3 条业务线时，就应该要考虑转到“山”字形了。

另一方面，也和各个业务线的相似度有关，相似度越高，意味着业务线之间有更多类似的逻辑，更适合“山”字形。比如，出行平台的各个出行方式相似度很高，适合“山”字形；但同一个公司的出行业务和互联网金融业务，差异比较大，就可以考虑“川”字形，而没必要把它们强行扭在一起。

所以说，中台实现了通用基础业务的平台化。从变化速度来看，企业基础的业务是相对固定的，而具体上层业务场景是相对多变的；从数量来看，基础业务数量是有限的，而具体业务场景是无限的。因此，有了完善的中台，我们就可以通过有限而比较固定的基础业务，来满足无限而快速变化的上层业务场景了。

此外，从业务角度来看，中台收敛了业务场景，统一了业务规则；从系统角度看，中台相当于操作系统，对外提供标准接口，屏蔽了底层系统的复杂性；从数据角度看，中台收敛了数据，比如使用同一套订单数据模型，让所有渠道的订单使用相同的订单模型，所有订单数据落到同一个订单库。

那么用一句话总结就是，中台通过实现基础业务的平台化，实现了企业级业务能力的快速复用。

好，接下来，我们就一起深入中台，具体了解下中台架构设计的细节。

如何落地一个中台架构？

通过课程之前的分享，你应该对微服务架构比较熟悉了，我也提到了中台架构紧跟着微服务架构，那么中台和微服务架构到底有什么区别和联系呢？

简单地说，我认为中台是微服务的升级。

在微服务架构下，我们搭建的是一个个离散的服务，如商品服务、订单服务等等。而在中台里，这些微服务升级为了商品中心、订单中心，每个中心更强调体系化，包括更好的业务通用能力，更好的系统运营能力（如监控、稳定性、性能的强化），更好的业务运营能力（比如商品中心自带配套的商品管理后台）。

每个服务中心都围绕核心业务，自成体系，成为一个微内核，这些微内核形成一个有机整体，共同构成了基础业务平台，也就是中台。松散的微服务 -> 共享服务体系 -> 中台，这是微服务架构向中台架构的演进过程。

现在大家谈论比较多的是业务中台，那我们就来具体看下一个典型的业务中台的结构。它一般包含三层，从上到下分别是通用聚合服务层、通用基础业务平台和通用中间件平台。

对于中台来说，基础业务能力由通用基础业务平台来实现；另外，通用聚合服务对基础业务进行组合，进一步提升了业务能力的易用性；而通用中间件平台，通过技术手段保证了业务中台的稳定性，三者一起实现了企业整体业务能力的复用。

那么关于具体如何落地中台，互联网企业和传统企业的侧重点则有所不同。

对于大的互联网企业来说，系统已经是类似于“山”字型的结构，进化到中台，更多的是各个基础服务点上的强化和面上的整合。

对于传统企业来说，系统基本上是“川”字型的结构，大量独立的商业套件组成遗留系统，落地中台是一个革命性的动作。

所以接下来，我就主要分析下传统企业如何落地中台，这样更能体现出中台的价值和落地的挑战。

首先，如下图所示，我们看下典型的传统企业中台架构设计是什么样的。

你可以看到，整个中台架构从上到下分为四个层次：

渠道 & 应用

渠道 & 应用层，这是整个系统的对外部分，包括了各个应用的前端，如 App、小程序、公众号等等，这些是需要定制的部分。同时，在对外部分，我们还会提供 Open API，供上下游企业调用。

应用平台

应用平台是各个具体应用的母体，它包含了各个应用的服务端，比如小程序服务端、App 服务端等等，这些服务端会针对具体场景，做流程编排和信息的聚合。

服务端和前端之间还有一个网关，网关实现前后端隔离，具体负责外部访问的安全验证和监控，以及内外部请求的路由和消息格式转换。

业务中台

业务中台是中台架构的核心，它包括一系列的通用基础服务，以及它上面的通用聚合服务和下面的技术平台，这个在前面已经详细介绍过了，我就不赘述了。

后台

后台包括两部分，第一部分是适配插件，用于连接商户内部系统和中台基础服务，比如，在中台的商品服务和后台 ERP 之间同步商品数据，在中台的会员服务和后台 CRM 之间同步会员信息。一般针对每个内部系统，都有一个适配插件，它起到了类似硬件驱动程序的作用，这个一般是定制化的。第二部分是企业内部系统，这个是企业的 IT 基础设施，业务最终会在这里落地。

OK，通过以上的介绍，你可以清晰地看到，中台代表了企业核心的业务能力，它自成体系，能够为 C 端的互联网场景提供通用的能力，并通过各种插件和后台打通。这样，经过中台的通用化和后台的插件适配后，我们最终就把企业的后台老系统，包装成一个面向互联网的平台，可以快速地给 C 端赋能。

总结

中台是从企业的业务战略高度，来考虑企业 IT 系统的建设，它的目标是实现企业整体业务能力的复用。从落地的角度看：

对于互联网企业来说，有大量微服务做基础，往中台转是改良，目的是更好地衔接前台和后台，实现业务的快速创新；

对于传统企业来说，内部有大量的遗留系统，落地中台是革命，目的是盘活老系统，全面实现企业的数字化转型。

互联网发展到现在，从最初的电商，到 O2O，再到现在的产业互联网，已经进入了深水区，很多传统企业都面临着数字化转型的挑战。架构上往中台转型，落好中台，真正发挥中台的价值，这将是一个长期的过程，也是企业业务复杂化的必然结果。

通过今天的分享，相信你对中台有了更深入的理解，对是否要往中台转型，你也能够做出更好的判断了。

最后，给你留一道思考题：现在中台很热，我们经常听到很多中台名词，它们分别是什么定位呢？

欢迎你在留言区与大家分享你的答案，如果你在学习和实践的过程中，有什么问题或者思考，也欢迎给我留言，我们一起讨论。感谢阅读，我们下期再见。

07 | 可复用架构：如何实现高层次的复用？

你好，我是王庆友。在前面几讲中，我们讨论了如何打造一个可扩展的架构，相信你对架构的可扩展有了一定的了解，而架构还有一个非常重要的目标，那就是可复用。所以从今天开始，我就来和你聊一聊，如何打造可复用的架构。

作为开发人员，你对复用这个概念一定不陌生。在开发过程中，我们把系统中通用的代码逻辑抽取出来，变成公共方法或公共类，然后在多个地方调用，这就是最简单的技术上的复用。

但一开始，我们不会过多地考虑复用，当一个新项目过来，我们会选择最直接的方式来实现，结果往往是欲速而不达，比如说：

好不容易搞定了一个项目，接着又有新的类似项目过来，我们又要从头再来；

项目的代码是定制的，项目结束后，系统维护的噩梦刚刚开始。

如果项目缺乏沉淀，每个项目都是全新的开始，出现这些情况，一点都不意外。而要想解决这个问题，我们一开始就要考虑系统的复用性。

复用，它可以让我们站在巨人的肩膀上，基于现有的成果，快速落地一个新系统。

那么，我们在做架构设计时，如何实现系统的高可复用呢？

今天，我就针对复用这个话题，首先和你介绍一下，复用具体都有哪些形式；然后，我会针对最有价值的业务复用，带你了解如何划分服务的边界，让你能够在工作中，设计一个可以高度复用的系统。

复用的分类

复用有多种形式，它可以分为技术复用和业务复用两大类。技术复用包括代码复用和技术组件复用；业务复用包括业务实体复用、业务流程复用和产品复用。

从复用的程度来看，从高到低，我们可以依次划分为产品复用 > 业务流程复用 > 业务实体复用 > 组件复用 > 代码复用。

接下来，我就按照复用度从低到高，对这些复用方式进行一一分析，帮助你更好地理解架构的可复用性。

技术复用

首先是代码级复用，这部分应该是你最熟悉的了。这里包括你自己打包的类库，第三方提供的 SDK，还有各种算法封装等。我们的代码可以直接调用它们，物理上也和我们的应用打包在一起，运行在同一个进程里。代码级复用是最低层次的复用，你可以把它当作你自己源代码的一部分。

再往上，是技术组件复用。这些组件有我们自己封装的，更多的是大量开源的中间件，比如 Redis、MQ、Dubbo 等；组件也包括各种开发框架，比如 Spring Cloud。这些基础组件技术复杂度很高，它们的存在，极大地简化了我们的开发工作。

值得注意的是，代码级复用和技术组件复用都属于工具层面，它们的好处是在很多地方都可以用，但和业务场景隔得有点远，不直接对应业务功能，因此复用的价值相对比较低。

业务复用

我们知道，系统最终是为业务而服务的，如果能够实现直接的业务复用，那系统开发的效率就更高。在前面的课程中，我们讨论架构的演进过程时，很多地方谈到了业务能力的复用，比如说，微服务强调单个业务实体的封装和复用，而中台进一步实现了企业级业务能力的复用。

所以接下来，我们就从比较简单的业务实体复用开始说起。

业务实体复用针对细分的业务领域，比如订单、商品、用户等领域。它对各个业务领域的数据和业务规则进行封装，将它变成上层应用系统可以直接使用的业务组件。

业务流程的复用针对的是业务场景，它可以把多个业务实体串起来，完成一个端到端的任务。比如说，下单流程需要访问会员、商品、订单、库存等多个业务，如果我们把这些调用逻辑封装为一个下单流程服务，那下单页面就可以调用这个流程服务来完成下单，而不需要去深入了解下单的具体过程。相比单个的业务实体复用，业务流程的复用程度更高，业务价值也更大。

最高层次的复用是对整个系统的复用，比如说一个 SaaS 系统（Software-as-a-Service），它在内部做了各种通用化设计，允许我们通过各种参数配置，得到我们想要的功能；或者说一个 PaaS（Platform-as-a-Service）平台，它会提供可编程的插件化支持，允许我们“嵌入”外部代码，实现想要的功能。

这种产品级的复用，它的复用程度无疑是最高的。这样的系统，在落地的时候，它无需核心的开发团队进行开发，只由外围的实施团队负责就可以了，这样，一个项目的上线就能简化为一次快速的实施，不但上线周期短，系统也更稳定。

当然，实现这样的复用，难度也是很大的，你既要对所在行业的业务有很全面的理解，又要有很强的抽象设计能力。这类系统中，比较典型的有 Salesforce 的 CRM 系统和 SAP 的 ERP 系统。

现在，我们先对复用做个总结。从技术复用到业务复用，越往上，复用程度越高，复用产生的价值也越大，但实现起来也越复杂，它能复用的场景就越有限。在实际工作中，技术层面上的复用相对比较简单，我们对这部分的认知也最多，而且由于开源的普及，现在有丰富的中间件让我们选择，我们可以基于它们，逐步构建适合自己的技术体系。

但如果我们能进一步打造业务中间件，并在这个基础上，形成业务平台，这样，我们就能实现更高的业务级复用，可以更高效地支持系统的快速落地。

而在实现业务组件化和平台化的过程中，首要的问题就是基础服务边界的划分。边界划分决定了服务的粒度和职责，在实际工作中，也是非常困扰我们和有争议的地方。

接下来，我就针对基础服务边界的划分，和你分享我自己在项目开发的过程中，总结的一些实用的原则和做法。

基础服务边界划分

服务边界划分要解决“我是谁”的问题，它实现了服务和周边环境的清晰切割。

我们都知道，服务包含了业务数据和业务规则，并提供接口给外部访问，其中，接口是服务的对外视图，它封装了服务的业务数据和规则。

所以从边界划分的角度来看，我们就是要确定哪些数据属于这个服务，哪些接口功能由这个服务提供。这里，我总结了 3 个基础服务边界划分的原则，供你设计时做参考。

首先，是服务的完整性原则

你在划分服务的边界时，需要确保服务内部数据的完整性。

举个例子，一个商品服务的数据模型，不仅要有商品基本信息，比如商品名称、价格、分类、图片、描述等；还需要包含商品的扩展信息，如商品的各种属性、商品标签等；最后还要包含各种复杂商品类型的定义，比如组合商品、套餐商品、多规格商品等。

另外，你还要保证服务功能的完整性。对于服务使用者来说，他们是以业务的角度看服务，而不是纯粹的数据角度。比如一个套餐商品，在服务内部，它是多个单品的复杂组合，但从服务调用者的角度来看，它就是一个商品。

那现在问题来了，对于套餐的价格，商品服务是给出一个最终价格呢？还是给出各个单品的价格，然后让调用方自己算最终价格呢？我们知道，套餐的价格不是各个单品价格累加的结果，它包含了一定的优惠，如果它的价格由服务调用方来算，这会导致商品的部分业务规则游离于服务外面，破坏了商品服务的功能完整性。

在实践中，有些服务只是存储基础数据，然后提供简单的增删改查功能，这样一来，服务只是一个简单的 DAO，变成了数据访问通道。这样的服务，它的价值就很有限，也容易被服务调用方质疑。因此，我们要尽可能在服务内部封装完整的业务规则，对外提供完整的业务语义，最大程度地简化服务的使用。

所以，当你在划分服务边界时，要保证服务数据完整、功能全面，这样才能支撑一个完整的业务领域。

其次，是服务的一致性原则

也就是说，服务的数据和职责要一致，谁拥有信息，谁就负责提供相应的功能。

服务内部的业务逻辑要尽量依赖内部数据，而不是接口输入的数据，否则会造成数据和业务规则的脱节（一个在外面，一个在里面），如果服务对外部的依赖性很强，就无法提供稳定的能力了。

很多时候，我们对一个功能到底划分到哪个服务，有很大的争议。这时，我们可以结合这个功能所依赖的数据来判断，如果功能所需要的大部分数据都存储在 A 服务里，那当然由 A 服务来提供接口比较合适，这样接口输入的数据比较少，不但简化了服务对外部的依赖，同时也降低了接口调用的成本。

给你举个例子，在订单小票上，我们经常能看到一些优惠信息，比如说商品原价是多少，其中因为满减优惠了多少，因为商品特价减免了多少。这个优惠计算的结果是订单的一部分，毫无疑问，它需要保存在订单服务里。

但这个订单的优惠计算过程，却不是由订单服务来负责，而是由独立的促销服务负责的。因为优惠计算所需要的优惠规则是在促销服务里定义的，促销服务可以在内部拿到所有的优惠规则，然后完成整个优惠计算。

否则，如果是由订单服务负责优惠计算，订单服务的调用者就需要在接口中提供完整的促销规则，不但调用成本高，而且外部促销规则的改变会影响订单服务的内部实现。

所以在这里，促销服务负责促销规则的维护，以及对应的优惠计算功能；订单服务负责优惠结果数据落地，以及后续的查询功能。这样，每个服务存储的数据和对外提供的功能是一致的。

最后一个，是正交原则

既然是基础服务，它们就处于调用链的底层，服务之间不会有任何的调用关系，也就是说基础服务相互之间是正交的。比如说会员服务和商品服务，它们代表不同维度的基础业务域，彼此之间不会有调用关系。

正交还有另外一种情况：服务之间有数据的依赖关系，但没有接口的调用关系。

比如说，订单明细里包含商品 ID 信息，但订单服务内部不会调用商品服务来获取商品详情。如果页面需要展示订单的商品详情，针对这个具体的业务场景，我们可以在上层的聚合服务里，通过聚合订单服务和商品服务来实现。

总结

可复用是架构设计的一个重要目标，今天我们对复用进行了梳理，包括复用有哪些形式，以及它们有哪些价值，相信你现在对复用已经有了一个整体的认识。业务上的复用比纯粹的技术复用有更高的价值，我们要尽量往这个方向上靠。

在实践中，落地基础服务是实现业务复用的有效方式，而基础服务边界的划分，它有科学的成分，但更多的是一种艺术，这里我提供了几个实用的划分原则，你可以在工作中结合实际情况，灵活地运用它们。

在专栏的下一讲，我会通过一个具体的订单服务例子，来帮助你更好地落地基础服务。

最后，给你留一道思考题：我们在落地服务时，有时会冗余存储其它服务的数据，你对这个有什么看法呢？

欢迎你在留言区与大家分享你的答案，如果你在学习和实践的过程中，有什么问题或者思考，也欢迎给我留言，我们一起讨论。感谢阅读，我们下期再见。

08 | 可复用架构案例（一）：如何设计一个基础服务？

你好，我是王庆友。

在上一讲中，我提到过，在架构设计中，要实现业务上的复用，一个比较可行的做法是，把各个基础业务封装成共享服务，供上层所有应用调用。所以今天，我就来和你聊一聊，如何从头开始，落地这样一个典型的共享服务。

我们知道，落地一个微服务其实并不困难，但要实现一个能够高度复用的共享服务并不容易，在落地过程中，经常会有一系列的问题困扰着我们。

我们事先对服务的边界没有进行很好的划分，结果在落地的过程中，大家反复争论具体功能的归属。

由于对业务的了解不够深入，我们要么设计不足，导致同一个服务有很多版本；要么服务过度设计，实现了一堆永远用不上的功能。

对于落地一个共享服务来说，服务边界的划分和功能的抽象设计是核心。服务边界确定了这个服务应该“做什么”，抽象设计确定了这个服务应该“怎么做”。

接下来，我就以一个实际的订单服务例子，为你详细讲解一下要如何重点解决这两个问题。这样你可以通过具体的案例，去深入地理解如何落地共享服务，实现业务能力的复用。

订单业务架构

不同企业的订单业务是不一样的，所以这里我先介绍下这个订单的业务场景。

这是个 O2O（Online To Offline，线上到线下）的交易业务，订单的来源有两个，一个是自有小程序或 App 过来的订单，还有一个是外卖平台过来的订单，然后这些线上的订单会同步到门店的收银系统进行接单和进一步处理。这里我放了一张订单的业务架构图，你可以到文稿中看下：

在这里，订单服务是和 4 个应用直接打交道的：

小程序服务端调用订单服务落地自有线上订单；

外卖同步程序接收三方外卖平台的订单，然后调用订单服务落地订单；

POS 同步程序通过订单服务拉取订单，并推送给商户内部的收银系统；

最后还有一个订单管理后台，通过订单服务查询和修改订单。

OK，接下来，我们就具体看下，如何从头开始落地这个订单服务。

订单服务边界划分

首先，我们要确定这个服务的边界，这是进行服务内部设计的前提。划分边界时，你需要对相关的业务场景有充分了解，并且在一定程度上，能够预测潜在的需求。在上一讲，我也和你分享了划分边界一些比较实用的原则和做法，你可以对照学习一下。

根据业务场景的分析，这个订单服务需要负责三个方面的功能。

基本信息管理

首先是订单基本信息管理，主要提供订单基础信息的增删改查功能，包括下单用户、下单商品、收货人、收货地址、收货时间、堂食或外卖、订单状态、取餐码等。

另外，你需要注意的是，这里有多个下单渠道，除了通用的订单信息，每个渠道还有特定的渠道相关信息，比如堂食的订单要有取餐码、外卖的订单要有收货人和收货地址等等，这个都需要在我们的数据模型里给出定义。

订单优惠管理

然后是订单优惠管理功能，这对应的是订单的小票信息，从最开始的商品金额，到最后需要用户实际支付的金额，中间会有一系列的折扣和减免，这些都是属于订单信息的一部分。这些信息我们需要展示给用户看，如果后续要进行订单成本的分摊，也需要用到它。

订单生命周期管理

最后是订单的生命周期管理功能，主要负责管理订单的状态变化。我们知道，从不同下单渠道过来的订单，它的状态变化过程是不一样的；不同行业的订单，它的状态变化过程也是不同的，所以订单服务的状态要做到通用，能够支持各种可能的状态定义和状态转换过程。这个也是订单服务设计的难点，我在后面会重点介绍。

好了，现在我们已经给出了订单服务的功能。为了更好地定义边界，在实践中，你还需要澄清哪些功能不属于服务，这样可以避免后续的很多争论。所以在这里，我会进一步给出订单服务不包括的功能，你在划分自己的服务边界时最好也能够明确给出。

第一，作为基础服务，订单服务不主动调用其他服务。

比如说，你想了解订单的用户详情、商品详情等等，这应该由上层应用通过调用相应的服务来实现，然后和订单信息组装在一起，而不是在订单服务内部直接调用其他服务，否则会导致基础服务之间相互依赖，职责模糊。

如果说这个信息整合的场景非常通用，我们可以创建一个在基础服务之上的聚合服务来实现，把订单信息、用户信息、商品信息整合在一起。

第二，订单服务不负责和第三方系统的集成。

在这里，订单需要在我们的订单服务和三方外卖平台，以及收银系统之间进行同步，这些同步功能都是针对第三方系统定制的，不具有通用性。而我们的订单服务作为基础服务，需要具备通用性，因此这些和外部系统对接的功能不会在订单服务的内部实现，而是由额外的同步程序实现。

小提示：这些同步程序可以主动调用订单服务，然后再和第三方对接，如果想实时获取订单信息的变化，同步程序可以订阅订单服务的消息通知，第一时间了解订单变化。

第三，订单服务不提供优惠计算或成本分摊逻辑。

订单服务不负责具体的优惠计算，只提供优惠结果的存储和查询，用于还原订单的费用组成。优惠的具体计算过程一般由专门的促销系统负责，成本的分摊一般由后续的财务系统负责。这个我们在上一讲中已经说过，这里就不详细解释了。

最后，该服务不提供履单详情，不负责详细物流信息的存储。

比如说，订单已经发送至上海、订单已经到达某某快递站等等这些信息，订单服务不负责提供这些详细信息，这些都是属于后续履单系统的职责。订单服务可以存储一些外部系统的单据号码，比如配送单号，这样能方便上层应用通过订单记录和配送系统进行关联，获取配送的详细信息。但订单服务只负责存储，不负责数据的进一步解释。

到这里，你可以看到，通过从正反两个方面说明订单服务的职责，我们就得到了一个边界很清晰、职责很聚焦的订单服务边界，所有人对它的职责认识是一致的，尽可能地避免了后续的争论。

订单服务内部设计

好，确定了这个订单服务要做什么之后，接下来，我们要解决的就是服务内部怎么做的问题了。

作为共享服务，我们要保证订单服务功能上的通用性，就需要同时对内部数据模型和外部接口进行良好的抽象设计。

订单状态通用化

对于数据模型来说，订单要存储哪些信息，已经比较明确了，具体你可以看下这个图。

但对于如何管理订单的状态，情况就比较复杂了。

我们知道，如果针对一个具体的项目，无论它的订单状态有多么的复杂，我们都可以事先精确地定义出来。但不同的行业甚至不同的企业，他们对于订单状态管理都是不一样的，订单服务作为一个共享服务，它必须要满足不同项目的订单状态管理。所以对于如何解决这个问题，这里我有两个思路供你参考。

一个是开放订单状态定义。

在这里，订单服务事先不限定订单有哪些状态，每个项目都可以自己定义有哪些订单状态。服务的调用方可以在接口里传递任意的状态值；订单服务只负责保存状态数据，不负责解释具体的状态，也不负责任何的规则校验，它允许订单从一个状态转换为其他任意的状态。

这样的设计，在理论上可以满足各种状态的定义，满足各种状态之间的变化，但这样做其实有很大的问题。在这里，订单状态是完全由外部负责管理的，上层应用的负担会很重，不但要负责定义有哪些状态，而且还要维护状态的转换规则，一不小心，订单可能从状态 A 非法地变成状态 B，导致业务出问题。

另外一个是应用和服务共同管理状态。

对于订单状态管理，应用和服务各自承担一部分职责，我们看下具体如何实现。

我们知道，无论订单的状态变化是如何的复杂，我们总是可以定义一个订单有哪些基本的状态，包括这些基本状态之间是如何变化的。比如，订单一开始都是用户下单后待支付，支付完成后变成一个有效的订单，然后由商家进行接单，制作完成后进行发货配送等等，订单最终的状态要么是完成，要么是取消。

这些订单的基本状态，我们称之为“主状态”，它们由订单服务负责定义，包括这些主状态之间的转换规则，比如已完成的订单不能变为已取消的订单。主状态的数量是比较有限的，状态之间的变化关系也是比较明确的。

这个主状态，我们对大量现有的业务场景进行总结和抽象，是完全可以定义出来的。在这个订单服务例子里，我们定义了如下图所示的订单状态机，包括有哪些主状态，以及它们的转化关系。

订单除了“主状态”，还有“子状态”。

比如，一个订单处于配送中，实际情况可能是“仓库已发货”，“货已到配送站”，或者是“快递员正在送货中”等等，那么在这些情况中，订单的主状态都是“配送中”，它的子状态就是细化的这几种情况。子状态有哪些具体的取值，不同的项目是不一样的，这个就开放给各个应用来定义。

所以，订单服务数据模型里有两个字段，其中的主状态由订单服务负责管理，包括主状态之间的变化规则；而子状态由上层应用来定义，管理子状态的变化规则，比如一个配送中的订单，它的子状态可以由“仓库已发货”，变为“快递员正在送货中”。

现在，我们就可以总结下这两种订单状态的设计思路。

第一种方案，我们不对订单状态进行管理，而是把订单的状态作为一个简单的属性存储，只支持订单状态简单的增删改查功能。我们知道，订单状态是订单业务规则的核心体现，这样的订单服务是没有灵魂的，也失去了大部分业务复用的价值。

第二种方案，应用和服务共同管理订单的状态，订单服务抓大放小，通过主状态管理把控住了订单的核心业务规则，同时把子状态开放给应用进行管理，为具体的业务场景提供了灵活性。通过主状态和子状态的结合，订单服务就满足了不同行业、不同企业的订单状态管理需求。

订单服务接口定义

说完了订单的状态管理，接下来，我们从调用方怎么使用服务的角度，来看下订单服务外部接口是如何设计的。

外部系统和服务的交互有两种方式，包括同步的服务接口调用和异步的消息通知。

首先是同步的服务接口调用。

为了方便外部调用方，我们在服务接口命名时，一定要规范和统一，接口名字要能够望文生义，方便调用者快速找到所需要的接口。并且，我们还要提供接口具体的请求和响应样例帮助说明。

具体的接口设计规范，我就不具体展开了，每个公司都要有明确的规范要求，这里我就说下常见的查询接口是如何设计的。

一个订单有很多字段，每次调用方要查询的信息可能都不相同，不同字段之间的组合方式有很多，我们不可能一一支持。

那么，我们怎么设计查询接口，来满足各种场景需求呢? 一般来说，我们可以根据返回字段数量的不同，提供三个不同粒度的查询接口来满足多样化的需求。

第一个是粗粒度接口，只返回订单最基本的 7-8 个字段，比如订单编号、订单状态、订单金额、下单用户、下单时间等等；第二个是中粒度接口，返回订单比较常用的十几个字段；第三个是细粒度接口，返回订单的详细信息。

这样，不同的查询需求，就可以根据要返回信息的详细程度，来选择合适的接口，通过这种方式，我们兼顾了要定义的接口数量和查询的性能。

其次是异步的消息通知。

订单服务除了提供同步的接口调用，还针对每次订单信息的变化，提供异步的消息通知，感兴趣的外部系统可以通过接收消息，第一时间感知订单的变化。

按照消息详细程度的不同，订单消息可以分为“胖消息”和“瘦消息”。

顾名思义，胖消息包含了尽可能多的字段，但传输效率低；瘦消息只包含最基本的字段，传输效率高。如果外部系统需要更多的信息，它们可以通过进一步调用订单服务的接口来获取。

在这个订单服务的例子里，如果是订单状态的变化，我们只需提供订单号、变化前后的状态即可，因此主要以瘦消息为主；如果是新订单的创建，由于订单的字段比较多，所以使用胖消息，避免外部系统进一步调用订单服务接口。你在实践中，可以根据实际情况，在消息的数据量和消费者处理消息的复杂度之间做平衡。

前面我们说了，订单服务不会主动调用外部系统的接口，这里的异步消息通知，就可以很好地保证外部系统及时感知订单的任何变化，同时避免订单服务和外部系统直接耦合。

总结

要想打造一个可高度复用的共享服务，你需要掌握最核心的两点：清晰的边界划分、内部的抽象设计。

今天，我通过一个实际的订单服务例子，帮助你理解如何清晰地定义服务的边界，以及如何通过抽象设计保证服务的通用性。你在实践中，一定要深入分析业务场景，识别真正的挑战在哪里，避免设计的简单化或过度复杂化。

通过今天的讲解，相信你在前一篇理论内容的基础上，对如何打造一个共享服务有了更深入的体会，希望你在工作中能不断地去实践，真正掌握这些技能。

最后，给你留一道思考题：在落地共享服务的时候，你碰到过哪些挑战，都是怎么解决的？

欢迎你在留言区与大家分享你的答案，如果你在学习和实践的过程中，有什么问题或者思考，也欢迎给我留言，我们一起讨论。感谢阅读，我们下期再见。

09 | 可复用架构案例（二）：如何对现有系统做微服务改造？

你好，我是王庆友。在上一讲中，我以订单服务为例，和你一起讨论了如何从头开始，设计一个共享服务。今天我们再来聊一聊：如何对现有系统做微服务化改造。

很多早期的互联网公司都有巨大的单体应用，底层的数据表集中放在一个数据库里，这些表加起来可能有几百张。对于这样的应用系统和数据库，我们往往需要对它们进行拆分，通过微服务化改造，保证系统能够不断地扩展和复用。

相比从头开始落地服务，对现有系统做微服务化改造，这会面临更多的挑战。

首先，应用和数据表紧密耦合在一起，代码模块和表是多对多的依赖关系。一个模块会访问多张表，多个模块也会对同一张表进行访问，而且由于表都在一个数据库里，开发人员往往会随意对表做关联，有时候甚至 Join 5~6 张表以上。这样，代码模块和表之间的关系是剪不断，理还乱，我们很难清晰地划分代码和数据表的边界，也就很难把它们封装成独立的微服务。

还有，系统现在已经在运行了，我们的改造不能影响业务的稳定性。那微服务落地后，现有的系统要怎么对接微服务，数据要怎么迁移，才能保证系统的平滑过渡呢？

所以，要想应对这些挑战，一方面，我们要保证比较合理的服务设计，才能达到优化系统架构的目的；另一方面，我们要做到整个过程对现有系统的影响比较小，才能达到系统改造顺利落地的目的。

接下来，我就以 1 号店库存服务化改造为例，让你深入理解，我们是如何把库存相关的功能和数据表，从现有系统里剥离出来，最终构建独立的库存服务，并实现和业务系统平滑对接的。

改造背景和目标

我们先来看下这次架构改造的背景和目标。

1 号店作为一个网上超市，售卖的商品种类有数十万个，包括 1 号店自营和第三方商家的商品。由于历史原因，所有商品相关的表都存在产品库里面，这里面有产品的表（产品、分类、品牌、组合关系、属性等）、商品 SKU 的表、商家和供应商的表、库存和价格的表等等，这些表加起来，数量超过了上百张。

我们知道，商品是电商业务的核心，几乎所有的前后台系统都需要访问这个产品库，而这些系统的开发人员，早期的时候，只关心如何实现业务功能，对这些表的访问是怎么方便怎么来，有些 SQL 语句会对大量的表做 Join 关联。所以说，虽然系统是类似分布式的，但数据库是集中式的，如下图所示：

这样的方式，就给系统的维护带来了一系列的问题。

从应用方面来说，各个系统功能重复建设，比如很多系统都会直接访问库存相关的表，类似的库存逻辑散布在很多地方；另外，如果修改了库存表的某个字段，这些系统同时会受影响，正所谓牵一发而动全身。

从数据库方面来说，数据库的可用性是比较差的，如果某个系统有慢查询，它就很可能拖垮整个产品数据库，导致它不可用；还有，这么多系统同时访问产品库，数据库的连接数也经常不够用。

所以，我们这次架构改造的目标，首先是对这个大数据库按照业务维度进行垂直拆分，比如分成产品数据库、库存数据库、价格数据库等等；然后基于这些拆分后的库，构建微服务，以接口的方式来支持数据库表的访问；最后将各个业务系统统一接入微服务，最终完成整个商品体系的微服务化改造。

微服务改造过程

你可以看到，这里涉及了多个微服务，如果同时进行服务化改造的话，牵扯太大，很难落地。于是，我们选择从库存微服务开始。一方面，库存的业务很重要，库存的规则也比较复杂，如果我们能够对库存逻辑进行优化，这会带来明显的业务价值；另一方面，电商的库存概念相对独立，涉及的表也比较少，我们可以相对容易地把它从现有体系中剥离出来。

整个改造过程，从确定库存相关的表开始，到最后把库存表从产品库迁移出来，落到单独的库存数据库为止，一共分为两个阶段，每个阶段包含了 3 个步骤，具体如下图所示：

准备阶段：这个阶段为微服务改造做好前期的准备工作，具体步骤包括了圈表、收集 SQL 和 SQL 拆分。

实施阶段：这个阶段实际落地微服务，具体步骤包括微服务开发、服务接入和数据库独立。

通过这些良好定义的步骤，我们就很好地保证了整个库存微服务改造的有序和可控。接下来，我就具体说明下改造的各个步骤，包括哪些人负责哪些事情、具体的挑战在什么地方，这样，你可以深入地理解整个改造过程。

准备阶段

准备阶段的第一步，就是圈表。产品数据库有 100 多张表，圈表就是用来确定库存微服务具体包含哪些表，也就是确定服务的数据模型。在确定了表以后，库存微服务就负责这些表的访问，当然，库存微服务也不会访问其它的表，而业务系统后续将通过库存微服务的接口，实现对这些表的访问。

圈表是微服务改造中比较有挑战性的地方，它实际上对应了服务的边界划分。只是针对老系统做服务化改造的时候，我们更多的是从数据库表的角度来考虑划分，这样更好落地。

针对库存微服务来说，我们要求圈定的表，一方面要满足所有的库存访问需求，这些表之间关系紧密，和其它的表关联不大；另一方面，这些表的数量不能太多，一般不超过十几张。这样，我们既容易拆分数据库，又能控制服务的粒度，保证功能聚焦。

在这个例子中，由于库存的概念比较独立，圈表相对比较容易，一共有 15 张表和库存直接相关，包括自营库存表 (这里有分表，实际是 12 张)、商家虚拟库存表、活动库存表和库存共享表，这些库存表之间是紧密相关的，它们一起决定了前台用户能看到的可用库存数量。

这些库存相关的表都有商品 ID 字段，和商品基本信息表关联，我们知道，库存数量的计算不依赖于商品的具体信息。所以，这些库存表和其它表的关系比较弱，这样我们就可以比较清晰地实现库存表和其它表的切分，简化了库存服务的落地。

在微服务改造中，确定哪些表属于这个服务，会直接影响后续的所有改造工作，这需要有经验的业务架构师和数据架构师参与进来，通过深入地分析现有的业务场景和表的关系，才能对库表进行合理的划分。

所以，你可以发现，对现有系统的改造，服务的边界划分主要是从圈表入手的，而不是从一个服务应该有哪些功能入手的，这一点和新服务设计是有所不同的。这有两方面原因：

一方面，如果确定了服务包含哪些表，也就大致确定了服务有哪些功能，而表是现成的，它比业务功能要直观很多，所以从表入手比较高效；

另一方面，如果从表入手，构造的服务和表是对应的，服务包含的是完整的表，不会产生一个表的一部分字段属于库存服务，而另一部分字段属于别的服务的情况，避免表字段的拆分带来额外的复杂性。

值得注意的是，因为这是对现有系统的改造，为了避免一下子引入太多变化，我们先不对库存的表结构进行调整，表结构的优化可以放在服务的升级版里做，这样对业务系统的影响也最小。

第二步是收集 SQL。在确定了哪些表属于库存服务后，我们会收集所有业务系统访问这些表的 SQL 语句，包括它的业务场景说明、访问频率等等。库存微服务后续就针对这些 SQL 进行封装，提供相应的接口给业务系统使用。

这里，服务开发团队负责提供 SQL 收集的 Excel 模板，各业务系统开发团队负责收集具体的 SQL。

第三步是拆分 SQL。对于收集过来的 SQL 语句，有些 SQL 不仅仅访问圈定的这几张库存表，还会和产品库中的其他表进行关联。

比如说，商品详情页需要展示商品详情，它会发起 SQL 查询商品基本信息表和库存表，一次性获取商品的基本信息和库存数量。针对这种情况，我们就需要把查询语句拆分为两条 SQL，先查询商品表获取商品基本信息，再查询库存表获取库存数量。

对于这样的 SQL 语句，我们就要求各个业务团队先进行拆分，保证最后提供给服务开发团队的 SQL，只包含访问库存的相关表。通过 SQL 拆分，我们切断了库存表和其他表的直接联系，等后面微服务落地后，业务系统就可以通过接入微服务，完成现有 SQL 的替换。

SQL 拆分，会涉及一定的业务系统改造，这部分工作主要由各个研发团队负责，一般情况下，性能可能会受些影响，但问题不是很大。

实施阶段

完成了圈表、SQL 收集和拆分以后，接下来，我们就进入了服务实际落地的阶段。

第四步是构建库存微服务。这里面包括了接口设计、代码开发、功能测试等步骤，服务开发团队会对业务方提供的 SQL 进行梳理，然后对接口做一定的通用化设计，避免为每个 SQL 定制一个单独的接口，以此保证服务的复用能力。

这部分工作由微服务开发团队负责，第一版的服务主要是做好接口设计，聚焦业务功能，以保证服务能够落地，业务系统能够顺利对接为目标。将来，服务可以持续迭代，内部做各种技术性优化，只要服务的接口保持不变，就不会影响业务系统。

第五步是接入库存微服务。库存服务经过功能和性能验证以后，会由各个业务开发团队逐步接入，替换原来的 SQL 语句。这部分工作主要由业务研发团队负责，难度不大，但需要耗费比较多的时间。

最后一步是数据库独立。当服务接入完成，所有的 SQL 语句都被替换后，业务系统已经不会直接访问这些库存的表。这时，我们就可以把库存相关的表，从原来的产品库中迁移出来，部署成为一个物理上独立的数据库。业务系统是通过服务来访问数据库的，因此，这个数据迁移对于业务系统来说是透明的，业务团队甚至都不用关心这些表的新位置。

通过库存表独立成库，我们可以从物理层面，切断业务团队对这些表的依赖，同时，也可以大幅度降低产品库的压力，特别是大促的时候，库存读写压力是非常大的，数据库独立也为库存服务后续的技术优化打下了基础。

这部分工作主要由微服务开发团队和 DBA 一起配合完成，主要是要避免业务系统还有遗漏的 SQL 语句，避免它们还在直接访问库存的表。我们可以在迁库前，通过代码扫描做好相应的检查工作。

改造完成后的库存微服务架构如下图所示，库存微服务一共包含了 15 张表，对外有 30 多个接口，几十个业务系统接入库存服务。平时，库存服务会部署 50 个实例，大促时会部署更多，我们很容易通过加机器的方式，实现库存服务的水平扩展。

微服务改造小结

到这里，我们的库存微服务就改造完成了，整个改造大概持续了 3 个月，主要是对接的工作比较耗时。

从前面的步骤中，你可以看到，除了做好库存服务本身的设计开发工作，相关团队之间的配合也是非常重要的。

在整个改造过程中，有很多团队之间沟通和确认的环节。比如说，服务开发团队圈定表以后，需要和业务开发团队一起确认，保证圈表的合理性；在业务团队拆分 SQL 的过程中，服务开发团队需要介入进去，帮助解决拆分时带来的性能和一致性问题；在服务接口设计和接入过程中，服务的接口可能需要重新调整，也可能有新的 SQL 进来，双方需要及时沟通，相互配合。

这些都是纯技术层面的问题，值得一提的是，系统改造不会产生直接的业务价值，对于业务开发团队来说，他们往往还需要承担大量新需求的开发工作。所以，从项目推进的角度来看，这种核心服务的改造，很多时候都是技术一把手工程。在库存微服务改造过程中，我们也是老板高度重视，大家事先定好时间计划，每周 Review 进度，协调各个团队工作的优先级，确保改造的顺利落地。

以上就是库存微服务改造的例子。1 号店的系统从 08 年就开始建设了，由于历史原因，形成了几个典型的大库，比如产品库、用户库等等，我们通过类似的微服务改造，逐步把这些大库拆分开，构建了一系列的基础服务，如订单服务、用户服务、产品服务、库存服务、价格服务等等。而且通过这些微服务化改造，我们同时提升了业务的复用性和系统的稳定性。

最后，我在这里放了一张 1 号店的总体系统架构图，你可以深入看下，一个历史包袱很重的系统，它是如何经过服务化改造，最终变成一个能够高度复用和扩展的平台的。

总结

好了，下面我总结一下今天所讲的内容。

基于现有系统进行改造和全新的服务设计是有所不同的，我们不能追求理想化和一步到位，而是要考虑到系统的平滑过渡，先实现微服务的顺利落地，后续再考虑各种优化。

今天，我通过 1 号店库存微服务改造的例子，给你提供了一种可行的微服务落地套路，让你可以顺利地完成老系统的架构升级。

相信通过今天的分享，你对现有系统如何进行微服务化改造有了更深入的理解，希望你在实践中也能灵活运用。

最后，给你留一道思考题：你在做现有系统服务化改造的过程中，具体碰到了哪些挑战，你又是如何克服的呢？

我是王庆友，欢迎你在留言区与大家分享你的思考，我们一起讨论。如果觉得有收获，也欢迎你把这篇文章分享给你的朋友。感谢阅读，我们下期再见。

10 | 可复用架构案例（三）：中台是如何炼成的？

你好，我是王庆友。

在第 8 讲中，我通过一个实际的订单服务案例，和你介绍了如何设计一个基础服务。今天，我就继续带你了解，如何在实际的业务场景中，通过一步步的架构升级，最后落地一个中台，实现企业级能力的复用。

通过前面的介绍，我们已经很清楚了共享服务和中台的价值，但在实践中，要不要对系统做这样的升级，我们还需要结合业务来判断，比如说：

业务上有什么重大变化，导致当前系统的弊端已经很明显，不能适应业务发展了呢？

架构改造时，如何在业务、系统、资源三者之间做好平衡，对系统进行分步式的改造呢？

我们知道，架构没有最好，只有最合适的。随着业务的发展，系统需要不断地升级，这是一个螺旋式上升的过程，如何结合当前的业务发展阶段，适时地推进架构改造，并能比较接地气地落地，是我们要追求的目标。

接下来，我以实际的订单系统改造为例，结合订单业务的发展和系统的痛点，为你介绍，如何推进架构从单体到共享服务、再到中台的改造过程，保证系统能够不断适配业务的升级。

先说下项目背景。公司作为供应商，为大型餐饮连锁企业打造 O2O 交易平台，包括三方聚合外卖、自有小程序、App 点餐，这些线上用户的订单最终会落到门店的收银系统，由门店进行履单。

公司的业务发展有一个变化过程，一开始只提供聚合外卖服务，后来进一步提供小程序 /App 下单服务。你可以发现，整个订单处理的架构也是随着业务的变化而不断演变的，下面我就为你一一介绍。

聚合外卖订单架构

一开始，我们提供的是聚合外卖服务，相应地，系统整体架构如下图所示：

这里一共有三个系统，分别是三方外卖平台、门店收银系统以及外卖系统。其中，外卖系统是我们开发的，其他两个都是我们要对接的外部系统，接下来，我说下系统具体的交互过程。

首先，用户在三方外卖平台（如美团、饿了么）下单；然后，我们的外卖系统通过外卖平台的 API 拉取用户的订单，把订单落到本地数据库；最后，门店的收银系统访问外卖系统提供的接口获取订单，在门店内部完成履单。当然，门店履单后，收银系统会反过来同步订单状态给外卖系统，外卖系统再同步订单状态到第三方外卖平台。

你可以看到，这里的外卖系统是一个单体应用，内部包含外卖同步接口和 POS 接口两个模块。其中，外卖同步接口负责和第三方外卖平台对接，它主要是针对不同的外卖平台做接口适配；而 POS 接口负责和门店的收银系统对接。这两个模块都是使用同一个外卖订单数据库。

从数据模型上看，系统的订单模型也是完全按照外卖订单的需求设计的，订单状态管理也相对比较简单，因为这些订单都是用户在第三方外卖平台已经完成支付的。所以，我们的外卖系统，主要是负责管理门店履单过程中带来的订单状态变化。

从系统架构上看，外卖系统从外卖平台接单，然后把订单推送给后面的收银系统，只需要一个应用、一个数据库、两套接口就可以支持，使用单体架构就能很好地满足外卖的接单需求。

小程序下单架构

接下来，随着公司业务的升级，除了提供聚合外卖服务之外，公司还提供自有小程序的下单服务。这样，消费者既可以在三方外卖平台下单，也可以在品牌自有的小程序里下单。

不同于三方外卖订单，小程序下单平台是一个完整的业务，它包括小程序用户注册、商品和菜单浏览、商品加购物车、在线支付等等。相应地，这里会有多个基础服务对应具体业务的处理。比如，商品服务提供前台的商品浏览功能，支付服务提供用户的支付功能，这些基础服务都是由独立的小程序服务端负责整合，然后提供接口供小程序前端访问。

当用户在小程序提交订单后，小程序前端会调用服务端的下单接口，然后服务端调用订单服务，在小程序的订单库里落地订单。现在我们已经完成了前台用户的下单，但后台的订单履行怎么处理呢？这里有两种选择：

小程序订单和外卖订单的处理类似，收银系统除了对接外卖系统，同时也对接小程序的订单服务。但这样一来，收银系统需要同时对接两套订单接口，它需要做大的改造。由于这是第三方的系统，我们在实践中很难落地。

我们把小程序订单当作一个特殊的外卖渠道，把小程序订单推送到外卖订单库里，最终还是由外卖系统来对接收银系统，也就是相当于小程序订单直接借用了外卖订单的履单通道。

当时由于项目上线的时间比较紧急，同时从系统稳定性的角度出发，避免对收银系统做大的改造，我们采用了第二种方式，小程序的订单处理就嫁接在已有的外卖系统上，整个系统架构如下图所示：

你可以看到，小程序下单平台和外卖系统相对独立，同时为了更好地解耦，小程序订单服务和外卖系统之间是通过消息系统同步订单数据的。

这个方案是一个比较务实的选择，通过复用外卖订单的履单通路，我们也实现了小程序订单的闭环处理。表面上看，我们节省了重新搭建系统的成本，也快速落地了小程序交易这条新业务线。

但这样的架构实际上是一种妥协，在后续的系统运行过程中，给我们带来了很多问题：

这里有两套订单系统，一套针对小程序订单，一套针对外卖订单。我们知道，两者的字段属性和订单状态定义都有不同的地方，我们把小程序的订单硬生生地套在了外卖订单的模型里，这样限制了小程序订单能力的扩展。

小程序订单处理链路过长，从小程序服务端 -> 订单服务 -> 小程序订单数据库 -> 消息系统 -> 外卖同步接口 -> 外卖订单数据库 -> POS 接口 -> 收银系统，一共包含了 8 个处理环节，系统整体的性能和可用性都存在很大问题。比如，取餐码已经从收银系统同步给了外卖系统，但由于消息队列堵塞，外卖系统不能及时同步给小程序的订单服务，这样导致了小程序用户不能及时地看到取餐码。

为了使两套订单系统解耦，我们使用了消息队列在两个库之间同步订单数据，这降低了系统整体的稳定性。实践中，也发生过多起消息队列故障导致的线上事故。

你可以发现，出现这些问题的根源是我们把小程序订单硬塞给外卖系统，一方面订单数据模型不匹配，另一方面由于这是两个系统的简单拼接，导致系统调用链路很长，影响了业务的扩展和系统的稳定性。

那有没有更好的办法，能够把这两个系统有机地结合起来呢？接下来，我们就来看下，如何通过一个统一的订单服务对两个系统进行深度的融合，从而灵活地支持多种订单业务。

统一订单服务架构

这里，我们把小程序订单服务提升为统一共享的订单服务，由它来落地所有类型的订单。对于这个统一的订单服务来说，外卖订单、小程序订单，或者是其他的新订单，都是它的下单来源，所有订单汇总在订单服务里，然后统一提供给收银系统进行履单。具体架构如下图所示：

你可以看到，系统架构经过调整，有两个大的变化：

原来外卖和小程序各自有一个订单库，现在合并为了一个订单库，由这个订单服务统一对外提供订单数据的访问和状态管理。

原来外卖系统的两个模块“外卖同步接口”和“POS 接口”，升级为了两个独立的应用。外卖同步接口变成外卖同步服务，对接外卖平台；POS 接口变成 POS 服务，对接门店的收银系统。它们都是通过统一订单服务存取订单数据。

经过升级，新的架构具备了明显的层次结构，自上而下分为三层：首先是各个渠道端，包括三方外卖平台、小程序前端和 POS 收银系统；然后，每个端都有相应的服务端来对接，比如外卖同步服务对接外卖平台、小程序服务端对接小程序、POS 服务对接收银系统；最后，这些服务端都统一调用底层的订单服务。

在这个架构里，如果我们要增加新的下单渠道，就非常方便，比如要支持 App 下单，我们提供 App 服务端即可；要新增加后台履单方式也非常方便，比如对于新的电子卡券类订单，它不需要经过收银系统，可以直接由企业的 OMS 系统（Order Management System，订单管理系统）处理，要实现这样的业务，我们只需新增加一个和 OMS 系统的适配应用就可以了。所以，这里就不仅仅是一个外卖订单和小程序订单的处理平台，而是升级成了一个完整的全渠道交易平台。

同时，订单处理的链路大大缩短，从小程序服务端 -> 订单服务 -> 订单数据库 -> POS 服务 -> 收银系统，只有 5 个节点，相比之前减少了 3 个，系统的可用性和端到端的性能得到了大幅度的提升。

最后，统一订单服务实现了统一的订单属性定义、统一的订单状态管理，以及订单数据的集中存储，这对后续的 BI 分析和数据中台建设非常有帮助。它们处理数据时，只需要从一个订单库拉取数据，解析一个订单数据模型就可以了。

中台架构

上面的统一订单服务整合了外卖和小程序的订单，并且为新的下单渠道预留扩展。按照同样的思路，我们可以构建统一的商品服务，同时满足外卖和小程序上商品的管理；可以构建统一的促销服务，同时支持线上和线下的促销活动；也可以构建统一的库存服务，实现线上和线下库存的同步和共享等等。

通过构建这样一系列的共享服务，我们就实现了各个渠道业务规则和业务数据的统一管理，最终我们落地了一个强大的业务中台，可以很方便地扩展各个业务，实现企业整体业务能力的复用。

最后，实际项目的中台架构如下图所示：

在这个架构中，前端有 3 个业务场景，分别是小程序点单、App 商城下单、外卖平台下单，每个业务场景都有相应的服务端负责对接。在各个服务端下面，还有一些辅助的应用，如购物车、秒杀、拼团等等。同时这里还有一个订单控制服务（Order Control Service，OCS），负责订单逻辑的编排以及前后台之间的状态同步，你可以把它看作是基础服务之上的聚合服务。

再底下就是核心的业务中台，它由 9 大服务中心组成，这些中心和商户内部系统进行对接。其中，商品中心和库存中心对接 ERP 系统，会员中心对接 CRM 系统，订单中心对接 POS 收银系统，这里的对接分别由对应的适配插件负责。

通过这个订单业务改造落地后的中台架构，你可以看到，中台由各个通用的基础服务构成，它是相对标准的；而插件是定制的，具体和每个企业的后台系统有关。这样，通过共享服务和中台，我们就把企业内部基础设施和线上业务场景有效地打通了，从系统架构的层面，为企业的全面数字化转型打下了良好的基础。

总结

今天，我从一个企业的订单业务变化出发，为你介绍了为什么要落地一个统一的订单服务，以及如何落地，并通过打造一系列类似的共享服务，逐步升级系统到中台架构。

相信通过这个实际案例，你进一步理解了如何通过共享服务和中台，实现业务能力的复用，并能根据公司的业务发展阶段，选择合适的时机、合适的架构，以接地气的方式对系统进行逐步改造。

最后，给你留一道思考题：目前你的公司有没有落地共享服务，它是怎么逐步演变过来的呢？

欢迎你在留言区与大家分享你的答案，如果你在学习和实践的过程中，有什么问题或者思考，也欢迎给我留言，我们一起讨论。感谢阅读，我们下期再见。

11 | 技术架构：作为开发，你真的了解系统吗？

你好，我是王庆友。从今天开始，我们就进入了技术架构模块，所以，这一讲，我想先跟你聊聊技术架构要解决什么问题。

对于开发人员来说，我们每天都在用技术。但你要知道，我们写的代码，其实只是系统的一小部分，我们了解的技术，也只是系统用到的一小部分。要深入掌握技术架构，我们就需要了解整体的系统。

面对一个复杂的系统，我想你可能经常会有以下困扰：

不清楚系统整体的处理过程，当系统出问题时，不知道如何有针对性地去排查问题。

系统设计时，经常忽视非业务性功能的需求，也不清楚如何实现这些目标，经常是付出惨痛的教训后，才去亡羊补牢。

不知你是否还记得，在第一讲“架构的本质”中，我已经说过，技术架构是从物理层面定义系统，并保障系统的稳定运行。那么今天，我会先分析下系统在物理上由哪些部分组成，让你可以从全局的角度看一个系统；然后再和你一起讨论，技术架构会面临哪些软硬件的挑战，以及它都有哪些目标，让你能够深入地了解技术架构。

系统的物理模型

对于大部分开发人员来说，我们主要的工作是写业务相关的代码，保证业务逻辑正确、业务数据准确，然后，这些业务代码经过打包部署后，变成实际可运行的应用。但我们写的代码只是系统的冰山一角，为了保证应用能正常运行，我们需要从端到端系统的角度进行分析。

我们先看下一个系统的具体组成，这里我为你提供了一个简化的系统物理模型，你可以了解一个系统大致包含哪些部分。

从用户请求的处理过程来看，系统主要包括五大部分。

首先是接入系统，它负责接收用户的请求，然后把用户的请求分发到某个 Web 服务器进行处理，接入系统主要包括 DNS 域名解析、负载均衡、Web 服务器这些组件。

接下来，Web 服务器会把请求交给应用系统进行处理。一般来说，我们是基于某个开发框架来开发应用的，比如 Java 应用一般是基于 Spring MVC 框架。

这个时候，开发框架首先会介入请求的处理，比如对 HTTP 协议进行解析，然后根据请求的 URL 和业务参数，转给我们写的业务方法。接下来，我们的应用代码，会调用开发语言提供的库和各种第三方的库，比如 JDK 和 Log4j，一起完成业务逻辑处理。在这里，我们会把开发框架、应用代码，还有这些库打包在一起，组成一个应用系统，作为独立的进程在 Web 服务器中进行部署和运行。

到这里，整个系统要做的事情就完了吗？

还没有呢，在我们的应用系统底下，还有基础平台，它由好几个部分组成：首先是各个语言的运行时，比如说 JVM；然后是容器或虚拟机；下面还有操作系统；最底下就是硬件和网络。

接入系统、应用系统、基础平台就构成一个最简单的系统。

在大多数情况下，应用系统还要借助大量外部的中间件来实现功能和落地数据，比如数据库、缓存、消息队列，以及 RPC 通讯框架等等。这里，我统称它们为核心组件，它们也是系统不可缺少的一部分。

除此之外，还有大量周边的支撑系统在支持应用的正常运行，包括日志系统、配置系统，还有大量的运维系统，它们提供监控、安全、资源调度等功能，它们和核心组件的区别是，这些系统一般不参与实际的用户请求处理，但它们在背后默默保障系统的正常运行。

到这里，你可以发现，一个端到端的系统是非常复杂的，它包含了大量的软硬件。为了保障我们的应用代码能够正常运行，我们就需要保证这里的每个组件不出问题，否则一旦组件出问题，很可能就导致系统整体的不可用。

技术架构的挑战

应用代码怎么组织（比如模块划分和服务分层），那主要是业务架构的事，这部分在前面我们已经讨论过很多了；而技术架构的职责，首先是负责系统所有组件的技术选型，然后确保这些组件可以正常运行。

我们知道，系统是由硬件和软件组成的。接下来，我们就分别从软硬件的角度来看下，技术架构都会面临什么挑战，我们需要如何应对。

硬件的问题

硬件是一个系统最基础的部分，负责真正干活的，但它有两方面的问题。

首先是硬件的处理能力有限。 对于服务器来说，它的 CPU 频率、内存容量、磁盘速度等等都是有限的。虽然说按照摩尔定律，随着制造工艺的发展，大概每隔 18 个月，硬件的性能可以提升一倍，但还是赶不上快速增长的系统处理能力的要求，特别是目前许多互联网平台，面向的都是海量的 C 端用户，对系统处理能力的要求可以说是没有上限的。

从技术架构的角度，提升硬件的处理能力一般有两种方式。

Scale Up

也就是垂直扩展，简单地说就是通过升级硬件来提升处理能力。CPU 不够快，升级内核数量；内存不够多，升级容量；网络带宽不够，升级带宽。所以说，Scale Up 实际上是提升硬件的质量。

Scale Out

也就是水平扩展，通过增加机器数量来提升处理能力。一台机器不够，就增加到 2 台、4 台，以及更多，通过大量廉价设备的叠加，增强系统整体的处理能力。所以说，Scale Out 是提升硬件的数量。

垂直扩展是最简单的方式，对系统来说，它看到的是一个性能更强的组件，技术架构上不需要任何改造。如果碰到性能有问题，垂直扩展是我们的首选，但它有物理上的瓶颈或成本的问题。受硬件的物理限制，机器的性能是有天花板的；或者有时候，硬件超出了主流的配置，它的成本会指数级增长，导致我们无法承受。

水平扩展通过硬件数量弥补性能问题，理论上可以应对所有服务器处理能力不足的情况，并实现系统处理能力和硬件成本保持一个线性增长的关系。

但水平扩展对于系统来说，它看到的是多个组件，比如说多台 Web 服务器。如何有效地管理大量的机器，一方面，使得性能上可以实现类似 1+1=2 的效果；另一方面，要让系统各个部分能够有效地衔接起来，稳定地运行，这不是一件容易的事情。我们需要通过很复杂的技术架构设计来保障，比如说，通过额外的负载均衡，来支持多台 Web 服务器并行工作。

硬件的第二个问题是，硬件不是 100% 的可靠，它本身也会出问题。

比如说，服务器断电了，网络电缆被挖断了，甚至是各种自然灾害导致机房整体不可用。尤其是一个大型系统，服务器规模很大，网络很复杂，一旦某个节点出问题，整个系统都可能受影响，所以，机器数量变多，也放大了系统出故障的概率，导致系统整体的可用性变差。我们在做技术架构设计时，就要充分考虑各种硬件故障的可能性，做好应对方案。比如说针对自然灾害，系统做异地多机房部署。

软件的问题

接下来我们说下软件的问题，这里的软件，主要说的是各种中间件和系统级软件，它们配合我们的应用代码一起工作。

软件是硬件的延伸，它主要是解决硬件的各种问题，软件通过进一步封装，给系统带来了两大好处。

首先是弥补了硬件的缺陷。比如 Redis 集群，通过数据分片，解决了单台服务器内存和带宽的瓶颈问题，实现服务器处理能力的水平扩展；通过数据多副本和故障节点转移，解决了单台服务器故障导致的可用性问题。

其次，封装让我们可以更高效地访问系统资源。比如说，数据库是对文件系统的加强，使数据的存取更高效；缓存是对数据库的加强，使热点数据的访问更高效。

但软件在填硬件的各种坑的同时，也给系统挖了新的坑。举个例子，Redis 集群的多节点，它解决了单节点处理能力问题，但同时也带来了新的问题，比如节点内部的网络有问题（即网络分区现象），集群的可用性就有问题；Redis 数据的多副本，它解决了单台服务器故障带来的可用性问题，但同时也带来了数据的一致性问题。

我们知道，分布式系统有个典型的 CAP 理论，C 代表系统内部的数据一致性，A 代码系统的可用性，P 代表节点之间的网络是否允许出问题，我们在这三者里面只能选择两个。对于一个分布式系统来说，网络出问题是比较常见的，所以我们首先要选择 P，这意味着我们在剩下的 C 和 A 之间只能选择一个。

CAP 理论只是针对一个小的数据型的分布式系统，如果放大到整个业务系统，C 和 A 的选择就更加复杂了。

比如有时候，我们直接对订单进行写库，这是倾向于保证数据一致性 C，但如果数据库故障或者流量太大，写入不成功，导致当前的业务功能失败，也就是系统的可用性 A 产生了问题。如果我们不直接落库，先发订单数据到消息系统，再由消费者接收消息进行落库，这样即使单量很大或数据库有问题，最终订单还是可以落地，不影响当前的下单功能，保证了系统的可用性，但可能不同地方（比如缓存和数据库）的订单数据就有一致性的问题。

鱼和熊掌不能兼得，系统无法同时满足 CAP 的要求，我们就需要结合具体的业务场景，识别最突出的挑战，然后选择合适的组件，并以合理的方式去使用它们，最终保障系统的稳定运行，不产生大的业务问题。

技术架构的目标

好，现在你已经了解了系统的复杂性和软硬件的问题，那技术架构就要选择和组合各种软硬件，再结合我们开发的应用代码，来解决系统非功能性需求。

什么是系统非功能性需求呢？这是相对于业务需求来说的，所谓的业务需求就是保证业务逻辑正确，数据准确。比如一个订单，我们要保证订单各项数据是准确的，订单优惠和金额计算逻辑是正确的。而一个订单页面打开需要多少时间，页面是不是每次都能打开，这些就和具体的业务逻辑没有关系，属于系统非功能性需求的范畴。产品经理在一般情况下，也不会明确提这些需求。非功能性需求，有时候我们也称之为系统级功能，和业务功能相区分。

那对于一个系统来说，技术架构都要解决哪些非功能性需求呢？

系统的高可用

可用性的衡量标准是，系统正常工作的时间除以总体时间，通常用几个 9 来表示，比如 3 个 9 表示系统在 99.9% 的时间内可用，4 个 9 表示 99.99% 的时间内可用，这里的正常工作表示系统可以在相对合理的时间内返回预计的结果。

导致系统可用性出问题，一般是两种情况：

一种是软硬件本身有故障，比如机器断电，网络不通。这要求我们要么及时解决当前节点的故障问题，要么做故障转移，让备份系统快速顶上。

还有一种是高并发引起的系统处理能力的不足，软硬件系统经常在处理能力不足时，直接瘫痪掉，比如 CPU 100% 的时候，整个系统完全不工作。这要求我们要么提升处理能力，比如采取水平扩展、缓存等措施；要么把流量控制在系统能处理的水平，比如采取限流、降级等措施。

系统的高性能

我们这里说的高性能，并不是指系统的绝对性能要多高，而是系统要提供合理的性能。比如说，我们要保证前端页面可以在 3s 内打开，这样用户体验比较好。

保证合理的性能分两种情况：

一种是常规的流量进来，但系统内部处理比较复杂，我们就需要运用技术手段进行优化。比如针对海量商品的检索，我们就需要构建复杂的搜索系统来支持。

第二种是高并发的流量进来，系统仍旧需要在合理的时间内提供响应，这就更强调我们做架构设计时，要保证系统的处理能力能够整体上做水平扩展，而不仅仅是对某个节点做绝对的性能优化，因为流量的提升是很难准确预计的。

系统的可伸缩和低成本

系统的业务量在不同的时间点，有高峰有低谷，比如餐饮行业有午高峰和晚高峰，还有电商的大促场景。我们的架构设计要保证系统在业务高峰时，要能快速地增加资源来提升系统处理能力；反之，当业务低谷时，可以快速地减少系统资源，保证系统的低成本。

高可用、高性能、可伸缩和低成本，这些技术架构的目标不是孤立的，相互之间有关联，比如说有大流量请求进来，如果系统有很好的伸缩能力，它就能通过水平扩展的方式，保证系统有高性能，同时也实现了系统的高可用。如果系统的处理能力无法快速提升，无法保证高性能，那我们还是可以通过限流、降级等措施，保证核心系统的高可用。

我在前面也提到，这些目标很多时候会冲突，或者只能部分实现，我们在做技术架构设计时，不能不顾一切地要求达到所有目标，而是要根据业务特点，选择最关键的目标予以实现。

比如说，一个新闻阅读系统，它和订单、钱没有关系，即使短时间不可用，对用户影响也不大。但在出现热点新闻时，系统要能支持高并发的用户请求。因此，这里的设计，主要是考虑满足高性能，而不用太过于追求 4 个 9 或 5 个 9 的可用性。

总结

系统比我们想象的要复杂得多，这里，我和你分享了系统的物理模型，相信你不再局限于我们自己写的代码，而是对系统的整体结构有了更清晰的认识。

你还记得吗？在前面介绍业务架构时，我和你分享的是系统 = 模块 + 关系，而在这里介绍技术架构时，我和你分享的是系统的物理模型。

因为业务架构解决的是系统功能性问题，我们更多的是从人出发，去更好地理解系统；而技术架构解决的是系统非功能性问题，我们在识别出业务上的性能、可用性等挑战后，更多的是从软硬件节点的处理能力出发，通过合理的技术选型和搭配，最终实现系统的高可用、高性能和可伸缩等目标。通过这一讲的介绍，相信你现在对技术架构的目标和常见的解决手段，已经有了更深入的理解。

当然，针对这些不同的目标，技术架构处理的原则和手段也是不一样的。后面的几讲中，我会针对每个目标，为你具体展开介绍。

最后，给你留一道思考题：技术架构除了我在课程中说的几个目标之外，还有哪些目标呢？

欢迎在留言区和我互动，我会第一时间给你反馈。如果觉得有收获，也欢迎你把这篇文章分享给你的朋友。感谢阅读，我们下期再见。

12 | 高可用架构：如何让你的系统不掉链子？

你好，我是王庆友。今天我和你聊一聊，如何实现系统的高可用。

在实际工作中，我们平常更关注系统业务功能的实现，而对于系统是否会出故障，总觉得那是小概率事件，一开始不会考虑得太多。然而系统上线后，我们会发现系统其实很脆弱，每个地方都可能会出问题，处理线上事故的时间往往超过了开发功能的时间。

所以，对于系统的高可用，我想你经常会有这样的疑问：系统的高可用真的很重要吗？如何实现系统的高可用，具体都有哪些手段呢？

十年前，我还在 eBay，那时候，我们有几个数据来说明系统宕机对公司的影响，我记得其中一个是系统每宕掉 1 秒，公司将损失三千美金的收入；现在的大型外卖平台也是如此，如果就餐高峰期宕掉 1 小时，平台至少损失几个亿的直接收入，更加不用说对公司品牌的影响。

但是我们知道，系统中包含了大量的软硬件设备，要保证所有的节点都可用，不是一件容易的事。所以今天这一讲，我会从系统高可用的角度出发，和你介绍如何才能做到让系统不掉链子。

系统有哪些故障点？

那么一个系统，它在运行的过程中，都可能会出现哪些故障呢？我们来看一个简化的系统处理过程。

首先，客户端在远程发起请求，经过接入系统处理后，请求被转发给应用系统；应用系统调用服务完成具体的功能；在这个过程中，应用和服务还会访问各种资源，比如数据库和缓存。这里，我用红色部分，标识出了整个处理过程中可能出现的故障点，如下图所示：

这些故障点可以归纳为三类：

资源不可用，包括网络和服务器出故障，网络出故障表明节点连接不上，服务器出故障表明该节点本身不能正常工作。

资源不足，常规的流量进来，节点能正常工作，但在高并发的情况下，节点无法正常工作，对外表现为响应超时。

节点的功能有问题，这个主要体现在我们开发的代码上，比如它的内部业务逻辑有问题，或者是接口不兼容导致客户端调用出了问题；另外有些不够成熟的中间件，有时也会有功能性问题。

下面，我们就来看看如何才能应对这些问题，实现系统的高可用。

高可用策略和架构原则

系统可能出问题的地方有很多，解决的方式也不一样，在讨论具体的解决手段之前，我想先说下高可用的总体解决思路，这样你就能更好地理解具体的实现方式。

要想让系统能够稳定可用，我们首先要考虑如何避免问题的发生。比如说，我们可以通过 UPS（Uninterruptible Power System，不间断电源）来避免服务器断电，可以通过事先增加机器来解决硬件资源不足的问题。

然后，如果问题真的发生了，我们就要考虑怎么转移故障（Failover）。比如说，我们可以通过冗余部署，当一个节点发生故障时，用其它正常的节点来代替问题节点。

如果故障无法以正面的方式解决，我们就要努力降低故障带来的影响。比如说流量太大，我们可以通过限流，来保证部分用户可以正常使用，或者通过业务降级的手段，关闭一些次要功能，保证核心功能仍旧可用。

最后是要快速恢复系统。我们要尽快找到问题的原因，然后修复故障节点，使系统恢复到正常状态。

这里我要强调的是，处理线上事故的首要原则是先尽快恢复业务，而不是先定位系统的问题，再通过解决问题来恢复系统。因为这样做往往比较耗时，这里给出的处理顺序也体现了这个原则。

那么结合前面介绍的系统故障点和高可用的解决思路，我们在做架构设计时，就可以从 正面保障 和 减少损失 两个角度来考虑具体的应对手段。下面，我就来和你分享一下高可用的设计原则。

正面保障

第一个设计原则是冗余无单点。

首先，我们要保证系统的各个节点在部署时是冗余的，没有单点。比如在接入层中，我们可以实现负载均衡的双节点部署，这样在一个节点出现问题时，另一个节点可以快速接管，继续提供服务。

还有远程网络通信，它会涉及到很多节点，也很容易会出现问题，我们就可以提供多条通信线路，比如移动 + 电信线路，当一条线路出现问题时，系统就可以迅速切换到另一条线路。

甚至，我们可以做到机房层面的冗余，通过系统的异地多 IDC 部署，解决自然灾害（如地震、火灾）导致的系统不可用问题。

第二个设计原则是水平扩展。

很多时候，系统的不可用都是因为流量引起的：在高并发的情况下，系统往往会整体瘫痪，完全不可用。

在前面的故障点介绍中，你可以看到，在应用层、服务层、资源层，它们的处理压力都是随着流量的增加而增加。上一讲中，我也提到过，由于硬件在物理上存在瓶颈，通过硬件升级（垂直扩展）一般不可行，我们需要通过增加机器数量，水平扩展这些节点的处理能力。

对于无状态的计算节点，比如应用层和服务层来说，水平扩展相对容易，我们直接增加机器就可以了；而对于有状态的节点，比如数据库，我们可以通过水平分库做水平扩展，不过这个需要应用一起配合，做比较大的改造。

减少损失

第三个原则是柔性事务。

我们知道，系统的可用性经常会和数据的一致性相互矛盾。在 CAP 理论中，系统的可用性、一致性和网络容错性，三个最多只能保证两个，在分布式系统的情况下，我们只能在 C 和 A 中选一个。

在很多业务场景中，系统的可用性比数据的实时一致性更重要，所以在实践中，我们更多地使用 BASE 理论来指导系统设计。在这里，我们努力实现系统的基本可用和数据的最终一致。

知识拓展：关于 BASE 理论的详细信息，你可以参考一下隔壁专栏《分布式协议与算法实战》的这篇文章，这里就不详细展开了。

我们平时对单个数据库事务的 ACID 特性非常熟悉，因为这里不存在 P，所以 C 和 A 都能得到很好地保证，这是一种刚性事务。但在复杂的分布式场景下，基于 BASE 理论，我们通常只能实现部分的 C（软状态和最终一致）和部分的 A（基本可用），这是一种柔性事务。

柔性事务具体的实现方式有很多，比如说，通过异步消息在节点间同步数据。当然，不同的方式，对 C 和 A 的支持程度是不一样的，我们在设计系统时，要根据业务的特点来决定具体的方式。

第四个原则是系统可降级。

当系统问题无法在短时间内解决时，我们就要考虑尽快止损，为故障支付尽可能小的代价。具体的解决手段主要有以下这几种。

限流：让部分用户流量进入系统处理，其它流量直接抛弃。

降级：系统抛弃部分不重要的功能，比如不发送短信通知，以此确保核心功能不受影响。

熔断：我们不去调用出问题的服务，让系统绕开故障点，就像电路的保险丝一样，自己熔断，切断通路，避免系统资源大量被占用。比如，用户下单时，如果积分服务出现问题，我们就先不送积分，后续再补偿。

功能禁用：针对具体的功能，我们设置好功能开关，让代码根据开关设置，灵活决定是否执行这部分逻辑。比如商品搜索，在系统繁忙时，我们可以选择不进行复杂的深度搜索。

做好监控

最后一个设计原则，是系统可监控。

在实践中，系统的故障防不胜防，问题的定位和解决也非常的困难，所以，要想全面保障系统的可用性，最重要的手段就是监控。

当我们在做功能开发的时候，经常会强调功能的可测试性，我们通过测试来验证这个功能是否符合预期，而系统可监控，就像业务功能可测试一样重要。通过监控，我们可以实时地了解系统的当前状态，这样很多时候，业务还没出问题，我们就可以提前干预，避免事故；而当系统出现问题时，我们也可以借助监控信息，快速地定位和解决问题。

好，为了帮助你更好地理解，我对这些架构原则做个小结。

无单点和水平扩展是从正面的角度，直接保障系统的可用性。无单点设计针对的是节点本身的故障，水平扩展针对的是节点处理能力的不足。

柔性事务和可降级是通过提供有损服务的方式来保证系统的可用性。柔性事务保证功能的基本可用和数据的最终一致，可降级通过损失非核心功能来保证核心功能的可用。

最后，无论我们采取了多么强大的高可用措施，我们还是不能充分相信系统，还需要借助额外的监控来及时发现系统的问题并加以解决。监控是我们的第二条保命措施。

高可用手段

好了，通过前面的介绍，你应该已经了解了系统的故障点，以及高可用的设计原则。下面我们就一起来看下，在实践中都有哪些手段来保障系统的高可用。这里，我会按照系统的处理顺序来给你做详细介绍。

客户端 -> 接入层

客户端到服务端通常是远程访问，所以我们首先要解决网络的可用性问题。

针对网络的高可用，我们可以拉多条线路，比如在企业私有的 IDC 机房和公有云之间，同时拉移动和电信的线路，让其中一条线路作为备份，当主线路有问题时就切换到备份线路上。

在接入层，也有很多成熟的 HA 方案，比如说，你可以选择 Nginx、HAProxy、LVS 等负载均衡软件，它们都能很好地支持双节点 +Keepalived 部署。这样当一个节点出了问题，另一个节点就可以自动顶上去，而且两个节点对外是共享一个虚拟 IP，所以节点的切换对外部是透明的。

这里，我们通过冗余和自动切换避免了单点的故障。

接入层 ->Web 应用

Web 应用通常是无状态的，我们可以部署多个实例，很方便地通过水平扩展的方式，提升系统的处理能力；接入层的负载均衡设备，可以通过各种算法进行多个 Web 实例的路由，并且对它们进行健康检测，如果某个实例有问题，请求可以转发到另一个实例进行处理，从而实现故障的自动转移。

通常情况下，我们还可以在接入层做限流，比如，在 Nginx 中设置每秒多少个并发的限制，超过这个并发数，Nginx 就直接返回错误。

这里，我们同时支持了 Web 节点的水平扩展、自动故障转移以及系统的可降级（限流）。

Web 应用 -> 内部服务

服务通常也是无状态的，我们也可以通过部署多个实例进行水平扩展。

有多种方式可以支持服务实例的发现和负载均衡，比如说，我们可以使用传统的代理服务器方式，进行请求分发；另外，很多的微服务框架本身就支持服务的直接路由，比如在 Spring Cloud 中，我们就可以通过 Eureka 进行服务的自动注册和路由。

应用通常会访问多个服务，我们在这里可以做服务的隔离和熔断，避免服务之间相互影响。

比如在 Spring Cloud 的 Hystrix 组件（开源熔断框架）中，我们可以为不同服务配置不同的线程池，实现资源隔离，避免因为一个服务响应慢，而占用所有的线程资源；如果某个服务调用失败，我们可以对它进行熔断操作，避免无谓的超时等待，影响调用方的整体性能。

在应用和服务的内部，针对具体的功能，我们还可以做一些功能开关。开关实际上是一个标志变量，它的值可以是 on/off， 我们在代码中可以根据它的值，来确定某一段逻辑是否要执行。开关的值可以在数据库或配置系统里定义，这样我们就能够通过外部的开关值，控制应用内部的行为，这个在 eBay 有大量的落地。

这里，我们同时支持了服务节点的水平扩展、自动故障转移以及系统的可降级（熔断和业务开关）。

访问基础资源

常见的资源包括关系数据库、缓存和消息系统，我就以它们为例来介绍一下。

关系数据库属于有状态服务，它的水平扩展没有那么容易，但还是有很多手段能够保障数据库的可用性和处理能力。

首先，我们可以做数据库的主从部署，一方面通过读写分离，提升数据库读的性能，减轻主库压力；另一方面，数据库有成熟的 MHA 方案，支持主库故障时，能够自动实现主从切换，应用可以通过 VIP 访问数据库，因此这个切换过程对应用也是透明的。

另外，我们也可以通过物理的水平分库方式，对数据进行分片，这样就有多个主库支持写入。水平分库会涉及较多的应用改造，后面会有一篇文章专门介绍 1 号店的订单水平分库项目，到时我们再详细讨论。

再说下缓存。在数据读写比很高的情况下，我们可以利用缓存优化数据库的访问性能，包括进程内部缓存和分布式缓存，缓存是应对高并发的有效武器。

很多缓存方案，比如 Redis 本身就支持集群方式，它可以通过多节点支持处理能力的水平扩展，通过数据的多副本来支持故障转移。

最后说下消息系统。消息系统有很多成熟的 MQ 组件，比如说 Kafka，它可以通过多节点部署来支持处理能力的水平扩展，也能通过数据的多分区，实现故障的自动切换，保证系统的可用性。

最后我想说的是，明天和意外你永远不知道哪个先到来，即使有了这些高可用措施，还是会有各种各样的意外等待着我们。所以，系统的监控非常重要，只有准确地了解系统当前的状况，我们在面对问题时，才能快速响应，处理到点子上。

总结

今天，我和你介绍了保障系统高可用都有哪些策略和设计原则，相信你现在对高可用的整体处理思路有了清楚的认识。

另外，我还针对典型的系统处理过程，和你介绍了各个环节都有哪些具体的高可用手段，希望你可以在工作中，结合系统的实际情况去落地它们。

接下来，我会通过几个实际的案例，来具体说明如何实现系统的高可用，你可以跟着课程继续学习，然后尝试着在实际的工作中去参考和灵活运用。

最后，给你留一道思考题：处理事故有三板斧的说法，你知道它们都是什么吗？你是怎么评价它们的呢？

欢迎在留言区和我互动，我会第一时间给你反馈。如果觉得有收获，也欢迎你把这篇文章分享给你的朋友。感谢阅读，我们下期再见。

13 | 高可用架构案例（一）：如何实现O2O平台日订单500万？

你好，我是王庆友。在上一讲中，我和你介绍了高可用系统的设计原则和常见手段。今天呢，我会通过一个实际的案例，告诉你具体如何落地一个高可用的架构，让你能够深入理解和运用这些高可用手段。

项目背景介绍

先说下项目的背景。这是一个小程序点餐平台，用户在小程序上点餐并支付完成后，订单会先落到订单库，然后进一步推送到门店的收银系统；收银系统接单后，推送给后厨系统进行生产；同时返回小程序取餐码，用户可以凭取餐码去门店取餐或收取外卖。

这个项目服务于一家大型的餐饮公司，公司在全国有大量的门店，他们准备搞一个长期的大型线上促销活动，促销的力度很大：用户可以在小程序上先领取优惠券，然后凭券再支付 1 元，就可以购买价值数十元的套餐。

结合以往的经验，以及这次的促销力度，我们预计在高峰时，前端小程序请求将会达到每秒 10 万 QPS，并且预计首日的订单数量会超过 500 万。在这种高并发的情况下，我们为了保证用户的体验，系统整体的可用性要达到 99.99%。

你可以先了解一下这个点餐平台的具体架构：

这里呢，我具体说下系统主要的调用过程，以便于你更好地理解它：

小程序前端通过 Nginx 网关，访问小程序服务端；

小程序服务端会调用一系列的基础服务，完成相应的请求处理，包括门店服务、会员服务、商品服务、订单服务、支付服务等，每个服务都有自己独立的数据库和 Redis 缓存；

订单服务接收到新订单后，先在本地数据库落地订单，然后通过 MQ 同步订单给 OMS 履单中心；

门店的收银系统通过 HTTP 远程访问云端的 OMS 履单中心，拉取新订单，并返回取餐码给 OMS，OMS 再调用小程序订单服务同步取餐码；

小程序前端刷新页面，访问服务端获得取餐码，然后用户可以根据取餐码到门店取餐或等待外卖。

高可用系统改造措施

我在前面也介绍了，这次活动的促销力度很大，高峰期流量将达到平时的数十倍，这就要求系统能够在高并发的场景下，保证高可用性。

所以，基于访问量、日订单量和可用性的指标，我们对原有系统进行了一系列改造，最终顺利地实现了首日 500 万订单，以及在大促期间，系统 4 个 9 的可用性目标。这个 500 万的订单量，也创造了中国单商户线上交易的历史记录。

在下面的系统架构图中，我标出了具体的改造点，主要有 10 处，接下来我就给你分别具体介绍一下，你可以通过这些具体的改造措施，来真正理解高可用系统的设计手段。

前端接入改造

这里的前端有两个，C 端的小程序和 B 端的门店收银系统。前端部分主要是对三个点进行改造，包括小程序端的 CDN 优化、Nginx 负载均衡，以及收银端的通信线路备份。

小程序端的 CDN 优化

用户点餐前，需要先浏览商品和菜单，这个用户请求的频率很高，数据流量大，会对服务端造成很大的压力。所以，针对这一点，我们通过 CDN 供应商，在全国各地构建了多个 CDN 中心，储存静态的商品数据，特别是图片，这样小程序前端可以就近访问 CDN，流量无需通过小程序服务端，缓解了服务端的压力。

Nginx 负载均衡

这个小程序点餐平台，之前是直接利用云服务商提供的 LB，它只有简单的负载均衡能力。为了能应对这次的高并发流量，现在我们独立搭建了数十台的 Nginx 集群，集群除了负载均衡，还提供限流支持，如果 QPS 总数超过了 10 万，前端的访问请求将会被丢弃掉。

另外，Nginx 在这里还有一个好处，就是可以实时提供每个接口的访问频率和网络带宽占用情况，能够起到很好的接入层监控功能。

补充说明：一台 Nginx 一般可以支持数万的并发，本来这里无需这么多台 Nginx，这是因为云服务商对单个 LB 的接入有网络带宽的限制，所以我们要通过提升 Nginx 的数量，来保证接入有足够的带宽。

收银端的通信线路备份

门店的收银系统会通过前置代理服务器，来访问云端的 OMS 系统，这个代理服务器部署在商户自己的 IDC 机房，原来只通过电信线路和云端机房打通。在这次改造中，我们增加了移动线路，这样当电信主线路出问题时，系统就可以快速地切换到移动线路。

应用和服务的水平扩展

首先，针对小程序服务端的部署，我们把实例数从十几台提升到了 100 台，水平扩展它的处理能力。在上面的架构图中，你可以看到，小程序服务端依赖了 7 个基础服务，每个基础服务也做了相应的水平扩展，由于应用和基础服务都是无状态的，因此我们很容易扩充。

这里的基础服务是 Java 开发的，原来是用虚拟机方式部署的，现在我们把基础服务全部迁移到了容器环境，这样在提升资源利用率的同时，也更好地支持了基础服务的弹性扩容。

订单水平分库

在大促情况下，下单高峰期，订单主库的写访问频率很高，一个订单会对应 6~7 次的写操作，包括了创建新订单和订单状态变更；订单的读操作，我们之前通过一主多从部署和读写分离，已经得到了支持。

但负责写入的主库只有一个实例，所以这次我们通过订单的水平分库，扩充了订单主库的实例数，改造后，我们有 4 个主库来负责订单数据写入。数据库的配置，也从原来的 8 核 16G 提升到了 16 核 32G，这样我们通过硬件的垂直扩展，进一步提升了数据库的处理能力。

这里的订单水平分库在实现上比较简单，我们是通过订单 ID 取模进行分库，基于进程内的 Sharding-JDBC 技术，实现了数据库的自动路由。后面的课程中，我会专门介绍电商平台的订单水平分库，它会更加复杂，到时你可以做个比较，如果有需要的话，也可以在实际项目参考落地。

异步化处理

你可以看到，在前台订单中心和后台 OMS 之间，我们需要同步订单数据，所以这两者是紧密耦合的。不过这里，我们通过消息系统对它们进行了解耦。 一方面，前台下单要求比较快，后台 OMS 的订单处理能力比较弱（OMS 库没有进行水平分库），通过消息的异步化处理，我们实现了对订单流量的削峰；另一方面，如果 OMS 有问题，以异步的方式进行数据同步，也不会影响前台用户下单。

还有在小程序服务端，在用户支付完成或者后台生成取餐码后，我们会以微信消息的方式通知用户，这个在代码中，也是通过异步方式实现的，如果微信消息发送不成功，用户还是可以在小程序上看到相关信息，不影响用户取餐。

主动通知，避免轮询

在原来的架构中，前台小程序是通过轮询服务端的方式，来获取取餐码；同样，商户的收银系统也是通过轮询 OMS 系统拉取新订单，这样的收银系统有上万个，每隔 10s 就会拉取一次。这种盲目轮询的方式，不但效率低，而且会对服务端造成很大的压力。

经过改造后，我们落地了消息推送中心，收银系统通过 Socket 方式，和推送中心保持长连接。当 OMS 系统接收到前台的新订单后，会发送消息到消息推送中心；然后，收银系统就可以实时地获取新订单的消息，再访问 OMS 系统拉取新订单。为了避免因消息推送中心出问题（比如消息中心挂掉了），导致收银系统拿不到新订单，收银系统还保持对 OMS 系统的轮询，但频率降低到了 1 分钟一次。

同理，小程序前端会通过 Web Socket 方式，和消息推送中心保持长连接。当 OMS 系统在接收到收银系统的取餐码后，会发送消息到消息推送中心。这样，小程序前端可以及时地获取取餐码信息。

缓存的使用

我们知道，缓存是提升性能十分有效的工具。这里的改造，就有两个地方使用了缓存。

当收银系统向 OMS 拉取新订单时，OMS 不是到数据库里查询新订单，而是把新订单先保存在 Redis 队列里，OMS 通过直接查询 Redis，把新订单列表返回给收银系统。

在商品服务中，菜单和商品数据也是放在了 Redis 中，每天凌晨，我们通过定时任务，模仿前端小程序，遍历访问每个商品数据，实现对缓存的预刷新，进一步保证缓存数据的一致性，也避免了缓存数据的同时失效，导致缓存雪崩。

一体化监控

在前面各个节点可用性优化的基础上，我们也在系统的监控方面做了很多强化。除了常规的 Zabbix 做系统监控、CAT 做应用监控、拉订单曲线做业务监控以外，我们还对系统实现了一体化的监控。

在这里，所有的节点都在一个页面里显示，包括 Web 应用、Redis、MQ 和数据库，页面也会体现节点之间的上下游关系。我们通过采集节点的状态数据，实时监测每个节点的健康程度，并且用红黄绿三种颜色，表示每个节点的健康状况。这样，我们就可以非常直观地识别出，当前的哪些节点有问题。

监控的效果如下图所示，在下一讲中，我就会为你具体地介绍这个监控系统。

在实践中，这套监控系统也确实发挥了巨大的作用。很多时候，在系统问题还没有变得严重之前，我们就能够识别出来，并能进行主动干预。

比如说，小程序服务端的部分节点有时候会假死，这在 Zabbix 监控里往往看不出来，但在我们的监控页面中，这些节点就会飘红，我们就可以通过重启节点来快速恢复。还有好几次，系统有大面积的节点出问题了，我们通过节点的上下游关系，很容易地定位出了真正出现问题的地方，避免所有人一窝蜂地扑上去排查问题。

除了这里我介绍的优化措施以外，我们也为系统可能出问题的地方做了各种预案。比如说，我们保留了部分虚拟机上部署的基础服务实例，这样如果容器出现了问题，基础服务可以快速切回到虚拟机上的实例。

系统改造小结

到这里为止，系统主要的优化措施就介绍完了，但我们是如何知道要配置多少个节点，有没有达到预定的效果呢？

对于这个问题，我们的做法是，按照 10 万 QPS 和 99.99% 的可用指标要求，通过大量的压测来确定的。

首先，我们对每个节点进行接口压测，做各种性能优化，确定好需要的机器数量；

然后，我们利用 JMeter，模拟小程序前端发起混合场景的调用，以此检验系统的抗压能力，以及在压力下，系统的可用性是否达到了预定的要求；

最后，我们在生产环境中根据压测环境，按照服务器 1:1 的数量进行部署，保证性能不打折，最终这个小程序下单平台总的机器规模，也达到了数百台的量级。

这里，我想结合着上一讲和你介绍的架构原则，来让你更深刻地理解这次系统可用性的改造过程。

从正面保障的角度来看，我们首先在各个环节都避免了单点，包括远程通信线路，这样能保证任意一个节点出了问题，都有其他实例可以顶上去；其次，我们通过节点的垂直扩展和水平扩展，大幅度提升了系统的处理能力，包括应用、服务和数据库的扩展；我们也有效地利用了 Redis 缓存，对高频的订单和菜单数据的读取进行了优化。

在柔性处理方面，我们通过异步处理，来优化系统的性能和避免大流量的直接冲击，包括使用消息系统解耦前台下单系统和后台 OMS 系统，以及通过及时的消息推送，避免前端盲目轮询服务端。

同时，我们在系统接入层，通过 Nginx 进行限流，为系统的可用性进行兜底，这样在流量超过预估时，能够有效地避免后端系统被冲垮。

最后，我们通过强有力的监控手段，可以实时全面地了解系统运行状况，随时为异常情况做好准备。

总结

今天，我与你分享了一个实际的 O2O 点餐平台，在面对高并发流量时，我们是如何对系统进行升级改造，保证系统的高可用的。相信你在上一讲理论的基础上，通过进一步结合实际的场景，能够深入地理解如何运用各种高可用的手段了。

高可用的处理方式有很多，我这里给你介绍的也只是一部分，希望你能够在实践中，结合具体的业务场景，灵活地落地高可用的设计。

不过，无论我们采取多么周密的措施，总会有些地方我们没有考虑到，系统可能会出现各种各样的问题，这个时候对系统进行全面的监控就非常重要了。下一讲我会就如何做好系统的监控，和你做详细的介绍。

最后，给你留一道思考题：你当前的系统有单点吗？这个单点有没有出过问题呢？

欢迎你在留言区与大家分享你的问题和思考，我们一起讨论。如果觉得有收获，也欢迎你把这篇文章分享给你的朋友。感谢阅读，我们下期再见。

14 | 高可用架构案例（二）：如何第一时间知道系统哪里有问题？

你好，我是王庆友。

在前面两讲中，我与你介绍了系统的高可用都有哪些设计原则和具体手段。其中我也特别提到，要想保证系统的高可用，我们还需要对系统进行全面有效的监控。

监控是系统的眼睛，无监控，不运维。今天我们就从监控的角度来聊聊如何保证系统的高可用。

在开发软件时，我们经常强调一个业务功能的可测性，甚至有一种说法是测试驱动开发。在开发之前，我们会先设计测试用例，再去考虑如何实现功能。同样，当我们对系统作了很多加固，也是希望能保证它的稳定可用。

但我们怎么判断系统的各个节点当前是否正常呢？这个就对应了节点的可监控性，如果你事先想好了系统应该如何监控，如何判断每个节点是否正常，那你就会更清楚应该采取什么样的措施。很多时候，我们可以从监控的角度来倒推系统的可用性设计。

监控的分类

在第 11 讲中，我和你介绍了系统的组成，它包括接入层、应用系统、中间件、基础设施这几个部分，那我们的监控也是针对这些部分来实施的。一般来说，监控可以分为 5 个层次，如下图所示：

从上到下，分别为用户体验监控、业务监控、应用监控、中间件监控、基础平台监控。

用户体验监控：指的是从前端用户的访问速度出发，来监测系统的可用性，包括页面能否打开、关键接口的响应时间等等，用户体验监控一般结合前端的埋点来实现。

业务监控：它是从业务结果的角度来看，比如说订单数、交易金额等等，业务监控也是最直观的，我们知道，如果业务数据没问题，系统整体也就没有问题。对于业务监控，我们一般是从数据库里定时拉取业务数据，然后以曲线的方式展示业务指标随着时间的变化过程。除了当前的曲线，一般还有同比和环比曲线。同比是和前一天的数据进行比较，环比是和一周前的数据进行比较，两方面结合起来，我们就能知道当前的业务指标有没有问题。

应用监控：指的是对自己开发的代码进行监控，比如接口在一段时间内的调用次数、响应时间、出错次数等等。更深入一点的应用监控还包含了调用链监控，我们知道，一个外部请求的处理过程包含了很多环节，比如说网关、应用、服务、缓存和数据库，我们可以通过调用链监控把这些环节串起来，当系统有问题时，我们可以一步步地排查。有很多 APM 工具可以实现调用链监控，如 CAT、SkyWalking 等等。

中间件监控：指的是对标准中间件进行监控，它是第三方开发的代码，比如数据库、缓存、Tomcat 等等，这些组件对应的是系统的 PaaS 层。这些中间件往往带有配套的监控系统，比如，RabbitMQ 就有自带的监控后台。

基础平台监控：指的是对系统底层资源进行监控，如操作系统、硬件设备等等，这个层次的监控对应的是系统的 IaaS 层。Zabbix 就是典型的基础设施监控工具，它可以监控 CPU、内存和磁盘的使用情况。

监控的痛点

我们知道，一个大型的互联网平台，背后对应的是大规模的分布式系统，有大量的软硬件节点一起协作，这里的任何节点都有可能出问题，所以我们需要通过监控，及时发现和解决问题，提升系统的可用性。

但想要实现高效的监控，这不是一件容易的事情。下面，我给你举一个线上事故处理的例子，你就能理解监控面临的挑战。

首先，Monitor 发现订单曲线突然跌停，当前的订单数量变为 0，于是，Monitor 快速拉起电话会议，或者在微信群里 @所有人进行排查。这时候，一大堆相关的或不相关的人，都开始排查自己负责的那部分系统，比如说，运维在 Zabbix 里检查网络和机器，开发在 ELK 系统（Elasticsearch+Logstash+Kibana）里检查错误日志，DBA 检查数据库。

过了一会儿，负责 App 服务端的开发人员，在 ELK 里发现有大量的调用下单服务超时，于是他去询问下单服务的开发人员这是怎么回事。下单服务的开发人员就去检索错误日志，结果发现调用会员服务有大量的超时情况，然后他就去问会员服务的开发人员这是怎么回事。会员服务的开发人员通过错误日志，发现会员数据库连接不上，于是他把问题反映给 DBA。DBA 先拉上负责网络的同事一起看，发现网络没啥问题，然后他再去检查会员数据库本身，这时，他发现有慢查询把 DB 给挂住了。

这样，通过一系列的接力式排查，问题终于找到了，最后 DBA 把慢查询杀掉，所有人都去检查自己的系统，发现没有新的错误情况，系统恢复了正常。而这个时候，距离问题的发生已经过去了很长时间，在这个期间，技术被老板催，老板被商户催，而商户也已经被用户投诉了 N 次。

以上的事故处理过程还算比较顺利的，毕竟我们通过顺藤摸瓜，最后找到并解决了问题。更多的时候，我们面对事故，就像是热锅上的蚂蚁，众说纷纭，谁也不能肯定问题出在哪里。结果呢，我们病急乱投医，胡乱干预系统，不但没能解决问题，而且往往引发了二次事故。

你可以发现，在这个例子中，虽然我们有应用日志监控，有 Zabbix 系统监控，有网络和数据库监控，但对于一个大规模的分布式系统来说，这种分散的监控方式在实践中有一系列的弊端。

首先，不同的节点，它的监控的方式是不一样的，相应地，监控的结果也在不同的系统里输出。

同时，系统不同部分的监控都是由不同的人负责的，比如说，运维负责的是基础平台监控，开发负责的是应用系统监控。而监控信息往往专门的人才能解读，比如应用监控，它需要对应的开发人员才能判断当前的接口访问是否有问题。

最后，系统作为一个整体，需要上下游各个环节的人一起协作，进行大量的沟通，才能最终找到问题。

你可以看到，这种监控方式是碎片化的，对于处理线上紧急事故，它无疑是低效的，这里有很多问题。

发现问题慢：业务监控的曲线一般 1 分钟更新一次，有时候因为正常的业务抖动，Monitor 还需要把这种情况排除掉。因此，他会倾向于多观察几分钟，这样就导致问题的确认有很大的滞后性。

定位问题慢：系统节点多，大量的人需要介入排查，而且由于节点依赖复杂，需要反复沟通才能把信息串起来，因此很多时候，这种排查方式是串行或者说无序的。一方面，无关的人会卷入进来，造成人员的浪费；另一方面排查效率低，定位问题的时间长。

解决问题慢：当定位到问题，对系统进行调整后，验证问题是否已经得到解决，也不是一件很直观的事情，需要各个研发到相应的监控系统里去进行观察，通过滞后的业务曲线观察业务是否恢复。

那么，我们怎么解决监控面临的这些困境，以高效的方式解决线上事故，保障系统的高可用呢？

解决思路

你可以看到，前面这种监控方式，它是碎片化和人工化的，它由不同的工具负责监控系统的不同部分，并且需要大量专业的人介入，并通过反复的沟通，才能把相关的信息拼接起来，最后定位到问题。

那我们能不能把系统所有的监控信息自动关联起来，并且以一种直观的方式展示，让所有人一看就明白是哪里出了问题，以及出问题的原因是什么呢？

从这个思路出发，对系统的监控，我们需要做到两点：

系统能够自动地判断每个节点是否正常，并直观地给出结果，不需要经过专业人员的分析。

系统能够自动把各个节点的监控信息有机地串起来，从整体的角度对系统进行监控，不需要很多人反复地进行沟通。

这里，我们可以借鉴一下道路交通监控的例子。

我们经常可以在市内的高架上看到交通拥堵示意图。在下面的这张交通信息图上，你可以看到，每条道路都通过上下左右不同的方位，有机地关联在一起，形成一个整体的交通网络；同时，在交通图上，通过红黄绿三种状态，实时地反映了每条道路的拥堵情况。这样，司机就可以非常直观地了解道路是否畅通，从而提前避开拥堵路段。

这里有几个关键词：实时、直观、整体。下面，我们就来对照下软件系统的监控，来看看要想实现类似的监控效果，我们应该怎么做。

首先要实时，我们需要第一时间知道系统当前是否有问题。

然后要直观，节点是否有问题，我们需要很直观地就能判断出来，就像交通图上的红黄绿颜色标识一样。我们知道，在发生紧急事故时，人脑很可能会处于错乱状态，这个时候，我们一定不能指望专业的头脑或者严密的分析来判断问题，这样不但慢，而且很容易出错。所以，系统哪些部分有问题，问题是否严重，以及出问题的大致原因是什么，这些信息，监控系统都必须能够直观地给出来。

最后是整体，我们需要针对系统做整体监控，就像交通图一样，它是针对周边整体的道路情况进行展示，我们也需要把系统的各个节点放在一起，清晰地给出节点依赖关系。系统真正出问题的地方往往只有一个，其他地方都是连带的，如果监控系统能够给出节点的上下游依赖关系，对于定位真正的问题源是非常有用的。

所以，对照道路交通监控的思路，我们可以采取这样的监控方式：

首先，系统中的每个节点对应交通图的一条道路；

然后，节点的健康状况对应道路的拥堵情况，节点同样也有红黄绿三种不同的颜色，来展示该节点是否正常；

最后，节点之间的调用关系对应道路的方位关系。

这样我们就能构建一个实时的、直观的、一体化的监控系统，类似交通图一样，可以一眼就看出系统的问题所在。

好，回到刚才事故处理的例子，如果我们的监控系统按照这种方式来设计，它的监控效果会是什么样的呢？

首先所有的节点，包括服务端应用、下单服务、会员服务还有其他服务，以及它们各自用到的缓存、消息队列和数据库，这些节点的健康状态我们在一个页面里就可以看到，包括它们的依赖关系。

如果会员数据库出了问题，我们根据依赖关系倒推，会员数据库 -> 会员服务 -> 下单服务 -> 服务端应用这 4 个节点都会爆红，而其他节点不受影响，保持绿色。服务端应用和下单服务节点会有错误消息提示接口调用超时，而会员服务和会员数据库节点的错误消息提示的是数据库连接超时。

这样其他绿色的节点，我们就不用排查了，然后我们观察爆红的节点，通过上下游依赖关系，就知道最终的问题很可能出在会员数据库上，DBA 重点检查会员数据库就可以了。当数据库问题解决以后，我们可以看到所有爆红的节点马上变绿，立即就能确认系统恢复了正常。

架构方案和效果

根据前面的思路，我们设计了监控系统的整体架构，如下图所示：

每个被监控的节点，均有对应的 Agent 负责采集健康数据，不同的节点类型，数据采集的方式也不一样：Web 节点通过 HTTP 接口调用，Redis 通过 Jredis，MQ 也通过对应的 API 接口，DB 则采用 JDBC。

Agent 每隔 3s 采集节点数据，然后上报数据给 Monitor Service；Monitor Service 负责确定节点当前的状态并保存到数据库，这样就完成了节点健康状态的检测；最后，前端 Dashboard 每隔 3s，拉取所有节点的状态，以红黄绿三种颜色在同一页面展示，同时还会显示具体的出错信息。

那我们是根据什么规则来判断节点的健康状态呢？

这里，我以 DB 为例简单说明一下。Agent 每隔 3 秒会去尝试连接数据库，并进行简单的表读写操作，如果连接和读写都能够成功，那就说明该 DB 当前的运行是正常的，相应的，在 Dashboard 里面，这个 DB 节点会显示为绿色。

Redis 和 MQ 类似，我们主要也是检测组件的可用性；Web 应用的健康规则会相对复杂一些，我们会结合 Web 应用接口的功能和性能来做综合判断。这个监控系统的设计，我还会在下一讲里具体介绍，你到时候可以深入理解其中的细节。

我们最后来看下监控的效果。

下图是某个业务系统的实际监控效果图，左边是系统的部署架构，最上面是两个 Web 应用，这两个应用分别有自己的 Web 服务器、MQ 和 Redis 节点。

提示：这里，我对细节做了模糊化处理，不过没关系，我主要的目的是让你能了解监控的效果，尽管图片模糊，但它不会影响你理解内容。

以左上角的应用为例，它的 Web 应用部署在 Docker 里面，所以这里只显示一个节点（虚拟机部署可以看到每个实例的 IP，但 Docker 容器无法看到，对外表现为一个地址）；对于 Redis，我们是购买公有云的服务，所以也是一个实例；但 MQ 是集群的方式，它有三个实例。

然后，这两个 Web 应用同时依赖后端的 3 个基础服务，这 3 个服务是并列的关系，每个服务又分别有自己的应用、MQ 和 Redis。所以，你可以看到，在这个监控页面里，节点的部署情况和依赖关系都是一目了然的。

在这个例子中，有一个节点显示为黄色，黄色说明它有问题，但并不严重。你可以在右边的异常消息列表里看到具体的原因（在最近 3s 内，这个 Web 应用的接口响应时间超过了正常值的 5 倍），每条异常消息包括了出错的节点、具体出错的接口、该接口的正常响应时间，以及当前的响应时间。这样，你就可以很方便地把左边的出错节点和右边的异常消息对应起来，知道哪些节点有错误，还有出错的原因是什么。

另外，如果你在左边的图里点击某个节点，会弹出新页面，显示该节点的历史出错信息，并且新页面里有链接可以直接跳到 Zabbix、CAT 和 ELK 系统，这样你可以在这些专门的系统里做进一步的排查。

所以说，这里的监控系统提供的是整体的监控信息，可以帮助你快速定位问题的根源，在很多情况下，我们通过这里给出的错误信息，就可以知道出错的原因。当然，如果碰到特别复杂的情况，你还是可以在这里快速关联到各个专业的监控系统去收集更深入的信息。

总结

今天，我与你介绍了一下监控的分类，你现在应该对监控有了比较深入的了解，知道一个完整的监控体系都包含了哪些内容。

此外，我也结合线上事故处理的例子，和你说明了碎片化的监控带来的一些问题，并给出了整体化的解决思路以及具体的落地方案。在实践中，这套监控系统也确实发挥了巨大的价值，让我们可以高效地应对线上事故，提升系统的可用性，希望你能够深入地领悟和掌握。

在下一讲中，我还会和你介绍这个方案的实现细节，这样，你也可以尝试着去落地类似的监控系统。

最后，给你留一道思考题： 你的公司都有哪些监控手段，当处理线上事故时，你遇到的最大的挑战是什么？

欢迎你在留言区与大家分享你的答案，如果你在学习和实践的过程中，有什么问题或者思考，也欢迎给我留言，我们一起讨论。感谢阅读，我们下期再见。

15 | 高可用架构案例（三）：如何打造一体化的监控系统？

你好，我是王庆友。

上一讲，我与你介绍了整体化监控系统的设计方案，今天我就带你深入它的内部设计，让你可以了解它具体是如何落地的。

这个监控系统主要分为 4 大部分：节点信息采集、节点接入、数据上报和前端展示。下面，我就来为你具体展开介绍。

节点信息采集

在上一讲中，我提到过，Agent 负责采集节点的健康数据，每隔 3s，主动访问一次；然后，Agent 会根据这些数据，结合相应的规则，来判断节点的健康状态。最终的健康状态有三种，分别是错误、警告和正常，这三种状态也对应了 Dashboard 中节点的红黄绿三种颜色。

节点分为 4 类：Web 应用、Redis、MQ 和数据库。下面我就来具体讲一下，系统是如何对它们进行监控的。

对于 Redis 节点，Agent 通过 Jredis API，尝试连接 Redis 实例并进行简单的读写。如果这个操作没有问题，就表明 Redis 当前的健康状态是正常的，否则就是错误的。

对于 MQ 节点，Agent 是通过 MQ API，来检测 MQ 节点是否有活跃的消费者在连接，同时检测队列积压的消息数量。如果没有活跃的消费者，或者未消费的消息超过了预设的值，就表明当前的 MQ 节点的健康状态是错误的，否则它就是正常的。

对于数据库节点，Agent 是通过 JDBC 去连接数据库，并对表进行简单的读写。如果操作成功，表明数据库的健康状态是正常的，否则就是错误的。

对于这三类节点，它们的健康状态只有正常和错误两种，没有警告状态。如果节点有问题，Agent 会同时给出具体的出错信息，比如节点连接错误、积压消息过多等等。

对于 Web 应用来说，Agent 采集的方式则稍微复杂一些，它会同时采集应用的功能和性能数据，具体包括最近 3s 的接口调用次数、接口平均响应时间、接口出错次数、节点的健康状态和错误消息。

这里我给你举一个 Web 节点请求和响应的例子，来帮助你直观地了解 Agent 是如何采集数据的。

请求：http://10.10.1.1/agent/check
返回信息：
 "status":“warning",
 "avg_time":“583.0",
 "call_count":"10",
 "error_count":"0",
 "error_info":" orderListGet: current average time= 583.0, total average time =109.84, 调用次数= 10"

Web 节点会预先提供一个 HTTP 接口，Agent 通过调用这个接口，返回当前 Web 实例最近 3s 的健康状态。

这里最主要的就是 status 字段，它表明了 Web 节点最近 3s 是否健康，如果是“error”或者“warning”，返回的结果还会包含 error_info 字段，它负责给出具体的错误信息。

Agent 在获取了这 4 类节点的健康状态后，会调用 Monitor Service 进行数据上报，如果节点有问题，上报内容还包括具体的错误消息。

总体的架构如下图所示：

你要注意的是，Agent 本身是一个独立的应用，它不需要和节点部署在一起，如果节点数量少，我们部署一个 Agent 实例就可以；如果节点的数量比较多，我们可以部署多个 Agent 实例，比如给每类节点部署一个实例。总的要求就是，让 Agent 能够在 3s 内，完成所有节点的健康信息收集就可以了。

另外，节点的连接信息，事先是配置在数据库里的，比如数据库节点的 IP 端口、账号和密码等等，当 Agent 启动的时候，它会通过 Monitor Service 获取节点配置信息，Agent 在运行过程中也会定期刷新这个配置。

接入监控系统

好，说完了节点信息的采集，下面我们来看下，这些节点要接入监控系统，都需要做些什么。

对于 Redis、MQ、DB 这三类节点，接入监控系统只需要提供配置信息就可以了，无需额外的开发。

而对于 Web 应用接入监控，我们需要对应用代码做些改造：

针对每次接口调用，应用程序需要在接口代码中记录本次调用的耗时以及出错状况；

应用程序需要汇总最近 3 秒的接口调用情况，根据规则，给出节点的健康状态；

应用程序提供一个对外的 HTTP 接口，供 Agent 来获取上一步给出的健康状态。

为了方便 Web 应用的接入，监控系统开发团队提供了 SDK，它内置了接口调用信息的统计和健康计算规则。应用程序借助 SDK，就可以给 Agent 提供最终的健康结果，也就是说 SDK 帮助应用完成了最复杂的第二步工作。

所以，对应用来说，它接入监控系统是非常简单的。

首先，在每个应用接口中，调用 SDK 提供的 logHeahthInfo 方法，这个方法的输入包括了接口名字、本次接口调用耗时和错误信息，这和我们平常接入日志系统是很类似的。

try{
         result = service.invoke(request)
         HealthUtil.logHealthInfo("xxx_method",
             (System.currentTimeMillis() - start)，null);
}catch (Exception e){
         HealthUtil.logHealthInfo("xxx_method",
         (System.currentTimeMillis() - start)，
           e.getMessage());}

然后，应用提供一个额外的 HTTP 接口，在接口中直接调用 SDK 内置的 healthCheck 方法，给 Agent 提供最终的健康信息。这些就是应用接入监控系统要做的全部事情。

@RequestMapping(value = "/agent/check")
 public String reportData(){
   return HealthUtil.healthCheck();
 }

我们可以看到，SDK 通过在接口方法中进行埋点，可以收集每次接口的调用情况，那它最终是怎么计算出当前节点的健康状况呢？

SDK 的内部，实际上是一个 HashMap 结构，它的 key 就是 Web 应用的各个接口名字，它的 value 是一个简单的对象，包含这个接口最近 3s 总的调用数量、总的出错次数和总的耗时等。当每次 Web 应用有接口调用时，我们在 HashMap 内部根据接口名字，找到对应的 value，然后增加这三个数值，就完成了接口调用数据的收集。

当 Agent 调用 HTTP 接口，拉取节点健康数据时，SDK 会计算节点的健康状况，具体规则如下：

如果最近 3s，接口调用没有发生错误，节点的健康结果就是正常；如果出错次数在 1 到 5 之间，健康结果就是警告；如果大于 5，健康结果就是错误。

如果最近 3s，接口响应时间超过正常值的 10 倍，健康结果就是错误；如果在 5 倍到 10 倍之间，健康结果就是警告，否则结果就是正常。

这里有个问题，接口调用响应时间的正常值是怎么来的呢？这个值不是预先设置的，我们知道，如果预先设置的话，这个数字很难确定。这里的正常值其实是 SDK 自动计算的，SDK 会记录应用从启动开始到目前为止，接口的总耗时和总调用次数，然后得出平均的响应时间，作为接口调用的正常耗时（总调用次数和总耗时也记录在 HashMap 的 value 里）。

你可以看到，Web 应用的健康状态判断是结合了应用的功能和性能的，两者是“或”的逻辑关系，只要某一项有问题，健康结果就是有问题。比如说，最近 3s 接口功能没出错，但耗时是正常的 10 倍以上，SDK 就会认为节点的健康状态是错误的。

值得注意的是，SDK 会针对每个接口进行分别计算，最后取最差接口的结果。比如说，应用有 10 个接口，如果其中 8 个接口是正常状态，1 个接口是警告状态，1 个接口是错误状态，那么该应用的健康结果就是错误状态。

还有一点，SDK 在 HashMap 内部，不会记录每个接口调用的详细日志，而是只维护几个简单的总数值，因此 SDK 对应用的内存和 CPU 影响，都可以忽略不计。

前端信息展示

现在，监控数据已经通过 Agent 和 Monitor Service 保存到数据库了，前端的 Dashboard 通过调用 Monitor Service 接口，就可以获取所有节点的最新健康状态（Dashboard 也是每 3s 刷新一次页面）。接下来我们就要考虑，如何在 Dashboard 里展示节点健康状态，这影响到我们能否直观地定位系统的问题。

首先，一个应用一般有多个实例，比如 Web 应用很可能部署了多个实例；

然后，应用之间有上下游依赖关系，比如 Web 应用依赖 Redis 和数据库。

我们在页面中，就需要把所有这些信息直观地体现出来，这对我们判断问题的源头很有帮助。

这里的页面显示有两种实现方式。

一种是页面定制的方式，我们把应用有哪些节点，以及应用的上下游依赖关系，在前端代码里固定死。但问题是，如果系统的部署有变动，页面就要重新调整。在我们的监控实践中，我们要监控很多套系统，这样我们就需要为每个系统定制页面，初始的工作量就很大，更加不用说后续的调整了。

所以，在实践中，我们采取了一种更加灵活的前端展现方式，能够通过一套前端代码，灵活地展示系统的节点以及依赖关系，效果上也非常直观。

它的具体实现方式是：我们把页面的展示内容分为三个层次，分组、应用和节点。一个页面代表一个系统，它包含多个分组，一个分组包含多个应用，一个应用包含多个节点（节点代表了一个具体的实例，有独立 IP）。

这里的分组实际上是对应用进行归类，比如说，共享服务是一个分组，它内部包含多个服务，这些服务是并列的关系。这样，我们通过分组在页面里的位置关系，来体现应用之间的上下游依赖关系。

如下图所示，红色圈里的是各个分组，蓝色圈里是各个应用。我们可以很清晰地看到，“应用层”分组里的会员应用，会调用“依赖服务”分组里的四个服务。

这里，你可以发现，“应用层”分组里只有 1 个应用，它采取了 1 行 1 列的布局，而“依赖服务”分组里有四个服务，它采用的是 2 行 2 列的布局。那么这个布局是怎么实现的呢？

首先，布局是在后台定义的，保存在数据库里。我们为每个系统预先设定好布局，类似 HTML 里的 Table 布局语法，行用 TR 表示，列用 TD 表示。我们根据页面显示要求，提前确定好分组和应用会占用多少行，多少列。前端通过 Monitor Service 的接口获取页面的布局信息，然后根据布局信息进行动态展示，如果系统的部署有变化，我们在管理后台调整布局就可以了，非常灵活。

这样，我们通过类似 Table 方式的布局，前端通过一套代码，就可以满足所有系统的节点展示需求，并且能够比较好地体现应用之间的上下游依赖关系，当系统有问题时，我们就可以很直观地判断出问题的根源在哪里。

在前面，我说的是一个页面代表一个系统，其实我们也可以对所有系统的节点做一个整体的大盘监控，这样我们只需要看一个大盘页面，就可以监控所有的节点，如下图所示：

大盘监控具体的实现方式是这样的：

首先，前端页面读取所有节点的健康状态，按照节点分类展示有问题的节点，并标识出相应的颜色；

然后，节点的具体出错信息也可以在大盘中展示；

最后，我们根据每个系统内部节点的健康状况，按照一定的规则，算出各个系统的总体健康状态，在页面展示系统的健康状态。

比如说一个系统，如果它下面有一个节点是错误状态，对应的系统状态就是红色的；超过两个节点是警告状态，对应系统状态就是黄色的。如果我们点击相应的系统节点，就会跳转到具体系统的监控页面中，我们可以进一步了解该系统内部各个节点的详细状态信息。

通过这个大盘监控，我们就能在一个页面里，知道当前哪些节点有问题、哪些系统有问题、具体出错信息是什么，我们平常监控这一个页面就可以了。

库表设计

最后，我简单介绍下监控系统的数据库表设计，主要的表有 3 张：

系统信息表，用来定义监控体系里有哪些系统，其中 Layout（布局）定义了该系统前端的布局方式。

节点信息表，用来定义节点的配置信息，其中节点类型可选的值有 Web 应用、Redis、MQ、DB 等等，节点类型决定了节点健康信息的获取方式。其他字段用于 Agent 如何去连接节点，还有邮箱和手机用于节点出错时，相应的人可以接收报警信息。

节点监控状态表，用来记录每个节点的最新健康状态，用于 Dashboard 显示。

到这里为止，我给你介绍完了整个系统的核心设计。从监控的层次来看，这个监控系统可以分为大盘级别监控 -> 系统级别监控 -> 节点级别监控，你甚至还可以快速关联到每个节点的专门监控系统，比如 Zabbix 的硬件监控、CAT 的应用监控、ELK 的日志监控等等，实现最粗粒度到最细粒度监控的一体化。

相比较各个专门的监控系统，我们这里不求对各类节点的监控做得多深入，而是大致上能反映节点的健康状况即可（如果我们要对组件做更深入的监控，组件的 API 也可以为我们提供非常详细的信息）。我们更强调的是要把系统的所有节点串起来，直观地反映它们的健康状况，避免监控系统的碎片化和专业化。

总而言之，这个监控系统就相当于是一个全身体检，不同于对某个器官的深入检查，它是把系统的各个部位都做了初步检查，并且给出了一个很容易阅读的结果报告。这个系统实现起来很简单，但非常实用，我们相当于用 20% 的成本，实现了 80% 的监控效果。

总结

今天，我与你分享了一体化监控系统具体的设计细节，相信你已经非常清楚了它的内部实现机制，如果有需要，你也可以在实践中尝试落地类似的监控系统。

这里，我讲得比较细，不仅仅是为了让你理解这个监控系统是怎么设计的，而是想和你分享做架构设计时，我们要做全面深入的考虑，要简化开发的对接工作，要简化用户的使用，这样的架构设计才能顺利落地，实现预期的价值。

比如在这里，我们为 Web 应用提供了 SDK，这降低了开发者的接入成本；我们通过页面的动态布局设计，避免了前端开发工作的定制化；我们通过大盘监控以及和现有监控系统进行打通，进一步方便了用户的使用，全面提升监控系统的价值。

最后，给你留一道思考题： 你觉得在做架构设计时，最大的挑战是什么？

欢迎你在留言区与大家分享你的答案，如果你在学习和实践的过程中，有什么问题或者思考，也欢迎给我留言，我们一起讨论。感谢阅读，我们下期再见。

16 | 高性能和可伸缩架构：业务增长，能不能加台机器就搞定？

你好，我是王庆友，今天我来和你聊一聊如何打造高性能和可伸缩的系统。

在课程的第 11 讲，我和你介绍了，技术架构除了要保证系统的高可用，还要保证系统的高性能和可伸缩，并且能以低成本的方式落地。在实践中呢，高性能、可伸缩和低成本紧密相关，处理的手段也比较类似，这里我就放在一起来给你讲解。

在实际的工作当中，我们一般会比较关注业务功能的实现，而很少关注系统的性能，所以我们经常会面临以下这些挑战：

系统的 TPS 很低，只要流量一大，系统就挂，加机器也没用；

机器的资源利用率很低，造成资源严重浪费。

我曾经就统计过公司云服务器的资源利用率，结果让我非常意外，有相当比例的服务器，它们的 CPU 和内存平均利用率长期不到 1%，但与此同时，这些系统整体的 TPS 只有个位数。这里，你可以发现，资源利用率低和系统性能低的现象同时并存，很显然，系统没有充分利用硬件资源，它的性能有很大的优化空间。

所以今天，我就先来给你介绍一下常用的性能数据，让你建立起对性能的基本概念；然后，我会和你具体讲解实现系统高性能和可伸缩的策略，让你能在实践中灵活运用。

常用的性能数据

对于服务器来说，1ms 的时间其实不算短，它可以做很多事情，我在这里列了几个基础的性能数据，你可以把它们看做是系统性能的基线。

你可以看到，内存的数据读取是 SSD 磁盘的 10 倍，SSD 磁盘又是普通磁盘的 10 倍，一个远程调用的网络耗时是机房内部调用的 1000 倍，一个分布式缓存访问相对于数据库访问，性能也有数十倍的提升。

了解了这些常用的性能数据，你就能对性能建立一个直观的认识，有些时候，我们采取一些简单的手段就能提升系统的性能。比如说，如果磁盘的 IO 访问是瓶颈，我们只要用 SSD 磁盘来代替机械硬盘，就能够大幅度地提升系统的性能。

高性能的策略和手段

那么对于一个实际的业务系统来说，情况就会复杂很多。一个外部请求进来，需要经过内部很多的软硬件节点处理，用户请求的处理时间就等于所有节点的处理时间相加。只要某个节点性能有问题（比如数据库），或者某项资源不足（比如网络带宽），系统整体的 TPS 就上不去。这也是在实践中，很多系统 TPS 只有个位数的原因。

不同类型的节点，提升性能的方法是不一样的，概括起来，总体上可以分为三类。

加快单个请求处理

这个其实很好理解。简单来说，就是当一个外部请求进来，我们要让系统在最短的时间内完成请求的处理，这样在单位时间内，系统就可以处理更多的请求。具体的做法主要有两种：

优化处理路径上每个节点的处理速度。比如说，我们可以在代码中使用更好的算法和数据结构，来降低算法的时间和空间复杂度；可以通过索引，来优化数据库查询；也可以在高读写比的场景下，通过缓存来代替数据库访问等等。

并行处理单个请求。我们把一个请求分解为多个子请求，内部使用多个节点同时处理子请求，然后对结果进行合并。典型的例子就是 MapReduce 思想，这在大数据领域有很多实际的应用。

同时处理多个请求

当有多个外部请求进来时，系统同时使用多个节点来处理请求，每个节点分别来处理一个请求，从而提升系统单位时间内处理请求的数量。

比如说，我们可以部署多个 Web 应用实例，由负载均衡把外部请求转发到某个 Web 实例进行处理。这样，如果我们有 n 个 Web 实例，在单位时间里，系统就可以处理 n 倍数量的请求。

除此之外，在同一个节点内部，我们还可以利用多进程、多线程技术，同时处理多个请求。

请求处理异步化

系统处理请求不一定要实时同步，请求流量的高峰期时间往往很短，所以有些时候，我们可以延长系统的处理时间，只要在一个相对合理的时间内，系统能够处理完请求就可以了，这是一种异步化的处理方式。

典型的例子呢，就是通过消息系统对流量进行削峰，系统先把请求存起来，然后再在后台慢慢处理。

我们在处理核心业务时，把相对不核心的逻辑做异步化处理，也是这个思路。比如说下单时，系统实时进行扣库存、生成订单等操作，而非核心的下单送积分、下单成功发消息等操作，我们就可以做异步处理，这样就能够提升下单接口的性能。

那么，在实践中，我们应该使用哪种方式来保障系统的高性能呢？答案是，我们需要根据实际情况，把三种手段结合起来。

首先，我们要加快单个请求的处理。单节点性能提升是系统整体处理能力提升的基础，这也是我们作为技术人员的基本功。但这里的问题是，节点的性能提升是有瓶颈的，我们不能超越前面说的基础操作的性能。至于把请求分解为多个小请求进行并行处理，这个在很多情况下并不可行，我们知道，MapReduce 也有使用场景的限制。

对多个请求进行同时处理是应对海量请求的强有力手段，如果我们能够水平扩展每一个处理节点，这样在理论上，系统处理请求的能力无限的。而这里的问题是，对于无状态的计算节点，我们很容易扩展，比如说 Web 应用和服务；但对于有状态的存储节点，比如说数据库，要想水平扩展它的处理能力，我们往往要对系统做很大的改造。

至于异步化处理，在某些场景下是很好的提升系统性能的方式，我们不用增加机器，系统就能够完成请求的处理。但问题是，同步调用变成异步的方式，往往会导致处理结果不能实时返回，有时候会影响到用户体验，而且对程序的改造也会比较大。

所以，我们在考虑系统高性能保障的时候，首先需要考虑提升单个请求的处理速度，然后再考虑多请求的并发处理，最后通过异步化，为系统争取更长的处理时间。

具体的处理手段，根据业务场景的不同，我们需要做综合考虑，在满足业务的基础上，争取对系统改造小，总体成本低。

可伸缩的策略和手段

我们经常说，业务是可运营的，而实际上，系统也是可运营的。我们可以动态地调整系统软硬件部署，在业务高峰期增加软硬件节点，在业务低谷期减少软硬件节点，这就是系统的可伸缩能力。

系统的可伸缩也有两种实现方式。

第一个是节点级别的可伸缩

对于无状态的节点，我们直接增减节点就可以了。比如说订单服务，白天我们需要 10 台机器来提供服务，到了半夜，由于单量减少，我们就可以停掉部分机器。

如果做得好，我们还可以实现弹性伸缩，让系统根据硬件的负载情况，来确定机器的数量。比如说，当服务器的 CPU 或内存使用率在 10% 以下了，系统就自动减少服务实例的数量。

而对于有状态的服务，我们需要能够支持状态数据的重新分布。比如进行水平分库的时候，要从 4 个库增加到 8 个库，我们需要把原先 4 个库的数据，按照新的分库规则，重新分布到 8 个库中。如果这个调整对应用的影响小，那系统的可伸缩性就高。

第二个是系统级别的可伸缩

我们知道，系统是一个整体，如果只是节点级别的伸缩，我们可能要对多个节点分别进行操作，而且不同节点的资源配置会相互影响，这样对各个节点的调整就非常复杂，影响了系统的可伸缩能力。如果能实现系统端到端的伸缩，同时对多个节点进行伸缩处理，那系统的可伸缩能力就更高了。

所以这里，我们可以把多个处理节点打包在一起，形成一个处理单元。

举个例子，针对交易场景，我们可以把商品浏览、加购物车、下单、支付这几个节点放一起，形成一个逻辑上的单元，在单元内部形成调用的闭环。具体使用的时候，我们可以按照用户维度，来划分交易单元。比如说，让交易单元 A 处理用户 ID 对 2 取模为 0 的用户下单流程，交易单元 B 处理用户 ID 对 2 取模为 1 的用户下单流程。

这样，我们对一个整体的交易单元进行扩容或者缩容，每增加一个交易单元，就意味着同时增加商品浏览、加购物车、下单、支付 4 个节点，这 4 个节点的处理能力是匹配的。你可以参考下面的这张交易单元化的示意图：

通过单元化处理，我们把相关的节点绑定在一起，同进同退，更容易实现系统的可伸缩。

而如果我们把单元扩大到系统的所有节点，这就是一个虚拟机房的概念。我们可以在一个物理机房部署多个虚拟机房，也可以在不同的物理机房部署多个虚拟机房，这样，部署系统就像部署一个应用一样，系统的可伸缩性自然就更好。

高性能和可伸缩架构原则

说完了高性能和可伸缩的策略，接下来，我再说下具体的架构设计原则，让你能够在实践中更好地落地。

可水平拆分和无状态

这意味着节点支持多实例部署，我们可以通过水平扩展，线性地提升节点的处理能力，保证良好的伸缩性以及低成本。

短事务和柔性事务

短事务意味着资源锁定的时间短，系统能够更好地支持并发处理；柔性事务意味着系统只需要保证状态的最终一致，这样我们就有更多的灵活手段来支持系统的高性能，比如说通过异步消息等等。

数据可缓存

缓存是系统性能优化的利器，如果数据能够缓存，我们就可以在内存里拿到数据，而不是通过磁盘 IO，这样可以大大减少数据库的压力，相对于数据库的成本，缓存的成本显然也更低。

计算可并行

如果计算可并行，我们就可以通过增加机器节点，加快单次请求的速度，提高性能。Hadoop 对大数据的处理就是一个很好的例子。

可异步处理

异步处理给系统的处理增加了弹性空间，我们可以利用更多的处理时间，来降低系统对资源的实时需求，在保证系统处理能力的同时，降低系统的成本。

虚拟化和容器化

虚拟化和容器化是指对基础资源进行了抽象，这意味着我们不需要再依赖具体的硬件，对节点的移植和扩容也就更加方便。同时，虚拟化和容器化对系统的资源切分得更细，也就说明对资源的利用率更高，系统的成本也就更低。举个例子，我们可以为单个 Docker 容器分配 0.1 个 CPU，当容器的处理能力不足时，我们可以给它分配更多的 CPU，或者增加 Docker 容器的数量，从而实现系统的弹性扩容。

实现了系统的高性能和可伸缩，就表明我们已经最大限度地利用了机器资源，那么低成本就是自然的结果了。

总结

在课程的开始，我给出了一些基础的性能指标，在具体的业务场景中，你就可以参考这些指标，来评估你当前系统的性能。

另外，我还分别针对系统的高性能和可伸缩目标，介绍了它们的实现策略和设计原则，在工作中，你可以根据具体的业务，由易到难，采取合适的手段来实现这些目标。

在实践中呢，实现高可用和可伸缩的手段也是多种多样的，接下来的课程中，我还会通过实际的案例，来具体说明实现这些目标的有效手段，帮助你更好地落地。

最后，给你留一道思考题：在工作中，你都采取过哪些手段保证了系统的高性能和可伸缩呢？

欢迎在留言区和我互动，我会第一时间给你反馈。如果觉得有收获，也欢迎你把这篇文章分享给你的朋友。感谢阅读，我们下期再见。

17 | 高性能架构案例：如何设计一个秒杀系统？

你好，我是王庆友。

在上一讲中，我和你详细介绍了打造一个高性能系统的应对策略和架构手段。那么今天，我就以 1 号店的秒杀系统为例，来具体说明如何实现一个高性能的系统。

背景和问题

先说下背景。在 2014 年的时候，1 号店作为网上超市类电商，经常在线上举行各种大促活动。比如进口牛奶促销活动，每次促销的牛奶有几十万盒，促销价格非常优惠，一般这样的促销活动会在某个整点的时间进行开卖（如上午 10 点）。对于这种质高价优并且是刚需的商品，会有大量的用户来抢购，俗话说“手快有，手慢无”，往往短短几分钟内，所有牛奶就能售卖完毕。

这本质上是一种秒杀活动，但商品数量非常大，一瞬间会有大量的用户流量涌入，流量可以高达平时的几十倍。而且和少量商品的秒杀不同，这些都是有效流量，最终会生成订单。

而在正常情况下，系统因为资源有限，只能处理 10% 的流量，无法处理剩下的 90% 流量，瞬间高并发的流量涌入，很大程度上会引起后台系统超时报错，导致用户下单不成功。这样一来，用户就会反复刷新页面，多次尝试下单，不但用户的体验不好，而且系统的压力会更大。

最终的结果就是，系统往往由于过载，整体处理能力下降，甚至瘫痪，导致所有用户都无法购买。就像下图表示的一样，在秒杀场景下，系统会面临这样的困境：

在这种情况下，对于用户来说，能不能买到商品，拼的是体力和人品，由于体验不好，用户会逐渐对活动失去兴趣；而对于系统来说，我们需要拼命地加机器来满足峰值流量。

每次 1 号店要进行大促的时候，在活动开始前，运营和技术人员会坐在一起，大家一起来预估活动的峰值流量，然后技术人员做评估，系统的哪些节点需要加机器，以及要加多少机器。但这样的做法其实存在几个问题：

首先，我们对峰值流量的预估以及要加多少机器都是拍脑袋的，和实际出入往往很大，一旦估计少了，系统同样会面临过载的风险；

其次，为了短暂的几分钟促销，我们需要增加大量的机器，事先要做很多的运维准备工作，不但浪费资源，而且效率很低；

最为关键的是，有些处理节点，系统不是通过加机器就能扩展处理能力的，比如商品库存数据库，下单时，我们需要扣库存，而为了防止库存更新冲突，我们需要锁定库存记录，导致系统的并发处理能力有限，这个问题单靠加机器是解决不了的。

总体方案

对于这种高并发情况，看来让系统单纯地通过加机器去硬扛，是不可行的。那么，我们有没有更好的办法，既保证用户体验，又保证系统能够轻松地应对流量挑战呢？

我们先来深入分析下业务场景。这个秒杀活动的特点是，在短期的 1~2 分钟内，用户流量很大，但只要促销的商品卖完，流量马上恢复常态。所以，对于前端短期内这么大的下单请求，后端如果实时处理，压力会非常大，但如果把这个处理时间延长到 10 分钟，后端是可以完成下单的。那对用户来说，商品优惠的力度这么大，他们关心的是能否买到，所以会愿意多等一段时间，而不是在页面上一次次点击下单，每次系统都提示下单失败。

当然，如果我们把订单处理的时间延长了，只要我们在前台告诉用户，系统已经接受了他们的订单，并且不断同步用户订单处理的进度，用户体验的问题其实也不大。

基于这个分析，我们就可以利用异步处理的思路来应对秒杀活动。

我们先在前端接收用户所有的下单请求，但不在后端实时生成订单，而是放在队列里；然后系统根据后端订单中心的实际处理能力，从队列里获取订单请求，再交给订单中心生成实际的订单。同时，系统告诉用户当前的处理进度，有多少订单排在 TA 的前面，TA 还要等多久。

这样对于用户来说，在前台下单一次就可以了，然后等系统慢慢处理，这也符合先到先得的原则，非常公平合理。对系统来说，只要根据大促的商品总量，一定程度上增强系统处理能力，保证下单请求从进来到最后处理完成，这个时间相对合理就可以了。

比如说，有 20 万件的商品，每人限购一件，预计用户会在 2 分钟内完成下单，但用户能够接受系统在 20 分钟内完成订单处理。这样，系统只要保证每分钟能处理 1 万订单就行；而如果不采取排队的方式，系统就需要每分钟处理 10 万订单，它的压力就会提升一个数量级。

基于排队的思路，系统总体架构设计如下图所示：

在这个架构中，我们在前台和后台下单系统之间，新增了排队系统，它包括排队区和处理区两个部分。系统整体的处理过程是这样的：

用户在商品详情页提交订单后，这个订单会作为预订单进入排队区，同时排队系统会返回用户一个排队编号，这个编号用于跟踪后续的订单处理进度；

用户被引导到一个等待页，这个页面会根据排队号，定时地查询排队系统，排队系统会返回预订单在队列中的位置信息，包括它前面还有多少未处理的预订单，以及后台系统大概还要多久会处理这个预订单，这样用户就不会焦虑；

在排队系统的处理区，有很多消费者，它们依次从排队区的队列里获取预订单，然后调用后台下单系统生成实际的订单；

随着预订单变成正式的订单，队列里的预订单会逐渐变少，如果当前的预订单已经从队列里被移除了，用户的等待页就会检测到这个情况，页面自动跳转到订单完成页，这就和常规的购物流程一样了，用户进行最后的支付，最终完成整个前台下单过程。

这里，你可以看到，前台的预订单有瞬时的大流量，但我们只是把它们放到队列里，这个处理起来很快，排队系统可以轻松应对；而后台生成实际的订单是匀速的，并且最大化地发挥了下单系统的处理能力。另一方面，对于用户体验来说，用户可以选择在等待页等候，实时获取订单处理进度的反馈，也可以选择离开，然后在用户中心的“待支付订单”里完成支付。通过这样的设计，排队系统既保证了系统处理订单的能力，也保证了用户良好的体验。

下面是一张用户等待页的效果图，你可以直观地了解秒杀系统的用户体验。

现在，你已经了解了秒杀系统的总体设计。接下来，我深入介绍下这个排队系统的内部设计细节，帮助你更好地理解它。

内部设计

首先，针对队列的技术选型，排队系统使用的是 Redis，而不是 MQ。因为相对于 MQ 来说，Redis 更轻量级，性能更好，它内置了队列数据结构，除了和 MQ 一样支持消息的先进先出以外，我们还可以获取队列的长度，以及通过排队号获取消息在队列中的位置，这样我们就可以给前端反馈预订单的处理进度。

对于秒杀场景来说，一个订单只能包含一个商品，这里我们为每个秒杀商品提供一个单独的队列，这样就可以分散数据在 Redis 中的存取，多个队列可以提供更好的性能。

关于队列的调度问题，也就是消费者优先从哪个队列里拿预订单，排队系统会结合下单时间和队列的长度来确定，以保证用户合理的时间体验。比如说，某个秒杀商品的队列很长，消费者会优先从这个队列拿预订单，从而避免用户等待太长的时间。

关于队列长度，为了保证用户能够买到商品，我们并不是把所有前台的下单请求都会放到队列里，而是根据参与活动的秒杀商品数量，按照 1:1 的比例，设置队列初始长度，这样就保证了进入队列的请求最终都能生成订单。

这个可用队列长度会随着预订单进入队列，不断地减少，当数值变为 0 时，下单前台会拒绝接受新请求进入队列，直接反馈用户下单失败。当然，如果后台订单生成异常或用户取消订单后，可用队列长度会增加，前台会重新开放预订单进入队列。

更多优化：建立活动库存

除了秒杀流程，系统还有常规的购物流程，这两个购物方式都是从详情页开始，到订单完成页结束。不同的地方是，常规购物流程走的是购物车和结算页，系统是同步处理的，这样可以有更好的用户体验。

在这里，我们在系统设计上，可以很好地同时支持秒杀流程和常规购物流程。

如果运营人员在后台上架商品的时候，设置这是一个秒杀商品，那么从详情页开始，系统就会引导用户走秒杀流程，否则就走常规购物流程。特别是在早期秒杀系统刚落地的时候，如果发现秒杀流程有问题，我们还可以快速切回到常规的购物流程，实现了一定程度上的系统互备。

此外，对于秒杀活动来说，参与活动的商品种类是有限的，但这些商品库存的扣减非常频繁，因此我们建立了活动库存的概念，把少量参与促销的商品种类单独放在一个库里，避免和大量常规的商品放在一起，这样也大幅度地提高了库存数据库的读写性能。

好了，通过这个秒杀系统的架构设计，你可以看到，我们巧妙地通过请求的异步化处理，对流量进行削峰，从而保证了系统的高性能。这里我们不需要增加太多的机器，在系统落地时，我们通过排队系统对前后台解耦，后台下单系统基本上也不需要修改，系统整体改造的工作量不大，整个落地过程也非常顺利。

不过值得注意的是，这种方式比较适合瞬时有高并发流量的场景，比如这里说的秒杀场景。如果订单高峰会持续一段较长的时间，而用户对订单处理又有比较高的时间要求，那就不适合采用这种异步削峰的方式。

举个例子，外卖订单的午高峰通常会持续两个小时，而用户普遍期待订单半小时能够送达。对于这种情况，我们就需要正面应对高峰流量，比如通过水平扩展各个节点，提升系统的处理能力。这也要求系统能够做到弹性伸缩，高效地支持资源的缩容或扩容，节省成本。

总结

今天，我针对 1 号店的大促业务挑战，与你分享了一个秒杀系统的具体设计，对照我在上一讲中介绍的高性能应对策略，秒杀系统主要使用了异步化处理的方式，这也符合实际的业务场景。

通过今天的分享，相信你对如何保障系统的高性能有了更深入的体会，如果你也有类似的瞬时高并发的场景，你也可以在实践中参考这里的做法。

最后，给你留一道思考题： 你的公司业务上有高并发的场景吗，系统是如何应对的呢？

欢迎给我留言，我会及时给你反馈。如果你觉得有收获，也欢迎你把这篇文章分享给你的朋友。感谢你的阅读，我们下期再见。

18 | 可伸缩架构案例：数据太多，如何无限扩展你的数据库？

你好，我是王庆友。在第 16 讲中，我和你介绍了很多可伸缩的架构策略和原则。那么今天，我会通过 1 号店订单水平分库的实际案例，和你具体介绍如何实现系统的可伸缩。

问题和解决思路

2013 年，随着 1 号店业务的发展，每日的订单量接近 100 万。这个时候，订单库已有上亿条记录，订单表有上百个字段，这些数据存储在一个 Oracle 数据库里。当时，我们已经实现了订单的服务化改造，只有订单服务才能访问这个订单数据库，但随着单量的增长以及在线促销的常态化，单一数据库的存储容量和访问性能都已经不能满足业务需求了，订单数据库已成为系统的瓶颈。所以，对这个数据库的拆分势在必行。

数据库拆分一般有两种做法，一个是垂直分库，还有一个是水平分库。

垂直分库

简单来说，垂直分库就是数据库里的表太多，我们把它们分散到多个数据库，一般是根据业务进行划分，把关系密切的表放在同一个数据库里，这个改造相对比较简单。

水平分库

某些表太大，单个数据库存储不下，或者数据库的读写性能有压力。通过水平分库，我们把一张表拆成多张表，每张表存放部分记录，分别保存在不同的数据库里，水平分库需要对应用做比较大的改造。

当时，1 号店已经通过服务化，实现了订单库的垂直拆分，它的订单库主要包括订单基本信息表、订单商品明细表、订单扩展表。这里的问题不是表的数量太多，而是单表的数据量太大，读写性能差。所以，1 号店通过水平分库，把这 3 张表的记录分到多个数据库当中，从而分散了数据库的存储和性能压力。

水平分库后，应用通过订单服务来访问多个订单数据库，具体的方式如下图所示：

原来的一个 Oracle 库被现在的多个 MySQL 库给取代了，每个 MySQL 数据库包括了 1 主 1 备 2 从，都支持读写分离，主备之间通过自带的同步机制来实现数据同步。所以，你可以发现，这个项目实际包含了水平分库和去 Oracle 两大改造目标。

分库策略

我们先来讨论一下水平分库的具体策略，包括要选择哪个分库维度，数据记录如何划分，以及要分为几个数据库。

分库维度怎么定？

首先，我们需要考虑根据哪个字段来作为分库的维度。

这个字段选择的标准是，尽量避免应用代码和 SQL 性能受到影响。具体地说，就是现有的 SQL 在分库后，它的访问尽量落在单个数据库里，否则原来的单库访问就变成了多库扫描，不但 SQL 的性能会受到影响，而且相应的代码也需要进行改造。

具体到订单数据库的拆分，你可能首先会想到按照用户 ID 来进行拆分。这个结论是没错，但我们最好还是要有量化的数据支持，不能拍脑袋。

这里，最好的做法是，先收集所有 SQL，挑选出 WHERE 语句中最常出现的过滤字段，比如说这里有三个候选对象，分别是用户 ID、订单 ID 和商家 ID，每个字段在 SQL 中都会出现三种情况：

单 ID 过滤，比如说“用户 ID=？”；

多 ID 过滤，比如“用户 ID IN(?,?,?)”；

该 ID 不出现。

最后，我们分别统计这三个字段的使用情况，假设共有 500 个 SQL 访问订单库，3 个候选字段出现的情况如下：

从这张表来看，结论非常明显，我们应该选择用户 ID 来进行分库。

不过，等一等，这只是静态分析。我们知道，每个 SQL 访问的频率是不一样的，所以，我们还要分析每个 SQL 的实际访问量。

在项目中，我们分析了 Top15 执行次数最多的 SQL （它们占总执行次数 85%，具有足够代表性），按照执行的次数，如果使用用户 ID 进行分库，这些 SQL 85% 会落到单个数据库，13% 落到多个数据库，只有 2% 需要遍历所有的数据库。所以说，从 SQL 动态执行次数的角度来看，用户 ID 分库也明显优于使用其他两个 ID 进行分库。

这样，通过前面的量化分析，我们知道按照用户 ID 分库是最优的选择，同时也大致知道了分库对现有系统会造成多大影响。比如在这个例子中，85% 的 SQL 会落到单个数据库，那么这部分的数据访问相对于不分库来说，执行性能会得到一定的优化，这样也解决了我们之前对分库是否有效果的疑问，坚定了分库的信心。

数据怎么分？

好，分库维度确定了以后，我们如何把记录分到各个库里呢？

一般有两种数据分法：

根据 ID 范围进行分库，比如把用户 ID 为 1 ~ 999 的记录分到第一个库，1000 ~ 1999 的分到第二个库，以此类推。

根据 ID 取模进行分库，比如把用户 ID mod 10，余数为 0 的记录放到第一个库，余数为 1 的放到第二个库，以此类推。

这两种分法，各自存在优缺点，如下表所示：

在实践中，为了运维方便，选择 ID 取模进行分库的做法比较多。同时为了数据迁移方便，一般分库的数量是按照倍数增加的，比如说，一开始是 4 个库，二次分裂为 8 个，再分成 16 个。这样对于某个库的数据，在分裂的时候，一半数据会移到新库，剩余的可以不用动。与此相反，如果我们每次只增加一个库，所有记录都要按照新的模数做调整。

在这个项目中，我们结合订单数据的实际情况，最后采用的是取模的方式来拆分记录。

补充说明：按照取模进行分库，每个库记录数一般比较均匀，但也有些数据库，存在超级 ID，这些 ID 的记录远远超过其他 ID。比如在广告场景下，某个大广告主的广告数可能占很大比例。如果按照广告主 ID 取模进行分库，某些库的记录数会特别多，对于这些超级 ID，需要提供单独库来存储记录。

分几个库？

现在，我们确定了记录要怎么分，但具体要分成几个数据库呢？

分库数量，首先和单库能处理的记录数有关。一般来说，MySQL 单库超过了 5000 万条记录，Oracle 单库超过了 1 亿条记录，DB 的压力就很大（当然这也和字段数量、字段长度和查询模式有关系）。

在满足前面记录数量限制的前提下，如果分库的数量太少，我们达不到分散存储和减轻 DB 性能压力的目的；如果分库的数量太多，好处是单库访问性能好，但对于跨多个库的访问，应用程序需要同时访问多个库，如果我们并发地访问所有数据库，就意味着要消耗更多的线程资源；如果是串行的访问模式，执行的时间会大大地增加。

另外，分库数量还直接影响了硬件的投入，多一个库，就意味着要多投入硬件设备。所以，具体分多少个库，需要做一个综合评估，一般初次分库，我建议你分成 4~8 个库。在项目中，我们拆分为了 6 个数据库，这样可以满足较长一段时间的订单业务需求。

分库带来的问题

不过水平分库解决了单个数据库容量和性能瓶颈的同时，也给我们带来了一系列新的问题，包括数据库路由、分页以及字段映射的问题。

分库路由

分库从某种意义上来说，意味着 DB Schema 改变了，必然会影响应用，但这种改变和业务无关，所以我们要尽量保证分库相关的逻辑都在数据访问层进行处理，对上层的订单服务透明，服务代码无需改造。

当然，要完全做到这一点会很困难。那么具体哪些改动应该由 DAL（数据访问层）负责，哪些由订单服务负责，这里我给你一些可行的建议：

对于单库访问，比如查询条件已经指定了用户 ID，那么该 SQL 只需访问特定库即可。此时应该由 DAL 层自动路由到特定库，当库二次分裂时，我们也只需要修改取模因子就可以了，应用代码不会受到影响。

对于简单的多库查询，DAL 层负责汇总各个分库返回的记录，此时它仍对上层应用透明。

对于带聚合运算的多库查询，比如说带 groupby、orderby、min、max、avg 等关键字，建议可以让 DAL 层汇总单个库返回的结果，然后由上层应用做进一步的处理。这样做有两方面的原因，一方面是因为让 DAL 层支持所有可能的聚合场景，实现逻辑会很复杂；另一方面，从 1 号店的实践来看，这样的聚合场景并不多，在上层应用做针对性处理，会更加灵活。

DAL 层还可以进一步细分为底层 JDBC 驱动层和偏上面的数据访问层。如果我们基于 JDBC 层面实现分库路由，系统开发难度大，灵活性低，目前也没有很好的成功案例。

在实践中，我们一般是基于持久层框架，把它进一步封装成 DDAL（Distributed Data Access Layer，分布式数据访问层），实现分库路由。1 号店的 DDAL 就是基于 iBatis 进一步封装而来的。

分页处理

水平分库后，分页查询的问题比较突出，因为有些分页查询需要遍历所有库。

举个例子，假设我们要按时间顺序展示某个商家的订单，每页有 100 条记录，由于是按商家查询，我们需要遍历所有数据库。假设库数量是 8，我们来看下水平分库后的分页逻辑：

如果是取第 1 页数据，我们需要从每个库里按时间顺序取前 100 条记录，8 个库汇总后共有 800 条，然后我们对这 800 条记录在应用里进行二次排序，最后取前 100 条；

如果取第 10 页数据，则需要从每个库里取前 1000（100*10）条记录，汇总后共有 8000 条记录，然后我们对这 8000 条记录进行二次排序后，取第 900 到 1000 之间的记录。

你可以看到，在分库情况下，对于每个数据库，我们要取更多的记录，并且汇总后，还要在应用里做二次排序，越是靠后的分页，系统要耗费更多的内存和执行时间。而在不分库的情况下，无论取哪一页，我们只要从单个 DB 里取 100 条记录即可，也无需在应用内部做二次排序，非常简单。

那么，我们如何解决分库情况下的分页问题呢？这需要具体情况具体分析：

如果是为前台应用提供分页，我们可以限定用户只能看到前面 n 页（这个限制在业务上也是合理的，一般看后面的分页意义不大，如果一定要看，可以要求用户缩小范围重新查询）；

如果是后台批处理任务要求分批获取数据，我们可以加大分页的大小，比如设定每次获取 5000 条记录，这样可以有效减少分页的访问次数；

分库设计时，一般还有配套的大数据平台负责汇总所有分库的记录，所以有些分页查询，我们可以考虑走大数据平台。

分库字段映射

分库字段只有一个，比如这里，我们用的是用户 ID，如果给定用户 ID，这个查询会落到具体的某个库。但我们知道，在订单服务里，根据订单 ID 查询的场景也很多见，不过由于订单 ID 不是分库字段，如果不对它做特殊处理，系统会盲目查询所有分库，从而带来不必要的资源开销。

所以，这里我们为订单 ID 和用户 ID 创建映射，保存在 Lookup 表里，我们就可以根据订单 ID，找到相应的用户 ID，从而实现单库定位。

Lookup 表的记录数和订单库记录总数相等，但它只有 2 个字段，所以存储和查询性能都不是问题，这个表在单独的数据库里存放。在实际使用时，我们可以通过分布式缓存，来优化 Lookup 表的查询性能。此外，对于新增的订单，除了写订单表，我们同时还要写 Lookup 表。

整体架构

通过以上分析，最终的 1 号店订单水平分库的总体技术架构如下图所示：

上层应用通过订单服务访问数据库；

分库代理实现了分库相关的功能，包括聚合运算、订单 ID 到用户 ID 的映射，做到分库逻辑对订单服务透明；

Lookup 表用于订单 ID 和用户 ID 的映射，保证订单服务按订单 ID 访问时，可以直接落到单个库，Cache 是 Lookup 表数据的缓存；

DDAL 提供库的路由，可以根据用户 ID 定位到某个库，对于多库访问，DDAL 支持可选的多线程并发访问模式，并支持简单的记录汇总；

Lookup 表初始化数据来自于现有的分库数据，当新增订单记录时，由分库代理异步写入。

如何安全落地？

订单表是系统的核心业务表，它的水平拆分会影响到很多业务，订单服务本身的代码改造也很大，很容易导致依赖订单服务的应用出现问题。我们在上线时，必须谨慎考虑。

所以，为了保证订单水平分库的总体改造可以安全落地，整个方案的实施过程如下：

首先，实现 Oracle 和 MySQL 两套库并行，所有数据读写指向 Oracle 库，我们通过数据同步程序，把数据从 Oracle 拆分到多个 MySQL 库，比如说 3 分钟增量同步一次。

其次，我们选择几个对数据实时性要求不高的访问场景（比如访问历史订单），把订单服务转向访问 MySQL 数据库，以检验整套方案的可行性。

最后，经过大量测试，如果性能和功能都没有问题，我们再一次性把所有实时读写访问转向 MySQL，废弃 Oracle。

这里，我们把上线分成了两个阶段：第一阶段，把部分非实时的功能切换到 MySQL，这个阶段主要是为了验证技术，它包括了分库代理、DDAL、Lookup 表等基础设施的改造；第二阶段，主要是验证业务功能，我们把所有订单场景全面接入 MySQL。1 号店两个阶段的上线都是一次性成功的，特别是第二阶段的上线，100 多个依赖订单服务的应用，通过简单的重启就完成了系统的升级，中间没有出现一例较大的问题。

项目总结

1 号店在完成订单水平分库的同时，也实现了去 Oracle，设备从小型机换成了 X86 服务器，我们通过水平分库和去 Oracle，不但支持了订单量的未来增长，并且总体成本也大幅下降。

不过由于去 Oracle 和订单分库一起实施，带来了双重的性能影响，我们花了很大精力做性能测试。为了模拟真实的线上场景，我们通过 TCPCopy，把线上实际的查询流量引到测试环境，先后经过 13 轮的性能测试，最终 6 个 MySQL 库相对一个 Oracle，在当时的数据量下，SQL 执行时间基本持平。这样，我们在性能不降低的情况下，通过水平分库优化了架构，实现了订单处理能力的水平扩展。

1 号店最终是根据用户 ID 后三位取模进行分库，初始分成了 6 个库，理论上可以支持多达 768 个库。同时我们还改造了订单 ID 的生成规则，使其包括用户 ID 后三位，这样新订单 ID 本身就包含了库定位所需信息，无需走 Lookup 映射机制。随着老订单归档到历史库，在前面给出的架构中，Lookup 表相关的部分就可以逐渐废弃了。

如果要扩充数据库的数量，从 6 个升到 12 个，我们可以分三步走：

增加 6 个 MySQL 实例，把现有 6 个库的数据同步到新的库，比如说，0 号库同步到 6 号库，1 号库同步到 7 号库等等；

在配置文件里把分库的取模从 6 变成 12；

通过数据库脚本，每个库删掉一半数据，比如对于 0 号库，删掉用户 ID%12=6 的记录，对于 6 号库，删掉用户 ID%12=0 的记录。

你可以看到，通过这样的分库方式，整个数据库扩展是非常容易的，不涉及复杂的数据跨库迁移工作。

订单的水平分库是一项系统性工作，需要大胆设计，谨慎实施。你需要把握住这几个要点：

首先，你需要在分库策略的指导下，结合实际情况，在每个方面做出最合适的选择；

其次，对于特殊场景，如分页查询，你需要具体问题具体解决；

最后，你要总体规划，控制好落地步骤，包括对系统改造、性能测试、数据迁移、上线实施等各个环节做好衔接，保证业务不出问题。

总结

今天我和你分享了 1 号店订单水平分库的实际案例，并给出了具体的做法和原因，相信你已经掌握了如何通过对数据库的水平拆分，来保证系统的高性能和可伸缩。

水平分库是针对有状态的存储节点进行水平扩展，相对于无状态的节点，系统改造的复杂性比较高，要考虑的点也比较多。通过今天的分享，希望你以后在设计一个复杂方案时，能够更全面地思考相关的细节，提升架构设计能力。

最后，给你留一道思考题：你公司的数据库有什么瓶颈吗，你计划对它做什么样的改造呢？

欢迎在留言区和我互动，我会第一时间给你反馈。如果觉得有收获，也欢迎你把这篇文章分享给你的朋友。感谢阅读，我们下期再见。

19 | 综合案例：电商平台技术架构是如何演变的？

你好，我是王庆友。

在前面的几讲中，我分别和你介绍了技术架构的高可用、高性能、可伸缩等目标，并通过实际的案例说明了如何实现这些目标。今天呢，我会通过一个综合案例，来说明电商平台的技术架构是如何演变的，让你可以全面地理解如何实现这些目标。

一个实际的电商系统很复杂，在案例介绍中，为了简化，我用比较有代表性的交易系统和账户系统来代表整体的电商系统，并具体分析这两个系统在电商平台发展过程中，它们都碰到了什么瓶颈，以及我们在技术架构上是如何解决的。

这一讲会包含很多架构图，每一张图都代表了不同时期的架构设计，为了方便你更好地理解它们，在每张架构图中，我都用红色方框圈出了当前架构存在的问题，用绿色实体部分代表了上一个架构所存在问题的解决办法，希望你听完今天的讲解，能够结合这些架构图，加深对技术架构的理解。

单体系统

第一代的电商系统是一个单体架构，所有的代码都打包在一个应用里，部署的时候会有多个实例，我们通过负载均衡，把用户请求分发到具体的实例中。这个时候，所有的数据表还在一个数据库里。

这里的问题是，单体应用的所有代码都放在一起，代码编译需要很长时间，应用启动也需要很长时间，并且代码相互依赖，开发效率低，并行开发困难。随着单体应用的体量越变越大，这些问题也越来越突出。

SOA 架构

针对单体应用体量过大带来的问题，我们对系统进行拆分，把整体系统分为多个子系统。比如在图中，我们把系统拆分为交易系统和账户系统，这两个系统（或者说服务）通过内部的负载均衡进行相互调用，这个时候，底层数据库还没有拆分，两个系统还是访问同一个数据库。

通过拆分，系统整体就变成了 SOA 架构，这样，我们减少了代码冲突，系统的开发也变得更加高效，部署的时候，我们也更容易针对各个系统的处理能力进行水平扩展。

但这里的问题是，内部服务通过中心化的负载均衡进行访问，中心化的负载均衡增加了服务的调用时间。此外，在电商场景下，内部的服务很多，服务调用的频率很高，每秒可能有上百万次，导致了负载均衡的连接能力不够。而且负载均衡是单点，如果它出了问题，很容易引发系统整体的可用性问题（即使负载均衡是多实例，当系统流量很大时，也会因为某台负载有问题，导致其他节点压力增大而引起雪崩效应）。

服务调用去中心化

针对内部服务路由中心化的问题，我们去掉了内部的负载均衡，加入了服务注册中心，比如 ZooKeeper。

当服务实例启动或退出时，它们会自动在注册中心进行注册或销毁，服务的客户端和注册中心保持长连接，可以实时地获取可用的服务列表；然后在客户端，根据相应的算法选择服务实例，直接调用服务。每次调用无需经过注册中心，如果注册中心有问题，也只是新的服务实例无法注册，或者是已有的服务实例无法注销，这对客户端调用服务的影响是非常有限的。

当然，通过注册中心和更体系化的微服务框架，我们还可以实现完善的服务治理，包括服务隔离、熔断、降级，这些都是对原来简单的负载均衡方式的加强，能够进一步提升服务的可用性。

现在，我们解决了服务调用的问题，但随着业务量逐渐变大，数据表越来越多，数据量也越来越大，单个数据库（比如 Oracle）的性能和储存容量已经无法满足需求了。这个时候，我们就需要对数据库进行改造，提升它的处理能力。

垂直分库

对于单个数据库性能和容量瓶颈，解决的办法就是，我们对数据库进行垂直拆分，按照业务拆分为交易数据库和账户数据库，这样就可以满足它们各自的容量和性能需求，同时也避免了不同业务数据表之间的相互耦合。

你可以认为垂直分库是系统拆分的第二阶段，这样，通过第一阶段的应用代码拆分和这里的数据库表拆分，交易系统和账户系统就可以独立发展。

不过，新的问题又来了，垂直分库后，每个数据库都是单实例。随着业务的发展，和原来系统只有单个数据库类似，现在交易系统也只有一个数据库，它的性能和容量还是有问题，并且数据库单实例也带来了可用性的问题，如果数据库挂了，相应的系统也就不可用。

水平分库及高可用部署

针对单个数据库的可用性问题，我们可以采用 MHA 高可用（Master High Availability）方式部署。比如数据库部署一主多从，通过 MHA 机制，我们可以实时检测主库的可用性，如果主库有问题，系统会自动 Failover（故障转移）到最新的从库。另一方面，我们还可以利用多个从库支持读写分离，减轻主库的访问压力。

针对单个数据库的性能和容量问题，首先我们可以引入缓存，在高读写比的场景下，让应用先访问缓存，大大减轻对底层数据库的压力。然后，我们可以对数据库按照某个维度（比如用户维度），进行水平拆分，把数据记录分布到多个实例中，最终分散主库的写压力以及数据存储的瓶颈（在上一讲中，我已经具体介绍过了，你可以点击链接去回顾内容）。

在实践中，我们还可以提供多套水平分库。比如说，针对交易数据，我们可以同时按照用户维度和商户维度进行水平分库，用户维度的库用于前台用户下单的场景，商户维度的库用于后台商家履单的场景。这里，只有用户维度的分库会支持写，我们通过数据同步机制，把用户维度分库的更新同步到商户维度的分库里。

当系统体量发展到了一定程度，我们又碰到了新的问题：单个机房的服务器不够用，无法在同一个机房找到更多的机器部署交易系统和账户系统。

多机房部署

对于单机房服务器不够的问题，我们可以在新的机房部署交易系统和账户系统，为了落地方便，所有服务还是注册到旧机房的注册中心，数据还是存放在旧机房的交易数据库和账户数据库。 这样，我们通过在新机房部署应用，对应用节点进行水平扩展，从而解决了单机房机器不足的问题。

但这里产生了跨机房访问的问题：首先，我们只有一个服务注册中心，服务实例一部分部署在老机房，一部分部署在新机房，对于服务调用者来说，它会同时访问新旧机房的服务实例；其次，数据库部署在老机房，新机房的应用会访问旧机房的数据库。

这两种情况，都会产生大量的跨机房访问，我们知道，根据机房物理距离的不同，跨机房访问的网络延时在数十毫秒到数百毫秒之间，是机房内部通信耗时的上千倍，这会对应用的性能产生很大影响，而且跨机房的网络可用性也经常是一个问题。

服务调用本地化

为了避免服务的跨机房访问，我们在新机房也单独部署了服务注册中心，让每个机房的服务注册到同机房的注册中心。这样，客户端的服务调用会路由到同机房的服务端，实现了服务调用的本地化，大大降低了跨机房通信带来的延时和不可用性问题。

这时，随着业务越来越复杂，新的问题又来了：交易系统会依赖很多周边服务。比如下单后，我们需要给用户送积分，交易系统会同步调用积分服务。但是同步调用积分服务，一方面会影响下单的性能，另一方面如果积分服务不可用，会导致核心的下单功能失败。

依赖分级管理

对于外部服务依赖的可用性问题，我们的解决办法是，针对这些外部依赖进行分级管理，根据依赖功能的重要性不同，把它们分为强依赖和弱依赖。

对于强依赖，我们实时同步调用，比如在用户下单时调用库存服务，由于库存非常重要，必须实时扣减，如果调用库存服务失败，下单也失败。

对于大量的弱依赖，我们以异步消息的方式进行信息同步，比如对于积分服务，可以通过柔性事务来保证数据的最终一致性，这样大大提升了核心系统的性能和可用性。

不过，这里存在的问题是，新机房的交易系统和账户系统都在访问老机房的数据库，有跨机房数据库访问的性能问题，以及老机房整体故障带来的可用性问题。比如说，机房断电，通信光纤有问题或者发生自然灾害，导致老机房整体不可用，这就会导致所有系统都不可用。

多机房独立部署

针对机房整体不可用的问题，解决方案是，我们在多个机房做对等的部署，这样每个机房的系统可以形成内部闭环，包括服务、注册中心和数据库，机房之间不产生直接的相互依赖，从而实现了机房级别的水平部署。

如果系统的单元化做得完善，我们还可以进一步支持虚拟机房的概念，一个物理机房可以部署多个虚拟机房，每个虚拟机房包含了一个完整的系统。通过多机房独立部署，我们极大地提升了系统的可用性、处理能力和可伸缩性，可以应对系统面临的各种异常情况。

另外，最近几年，容器化技术的发展很快，原来很多的电商平台都是基于虚拟机部署，现在也纷纷改造为用 Docker+K8s 的方式部署，这大大提升了资源的利用率、系统的弹性伸缩能力。在面临资源瓶颈时，你可以考虑用这种方式来优化系统的部署。

总结

今天，我基于一个简化的电商系统模型，与你分享了电商平台的技术架构发展过程，我们是如何通过一步步的架构升级，解决系统各个阶段出现的高可用、高性能和可伸缩问题的，相信你现在对技术架构如何应对各种系统性挑战，有了更深入的认识。

值得注意的是，系统的技术架构变化不一定要完全遵循这个过程，不同的业务、不同的发展阶段，对系统的要求都是不一样的，这里我给出的只是典型的问题和解决手段，希望你在工作中，能够具体情况具体分析，灵活地运用这些手段。

业务在不断发展，新的问题会不断出现，但技术也在不断地进步，解决的手段层出不穷，我们需要不断学习，找到新的手段来解决问题。

最后，给你留一道思考题：你的公司当前的系统架构处于哪个阶段，面临什么样的问题呢？

欢迎在留言区和我互动，我会第一时间给你反馈。如果觉得有收获，也欢迎你把这篇文章分享给你的朋友。感谢阅读，我们下期再见。

20 | 从务实的角度，给你架构设计的重点知识和学习路径

你好，我是王庆友。

到目前为止，我们已经讲完了业务架构和技术架构的相关内容，相信你现在对架构有了更深入的理解。

学习架构呢，要掌握的东西有很多，你是不是开始担心自己一辈子都学不完呢？其实，我们也不需要一下子铺开学习所有的架构技能，重要的是把控好学习的节奏，在适当的时间学习适当的内容，我们可以结合实际工作，一步步地成长。所以今天这一讲，我想给你提供一些架构学习的重点方向和路径建议。

架构原则汇总

在技术架构篇，我针对系统的高可用、高性能、可伸缩和低成本，给你介绍了很多的架构设计原则，不同的原则对应着不同的目标，这里我把这些架构原则和目标汇总成一个表格，来帮助你更直观地了解它们。

限于篇幅，这里我挑选几个原则来重点说下：

可回滚 / 可禁用

可回滚原则确保了系统可以向后兼容，当系统升级出现问题的时候，我们可以回滚到旧版本，保证系统始终可用。

不过有些时候，系统回滚很困难。举个例子，如果数据库的新旧表结构差异很大，除了回滚代码，我们还要回滚数据库，这样操作起来往往需要很长时间，系统的可回滚性就比较差。所以在设计时，我们要尽量考虑数据库修改和代码的兼容性，并提前做好系统回滚的预案。

可禁用原则要求我们提供功能是否可用的配置，在系统出现故障时，我们能够快速下线相应的功能。比如说，新的商品推荐算法有问题，我们可以通过程序开关禁用这个功能。

使用成熟技术

作为开发人员，我们都很想尝试新技术，但我们知道，新的技术还没有经过充分的验证，它往往会存在很多隐藏的 Bug。

所以，作为架构师，我们在做方案设计的时候，一方面，要从系统的稳定性出发，尽量选择成熟的技术，避免因为新技术的坑而导致系统可用性出现问题；另一方面，选择成熟的技术也意味着选择了团队熟悉的技术，这样学习成本低，落地快。

使用同质化硬件

我们在做硬件选型的时候，要尽量选择相同的硬件和相同的配置。

比如说，对于服务器，我们选择同样的 CPU 和内存配置，以及同样的操作系统版本，这样我们更容易通过统一的自动化脚本，对节点进行配置，对系统做水平扩展时也会更加容易。

架构落地过程

这些架构原则都是我们要深入理解，并且在实践中要逐渐运用和掌握的。那么下面，我就带你来了解一下架构的具体落地过程，帮助你更好地理解架构师的职责和技能要求。

简单地说，架构师的职责就是负责设计架构，并跟踪架构的实施过程，解决过程中出现的疑难问题，确保架构顺利落地。在第 1 讲“架构的本质”中，我和你介绍过架构师的能力模型，比如抽象思维、平衡取舍、沟通能力等等。接下来，我就结合架构的落地过程和架构师的能力模型，来具体说下架构师是如何开展工作的。

架构师的工作从接到项目需求，或者从自己主动识别系统当前的问题开始，TA 的工作过程可以分为三个大阶段。

首先，架构师要和产品经理或者业务人员沟通，了解业务；和开发人员沟通，了解系统。

了解完系统和业务后，架构师接下来就要设计具体的方案，方案设计要分三步走：

首先，架构师针对业务需求，分解相应功能到现有的各个系统，把系统的各个部分串起来，这个第一版的方案至少要能够在表面上解决当前的问题，这样就形成一个草根的方案。

然后，架构师要进一步深入思考业务的本质，对现有的草根方案进行升华，比如说，通过抽象，让方案更加通用，可以解决多个类似的或潜在的业务需求，这样，草根的方案就变成了一个高大上的方案，这里很考验架构师的透过问题看本质和抽象总结的能力，

接下来，基于现有的各项约束，比如时间、资金和人员技术能力等因素，架构师要对方案进行简化，把高大上的方案变成一个接地气的方案，以最小的代价实现最大的价值，这里很考验架构师的平衡取舍能力。

方案设计好之后，最后还要进行宣讲，架构师需要说服相关的人员接受方案，并且在后续的方案执行中，负责跟踪架构的落地，如果过程中有疑难问题，架构师还要协助解决。

所以，我们可以看到，架构师在设计方案时，会有一个反复迭代的过程，最终才能得到一个简约而不简单的方案。并且在方案设计的前后，架构师还需要和大量的人员进行沟通，这些都需要架构师具备宽广的知识面和良好的沟通能力。

架构师知识结构

那么，架构师都需要掌握哪些具体的技能呢？这里我给你提供了一个简化的架构师技能图谱，可以帮助你循序渐进地学习这些架构技能。

首先，作为架构师，我们需要了解计算机硬件和操作系统的相关知识，它们是负责具体干活的，如果对它们有深入的了解，我们就能知道系统底层是怎么执行的，在做具体设计的时候，我们也就可以做各种优化。比如说，在设计 RPC 通讯框架时，我们可以通过 IO 多路复用和内存零拷贝技术，来提升服务端并发处理请求的能力。

在这之上就是具体技术相关的内容，从浅到深可以分为三个部分：

第一部分是开发相关的基本知识，比如数据结构和算法、具体的开发语言、常用的设计模式以及开发框架等等，这样你就具备了基本的开发能力。

第二部分是各种中间件知识，常用的中间件包括数据库、缓存、消息系统、微服务框架等等，对于这些核心中间件，我们不但要了解具体的用法，还要深入理解它们的适用场景。这样你就能写出高效健壮的代码，能够独立承担一个子系统的开发。

继续往下深入，你还要学习分布式系统相关的知识，包括底层网络和分布式通信技术，这样你就可以了解系统是怎么连接在一起的。除此之外，你还要了解一些周边的系统，比如大数据平台、运维监控系统、接入系统等等，这样，你就可以了解系统端到端的运行过程，从技术架构上保证系统的稳定可用。

掌握了这些技术能力之后，你就可以逐渐往全面的架构师发展了。比如说，你可以结合业务，来设计应用体系，包括数据模型和服务设计；你可以了解各种应用架构模型，知道它们的优缺点和适用场景，能够定义一个良好的应用依赖关系。

再往上，就是成为业务领域专家。在这个阶段，你已经知道如何通过业务拆分，实现业务之间的解耦；如何通过业务抽象，实现业务的扩展和重用。

到最后，你已经对各种架构设计的目标和架构原则都非常了解了，知道面对一个具体的问题，大致都有哪些解决的手段；然后，经过大量的实践，你能够把技术架构、应用架构、业务架构融会贯通，并针对具体情况，对架构的各个目标做良好的平衡。当然，作为架构师，你还要和一系列的人员打交道，这时候就需要你培养更多的软技能，能把复杂的架构问题以简单的方式表达出来。

架构师成长路径

现在，你已经清楚了作为一个架构师，TA 需要具备什么样的知识结构。如果你想成为一名架构师，在不同的成长阶段，你还需要学习不同的内容。这里，我以 Java 为例，进一步给出学习的重点内容，给你提供更具体的参考。

第一个阶段是初级开发阶段。

在这个阶段，你需要深入学习数据结构和算法，并且一定要深入掌握单体应用的分层架构，因为这是架构设计的基础。

另外，对 JDK 的一些核心类，你不能仅仅停留在使用层面，而是要深入研读源代码，了解它的内部设计。这样你就知道如何开发一个高效的程序，如何进行各种代码级的调优。

第二个阶段是高级开发阶段。

首先，你需要非常了解设计模式，每个设计模式都可以看做是一个小型的架构设计，这里面有很好的设计原则和抽象思维，你在做系统设计时可以借鉴它们。

然后，你需要非常了解核心的中间件，包括 DB、微服务框架、缓存和消息系统，要清楚地了解它们的适用场景（比如消息系统的削峰、解耦和异步），知道如何对它们进行调优，以及了解它们都有哪些常见的坑等等，核心中间件是我们做技术选型的基础。

同时，你要深入掌握数据库设计和服务接口设计，了解它们的最佳设计实践，它们承载了系统核心的业务数据和业务逻辑。

最后，你需要进一步研读源码，源码是活的教材，它包含了大量实用的设计原则和技巧。这里我建议你选择一些开源的开发框架和 RPC 通信框架，去深入了解它们内部的实现原理，比如 Spring 和 Netty。

第三个阶段是架构师阶段，成为技术专家。

首先，你需要深入了解网络通信，比如说网络分层和 HTTP/TCP 协议，还有各种常见的 RPC 通讯框架，了解它们的特性和适用场景，这样你在设计分布式系统时，就能够进行合理的技术选型。

然后是了解底层系统，包括 JVM、操作系统和硬件原理，再往上延伸到系统的接入部分，了解常见的负载均衡特性和用法，这样你可以对整体的系统有个透彻的了解，把各个环节可以很好地衔接起来。这里，我特别建议你去读下 Java 和 JVM 的规格说明书，了解 Java 的底层设计。

最后，你需要熟练掌握各种设计工具和方法论，比如领域驱动设计和 UML，了解常用的架构设计原则，这样你就能够结合业务，选择合适的应用架构和技术架构并进行落地。在这一阶段，对你总的要求就是能够从端到端的角度进行业务分析和系统设计。

第四阶段是大师阶段。

在这个阶段，你需要对架构的各个目标都非常了解，除了业务系统设计，你还要对运维和监控有深入的认知。同时，你需要了解业界的架构实践，跟踪技术的发展趋势，如果出来一项新技术，你可以比较准确地对它进行定位，把它纳入到自己的能力体系当中。

另外，在这个阶段，你也已经通过大量的实践，培养了很好的软技能，比如沟通能力、项目管理能力等等。那么最后，你就能做到技术和业务的融会贯通，可以平衡各种架构目标，设计非常实用和接地气的架构，并保障它的顺利落地。

架构师境界

你可以发现，架构师的能力是一个逐渐提升的过程，如果从架构师的境界来看，由浅到深可以分为四层：第一层看山不是山，第二层看山是山，第三层看山不是山，第四层看山是山。

这是一个螺旋式上升的过程，那么它究竟是什么意思呢？

刚接手项目的时候，你对业务还不太了解，经常会被业务方冒出的术语弄得一愣一愣的，如果把现有问题比作山，那就是横看成岭侧成峰，你根本摸不透，此时看山不是山。

经过业务梳理和深入了解系统以后，你能够设计出一个简单的方案，把各个系统串起来，能解决当前的问题，对当前的这个“山”能够看清楚全貌，此时就做到了看山是山。但这样的方案往往设计不够，只能解决表面问题，碰到其它类似问题或者问题稍微变形，系统还需要重新开发。

通过进一步抽象，你能够发现问题的本质，明白了原来这个问题是共性的，后续还会有很多类似的问题。然后你就对设计进行总结和升华，得到一个通用的方案，它不光能解决当前的问题，还可以解决潜在的问题。此时，你看到的已经是问题的本质，看山不是山。但这样的方案往往会过度设计，太追求通用化，会创造出过多的抽象概念，理解和实现起来都特别困难，过犹不及。

最后回到问题本身，你能够去除过度的抽象，给出的设计简洁明了，增之一分嫌肥，减之一分嫌瘦，既能解决当前的问题，又保留了一定的扩展能力，此时问题还是那个问题，山还是那个山。这样的方案在了解问题本质的基础上，同时考虑到了现状，评估了未来，不多做，不少做。

你可以对照这四个境界，来评估你当前的架构能力，不断地提升对自己的要求。

总结

今天，我汇总了常见的技术架构设计原则，它们都是实践的总结，你在做架构设计时，可以参考这些原则，在项目中采取相应的手段来实现架构目标。值得注意的是，在做具体的架构设计时，你需要对设计进行反复迭代，才能最终得到一个高性价比的方案。

针对架构师的成长，我也给你提供了相应的知识结构和可行的进阶之路，希望你能够一步步成长，最终实现自己的理想。

读万卷书，行万里路。架构师的成长尤其如此，架构没有速成之路，我们先要“读万卷书”，学习各种架构需要的技能，然后“行万里路”，通过大量的实践把架构知识变成架构能力。

最后，给你留一道思考题：一个架构方案从调研到设计再到落地，你觉得最困难的地方是什么？

欢迎在留言区和我互动，我会第一时间给你反馈。如果觉得有收获，也欢迎你把这篇文章分享给你的朋友。感谢阅读，我们下期再见。

结束语 | 和你聊聊我的架构心路历程

你好，我是王庆友。

今天是专栏的最后一篇，我和你分享的核心内容马上就要结束了，我是感到既欣慰，又觉得如释重负。

说实话，架构的话题不好写，内容涉及面比较广，既要有理论的高度，又要有实践的细节，才能把这个话题讲得透彻。我看到很多的同学在评论里反馈课程的内容很干，都在反复地吸收和体会，这说明课程的内容对你有价值，相信你也有了很多的收获。在这里，我也很感谢你一直保持着学习的热情。

不知你有没有感觉到，整个专栏的内容既很虚又很实，这里“虚”指的是理论的高度概括，“实”指的是接地气的案例介绍。所以在这里，我想再和你简单地分享一下专栏的写作思路，帮助你更好地理解和使用这个专栏。

专栏的写作思路

整个专栏一共有 7 讲是理论篇，这个也是专栏的主体内容框架，如下图所示。

第 1 讲“架构的本质”，是专栏的总纲，通过架构的本质和架构分类体系介绍，让你迈进架构认知的大门。

然后，针对业务架构和技术架构，分别有一篇文章深入介绍它们的定位，帮助你从源头区分这两种架构，业务架构聚焦人脑如何理解业务，针对的是业务性功能；技术架构聚焦电脑具体如何干活，针对的是系统性功能，这两者的目标以及处理手段都是不同的。

（补充说明：应用架构更多的是业务架构的具体落地，在专栏中，我把应用架构和业务架构糅合，比如说第 4 讲“电商平台架构是如何演变的”，实际上讲的是应用架构。）

最后，针对每种架构的核心目标，都有一篇文章专门分析每个目标的实质和实现手段。

通过这 7 篇偏理论性的文章，我希望帮你建立对架构的体系认知，让你能从总体上清楚架构设计要做什么以及如何做。以后，当你碰到更多的架构相关内容，都可以往这个架构框框里面套，进一步丰富和完善这个体系。

除了相对体系化，整个专栏还有一个特点就是案例丰富，在我刚才说的偏理论的 7 篇文章中，就有大量的案例片段。此外，针对 4 个核心的架构目标，我也分别提供了 3 个完整的实际案例，让你能够从多个角度理解实现架构目标的手段，你也可以通过这些案例，深入体会架构设计的具体过程。

你可以发现，无论是理论，还是案例部分，我都是用自己的语言来描述，和你分享的是我自己对架构的理解。如果你在架构上有比较多的经验，可以马上领悟到要点；如果一下子消化不了，你也可以多读几遍，相信你每次都会有新的收获。

我的架构实践

我在成为架构师的过程中，其实也是自己一路摸索过来的，现在回头再看这个过程，我觉得有些知识和技能，对于架构师的成长非常重要，这里我想和你分享一下，希望能对你有所启发。

GoF 设计模式和 J2EE 设计模式

我对 GoF 的 23 个设计模式和 J2EE 设计模式都作过深入了解，GoF 设计模式的粒度比较小，针对的是类级别的关系定义；J2EE 设计模式的粒度比较大，针对的是企业级系统设计。

这些设计模式都很经典，它们提炼了不同业务场景下的解决方案，让你能够很体系地理解问题是什么（What），怎么解决的（How），以及为什么要这么做（Why）。通过学习这些设计模式，我培养了良好的抽象设计能力，也很好地了解了具体的设计手段。

JVM/Java 规格说明

我看过很多遍 JVM/Java 设计规格说明书，通过理解语言的底层机制，我对 Java 的上层特性有了更透彻的了解。比如对于 Java 的垃圾收集，如果你很清楚它的原理和运行机制，你就知道如何优化代码，以及当系统出现 OOM 的时候，如何去快速定位和解决问题。

源代码阅读

对源代码的阅读也很重要， 我读过很多 JDK 核心类的源码，比如说 String、HashMap 和 Future 等等；我也深入阅读过一些开源框架，比如 CXF 框架、Hessian 通信协议等等。

通过阅读源码，你可以深入了解相应的内容，还可以通过理论结合实际，深入掌握设计技巧。

分布式通信

我从开发一路走过来，接触过很多分布式通信协议，比如最早的 DCOM、RMI、CORBA 和 Web Service，再到后面百花齐放的 Hessian、GRPC、Thrift 等等；至于具体的开发框架，我也深入了解过 Axis、Dubbo、Spring Cloud 等等。对这些技术的优劣点和适用场景的系统了解和学习，让我在设计分布式系统时，能够选择合理的应用之间的集成方式。

数据库和 API 设计

数据库设计和接口设计是架构设计中很重要的内容。

我做过大量的数据库设计，它代表了系统数据层面的抽象。如果你能合理地定义数据模型，那么系统的业务逻辑和性能基本也就确定了。另外，我也曾经做过比较完整的 Open API 平台，这些接口设计经验，让我掌握了如何通过适度的抽象保证接口的复用。

1 号店架构设计

我在 1 号店承担了很多大项目的设计，有些是偏业务的，比如针对基础业务的服务化改造；有些是偏技术的，比如订单水平拆库和灰度发布系统。通过这些架构实践，把我很多的架构知识变成了实际的架构能力，并且通过方案的整体设计，让我可以把原有各个部分的能力整合在一起。

总而言之，你可以看到，我的成长经历虽然没有一个明显的主线，但还是隐隐有迹可循的，大致可以遵循以下过程。

打造基础能力

首先，你要对架构设计的各个要点有深入的了解，包括数据结构和算法、设计模式、数据库和服务设计、分布式通信等等。对于这些要点，你不能仅仅停留在使用层面，而是要深入理解它们的内部机制，这样你就打造了扎实的基础能力。

建立体系

在了解了各个设计要点的基础上，你需要对架构设计建立体系化的认知，能够从整体上认识架构，清楚架构的设计目标、设计过程和设计手段。你之前是从各个局部来考虑问题，现在要变为从整体的角度来考虑问题。在架构设计上，我们宁可要精确的模糊，也不要模糊的精确。

纳入体系

有了整体的认知体系以后，你就有了存放架构内容的框架。然后，你可以从广度和深度两个方面，来不断丰富和完善你对架构的认知。

实践运用

最后，通过大量的实践，你就可以把原先储备的架构知识，以及各项基础能力有效地串接起来，最终打造完整的架构能力。

架构师的能力，既涉及到业务和技术，又涵盖了它们的广度和深度。在成为架构师的过程中，你可以积累各项能力，把以往的知识和经验串起来，这其实就相当于一个银行，你可以不断地储蓄，然后进行整体输出，这是一个很好的个人成长和发挥价值的途径。

写在最后

最后，我想说的是，这是技术最好的时代，我们有很好的技术可以选择，有很开放的技术分享氛围，有很好的技术回报。但这也是技术最差的时代，技术太多，变化太快，我们还需要不断地学习。

想要成为信息时代的弄潮儿，除了努力，你还需要有方向，我希望这个架构专栏，可以成为你学习架构的指路明灯，帮助你更好地成长。

好了，这就是我作为一个架构老兵，想和你分享的经历和思考。专栏的正文更新到这里就要告一段落，但是更新的结束并不意味着我们之间就要切断联系，之后呢，我还会针对整个专栏的内容，给你一套系统性的结课测试题，让你可以检测一下自己的学习成果。并且，我还会继续回复你的留言，如果你对于架构有新的问题和思考，也欢迎继续和我一起交流。

在文章的结尾，我还为你准备了一份毕业问卷，题目不多，希望你能抽出几分钟时间填写一下，我非常希望听听你对这个课程的意见和建议，欢迎你在问卷中畅所欲言，期待你的反馈！

我是王庆友，感谢你一直以来的学习和坚持，也感谢你的留言和反馈，相信对你对我，这都是一段非常有意义的成长经历，让我们一起享受架构学习的乐趣吧，我们江湖再见！