This commit is contained in:
louzefeng
2024-07-09 18:38:56 +00:00
parent 8bafaef34d
commit bf99793fd0
6071 changed files with 1017944 additions and 0 deletions

View File

@@ -0,0 +1,149 @@
<audio id="audio" title="02 | 业务架构:作为开发,你真的了解业务吗?" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/ab/22/aba8fe4a1b83d403a92944ec51085322.mp3"></audio>
你好,我是王庆友,今天我们一起聊聊业务架构。
作为开发人员我们平常讨论比较多的是技术层面的东西比如Spring框架、Redis缓存、MySQL数据库等等我们喜欢讨论这些是因为纯技术的东西比较通用和业务相关性不大沟通起来比较方便。
但你要知道,一个项目能否成功落地,首先需要的是把业务分析做到位,至于选用什么技术来实现,这是我们第二位才去考虑的因素。**从架构角度看,业务架构是源头,然后才是技术架构**。所以,作为专栏的第二讲,今天我们就从业务架构开始说起。
在软件开发的过程中,你肯定知道需求分析是怎么回事,但不一定知道业务架构设计是怎么回事;你也肯定清楚需要产品经理这个角色,但不一定清楚有时还需要业务架构师这个角色。关于需求分析和业务架构设计,相信你经常会有以下几个疑问:
1. **业务架构师和产品经理有什么区别?**
1. **需求分析和业务架构设计有什么区别,业务架构到底有什么用?**
我们知道,项目的开发都是从收集业务需求开始的,原始的需求一般来自于最终用户。但是,每个用户其实只清楚自己所负责的那部分,因此这些原始需求往往是零散和碎片化的,特别是当一个业务流程跨多个部门的时候,更没有一个人能够说清楚这个业务的全貌。
所以说,仅仅基于这些原始的需求来指导开发是远远不够的,这时,就需要产品经理和架构师介入进来,填补这段空白。
接下来,我们就一起看下,产品经理和架构师在这个过程中都会做些什么,他们是如何帮助业务落地的。
## 产品经理的职责
简单来说,产品经理的职责就是:**告诉用户,系统长什么样子;告诉开发,他要实现什么功能。**
产品经理首先会收集用户的原始需求,然后,将它们梳理成一个个业务流程,每个业务流程由多个业务步骤组成。一个业务步骤包含三部分的内容:输入、输出和业务功能。
比方说,一个典型的交易流程,它包含商品浏览、商品加购物车、下单、支付等步骤。其中,下单步骤的输入,就是订单的各种信息,下单的功能,就是整合这些信息,创建一个具体的订单,而下单的输出结果,就是新创建的订单。
<img src="https://static001.geekbang.org/resource/image/9e/e9/9e0b775a1b358ec4deef9a1bd066cce9.jpg" alt="">
需求梳理好后,产品经理会把每个步骤具体化为页面原型。在原型中,会以直观的方式给出各个步骤的输入或输出,以及用户的操作过程,最后再把这些页面串起来,形成一个业务流程。
你可以看到,经过产品经理的工作,大量零散的原始需求经过梳理和关联,变成一系列有序的业务流程,以及流程里面的业务步骤(业务步骤也称之为业务节点),然后产品经理把这一系列的业务流程和业务节点以用户界面的方式定义出来,总的来说,产品经理定义了系统的外表。
这些产出对于用户了解系统长什么样子,应该如何使用这个系统,以及系统是否满足他们的需求来说,是足够的,但对于开发者来说还远远不够,因为他们需要能进一步看到系统的内部结构。
而这一步,就是业务架构师要做的事情。
## 业务架构师的职责
在这之前,我们不妨先思考下,如果是按照产品的输出,直接以业务流程的角度来构建系统,会是什么样子呢?
如果按照这个思路,我们将为每个业务流程搭建一个对应的系统模块,然后业务流程中的每个业务步骤,将对应系统模块中的一个接口,包括它的功能、输入和输出。
就拿前面的购物流程来说我们设计一个购物流程模块里面包含商品查询、添加购物车、下单和支付接口来分别对应流程里的4个业务步骤。
以这样的方式构建系统,表面上看起来,业务和系统的映射好像非常简单,但在实际中,落地的难度非常很大。因为只是这样一个小小的购物流程模块,就要同时涉及商品、购物车、下单和支付四个业务,模块的开发者要同时非常清楚这四部分的数据模型和业务逻辑。
同样的道理,系统里的其他模块也是包含多个业务领域的内容,如果一个业务领域的需求发生了变化,比如说,订单要增加一个新的状态,那么所有涉及该订单的模块都要知道这个变化,并要做出相应的调整。这就要求,每个开发者都是全知全能的,对所有业务都了如指掌,我们知道,这是不可能的。
每个业务都有其本身的专业性,比如订单业务、商品业务、支付业务,它们的数据模型和业务逻辑都相当复杂,构成了一个个相对独立的业务领域。如果我们是按照业务流程来划分系统模块,结果是把不同业务混在了一个模块里,所以,这种模块划分的方式并没有降低总的业务复杂度。
我们可以换一种做法,先把所有的业务流程拆散,这样得到了一堆业务节点;然后把业务节点进行归类,相关的业务节点放在同一个系统模块里。判断节点是否相关,主要看它们是否属于同一个业务领域,比如一个订单查询的节点,和订单创建的节点,它们都属于订单域,那么这些节点都可以归属到同一个订单模块里。
下图就清楚地表示出了系统模块按业务流程拆分,和按业务域拆分的不同。
<img src="https://static001.geekbang.org/resource/image/6e/74/6e4fcfb3784531aa4365730b90fa7374.jpg" alt="">
- 如果按照业务流程来拆分系统模块,那么,有多少业务流程,就有多少个系统模块,这个对应关系比较直接,但实现起来很困难。
- 如果按照业务域来拆分,有多少业务领域,就有多个系统模块,流程中的业务节点按照业务域的不同,可以划分到不同的系统模块。
在实际业务场景中,一个业务节点可能会涉及不同业务领域的功能。比如说,一个下单节点,会涉及到获取商品信息、获取用户信息、扣库存、下订单等多个业务功能,那么你就可以进一步分解这个节点的功能,把不同的功能分到对应的业务域和系统模块。
基于业务域,构建了系统模块后,我们就可以按照这样的方式还原整个业务流程,比如上面的购物流程例子,我们就可以这样还原它:
>
**购物流程=商品模块.商品搜索+购物车模块.添加商品+订单模块.创建订单+支付模块.支付**
如果你把这个定义画成序列图,就很直观和容易理解,也比较符合开发人员思维,系统实现起来非常容易。通过这种系统模块之间的不同功能组合,我们很容易给出各个业务流程的定义。
所以,**对业务架构师来说TA的工作就是把业务流程和节点打散按照业务域的维度来划分系统模块并定义这些模块之间的关系最终形成一个高度结构化的模块体系**。这样,开发人员既容易理解业务,又方便落地系统。
现在,我们就可以回答文章开头的问题了,**产品经理和业务架构师的工作既有区别又有联系,简单地说,产品经理定义了系统的外观,满足了用户;业务架构师在此基础上,进一步定义了系统的内部模块结构,满足了开发人员。**
当然,满足现有的业务需求,保证系统顺利落地,这只是业务架构的最基本目标,业务架构的意义远不止于此,它有一系列更高的目标,下面,我就逐一为你展开介绍。
## 架构目标之一:业务的可扩展
第一个目标是业务的可扩展,我们都知道,业务需求是不断变化的,不断创新是业务的内在要求。而对于系统来说,它的要求却是相对稳定,尽量避免大的调整。
**那么,我们如何才能实现业务的快速变化和系统的相对稳定呢?**
这也是业务架构要重点解决的问题,具体地讲,业务架构设计要能支持打造一个柔性系统,通过提供良好的业务扩展性,允许业务不断调整和快速生长。
可以看到下图中,左边部分就比较形象地展示了业务和系统的不同特点:**业务的主题是变化和创新,系统的主题是稳定和可靠。**
<img src="https://static001.geekbang.org/resource/image/67/23/677b2ee621f753e67730b156eeed2023.jpg" alt="">
在右边图中,我们通过巧妙的业务架构设计,很好地解决了业务和系统之间的矛盾。
这里,我们把业务平台和业务线剥离开,让业务平台封装基础通用的功能,这样,它就变得相当地稳定;让各个业务线包含自己的个性化需求,业务线只依赖业务平台,业务线彼此之间互相独立,可以自由变化。这样的业务架构设计,就同时保证了系统的相对稳定和业务的快速创新。
为了帮助你更好地理解业务架构的扩展性,这里,我给出了支付宝的业务架构变化过程。
<img src="https://static001.geekbang.org/resource/image/78/b9/78f16439bc590b8e2e5d6e105cd89bb9.jpg" alt="">
在支付宝一代的业务架构中,前台的业务和后台的业务直接耦合,形成了多对多的网状结构,如果修改一个后台业务线,就会影响到很多前台业务线;如果增加一条新的前台业务线,需要同时和很多后台业务线对接,这样的架构无疑是对业务的扩展非常不利的。
而在支付宝二代业务架构中,你会发现,他们在前后台业务线之间,构建了独立的支付清算平台,从而实现了前台业务和后台业务的解耦。
在这里,不管前台业务,还是后台业务,都只需要对接中间的支付清算平台,把系统的变化收敛到一个点,而业务线之间相互不影响,这样的方式,自然可以很好地支持业务扩展。
好了,这里我们说完了业务架构的可扩展目标,接着再说说业务架构的另一个目标:可复用。
## 架构目标之二:业务的可复用
你肯定会有这样的体验:一个项目过来,你和伙伴们一起加班加点、紧赶慢赶,总算把它成功落地了。结果这时候又有另一个类似的项目过来,你们又要按照同样的方式,重新吃一遍苦,结果就是开发不满意,项目经理不满意,老板也不满意。
**对于类似的业务需求,如果原来做的工作可以大量复用的话,这是非常理想的结果,无论对于开发效率和开发质量的提升都很有帮助。**
当然,能不能复用,能在多大程度上复用,这和业务架构设计很有关系,也是业务架构设计的重要目标之一。
**那么,业务架构设计如何实现业务的可复用呢?**
你可以试想一下,在业务架构设计中,如果只是简单地基于业务流程来定义系统模块,这个系统模块就要和业务流程严格对应。我们知道,业务流程对应业务场景,而业务场景是经常变化或是定制的,这就导致系统模块也是经常变化和定制的,那么,这样的系统模块就很难在不同业务场景中复用。
如果我们按照业务域来划分业务,把业务流程中的节点拆分到各个业务域,按照业务域构造系统模块,这样的复用效果会如何呢?
我们都知道,业务域是相对固定的,它有明确的数据模型和业务规则,这样一来,系统模块也就比较固定和通用,也就具备比较好的复用基础。
但要想实现高复用,业务架构对系统模块的定义,还有更多的要求。
**首先,模块的职责定位要非常清晰**。对于模块来说,在定位范围内的职责要全部涵盖到,而不在这个范围的职责全部不要。
**其次,模块的数据模型和接口设计要保证通用**。架构师需要归纳业务场景,通过抽象提炼,形成通用化的设计,以此来满足多个类似场景的需求。
>
小提示:清晰的模块定位和通用化设计,是模块能够复用的内在要求。
**最后,实现模块的高复用,还需要做好业务的层次划分**。我们知道,越是底层的业务,它就相对更固定。举个例子,同样是订单业务域,对于底层订单的增删改查功能,不同类型的订单都是一样的,但对于上层的订单生命周期管理,外卖订单和堂食订单可能就不一样。
所以,在做高复用设计时,我们可以尝试把一个业务域按照层次拆分得更细,比如,把订单模块拆分为多个上层订单模块和一个基础订单模块,这样,基础订单模块对于所有类型的订单,都能够提供复用。
就拿当前非常流行的微服务架构来说,很多公司在微服务的基础上,通过服务分层,进一步落地了共享服务体系和中台架构,这些都是业务架构复用能力的体现。
下面是一个三方支付平台的业务架构图,你可以看下,在一个实际的业务架构中,模块是怎么划分的,架构的可扩展和高复用是如何体现的。
<img src="https://static001.geekbang.org/resource/image/aa/cd/aa9d4457111224ced6c4c26c4ea03acd.jpg" alt="">
## 总结
今天,我带你了解了产品经理和业务架构师的不同职责,产品经理是站在用户的角度进行需求分析,而业务架构师是站在开发者的角度定义系统内部结构。通过今天的讲解,你应该对业务架构也有了更清楚的认识。
除了满足当前的业务需求外,业务架构师还需要面向未来,实现业务的可扩展和高复用两大目标,我也大致介绍了架构师实现这些目标的思路。在接下来的文章里,我还会针对这两大目标,结合实际案例,具体讲解如何实现它们,让你能更加深入地理解业务架构设计,并可以在工作中学会去运用这些手段。
**最后,给你留一道思考题**:产品经理和业务架构师都是分析业务,产品经理为什么不能兼业务架构师的角色?他们的能力模型有什么区别?
欢迎在留言区和我互动,我会第一时间给你反馈。如果这节课对你有帮助,也欢迎你把它分享给你的朋友。感谢阅读,我们下期再见。

View File

@@ -0,0 +1,169 @@
<audio id="audio" title="03 | 可扩展架构:如何打造一个善变的柔性系统?" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/09/99/097098ed411c5246d3a825389a071399.mp3"></audio>
你好,我是王庆友,今天我和你聊一聊如何打造可扩展的架构。
在实际工作中,业务需求总在不断变化,因此,你经常会面临以下这些问题:
- 如何快速地上线新业务?老板很可能明天就想看到效果。
- 对某个功能进行修改,如何不影响到系统其它的功能?
对于新的需求变化,我们一方面要快快搞定,另一方面要稳稳接住。但问题是软件虽然姓“软”,但也不是想变就能变,如果事先没有经过良好的设计,调整起来,往往牵一发动全身,导致系统到处出问题。
那如何设计一个具有良好扩展性的系统,能够快速支持业务变化落地呢?
接下来,我们围绕系统的可扩展,先来了解下什么是系统,什么样的系统才能具备良好的扩展能力。然后通过一个实际的例子,说明如何通过架构手段打造一个可扩展的系统。
## 系统的构成:模块+关系
我们天天和系统打交道,但你有没想过系统到底是什么?在我看来,系统内部是有明确结构的,它可以简化表达为:**系统 = 模块+关系。**
<img src="https://static001.geekbang.org/resource/image/9e/8d/9ebf45d4bbaa1cf35873c2def64b618d.jpg" alt="">
在这里,模块是系统的基本组成部分,它泛指子系统、应用、服务或功能模块。关系指模块之间的依赖关系,简单地讲,就是模块之间有调用,我们知道,调用区分发起方和服务方,因此,依赖关系是有方向性的。
这个模型虽然简单,但它给我们提供了一个深入分析系统的工具。接下来,我们就从业务扩展性出发,讨论什么样的模块是容易修改的,什么样的依赖关系是容易调整的。
### 模块
我们先看模块,模块定义系统都有哪些基本的“玩家”,分别承担什么职责。从业务的角度看,每个模块都代表了某个业务概念,或者说业务领域。
模块内部由数据和业务逻辑组成,其中数据是核心,业务逻辑围绕着数据,对数据做进一步加工,方便外部使用。
从扩展性的角度出发,首先,我们对模块的要求是:**定位明确,概念完整。**
每个模块要有明确的定位模块有了定位说明我们已经想清楚了它的核心职责是什么这样每个人对它的期望和理解就会一致。在实践中我们经常会争论一个功能应该放到A模块还是B模块表面上看各有各的道理谁也说不服谁但如果对照模块的定位回到模块设计的初心我们往往很快就能有答案。
定位比较抽象,在具体划分模块职责的时候,要保证模块业务概念的完整性。数据上,模块需要覆盖对应业务领域的全部数据,比如一个订单模块,它要覆盖所有渠道的订单,包括三方平台的订单、自有商城的订单、线下门店的订单等,这些不同类型订单的数据模型和实际数据,都由订单模块负责。
功能上,模块要包含业务领域的全部功能,比如订单模块包含所有订单相关的功能,包括订单数据的增删改查、订单业务规则校验、订单的状态和生命周期管理等。
其次,模块还要:**自成体系,粒度适中。**
模块的业务逻辑**尽量围绕自身内部数据进行处理**,对外部依赖越小,模块的封装性越好,稳定性也越强,不会随着外部模块的调整而调整。
模块的粒度要保持适中,不能为了追求定位清晰,把粒度划分得很小,导致系统的碎片化。比如系统早期的时候,一般我们把积分功能放到用户模块里面,不单独构建积分模块,如果后续积分的概念越来越突出,承载的业务越来越复杂,到时候可以把积分功能分离出来,单独成模块。
这里,为帮助你更好的理解,我举一个模块划分的反面例子。在实际工作中,很多老系统都有体量很大的模块,我们称之为“肿瘤”,它的特点就是定位模糊,职责泛滥,功能无所不包,这样,模块的可维护性很差,没人敢轻易对它动刀子。
好了,说完了模块,我们再继续看下模块的依赖关系。
### 依赖关系
依赖关系定义了模块如何协作,一起完成业务流程,依赖关系实质上体现的是模块的组织结构。
如果不对模块的依赖关系做针对性设计的话依赖关系就是一个多对多的网状结构一个有N个模块的系统理论上有N×N个依赖关系如果考虑依赖具有方向性这个数字还要加倍。
所以,要简化模块的依赖关系,我们就要同时简化依赖的方向和减少依赖的数量。
首先,我们希望模块之间的依赖是单向的,尽量避免相互调用,为什么单向更好呢?我们知道业务流程是有顺序的,如果模块依赖关系越直观地体现业务流程的顺序,越能帮助人理解,否则,我们会被双向的依赖箭头绕的晕头转向,很难通过模块之间的依赖关系还原实际业务的处理过程。
接下来,我们看下模块的组织结构。我们知道,网状结构是一种松散的结构,节点之间的依赖关系比较复杂,一般用于表示非正式的关系,比如人群的社交关系;而层次结构是一种更有序的结构,一般用于表示正式的关系,比如公司内部的人员关系。
在模块的组织结构设计上也是如此,我们要尽量把网状结构转化为层次结构,模块结构层次化是简化模块依赖关系的有力手段。
具体做法就是,我们按照模块定位的不同,把模块划分为不同层次,比如划分为上面的应用层和下面的资源层。这样,一个层通过把多个模块组织在一起,就形成了概念上更大粒度的模块。有了层以后,我们理解业务时,因为模块定位相同,往往关注这个更大粒度的层就可以,依赖关系只要指向这个层,而不是层里面的各个模块。这样,从人理解业务的角度,依赖的数量大幅度地减少了。
另外,我们知道,层与层之间的依赖关系都是层与层之间自上而下的依赖,相对于多对多的网状依赖,层次依赖的方向更清晰,特别符合人的理解习惯。
举个具体例子作为开发我们都比较了解MVC架构系统模块按照定位分为表示层、应用层、聚合服务层、基础服务层。
- **表示层**对应前端的模块如App、小程序、公众号等属于View层。
- **应用层**对应和前端表示层直接关联的服务端属于Control层。
- **聚合服务层**如果系统业务比较复杂经常需要单独的聚合服务层负责业务流程的编排组合这个属于Model层的加强。
- **基础服务层**代表最基础的业务模块管理如订单、商品、用户等属于实际的Model层。
我在这里贴了一张MVC分层结构图你可以看到模块总体上是非常清晰的层次结构。
<img src="https://static001.geekbang.org/resource/image/7d/b0/7d0eb4e3c00a289568a1fca3deaf83b0.jpg" alt="">
现在,我们清楚了一个可扩展系统对模块和依赖关系的要求,接下来,我们再回到系统扩展性目标,做个深入总结。
## 扩展性的本质
在文章开头,我们说因为业务总在变化,所以需要架构设计给系统提供良好的扩展性。
这只是表象深层的原因是一个新的需求进来系统不只是为它增加一个新功能这么简单系统的调整会引起一系列的连锁反应从而大面积地影响系统的现有功能。架构设计时如果模块划分的不好一个N个模块的系统它的复杂度就是N×N这个在上一讲介绍的支付宝一代架构中体现得很明显。如果再加一个新的模块复杂度就变成(N+1)×(N+1),系统的复杂度随着功能的数量指数级地上升,这样一来,当系统的规模到一定程度,复杂度就会失控,导致系统彻底无序。
所以,要支持系统的扩展,架构设计上必须能够控制系统的复杂度,面对新需求,要让系统复杂度做加法而不是乘法,从而保证系统的调整是局部化和最小化的,所以,业务架构扩展性的本质是:**通过构建合理的模块体系,有效地控制系统复杂度,最小化业务变化引起的系统调整。**
那如何打造一个合理的模块体系呢?具体的架构手段就是按照业务对系统进行拆分和整合:**通过拆分,实现模块划分;通过整合,优化模块依赖关系。**
接下来我们以一个在线出行公司为例它有出租车、快车和顺风车3条业务线来具体看下如何为它打造合理的模块体系。
## 打造可扩展的模块体系:模块拆分
我们先对系统进行模块化拆分,拆分有两种方式:**水平拆分和垂直拆分。**
<img src="https://static001.geekbang.org/resource/image/c2/82/c2bad813a8dbaca121a701abebf82682.jpg" alt="">
### 水平方向拆分
水平拆分是指从上到下把系统分为多层,按照系统处理的先后顺序,把业务拆分为几个步骤。
比如整个叫车过程我们可以分为UI展现、地图搜索、运力调度和订单支付等几个环节这是根据系统的处理过程进行划分的。
这样一来,我们就把一个复杂流程,分解为几个相对独立的环节,分别进行处理,这么做带来了很多好处。
首先UI展现部分独立成为一个模块实现了前后端的分离。我们知道前端的用户体验和界面样式会经常变化而后端的数据和业务逻辑相对稳定通过水平拆分我们实现了稳定部分和不稳定部分的分开避免相互影响。
这里的后端包含三个模块,其中地图搜索负责路径规划,运力调度负责人车匹配,订单支付负责交易管理。
可以看到,通过水平拆分,可以使每一块职责都比较明确,功能内聚,每个模块管理自己内部的复杂性。同时,模块之间相互松耦合,一个模块的修改不影响另一个模块,比如地图搜索模块中改变了优先路径的推荐,不会影响运力调度模块中的人车匹配算法。
水平分层可以很好地满足现有业务做深度扩展,当业务有变化时,系统在特定层做调整,对其他层影响有限,这样把变化局限在一个小范围。
### 垂直方向拆分
垂直拆分指的是按照不同的业务线拆分,比如,将整个出行业务分为出租车业务、快车业务和顺风车业务,按照不同的业务场景,自上而下进行竖切,让每个业务都自成体系,形成自己的业务闭环。
通过垂直拆分,一个复杂的出行场景就拆分为几个具体的场景,我们可以根据各个业务线的特点去设计系统,从而降低了整个系统的复杂性。
垂直拆分可以很好地满足业务广度上的扩展,比如说增加一条新的业务线,可以按照这个思路落地系统。
**一般做业务架构时,我们先考虑垂直拆分,从大方向上,把不同业务给区分清楚,然后再针对具体业务,按照业务处理流程进行水平拆分。**
如果同时进行垂直拆分和水平拆分,一个大系统被拆分为了一个二维的模块矩阵,每个模块既属于某个业务线,也属于业务流程的某个环节。这样一来,每个模块的职责都很清晰,当业务变化了,我们可以清楚地知道,这个变化涉及哪些模块,然后,对这些模块进行相应的调整就可以。
为了帮你更好地理解这两种拆分方式的好处,我这里举个搭积木的例子。经过拆分,每个业务模块都成为一个积木,然后,我们以搭积木的方式来构造系统。当业务发生变化,我们就调整对应的积木,如果系统拆分得合理,拆分后的模块就具有良好的封装性,也就意味着我们主要是调整积木的内部,而它的外观基本不变。这样一来,相邻的积木不会受到影响,系统整体也不需要大的调整。结果是,系统的变化是局部和可控的,保证了灵活的应对变化能力。
## 打造可扩展的模块体系:模块整合
系统拆完后,接下来就是模块整合的工作,整合也有两种好的手段:通用化和平台化。
### 通用化整合
通用化指的是通过抽象设计,让一个模块具备通用的能力,能够替代多个类似功能的模块。
回到刚才的出行平台我们发现3条业务线都有地图搜索、运力调度、订单支付这些模块不同的业务线之间这些同名的模块逻辑高度类似只是细节方面有差别。
那么我们能不能对这些类似的模块进行抽象化处理整合成一个通用的模块呢答案是可以的我们可以在模块接口中通过输入参数标识调用来自哪个业务是出租车、快车还是顺风车然后在模块内部针对不同业务线的差异化部分做针对性处理。结果可能是这个通用模块增加5%的逻辑但避免了95%的重复逻辑,这样,经过通用化整合,新的模块以很低的代价,就为多个业务线提供了复用。而且,当新的业务线进来,很可能这个通用化的模块,就已经提供了现成的支持。
**通过模块通用化,模块的数量减少了,模块的定位更清晰,概念更完整,职责更聚焦。在实践中,当不同业务线对某个功能需求比较类似时,我们经常会使用这个手段。**
### 平台化整合
平台化是把定位相同的模块组织在一起,以组团的方式对外提供服务。对于外部系统来说,我们可以把这些模块看成是一个整体,一起对业务场景提供全面的支撑。
如下图所示,我们可以看到,地图搜索、运力调度、订单支付,都是各个业务线都需要的基础和通用的业务能力,当我们增加新的业务线时,还是离不开这些基础能力。
<img src="https://static001.geekbang.org/resource/image/76/3e/76faef600320f2b8974b4860cd57de3e.jpg" alt="">
所以,我们可以把这些基础模块放在同一层,构成一个基础业务平台。之前,它们是一个个离散的服务,独立地输出能力,现在变成一个大的业务平台,可以提供整体的能力输出。
通过打造业务平台,一方面,我们对多个业务模块进行包装,形成更大粒度的抽象,相当于减少了模块的数量;另一方面,作为平台,它的定位更明确,系统依赖关系也更清晰;而且,如果新的业务线进来,它可以基于业务平台快速落地。
**业务平台化是模块依赖关系层次化的一个特例,只是它偏向于基础能力,在实践中,当业务线很多,业务规则很复杂时,我们经常把底层业务能力抽取出来,进行平台化处理。**
## 总结
好了,下面我来总结一下今天所讲的内容。
首先,我们对系统进行建模,系统=模块+关系,这样会简化你对系统的认识。基于这个模型,我们对模块划分和关系定义提出具体的要求,你可以在实际设计时参考这些要求。
另外,我们深入地分析了扩展性的本质。系统的扩展能力来自于内部模块体系的有序,这样才能低成本地应对业务变化,认识到了这一点,有助于你从根本上理解和重视架构的扩展性设计。
然后,我提供了一个出行平台的例子,来帮助你理解,如何通过模块拆分和整合的手段,具体地设计一个可扩展的架构,希望你能在工作中灵活运用。
**最后,给你留一道思考题**:你所在公司里有没有类似的肿瘤系统,它包含了太多职责,导致系统内部结构混乱,大家都不敢对它进行调整?
欢迎在留言区和我互动,我会第一时间给你反馈。如果这节课对你有帮助,也欢迎你把它分享给你的朋友。感谢阅读,我们下期再见。

View File

@@ -0,0 +1,152 @@
<audio id="audio" title="04 | 可扩展架构案例(一):电商平台架构是如何演变的?" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/79/74/79c4b48ea8478c86b886fb53b996b574.mp3"></audio>
你好,我是王庆友。
上一讲中,我们介绍了如何打造一个可扩展的架构。今天,我就针对最近十几年电商平台的架构变化过程,来具体说明下,为了支持业务的快速发展,架构是如何一步步演进的。
从2003年淘宝上线开始国内电商平台经历了高速的发展在这个过程中系统遇到了很多的挑战比如说:
- 如何针对当前的业务现状,选择合适的架构呢?
- 如何在业务发展过程中,升级改造架构,并保证系统的平滑过渡呢?
接下来,我会结合自己的工作实践,和你一起探讨架构的演变历程,你可以从中了解到各种架构的优劣点和适用性,然后在实际工作中选择合适的架构。
这里,我总结了国内电商平台架构发展的大致过程,你可以结合图片参考下。
<img src="https://static001.geekbang.org/resource/image/18/f1/1819615457ee2aaef36242287c8a16f1.jpg" alt="">
我们可以看到,从最初的单体架构到最新的中台架构,架构的可扩展性越来越强,这些都是系统不断适应业务复杂化的结果。下面,我就结合电商业务的变化,按照顺序和你介绍下各个架构。因为篇幅的原因,对于中台架构,我会放在后面的文章里重点介绍。
## 单体架构
在单体架构中只有一个应用所有代码跑在一个进程所有的表放在一个DB里。第一代电商平台都是单体架构比如说淘宝在最初的3年它的系统就是一个巨大的单体应用。
单体应用内部一般采用分层结构从上到下一般分为表示层、业务层、数据访问层、DB层。表示层负责用户体验业务层负责业务逻辑数据访问层负责DB的数据存取。
<img src="https://static001.geekbang.org/resource/image/a1/34/a12ac37db91a57a5d3314f1e35fa6c34.jpg" alt="">
我们可以看到,各个层的职责,正好对应业务处理的不同阶段,所以,单体架构在水平方向上,通过层次化的划分,降低了业务的深度复杂性(所谓的业务深度,指的是业务流程从开始到结束的长度)。
不过在垂直方向上单体应用缺乏清晰的边界上下层模块之间是多对多的网状依赖关系比如业务层的某个模块上图中BO1可能调用数据访问层的所有模块DAO1~3 同样的道理,数据访问层的某个模块,也可能被业务层的所有业务模块给调用。
所以,单体架构中的模块只是在逻辑上独立,并没有在物理上严格分开,导致系统在落地时,模块的职责和边界划分比较随意,相应地,模块之间的依赖关系也比较模糊。所以,**在单体架构中,模块结构是否合理,很大程度上依赖于开发者的个人水平。**
在电商发展的初期,业务并不复杂,比如前台的首页、搜索页、详情页、结算页等,页面的功能都比较简单,可以放在一个应用里处理,这样,使用单体架构就可以快速落地系统。但当业务开始变得复杂时,每个页面都发展为一个独立的业务体系,比如说首页,它原先展示相对固定的内容,现在发展为一个动态的千人千面系统。
这样一来,业务的复杂度急剧上升,模块的数量也大幅度增加了,我们就很难在单体架构里,通过构建一个清晰的模块体系来支持系统的扩展。而且,所有代码放在一个代码库里管理,如果多团队并行开发的话,很容易发生代码冲突,这样也难以满足系统的快速扩展。
举个例子07年的时候eBay网站总体上也是一个单体应用它的核心工程有数百万行代码由于代码合并和编译非常复杂他们甚至有专门的团队负责代码合并有专门的团队负责编译脚本开发另外还有一套复杂的火车模型来协调不同团队之间的并行开发和上线。
所以,当业务系统的体量变大时,单体架构的弊端就充分暴露出来了,我们就需要对系统进行有效的拆分,比如把首页、搜索页、详情页、结算页拆成一个个独立的应用,分别进行管理。于是,分布式架构就应运而生了。
## 分布式架构
分布式架构,简单来说就是系统由多个独立的应用组成,它们互相协作,成为一个整体。
分布式架构包括了多个应用每个应用分别负责不同的业务线当一个应用需要另一个应用的功能时会通过API接口进行调用。在分布式架构中API接口属于应用的一部分它和表示层共享底层的业务逻辑你可以认为API相当于应用在实现本身业务的基础上开了个小窗口给外部使用。
关于分布式的具体架构,你可以参考下图:
<img src="https://static001.geekbang.org/resource/image/31/8b/31232dae641f735182171b1290345f8b.jpg" alt="">
你可以看到,分布式架构在单体应用的基础上,进一步对系统按照业务线,进行了业务广度上的切分(所谓业务广度,指的是不同业务线的数量),这样就把一个大系统的业务复杂度,分割成多个小业务的复杂度,从而降低了整体的复杂度。通过拆分后,各个应用之间的耦合度低,就可以很好地支持多团队的并行开发。
但分布式架构也有局限性作为应用的开发者除了要满足自身业务的需求之外同时还需要考虑外部业务的需求这两部分经常会打架。比如由于自身业务的需求引起底层的业务逻辑修改这时会同时影响API接口功能导致其他业务受影响同样的道理外部业务需求过来需要API接口做调整即使不影响底层业务逻辑也会导致整个应用重新部署影响自身业务的稳定性。
另外在分布式架构下每个应用都是从头到尾自搭一套完整的体系导致业务之间重复造轮子造成资源浪费。举个例子在2008年淘宝还没有开始服务化改造之前不同业务线的用户、商品、订单逻辑非常类似导致了整个系统有超过1/3的核心代码重复。
所以,你可以发现,**分布式架构适用于业务相关性低、耦合少的业务系统**。举个例子企业内部的管理系统分别服务于不同的职能部门比如财务系统和HR系统就比较适合按照分布式架构去落地。
但在电商场景下,业务都是围绕交易展开的,各个页面(应用)都需要和商品、用户、订单、库存打交道,对于这样业务相互依赖、应用之间需要紧密协作的场景,在系统架构方面,是否有更好的手段,可以更高效地集成这些应用呢?
答案是有的SOA架构就可以有效地解决这个问题。 接下来,我们就具体了解下。
## SOA架构
SOA架构Service Oriented Architecture是一种面向服务的架构它的发展经历了两个阶段传统的SOA架构它解决的是企业内部大量异构系统集成的问题新的SOA架构它解决的是系统重复建设的问题。下面我就来和你详细介绍一下。
从2000年开始很多传统企业进入了信息化建设高潮先后采购了很多系统比如ERP、OA、CRM等等。这些系统都是由不同的供应商提供的落地后就形成了很多的信息孤岛。随着业务的发展企业需要打通这些不同的系统那么问题来了这些系统使用不同的技术事先也没有提供开放接口给外部使用那我们如何才能有效地集成这些系统呢
解决的办法是每个系统首先把外部需要的能力封装为一个个粗粒度的接口打包成一个独立的服务然后外部系统通过这个服务访问系统内部解决不同系统相互集成的问题。经过这样的改造系统最后就变成了一个面向服务的SOA架构。
这就是一个传统的SOA架构如下图所示
<img src="https://static001.geekbang.org/resource/image/8a/71/8ab0eb6f1e4a96928f2b7cdc34da8371.jpg" alt="">
你可以看到在SOA架构中每个服务都对应一个现有的系统所有这些服务都部署在一个中心化的平台上我们称之为企业服务总线ESBEnterprise Service BusESB负责管理所有调用过程的技术复杂性包括服务的注册和路由、各种通信协议的支持等等。
比如说09年的时候eBay就基于Axis 2开发了自己的SOA框架让各个系统通过提供标准的服务来满足外部调用需求。 比如后台搜索系统本身是C++开发的但是它通过提供Java服务封装常见的搜索功能就方便了其他系统大多是Java和搜索系统进行集成。
以上讲的是传统SOA架构它主要用于解决遗留系统的集成问题。而新的SOA架构它利用服务共享的思想解决系统的重复开发问题。
举个淘宝的例子淘宝的系统基本是自建的系统相互打通的问题不大。但经过一段时间的自然生长系统重复建设的问题很突出前面也提到有超过1/3的核心代码重复。针对这种情况我们就可以通过服务化手段把通用的逻辑和数据从各个业务系统里抽取出来封装成独立的服务提供给所有业务进行共享。
基于这个思路淘宝花了2~3年时间先后落地了用户、商品、订单、库存、店铺、营销等服务搭建了共享服务体系。通过共享淘宝不仅提升了开发效率和质量也加强了系统的扩展能力。
新的SOA架构如下图所示
<img src="https://static001.geekbang.org/resource/image/fc/5f/fcb0c3b41c2578927332e3241663535f.jpg" alt="">
所以我们可以看到相对于分布式架构SOA架构给系统的扩展带来了一系列的好处
- 首先,它通过服务化思想,提供更好的业务封装性,并通过标准技术,能更友好地对外输出业务能力;
- 其次SOA服务不依附于某个具体应用它可以独立地部署和扩展这样避免了直接影响现有的系统
- 最后,服务通过封装通用的业务逻辑,可以供所有应用共享,解决了重复造轮子的问题。
不过虽然SOA服务化的思想很好但在系统实现上比较重落地比较困难。**那有没有更轻量级的架构,使得系统各个部分更容易构建和相互协作呢?**
这时候,微服务架构便悄悄地登场了。
## 微服务架构
关于微服务,大家都不陌生,但究竟什么是微服务,每个人的理解可能都不一样。接下来,我就基于自己的服务化实践,和你分享我的看法。
微服务概念的提出,一开始是用来和单体架构做区分的。我们知道,单体架构和分布式架构,实际上都是围绕一个大的业务线来构建应用,当业务变得复杂时,就无法做到模块边界和依赖关系的清晰划分,模块局部的调整往往会导致系统整体的调整,使得系统很难扩展。
**而微服务围绕更小的业务单元构建独立的应用。**
比如说,一个飞机航班预订系统,我们可以把它划分为预订航班、时间表查询、计算票价、分配座位等几个小应用(微服务)来落地。那么经过划分后,每个小应用都比较简单,只关注于一个业务功能即可。
这里要注意的是每个微服务都是负责端到端的业务包括前端的UI展现部分和后端业务逻辑。微服务的团队成员可能包括产品、开发、测试、运维等人员由这个小团队负责应用的整个生命周期管理。
因此,从一定程度上说,微服务叫做微应用,或者说微产品,更合适一点,你也可以认为微服务架构是拆分得更细的分布式架构。
另外微服务强调围绕业务进行清晰的业务和数据边界划分并通过良好定义的接口输出业务能力这和SOA架构里的服务有点类似。**但两者不同的地方在于微服务是去中心化的不需要SOA架构中ESB的集中管理方式。**
一方面,**微服务强调所谓的哑管道**即客户端可以通过HTTP等简单的技术手段访问微服务避免重的通信协议和数据编码支持。另一方面**微服务强调智能终端**,所有的业务逻辑包含在微服务内部,不需要额外的中间层提供业务规则处理。
这样子微服务提供方可以自由地选择语言和工具来落地微服务服务的部署和维护上也更灵活从这个意义上来说你也可以认为微服务是轻量级的SOA服务。
所以说,微服务兼有应用和服务的特征,你可以把微服务理解为:
>
**微服务=小应用+小服务。**
以上就是微服务架构设计的初衷,但在实践中,我们更多地把微服务当做一个小服务,而不是一个端到端的小应用,那么为什么会这样呢?这里有几个原因。
首先,我们很难把一个大系统,按照端到端业务的方式,拆分为一个个应用;而拆分为服务是比较灵活的,我们可以把系统核心的业务逻辑和数据封装成服务,其它部分还是以应用的方式落地。另一方面,微服务要求团队人员跨多个职能,构建独立的小团队,来负责服务完整的生命周期,这就需要把现有的职能团队打散后重组,这种人员组织的调整实际上也很难落地。
我们可以看到,微服务强调围绕端到端的小业务功能,通过组建跨职能的团队,来进行落地,这只是一种理想化的做法。所以,**在实践中,我们往往弱化微服务的小应用定位,然后扩大化微服务小服务的定位,我们不再强调端到端的业务封装,而是可以有各种类型的微服务。**
比如说封装底层基础业务的是共享微服务封装流程的是聚合微服务封装具体业务场景的服务端是应用微服务封装基础中间件如Redis缓存、消息推送的是系统微服务。当然这些服务在具体落地时我们还是采取去中心化的机制使用轻量级的通讯框架最后把它们打造成一个个技术上轻量级的、功能职责上细分的微服务。
所以,基于这样的思路,微服务就很容易构建,同时,也像水电煤一样,容易被我们使用。然后,我们在这个基础上组装微服务,像搭积木一样搭建系统,这样的系统更具弹性,更容易扩展。
**值得注意的是,我们需要对服务依赖关系进行有效的管理,打造一个有序的微服务体系**。否则的话,东一个服务,西一个服务,这样会让系统变得碎片化,难以维护和扩展。
所以我这里也放了一张图,来帮助你理解,一个有序的层次化微服务体系大致是什么样子的。
<img src="https://static001.geekbang.org/resource/image/4f/05/4f361c17295a651ccc1b49bae6219005.jpg" alt="">
## 总结
今天我与你分享了电商平台架构的发展过程从单体架构到分布式架构再到SOA架构和微服务架构每种架构都针对前一种架构的缺点做了改进架构的扩展性也变得越来越好可以满足更高的业务复杂性要求。
但值得注意的是,每种架构都有两面性,既有优点,又有缺点,在实际系统中,这些架构也都是并存的。**架构没有最好,只有最合适的**。我们做架构设计时,一定要根据当前业务的特点,选择合适的架构。
通过今天的分享,相信你对架构的扩展性有了更深入的理解,也能够根据公司的业务现状,进行更合理的架构选型了。
**最后,给你留个思考题**:现在人人都在落地微服务,你在这方面有什么经验和教训吗?
欢迎你在留言区与大家分享你的答案,如果你在学习和实践的过程中,有什么问题或者思考,也欢迎给我留言,我们一起讨论。感谢阅读,我们下期再见。

View File

@@ -0,0 +1,199 @@
<audio id="audio" title="05 | 可扩展架构案例App服务端架构是如何升级的" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/91/8f/91da4ad31d9050258af8ac557edb148f.mp3"></audio>
你好,我是王庆友。
上一讲,我与你介绍了电商平台从单体架构到微服务架构的演变过程。那么今天,我会通过一个**1号店App服务端架构改造**的例子,来具体说明架构的演变过程,让你能更深入地理解架构演变背后的原因。
先让时间拨回到2012年当时随着智能设备的普及和移动互联网的发展移动端逐渐成为用户的新入口各个电商平台都开始聚焦移动端App。这个时候1号店也开始试水移动端购物从那时起1号店App的服务端架构一共经历了三个版本的变化。
接下来我就为你具体介绍App服务端架构变化的过程以及原因。
## V1.0架构
我先说说最开始的1.0版本。当时的情况是App前端的iOS和Android开发团队是外包出去的而App的服务端是由1号店内部一个小型的移动团队负责的这个团队主要负责提供App前端需要的各个接口接口使用的通信协议是HTTP+JSON。
具体的架构如下图所示:
<img src="https://static001.geekbang.org/resource/image/1c/45/1c2cc4298788d157851d08b5a49e9b45.jpg" alt="">
这个架构比较简单App的服务端整体上就一个应用由移动团队来维护所有对外接口服务端内部有很多Jar包比如商品搜索、商品详情、购物车等等这些Jar包包含了各个业务线的业务逻辑及数据库访问它们由各个业务线的开发者负责提供。
你可以看到这个1.0版本的服务端实际上就是一个单体应用只是对外的接口和内部Jar包分别由不同的团队来提供这个架构的优点和缺点同样都非常明显。
**它的优点是简单方便**。App前端的外包团队只需要对接后端的一个移动团队就可以了然后移动团队通过现成的Jar包封装各个业务线的功能。至于这些Jar包业务线团队也无需额外去开发。
为什么呢我们知道早期的电商平台都是先有PC端应用再推AppApp最开始的功能大多是从已有的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接口。
整体架构如下图所示:
<img src="https://static001.geekbang.org/resource/image/2e/a5/2e91cb605d041f27523d7183aae053a5.jpg" alt="">
对于各个业务团队来说他们现在走向了前台每个团队负责各个业务线的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端接口各自在物理上独立但它们共享核心的业务逻辑。**
拆分后的架构如下图所示:
<img src="https://static001.geekbang.org/resource/image/b3/bd/b3ad4bbf52ed59e676d488642ffab4bd.jpg" alt="">
这样拆分的结果是原来大的服务端变成了3个应用包括一个App端接口应用一个PC端Web应用还有一个核心业务逻辑服务3个部分都是独立维护和部署的。
**除此之外,架构改造还考虑了移动端自身的特点。**
一方面,每个移动端接口需要调用对应的后台服务,进行业务逻辑处理,这个是个性化的,每个接口的处理逻辑都不一样;另一方面,每个移动端接口都需要进行系统级的功能处理,比如前面所说的安全验证、接口监控等,这个是共性的,每个接口的处理方式都是一样的。
那么,在架构上,我们就需要把共性的系统级功能进行集中处理,把个性化的业务功能进行分散处理。
**最后我们结合服务端的应用拆分以及对移动接口本身的改造落地了服务端V3.0架构。**
如下图所示:
<img src="https://static001.geekbang.org/resource/image/d0/ec/d0e1b34d0f908391e1253fd1f93689ec.jpg" alt="">
在这里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架构的变化过程并且通过移动网关的方式一定程度上实现了平台化。
在这里,你可以清晰地看到,公司每个阶段的业务,都有它不同的特点,我们选择的架构必须能够适配它,**过度设计和设计不足,同样都是有害的。**
通过今天的分享,相信你对各种架构的优缺点,以及业务上的适用性有了更进一步的了解。他山之石,可以攻玉。架构的策略和原则是通用的,希望你能够通过实战不断去领会和运用。
**最后,给你留一道思考题**:你都做过哪些系统改造,改造前是什么架构,改造后又是什么架构,过程中有哪些挑战呢?
欢迎在留言区和我互动,我会第一时间给你反馈。如果觉得有收获,也欢迎你把这篇文章分享给你的朋友。感谢阅读,我们下期再见。

View File

@@ -0,0 +1,166 @@
<audio id="audio" title="06 | 可扩展架构案例(三):你真的需要一个中台吗?" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/34/ba/34a6dbb60a9c4e4feb98931a4d2e58ba.mp3"></audio>
你好,我是王庆友。前面的课程,我们从单体架构开始,讲到了微服务,今天我们就接着讲最新的中台架构。
关于中台,最近比较火,你可能也听到过不少关于它的讨论,但中台究竟是什么?它能解决什么问题?相信你不一定非常清楚。今天,我就为你解决这些困惑。
讲中台之前,我们先来理解下前台和后台,这样,你才能更清楚中台的定位。
<img src="https://static001.geekbang.org/resource/image/7e/b2/7e8f2b10eba1efbe492ca6b10d080ab2.jpg" alt="">
**前台**比较好理解,指的是**面向C端的应用**,比如像微信、淘宝这样的应用。不过,你要注意,前台不仅仅是指前端,它还包含和前端配套的服务端。
**后台**指的是**企业内部系统**比如ERP、CRM、仓库管理系统等等主要是面向企业内部人员使用。对于传统企业来说之前只有线下场景通过内部的后台就能完成所有业务流程而对于互联网企业或者逐步开展线上业务的传统企业来说同时需要前台和后台一起协作完成业务的闭环。
但问题是,前台和后台的特性是不一样的。**前台对外**,我们知道,消费者的需求快速多变,所以前台需要能快速响应,做到低成本试错;而**后台对内**,企业内部的业务流程不能经常变,所以后台需要稳定,不能随意调整,一旦改动,影响面广,成本很高。
简单地说,前台要快,后台要稳,因此在业务扩展时,我们经常会遇到以下两类挑战:
- 这个营销思路很棒,老板希望能马上验证,前台好改,但后台调整起来需要好几个月;
- 后台系统技术旧,性能差,接口不开放,前台对接起来很麻烦,而且一有促销活动,后台立马就挂。
第一类挑战,在互联网企业比较普遍,前台经常玩各种花样,要求快;第二类挑战,在传统企业很典型,大量的后台都是早期采购的商业套件,新的线上应用很难直接对接内部老系统。
你可以发现前台和后台是企业IT系统的一体两面它们需要紧密协作共同服务于企业的业务战略。但两者对业务稳定性的要求不一样在技术上也普遍存在脱节现象。
**所以,如何实现前后台的平滑对接,这是一个巨大的挑战,中台架构因此而生。**
接下来,我会结合自己在中台方面的实践,和你深入聊下中台的定位,以及具体的中台架构,让你可以轻松应对这个挑战。
## 中台的定位
讲中台前我先举一个你比较熟悉的Windows系统的例子<br>
<img src="https://static001.geekbang.org/resource/image/cc/ab/cc4f9f8a5807e03e84ee74c1680087ab.jpg" alt="">
在Windows系统里最上面是各种桌面应用比如Office套件等这些是用户能够直接看到的部分最底下是各种硬件设备比如磁盘、内存、CPU等中间是操作系统它处于软硬件之间。
我们知道,理论上,桌面应用可以直接操作底层硬件,完成所需要的功能。比如,我们用低级的汇编语言去开发应用,就可以通过端口来直接操作硬件。但很显然,这种开发方式的效率很低,代码的可读性和可维护性也很差。
但是如果我们在中间加上一层操作系统通过操作系统向下管理硬件屏蔽各种硬件的差异和复杂性向上提供简洁的API接口我们就可以使用各种高级语言通过调用API很方便地操作硬件了。
在这个里面,操作系统在底层硬件和上层应用之间,起到了很好的衔接作用。
我们就对照Windows操作系统的例子来看下传统企业的IT系统。比如说麦当劳它经过多年的信息化建设购买了大量的商业套件如总部使用的ERP、门店使用的收银系统等等这些系统都属于后台的范畴面向企业内部管理针对的是传统的线下业务。
现在,随着麦当劳的业务发展,要往新零售转型,比如说,他们要提供线上小程序点餐服务,为消费者创造更好的用户体验。
但是这个小程序点餐服务不是孤立的它离不开内部系统的支撑。比如小程序展示的菜品来自于后台ERP小程序下的订单会进入门店的收银系统和厨房作业系统。
那么问题来了,**这些C端应用与内部后台系统要如何打通呢**
理论上C端的应用也是可以直接调用后台老系统来实现打通的比如在麦当劳的例子中小程序服务端可以直接调用ERP获取菜品信息提供给小程序前端进行展示。但这个和Windows系统里的桌面应用直接控制硬件设备类似这里前后台的直接对接是非常低效的。
我们知道小程序服务于C端ERP服务于B端ERP建设在前小程序建设在后。ERP系统在实施的时候完全没有考虑小程序点餐场景两者在业务流程、数据模型、技术栈、性能要求等方面差异都很大导致直接的对接非常困难。
而且如果有新的C端场景进来又要从头到尾对接一遍重新吃一遍苦。这是一种硬着陆的方式如果新业务上线采取这种方式那至少需要好几个月时间根本无法满足业务快速创新的要求。
这时如果有个中间层来负责C端应用与内部后台系统的平滑衔接帮助新的C端应用软着陆这样就会非常高效。这里我对比了操作系统和新零售中台如下图所示
<img src="https://static001.geekbang.org/resource/image/f2/b9/f2da313161166386baa79f054c1d68b9.jpg" alt="">
以麦当劳为例如果我们对内部老系统进行包装对外提供标准的API这样就能把旧的IT基础设施转换成面向互联网的业务平台。然后新的C端应用可以快速基于这个业务平台来构建而不用关心底层老系统的实现细节。**这个中间层就是中台。**
你可以看到中台相当于企业的商业操作系统通过对后台的包装为前台提供全方位的支持。这里需要注意的是中台不仅仅是前后台之间简单的适配器中台本身也会落业务数据有完整的业务规则就像Windows操作系统一样它在适配硬件的基础上进一步提供内存管理、进程调度等功能为上层应用提供体系化的支持。
对于互联网企业来说前后台虽然是同时建设的它们在功能上能够衔接起来但前台求快后台求稳。所以在这里中台可以先承接前台的业务和数据和前台构成C端业务的小闭环支持业务的快速创新等业务模式验证后中台和后台再进一步彻底打通构成业务的大闭环。
现在你已经了解了中台的定位,可能会想,**企业处于什么样的发展阶段,需要落地中台呢?**
接下来,我就结合一个出行平台的发展过程,来说明中台的适用性,让你能够在合适的时机选择落地中台。
## 中台的适用性
一个出行平台当公司发展从0到1的阶段时往往只有一条业务线比如说出租车业务我们直接根据它的需求落地系统即可。随着公司发展到从1到n的阶段时业务线会逐渐增加比如增加了快车、顺风车等业务。
这时,从系统落地的角度,我们有两种做法。
**第一种是独立地建设新业务线,这样,各个业务线并列,系统整体上是一个“川”字型的结构。**
如下图左边部分所示:
<img src="https://static001.geekbang.org/resource/image/51/45/511406013d3ae63a2a15acfaa1ba3545.jpg" alt="">
但是,如果各个业务线的业务逻辑非常类似,子系统之间会有大量的代码复制,这就会导致重复建设以及多头维护的问题。显然,这是非常低效的,本来我们想能尽快上线新的业务线,但结果是欲速而不达。
**第二种做法是,把各业务线中相同的核心逻辑抽取出来,通过抽象设计,实现通用化,共同服务于所有业务线的需求,系统结构整体上是一个“山”字型。**
“山”字型的上面三竖,代表各个业务线定制的应用;最底下一横,代表通用层,它把各个业务线有机粘合在一起,实现了业务逻辑和业务规则的统一,如上图中的右边所示。
**这样,我们就能一处建设,多处复用,一处修改,多处变化,从而实现最大程度的复用。**
那我们什么时候,需要从“川”字型转为“山”字形呢?
- 一方面,这和公司业务线的**数量**有关业务线越多意味着重复建设的成本会更大当我们开始上第3条业务线时就应该要考虑转到“山”字形了。
- 另一方面,也和各个业务线的**相似度**有关,相似度越高,意味着业务线之间有更多类似的逻辑,更适合“山”字形。比如,出行平台的各个出行方式相似度很高,适合“山”字形;但同一个公司的出行业务和互联网金融业务,差异比较大,就可以考虑“川”字形,而没必要把它们强行扭在一起。
所以说,中台实现了通用基础业务的平台化。从**变化速度**来看,企业基础的业务是相对固定的,而具体上层业务场景是相对多变的;从**数量**来看,基础业务数量是有限的,而具体业务场景是无限的。因此,有了完善的中台,我们就可以通过有限而比较固定的基础业务,来满足无限而快速变化的上层业务场景了。
此外,从**业务角度**来看,中台收敛了业务场景,统一了业务规则;从**系统角度**看,中台相当于操作系统,对外提供标准接口,屏蔽了底层系统的复杂性;从**数据角度**看,中台收敛了数据,比如使用同一套订单数据模型,让所有渠道的订单使用相同的订单模型,所有订单数据落到同一个订单库。
那么用一句话总结就是,**中台通过实现基础业务的平台化,实现了企业级业务能力的快速复用。**
好,接下来,我们就一起深入中台,具体了解下中台架构设计的细节。
## 如何落地一个中台架构?
通过[课程之前的分享](https://time.geekbang.org/column/article/205832),你应该对微服务架构比较熟悉了,我也提到了中台架构紧跟着微服务架构,那么中台和微服务架构到底有什么区别和联系呢?
简单地说,我认为**中台是微服务的升级。**
在微服务架构下,我们搭建的是一个个离散的服务,如商品服务、订单服务等等。而在中台里,这些微服务升级为了商品中心、订单中心,每个中心更强调体系化,包括更好的业务通用能力,更好的系统运营能力(如监控、稳定性、性能的强化),更好的业务运营能力(比如商品中心自带配套的商品管理后台)。
每个服务中心都围绕核心业务,自成体系,成为一个微内核,这些微内核形成一个有机整体,共同构成了基础业务平台,也就是中台。**松散的微服务-&gt;共享服务体系-&gt;中台**,这是微服务架构向中台架构的演进过程。
现在大家谈论比较多的是业务中台,那我们就来具体看下一个典型的业务中台的结构。它一般包含三层,从上到下分别是**通用聚合服务层**、**通用基础业务平台**和**通用中间件平台**。
<img src="https://static001.geekbang.org/resource/image/11/87/115a36d4713c02698c215d6af6fdb187.jpg" alt="">
对于中台来说,基础业务能力由通用基础业务平台来实现;另外,通用聚合服务对基础业务进行组合,进一步提升了业务能力的易用性;而通用中间件平台,通过技术手段保证了业务中台的稳定性,三者一起实现了企业整体业务能力的复用。
那么关于具体如何落地中台,互联网企业和传统企业的侧重点则有所不同。
- 对于大的**互联网企业**来说,系统已经是类似于“山”字型的结构,进化到中台,更多的是各个基础服务点上的强化和面上的整合。
- 对于**传统企业**来说,系统基本上是“川”字型的结构,大量独立的商业套件组成遗留系统,落地中台是一个革命性的动作。
所以接下来,我就主要分析下传统企业如何落地中台,这样更能体现出中台的价值和落地的挑战。
首先,如下图所示,我们看下典型的传统企业中台架构设计是什么样的。
<img src="https://static001.geekbang.org/resource/image/e9/b1/e90d7eb2cc9118bf50e3d2d600245bb1.jpg" alt="">
**你可以看到,整个中台架构从上到下分为四个层次:**
### 渠道&amp;应用
渠道&amp;应用层,这是整个系统的**对外部分**包括了各个应用的前端如App、小程序、公众号等等这些是需要定制的部分。同时在对外部分我们还会提供Open API供上下游企业调用。
### 应用平台
应用平台是各个具体应用的**母体**它包含了各个应用的服务端比如小程序服务端、App服务端等等这些服务端会针对具体场景做流程编排和信息的聚合。
服务端和前端之间还有一个**网关**,网关实现前后端隔离,具体负责外部访问的安全验证和监控,以及内外部请求的路由和消息格式转换。
### 业务中台
业务中台是中台架构的**核心**,它包括一系列的通用基础服务,以及它上面的通用聚合服务和下面的技术平台,这个在前面已经详细介绍过了,我就不赘述了。
### 后台
后台包括两部分,第一部分是**适配插件**用于连接商户内部系统和中台基础服务比如在中台的商品服务和后台ERP之间同步商品数据在中台的会员服务和后台CRM之间同步会员信息。一般针对每个内部系统都有一个适配插件它起到了类似硬件驱动程序的作用这个一般是定制化的。第二部分是**企业内部系统**这个是企业的IT基础设施业务最终会在这里落地。
OK通过以上的介绍你可以清晰地看到**中台代表了企业核心的业务能力它自成体系能够为C端的互联网场景提供通用的能力并通过各种插件和后台打通**。这样经过中台的通用化和后台的插件适配后我们最终就把企业的后台老系统包装成一个面向互联网的平台可以快速地给C端赋能。
## 总结
中台是从企业的业务战略高度来考虑企业IT系统的建设它的目标是实现企业整体业务能力的复用。从落地的角度看
- **对于互联网企业来说**,有大量微服务做基础,往中台转是改良,目的是更好地衔接前台和后台,实现业务的快速创新;
- **对于传统企业来说**,内部有大量的遗留系统,落地中台是革命,目的是盘活老系统,全面实现企业的数字化转型。
互联网发展到现在从最初的电商到O2O再到现在的产业互联网已经进入了深水区很多传统企业都面临着数字化转型的挑战。架构上往中台转型落好中台真正发挥中台的价值这将是一个长期的过程也是企业业务复杂化的必然结果。
通过今天的分享,相信你对中台有了更深入的理解,对是否要往中台转型,你也能够做出更好的判断了。
**最后,给你留一道思考题**:现在中台很热,我们经常听到很多中台名词,它们分别是什么定位呢?
欢迎你在留言区与大家分享你的答案,如果你在学习和实践的过程中,有什么问题或者思考,也欢迎给我留言,我们一起讨论。感谢阅读,我们下期再见。

View File

@@ -0,0 +1,118 @@
<audio id="audio" title="07 | 可复用架构:如何实现高层次的复用?" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/cc/8a/cc17aecb9cc046adab6e4945aa8b1e8a.mp3"></audio>
你好,我是王庆友。在前面几讲中,我们讨论了如何打造一个可扩展的架构,相信你对架构的可扩展有了一定的了解,而架构还有一个非常重要的目标,那就是可复用。所以从今天开始,我就来和你聊一聊,如何打造可复用的架构。
作为开发人员,你对复用这个概念一定不陌生。在开发过程中,我们把系统中通用的代码逻辑抽取出来,变成公共方法或公共类,然后在多个地方调用,这就是最简单的技术上的复用。
但一开始,我们不会过多地考虑复用,当一个新项目过来,我们会选择最直接的方式来实现,结果往往是欲速而不达,比如说:
- 好不容易搞定了一个项目,接着又有新的类似项目过来,我们又要从头再来;
- 项目的代码是定制的,项目结束后,系统维护的噩梦刚刚开始。
如果项目缺乏沉淀,每个项目都是全新的开始,出现这些情况,一点都不意外。而要想解决这个问题,我们一开始就要考虑系统的复用性。
**复用,它可以让我们站在巨人的肩膀上,基于现有的成果,快速落地一个新系统。**
那么,我们在做架构设计时,如何实现系统的高可复用呢?
今天,我就针对复用这个话题,首先和你介绍一下,复用具体都有哪些形式;然后,我会针对最有价值的业务复用,带你了解如何划分服务的边界,让你能够在工作中,设计一个可以高度复用的系统。
## 复用的分类
复用有多种形式,它可以分为技术复用和业务复用两大类。**技术复用**包括代码复用和技术组件复用;**业务复用**包括业务实体复用、业务流程复用和产品复用。
从复用的程度来看,从高到低,我们可以依次划分为产品复用&gt;业务流程复用&gt;业务实体复用&gt;组件复用&gt;代码复用。
<img src="https://static001.geekbang.org/resource/image/7f/4c/7f1f6ba6fda64d49f29f5ebac518b24c.jpg" alt="">
接下来,我就按照复用度从低到高,对这些复用方式进行一一分析,帮助你更好地理解架构的可复用性。
### 技术复用
首先是**代码级复用**这部分应该是你最熟悉的了。这里包括你自己打包的类库第三方提供的SDK还有各种算法封装等。我们的代码可以直接调用它们物理上也和我们的应用打包在一起运行在同一个进程里。代码级复用是最低层次的复用你可以把它当作你自己源代码的一部分。
再往上,是**技术组件复用**。这些组件有我们自己封装的更多的是大量开源的中间件比如Redis、MQ、Dubbo等组件也包括各种开发框架比如Spring Cloud。这些基础组件技术复杂度很高它们的存在极大地简化了我们的开发工作。
**值得注意的是,代码级复用和技术组件复用都属于工具层面,它们的好处是在很多地方都可以用,但和业务场景隔得有点远,不直接对应业务功能,因此复用的价值相对比较低。**
### 业务复用
我们知道,系统最终是为业务而服务的,如果能够实现直接的业务复用,那系统开发的效率就更高。在前面的课程中,我们讨论架构的演进过程时,很多地方谈到了业务能力的复用,比如说,[微服务](https://time.geekbang.org/column/article/205832)强调单个业务实体的封装和复用,而[中台](https://time.geekbang.org/column/article/207121)进一步实现了企业级业务能力的复用。
所以接下来,我们就从比较简单的业务实体复用开始说起。
**业务实体复用针对细分的业务领域**,比如订单、商品、用户等领域。它对各个业务领域的数据和业务规则进行封装,将它变成上层应用系统可以直接使用的业务组件。
**业务流程的复用针对的是业务场景**,它可以把多个业务实体串起来,完成一个端到端的任务。比如说,下单流程需要访问会员、商品、订单、库存等多个业务,如果我们把这些调用逻辑封装为一个下单流程服务,那下单页面就可以调用这个流程服务来完成下单,而不需要去深入了解下单的具体过程。相比单个的业务实体复用,业务流程的复用程度更高,业务价值也更大。
**最高层次的复用是对整个系统的复用**比如说一个SaaS系统Software-as-a-Service它在内部做了各种通用化设计允许我们通过各种参数配置得到我们想要的功能或者说一个PaaSPlatform-as-a-Service平台它会提供可编程的插件化支持允许我们“嵌入”外部代码实现想要的功能。
这种产品级的复用,它的复用程度无疑是最高的。这样的系统,在落地的时候,它无需核心的开发团队进行开发,只由外围的实施团队负责就可以了,这样,一个项目的上线就能简化为一次快速的实施,不但上线周期短,系统也更稳定。
当然实现这样的复用难度也是很大的你既要对所在行业的业务有很全面的理解又要有很强的抽象设计能力。这类系统中比较典型的有Salesforce的CRM系统和SAP的ERP系统。
现在,我们先对复用做个总结。**从技术复用到业务复用,越往上,复用程度越高,复用产生的价值也越大,但实现起来也越复杂,它能复用的场景就越有限。**在实际工作中,技术层面上的复用相对比较简单,我们对这部分的认知也最多,而且由于开源的普及,现在有丰富的中间件让我们选择,我们可以基于它们,逐步构建适合自己的技术体系。
**但如果我们能进一步打造业务中间件,并在这个基础上,形成业务平台,这样,我们就能实现更高的业务级复用,可以更高效地支持系统的快速落地。**
而在实现业务组件化和平台化的过程中,首要的问题就是基础服务边界的划分。边界划分决定了服务的粒度和职责,在实际工作中,也是非常困扰我们和有争议的地方。
接下来,我就针对基础服务边界的划分,和你分享我自己在项目开发的过程中,总结的一些实用的原则和做法。
## 基础服务边界划分
服务边界划分要解决“我是谁”的问题,它实现了服务和周边环境的清晰切割。
我们都知道,服务包含了业务数据和业务规则,并提供接口给外部访问,其中,接口是服务的对外视图,它封装了服务的业务数据和规则。
所以从边界划分的角度来看我们就是要确定哪些数据属于这个服务哪些接口功能由这个服务提供。这里我总结了3个基础服务边界划分的原则供你设计时做参考。
### 首先,是服务的完整性原则
你在划分服务的边界时,需要确保服务内部数据的完整性。
举个例子,一个商品服务的数据模型,不仅要有商品基本信息,比如商品名称、价格、分类、图片、描述等;还需要包含商品的扩展信息,如商品的各种属性、商品标签等;最后还要包含各种复杂商品类型的定义,比如组合商品、套餐商品、多规格商品等。
另外,你还要保证服务功能的完整性。对于服务使用者来说,他们是以业务的角度看服务,而不是纯粹的数据角度。比如一个套餐商品,在服务内部,它是多个单品的复杂组合,但从服务调用者的角度来看,它就是一个商品。
那现在问题来了,对于套餐的价格,商品服务是给出一个最终价格呢?还是给出各个单品的价格,然后让调用方自己算最终价格呢?我们知道,套餐的价格不是各个单品价格累加的结果,它包含了一定的优惠,如果它的价格由服务调用方来算,这会导致商品的部分业务规则游离于服务外面,破坏了商品服务的功能完整性。
在实践中有些服务只是存储基础数据然后提供简单的增删改查功能这样一来服务只是一个简单的DAO变成了数据访问通道。这样的服务它的价值就很有限也容易被服务调用方质疑。因此我们要尽可能在服务内部封装完整的业务规则对外提供完整的业务语义最大程度地简化服务的使用。
**所以,当你在划分服务边界时,要保证服务数据完整、功能全面,这样才能支撑一个完整的业务领域。**
### 其次,是服务的一致性原则
也就是说,服务的数据和职责要一致,谁拥有信息,谁就负责提供相应的功能。
服务内部的业务逻辑要尽量依赖内部数据,而不是接口输入的数据,否则会造成数据和业务规则的脱节(一个在外面,一个在里面),如果服务对外部的依赖性很强,就无法提供稳定的能力了。
很多时候我们对一个功能到底划分到哪个服务有很大的争议。这时我们可以结合这个功能所依赖的数据来判断如果功能所需要的大部分数据都存储在A服务里那当然由A服务来提供接口比较合适这样接口输入的数据比较少不但简化了服务对外部的依赖同时也降低了接口调用的成本。
给你举个例子,在订单小票上,我们经常能看到一些优惠信息,比如说商品原价是多少,其中因为满减优惠了多少,因为商品特价减免了多少。这个优惠计算的结果是订单的一部分,毫无疑问,它需要保存在订单服务里。
但这个订单的优惠计算过程,却不是由订单服务来负责,而是由独立的促销服务负责的。因为优惠计算所需要的优惠规则是在促销服务里定义的,促销服务可以在内部拿到所有的优惠规则,然后完成整个优惠计算。
否则,如果是由订单服务负责优惠计算,订单服务的调用者就需要在接口中提供完整的促销规则,不但调用成本高,而且外部促销规则的改变会影响订单服务的内部实现。
所以在这里,促销服务负责促销规则的维护,以及对应的优惠计算功能;订单服务负责优惠结果数据落地,以及后续的查询功能。这样,每个服务存储的数据和对外提供的功能是一致的。
### 最后一个,是正交原则
既然是基础服务,它们就处于调用链的底层,服务之间不会有任何的调用关系,也就是说基础服务相互之间是正交的。比如说会员服务和商品服务,它们代表不同维度的基础业务域,彼此之间不会有调用关系。
正交还有另外一种情况:服务之间有数据的依赖关系,但没有接口的调用关系。
比如说订单明细里包含商品ID信息但订单服务内部不会调用商品服务来获取商品详情。如果页面需要展示订单的商品详情针对这个具体的业务场景我们可以在上层的聚合服务里通过聚合订单服务和商品服务来实现。
## 总结
可复用是架构设计的一个重要目标,今天我们对复用进行了梳理,包括复用有哪些形式,以及它们有哪些价值,相信你现在对复用已经有了一个整体的认识。**业务上的复用比纯粹的技术复用有更高的价值,我们要尽量往这个方向上靠。**
在实践中,落地基础服务是实现业务复用的有效方式,而基础服务边界的划分,它有科学的成分,但更多的是一种艺术,这里我提供了几个实用的划分原则,你可以在工作中结合实际情况,灵活地运用它们。
在专栏的下一讲,我会通过一个具体的订单服务例子,来帮助你更好地落地基础服务。
**最后,给你留一道思考题:我们在落地服务时,有时会冗余存储其它服务的数据,你对这个有什么看法呢?**
欢迎你在留言区与大家分享你的答案,如果你在学习和实践的过程中,有什么问题或者思考,也欢迎给我留言,我们一起讨论。感谢阅读,我们下期再见。

View File

@@ -0,0 +1,167 @@
<audio id="audio" title="08 | 可复用架构案例(一):如何设计一个基础服务?" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/9b/ef/9bd028030105cbe0b2c92985b8f254ef.mp3"></audio>
你好,我是王庆友。
在上一讲中,我提到过,在架构设计中,要实现业务上的复用,一个比较可行的做法是,把各个基础业务封装成共享服务,供上层所有应用调用。所以今天,我就来和你聊一聊,如何从头开始,落地这样一个典型的共享服务。
我们知道,落地一个微服务其实并不困难,但要实现一个能够高度复用的共享服务并不容易,在落地过程中,经常会有一系列的问题困扰着我们。
- 我们事先对服务的边界没有进行很好的划分,结果在落地的过程中,大家反复争论具体功能的归属。
- 由于对业务的了解不够深入,我们要么设计不足,导致同一个服务有很多版本;要么服务过度设计,实现了一堆永远用不上的功能。
**对于落地一个共享服务来说,服务边界的划分和功能的抽象设计是核心。**服务边界确定了这个服务应该“做什么”,抽象设计确定了这个服务应该“怎么做”。
接下来,我就以一个**实际的订单服务例子**,为你详细讲解一下要如何重点解决这两个问题。这样你可以通过具体的案例,去深入地理解如何落地共享服务,实现业务能力的复用。
## 订单业务架构
不同企业的订单业务是不一样的,所以这里我先介绍下这个订单的业务场景。
这是个O2OOnline To Offline线上到线下的交易业务订单的来源有两个一个是自有小程序或App过来的订单还有一个是外卖平台过来的订单然后这些线上的订单会同步到门店的收银系统进行接单和进一步处理。这里我放了一张订单的业务架构图你可以到文稿中看下
<img src="https://static001.geekbang.org/resource/image/f6/f5/f60dec4aff0201e54f8a509046e0aef5.jpg" alt="">
在这里订单服务是和4个应用直接打交道的
- **小程序服务端**调用订单服务落地自有线上订单;
- **外卖同步程序**接收三方外卖平台的订单,然后调用订单服务落地订单;
- ** POS同步程序**通过订单服务拉取订单,并推送给商户内部的收银系统;
- 最后还有一个**订单管理后台**,通过订单服务查询和修改订单。
OK接下来我们就具体看下如何从头开始落地这个订单服务。
## 订单服务边界划分
首先,我们要确定这个服务的边界,这是进行服务内部设计的前提。划分边界时,你需要对相关的业务场景有充分了解,并且在一定程度上,能够预测潜在的需求。在[上一讲](https://time.geekbang.org/column/article/207876),我也和你分享了划分边界一些比较实用的原则和做法,你可以对照学习一下。
根据业务场景的分析,这个订单服务需要负责三个方面的功能。
### 基本信息管理
首先是订单基本信息管理,主要提供订单基础信息的增删改查功能,包括下单用户、下单商品、收货人、收货地址、收货时间、堂食或外卖、订单状态、取餐码等。
另外,你需要注意的是,这里有多个下单渠道,除了通用的订单信息,每个渠道还有特定的渠道相关信息,比如堂食的订单要有取餐码、外卖的订单要有收货人和收货地址等等,这个都需要在我们的数据模型里给出定义。
### 订单优惠管理
然后是订单优惠管理功能,这对应的是订单的小票信息,从最开始的商品金额,到最后需要用户实际支付的金额,中间会有一系列的折扣和减免,这些都是属于订单信息的一部分。这些信息我们需要展示给用户看,如果后续要进行订单成本的分摊,也需要用到它。
### 订单生命周期管理
最后是订单的生命周期管理功能,主要负责管理订单的状态变化。我们知道,从不同下单渠道过来的订单,它的状态变化过程是不一样的;不同行业的订单,它的状态变化过程也是不同的,所以**订单服务的状态要做到通用**,能够支持各种可能的状态定义和状态转换过程。这个也是订单服务设计的难点,我在后面会重点介绍。
好了,现在我们已经给出了订单服务的功能。**为了更好地定义边界,在实践中,你还需要澄清哪些功能不属于服务**,这样可以避免后续的很多争论。所以在这里,我会进一步给出订单服务不包括的功能,你在划分自己的服务边界时最好也能够明确给出。
**第一,作为基础服务,订单服务不主动调用其他服务。**
比如说,你想了解订单的用户详情、商品详情等等,这应该由上层应用通过调用相应的服务来实现,然后和订单信息组装在一起,而不是在订单服务内部直接调用其他服务,否则会导致基础服务之间相互依赖,职责模糊。
如果说这个信息整合的场景非常通用,我们可以创建一个在基础服务之上的聚合服务来实现,把订单信息、用户信息、商品信息整合在一起。
**第二,订单服务不负责和第三方系统的集成。**
在这里,订单需要在我们的订单服务和三方外卖平台,以及收银系统之间进行同步,这些同步功能都是针对第三方系统定制的,不具有通用性。而我们的订单服务作为基础服务,需要具备通用性,因此这些和外部系统对接的功能不会在订单服务的内部实现,而是由额外的同步程序实现。
>
小提示:这些同步程序可以主动调用订单服务,然后再和第三方对接,如果想实时获取订单信息的变化,同步程序可以订阅订单服务的消息通知,第一时间了解订单变化。
**第三,订单服务不提供优惠计算或成本分摊逻辑。**
订单服务不负责具体的优惠计算,只提供优惠结果的存储和查询,用于还原订单的费用组成。优惠的具体计算过程一般由专门的促销系统负责,成本的分摊一般由后续的财务系统负责。这个我们在上一讲中已经说过,这里就不详细解释了。
**最后,该服务不提供履单详情,不负责详细物流信息的存储。**
比如说,订单已经发送至上海、订单已经到达某某快递站等等这些信息,订单服务不负责提供这些详细信息,这些都是属于后续履单系统的职责。订单服务可以存储一些外部系统的单据号码,比如配送单号,这样能方便上层应用通过订单记录和配送系统进行关联,获取配送的详细信息。但订单服务只负责存储,不负责数据的进一步解释。
到这里,你可以看到,通过从正反两个方面说明订单服务的职责,我们就得到了一个边界很清晰、职责很聚焦的订单服务边界,所有人对它的职责认识是一致的,尽可能地避免了后续的争论。
## 订单服务内部设计
好,确定了这个**订单服务要做什么**之后,接下来,我们要解决的就是**服务内部怎么做**的问题了。
作为共享服务,我们要保证订单服务功能上的通用性,就需要同时对内部数据模型和外部接口进行良好的抽象设计。
### 订单状态通用化
对于数据模型来说,订单要存储哪些信息,已经比较明确了,具体你可以看下这个图。
<img src="https://static001.geekbang.org/resource/image/a0/83/a0976cc0a2fee60922a9253809a57183.jpg" alt="">
但对于如何管理订单的状态,情况就比较复杂了。
我们知道,如果针对一个具体的项目,无论它的订单状态有多么的复杂,我们都可以事先精确地定义出来。**但不同的行业甚至不同的企业,他们对于订单状态管理都是不一样的,订单服务作为一个共享服务,它必须要满足不同项目的订单状态管理。**所以对于如何解决这个问题,这里我有两个思路供你参考。
**一个是开放订单状态定义。**
在这里,订单服务事先不限定订单有哪些状态,每个项目都可以自己定义有哪些订单状态。服务的调用方可以在接口里传递任意的状态值;订单服务只负责保存状态数据,不负责解释具体的状态,也不负责任何的规则校验,它允许订单从一个状态转换为其他任意的状态。
这样的设计在理论上可以满足各种状态的定义满足各种状态之间的变化但这样做其实有很大的问题。在这里订单状态是完全由外部负责管理的上层应用的负担会很重不但要负责定义有哪些状态而且还要维护状态的转换规则一不小心订单可能从状态A非法地变成状态B导致业务出问题。
**另外一个是应用和服务共同管理状态。**
对于订单状态管理,应用和服务各自承担一部分职责,我们看下具体如何实现。
我们知道,无论订单的状态变化是如何的复杂,我们总是可以定义一个订单有哪些基本的状态,包括这些基本状态之间是如何变化的。比如,订单一开始都是用户下单后待支付,支付完成后变成一个有效的订单,然后由商家进行接单,制作完成后进行发货配送等等,订单最终的状态要么是完成,要么是取消。
**这些订单的基本状态,我们称之为“主状态”,它们由订单服务负责定义**,包括这些主状态之间的转换规则,比如已完成的订单不能变为已取消的订单。主状态的数量是比较有限的,状态之间的变化关系也是比较明确的。
这个主状态,我们对大量现有的业务场景进行总结和抽象,是完全可以定义出来的。在这个订单服务例子里,我们定义了如下图所示的订单状态机,包括有哪些主状态,以及它们的转化关系。
<img src="https://static001.geekbang.org/resource/image/0d/74/0d773b0d18e318b2b5260a0430c42c74.jpg" alt="">
**订单除了“主状态”,还有“子状态”。**
比如,一个订单处于配送中,实际情况可能是“仓库已发货”,“货已到配送站”,或者是“快递员正在送货中”等等,那么在这些情况中,订单的主状态都是“配送中”,它的子状态就是细化的这几种情况。**子状态有哪些具体的取值,不同的项目是不一样的,这个就开放给各个应用来定义。**
所以,订单服务数据模型里有**两个字段**,其中的主状态由订单服务负责管理,包括主状态之间的变化规则;而子状态由上层应用来定义,管理子状态的变化规则,比如一个配送中的订单,它的子状态可以由“仓库已发货”,变为“快递员正在送货中”。
现在,我们就可以总结下这两种订单状态的设计思路。
**第一种方案,我们不对订单状态进行管理**,而是把订单的状态作为一个简单的属性存储,只支持订单状态简单的增删改查功能。我们知道,订单状态是订单业务规则的核心体现,这样的订单服务是没有灵魂的,也失去了大部分业务复用的价值。
**第二种方案,应用和服务共同管理订单的状态**,订单服务抓大放小,通过主状态管理把控住了订单的核心业务规则,同时把子状态开放给应用进行管理,为具体的业务场景提供了灵活性。通过主状态和子状态的结合,订单服务就满足了不同行业、不同企业的订单状态管理需求。
### 订单服务接口定义
说完了订单的状态管理,接下来,我们从调用方怎么使用服务的角度,来看下订单服务外部接口是如何设计的。
外部系统和服务的交互有**两种方式**,包括同步的服务接口调用和异步的消息通知。
**首先是同步的服务接口调用。**
为了方便外部调用方,我们在服务接口命名时,一定要规范和统一,接口名字要能够望文生义,方便调用者快速找到所需要的接口。并且,我们还要提供接口具体的请求和响应样例帮助说明。
具体的接口设计规范,我就不具体展开了,每个公司都要有明确的规范要求,这里我就说下常见的查询接口是如何设计的。
一个订单有很多字段,每次调用方要查询的信息可能都不相同,不同字段之间的组合方式有很多,我们不可能一一支持。
**那么,我们怎么设计查询接口,来满足各种场景需求呢?**一般来说,我们可以根据返回字段数量的不同,提供三个不同粒度的查询接口来满足多样化的需求。
第一个是**粗粒度接口**只返回订单最基本的7-8个字段比如订单编号、订单状态、订单金额、下单用户、下单时间等等第二个是**中粒度接口**,返回订单比较常用的十几个字段;第三个是**细粒度接口**,返回订单的详细信息。
这样,不同的查询需求,就可以根据要返回信息的详细程度,来选择合适的接口,通过这种方式,我们兼顾了要定义的接口数量和查询的性能。
**其次是异步的消息通知。**
订单服务除了提供同步的接口调用,还针对每次订单信息的变化,提供异步的消息通知,感兴趣的外部系统可以通过接收消息,第一时间感知订单的变化。
**按照消息详细程度的不同,订单消息可以分为“胖消息”和“瘦消息”。**
顾名思义,**胖消息**包含了尽可能多的字段,但**传输效率低****瘦消息**只包含最基本的字段,**传输效率高**。如果外部系统需要更多的信息,它们可以通过进一步调用订单服务的接口来获取。
在这个订单服务的例子里,如果是订单状态的变化,我们只需提供订单号、变化前后的状态即可,因此主要以瘦消息为主;如果是新订单的创建,由于订单的字段比较多,所以使用胖消息,避免外部系统进一步调用订单服务接口。你在实践中,可以根据实际情况,在消息的数据量和消费者处理消息的复杂度之间做平衡。
前面我们说了,订单服务不会主动调用外部系统的接口,这里的异步消息通知,就可以很好地保证外部系统及时感知订单的任何变化,同时避免订单服务和外部系统直接耦合。
## 总结
要想打造一个可高度复用的共享服务,你需要掌握最核心的两点:**清晰的边界划分、内部的抽象设计。**
今天,我通过一个实际的订单服务例子,帮助你理解如何清晰地定义服务的边界,以及如何通过抽象设计保证服务的通用性。你在实践中,一定要深入分析业务场景,识别真正的挑战在哪里,避免设计的简单化或过度复杂化。
通过今天的讲解,相信你在前一篇理论内容的基础上,对如何打造一个共享服务有了更深入的体会,希望你在工作中能不断地去实践,真正掌握这些技能。
**最后,给你留一道思考题:**在落地共享服务的时候,你碰到过哪些挑战,都是怎么解决的?
欢迎你在留言区与大家分享你的答案,如果你在学习和实践的过程中,有什么问题或者思考,也欢迎给我留言,我们一起讨论。感谢阅读,我们下期再见。

View File

@@ -0,0 +1,130 @@
<audio id="audio" title="09 | 可复用架构案例(二):如何对现有系统做微服务改造?" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/4e/f7/4e6e289aa5f4eb9f7bcf146327bbddf7.mp3"></audio>
你好,我是王庆友。在上一讲中,我以订单服务为例,和你一起讨论了如何从头开始,设计一个共享服务。今天我们再来聊一聊:**如何对现有系统做微服务化改造**。
很多早期的互联网公司都有巨大的单体应用,底层的数据表集中放在一个数据库里,这些表加起来可能有几百张。对于这样的应用系统和数据库,我们往往需要对它们进行拆分,通过微服务化改造,保证系统能够不断地扩展和复用。
相比从头开始落地服务,对现有系统做微服务化改造,这会面临更多的挑战。
首先应用和数据表紧密耦合在一起代码模块和表是多对多的依赖关系。一个模块会访问多张表多个模块也会对同一张表进行访问而且由于表都在一个数据库里开发人员往往会随意对表做关联有时候甚至Join 5~6张表以上。这样代码模块和表之间的关系是剪不断理还乱我们很难清晰地划分代码和数据表的边界也就很难把它们封装成独立的微服务。
还有,系统现在已经在运行了,我们的改造不能影响业务的稳定性。那微服务落地后,现有的系统要怎么对接微服务,数据要怎么迁移,才能保证系统的平滑过渡呢?
所以,要想应对这些挑战,一方面,我们要保证比较合理的服务设计,才能达到优化系统架构的目的;另一方面,我们要做到整个过程对现有系统的影响比较小,才能达到系统改造顺利落地的目的。
接下来我就以1号店库存服务化改造为例让你深入理解我们是如何把库存相关的功能和数据表从现有系统里剥离出来最终构建独立的库存服务并实现和业务系统平滑对接的。
## 改造背景和目标
我们先来看下这次架构改造的背景和目标。
1号店作为一个网上超市售卖的商品种类有数十万个包括1号店自营和第三方商家的商品。由于历史原因所有商品相关的表都存在产品库里面这里面有产品的表产品、分类、品牌、组合关系、属性等、商品SKU的表、商家和供应商的表、库存和价格的表等等这些表加起来数量超过了上百张。
我们知道商品是电商业务的核心几乎所有的前后台系统都需要访问这个产品库而这些系统的开发人员早期的时候只关心如何实现业务功能对这些表的访问是怎么方便怎么来有些SQL语句会对大量的表做Join关联。所以说虽然系统是类似分布式的但数据库是集中式的如下图所示
<img src="https://static001.geekbang.org/resource/image/d6/27/d60dcd957d63ac2b92bf0158a3fb9c27.jpg" alt="">
这样的方式,就给系统的维护带来了一系列的问题。
- 从**应用方面**来说,各个系统功能重复建设,比如很多系统都会直接访问库存相关的表,类似的库存逻辑散布在很多地方;另外,如果修改了库存表的某个字段,这些系统同时会受影响,正所谓牵一发而动全身。
- 从**数据库方面**来说,数据库的可用性是比较差的,如果某个系统有慢查询,它就很可能拖垮整个产品数据库,导致它不可用;还有,这么多系统同时访问产品库,数据库的连接数也经常不够用。
所以,我们这次架构改造的目标,首先是对这个大数据库按照业务维度进行垂直拆分,比如分成产品数据库、库存数据库、价格数据库等等;然后基于这些拆分后的库,构建微服务,以接口的方式来支持数据库表的访问;最后将各个业务系统统一接入微服务,最终完成整个商品体系的微服务化改造。
## 微服务改造过程
你可以看到,这里涉及了多个微服务,如果同时进行服务化改造的话,牵扯太大,很难落地。于是,我们选择从**库存微服务**开始。一方面,库存的业务很重要,库存的规则也比较复杂,如果我们能够对库存逻辑进行优化,这会带来明显的业务价值;另一方面,电商的库存概念相对独立,涉及的表也比较少,我们可以相对容易地把它从现有体系中剥离出来。
整个改造过程从确定库存相关的表开始到最后把库存表从产品库迁移出来落到单独的库存数据库为止一共分为两个阶段每个阶段包含了3个步骤具体如下图所示
<img src="https://static001.geekbang.org/resource/image/d5/5a/d59b641233f9c9190be00119330da75a.jpg" alt="">
- **准备阶段**这个阶段为微服务改造做好前期的准备工作具体步骤包括了圈表、收集SQL和SQL拆分。
- **实施阶段**:这个阶段实际落地微服务,具体步骤包括微服务开发、服务接入和数据库独立。
通过这些良好定义的步骤,我们就很好地保证了整个库存微服务改造的有序和可控。接下来,我就具体说明下改造的各个步骤,包括哪些人负责哪些事情、具体的挑战在什么地方,这样,你可以深入地理解整个改造过程。
### 准备阶段
**准备阶段的第一步,就是圈表。**产品数据库有100多张表圈表就是用来确定库存微服务具体包含哪些表也就是确定服务的数据模型。在确定了表以后库存微服务就负责这些表的访问当然库存微服务也不会访问其它的表而业务系统后续将通过库存微服务的接口实现对这些表的访问。
圈表是微服务改造中比较有挑战性的地方,它实际上对应了服务的边界划分。只是针对老系统做服务化改造的时候,我们更多的是从数据库表的角度来考虑划分,这样更好落地。
针对库存微服务来说,我们要求圈定的表,一方面要满足所有的库存访问需求,这些表之间关系紧密,和其它的表关联不大;另一方面,这些表的数量不能太多,一般不超过十几张。这样,我们既容易拆分数据库,又能控制服务的粒度,保证功能聚焦。
在这个例子中由于库存的概念比较独立圈表相对比较容易一共有15张表和库存直接相关包括自营库存表(这里有分表实际是12张)、商家虚拟库存表、活动库存表和库存共享表,这些库存表之间是紧密相关的,它们一起决定了前台用户能看到的可用库存数量。
这些库存相关的表都有商品ID字段和商品基本信息表关联我们知道库存数量的计算不依赖于商品的具体信息。所以这些库存表和其它表的关系比较弱这样我们就可以比较清晰地实现库存表和其它表的切分简化了库存服务的落地。
<img src="https://static001.geekbang.org/resource/image/d8/41/d8d9fd5198ae90448b74a509e6915241.jpg" alt="">
在微服务改造中,确定哪些表属于这个服务,会直接影响后续的所有改造工作,这需要有经验的业务架构师和数据架构师参与进来,通过深入地分析现有的业务场景和表的关系,才能对库表进行合理的划分。
所以,你可以发现,**对现有系统的改造,服务的边界划分主要是从圈表入手的,而不是从一个服务应该有哪些功能入手的,这一点和新服务设计是有所不同的。**这有两方面原因:
- 一方面,如果确定了服务包含哪些表,也就大致确定了服务有哪些功能,而表是现成的,它比业务功能要直观很多,所以从表入手比较高效;
- 另一方面,如果从表入手,构造的服务和表是对应的,服务包含的是完整的表,不会产生一个表的一部分字段属于库存服务,而另一部分字段属于别的服务的情况,避免表字段的拆分带来额外的复杂性。
值得注意的是,因为这是对现有系统的改造,为了避免一下子引入太多变化,我们先不对库存的表结构进行调整,表结构的优化可以放在服务的升级版里做,这样对业务系统的影响也最小。
**第二步是收集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个实例大促时会部署更多我们很容易通过加机器的方式实现库存服务的水平扩展。
<img src="https://static001.geekbang.org/resource/image/ed/e4/edd3896352ae263f6ae6f7c773d8f2e4.jpg" alt="">
## 微服务改造小结
到这里我们的库存微服务就改造完成了整个改造大概持续了3个月主要是对接的工作比较耗时。
从前面的步骤中,你可以看到,**除了做好库存服务本身的设计开发工作,相关团队之间的配合也是非常重要的。**
在整个改造过程中,有很多**团队之间沟通和确认**的环节。比如说服务开发团队圈定表以后需要和业务开发团队一起确认保证圈表的合理性在业务团队拆分SQL的过程中服务开发团队需要介入进去帮助解决拆分时带来的性能和一致性问题在服务接口设计和接入过程中服务的接口可能需要重新调整也可能有新的SQL进来双方需要及时沟通相互配合。
这些都是纯技术层面的问题,值得一提的是,系统改造不会产生直接的业务价值,对于业务开发团队来说,他们往往还需要承担大量新需求的开发工作。所以,**从项目推进的角度来看,这种核心服务的改造,很多时候都是技术一把手工程**。在库存微服务改造过程中我们也是老板高度重视大家事先定好时间计划每周Review进度协调各个团队工作的优先级确保改造的顺利落地。
以上就是库存微服务改造的例子。1号店的系统从08年就开始建设了由于历史原因形成了几个典型的大库比如产品库、用户库等等我们通过类似的微服务改造逐步把这些大库拆分开构建了一系列的基础服务如订单服务、用户服务、产品服务、库存服务、价格服务等等。而且通过这些微服务化改造我们同时提升了业务的复用性和系统的稳定性。
最后我在这里放了一张1号店的总体系统架构图你可以深入看下**一个历史包袱很重的系统,它是如何经过服务化改造,最终变成一个能够高度复用和扩展的平台的。**
<img src="https://static001.geekbang.org/resource/image/45/4a/45ee463d98340c23d9b0830acb4b074a.jpg" alt="">
## 总结
好了,下面我总结一下今天所讲的内容。
**基于现有系统进行改造和全新的服务设计是有所不同的,我们不能追求理想化和一步到位,而是要考虑到系统的平滑过渡,先实现微服务的顺利落地,后续再考虑各种优化。**
今天我通过1号店库存微服务改造的例子给你提供了一种可行的微服务落地套路让你可以顺利地完成老系统的架构升级。
相信通过今天的分享,你对现有系统如何进行微服务化改造有了更深入的理解,希望你在实践中也能灵活运用。
**最后,给你留一道思考题:**你在做现有系统服务化改造的过程中,具体碰到了哪些挑战,你又是如何克服的呢?
我是王庆友,欢迎你在留言区与大家分享你的思考,我们一起讨论。如果这节课对你有帮助,也欢迎你把它分享给你的朋友。感谢阅读,我们下期再见。

View File

@@ -0,0 +1,108 @@
<audio id="audio" title="10 | 可复用架构案例(三):中台是如何炼成的?" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/1d/02/1df62c48be984c8c1b5b784faff0f002.mp3"></audio>
你好,我是王庆友。
在[第8讲](https://time.geekbang.org/column/article/209138)中,我通过一个实际的订单服务案例,和你介绍了如何设计一个基础服务。今天,我就继续带你了解,如何在实际的业务场景中,通过一步步的架构升级,最后落地一个中台,实现企业级能力的复用。
通过前面的介绍,我们已经很清楚了共享服务和中台的价值,但在实践中,要不要对系统做这样的升级,我们还需要结合业务来判断,比如说:
1. **业务上有什么重大变化,导致当前系统的弊端已经很明显,不能适应业务发展了呢?**
1. **架构改造时,如何在业务、系统、资源三者之间做好平衡,对系统进行分步式的改造呢?**
我们知道,架构没有最好,只有最合适的。随着业务的发展,系统需要不断地升级,这是一个螺旋式上升的过程,如何结合当前的业务发展阶段,适时地推进架构改造,并能比较接地气地落地,是我们要追求的目标。
接下来,我以实际的订单系统改造为例,结合订单业务的发展和系统的痛点,为你介绍,如何推进架构从单体到共享服务、再到中台的改造过程,保证系统能够不断适配业务的升级。
先说下项目背景。公司作为供应商为大型餐饮连锁企业打造O2O交易平台包括三方聚合外卖、自有小程序、App点餐这些线上用户的订单最终会落到门店的收银系统由门店进行履单。
公司的业务发展有一个变化过程,一开始只提供聚合外卖服务,后来进一步提供小程序/App下单服务。你可以发现整个订单处理的架构也是随着业务的变化而不断演变的下面我就为你一一介绍。
## 聚合外卖订单架构
一开始,我们提供的是聚合外卖服务,相应地,系统整体架构如下图所示:
<img src="https://static001.geekbang.org/resource/image/d3/46/d3a670ed6f529c91a17603cd91733c46.jpg" alt="">
这里一共有三个系统,分别是三方外卖平台、门店收银系统以及外卖系统。其中,外卖系统是我们开发的,其他两个都是我们要对接的外部系统,接下来,我说下系统具体的交互过程。
首先用户在三方外卖平台如美团、饿了么下单然后我们的外卖系统通过外卖平台的API拉取用户的订单把订单落到本地数据库最后门店的收银系统访问外卖系统提供的接口获取订单在门店内部完成履单。当然门店履单后收银系统会反过来同步订单状态给外卖系统外卖系统再同步订单状态到第三方外卖平台。
你可以看到这里的外卖系统是一个单体应用内部包含外卖同步接口和POS接口两个模块。其中**外卖同步接口**负责和第三方外卖平台对接,它主要是针对不同的外卖平台做接口适配;而 **POS接口**负责和门店的收银系统对接。这两个模块都是使用同一个外卖订单数据库。
从**数据模型**上看,系统的订单模型也是完全按照外卖订单的需求设计的,订单状态管理也相对比较简单,因为这些订单都是用户在第三方外卖平台已经完成支付的。所以,我们的外卖系统,主要是负责管理门店履单过程中带来的订单状态变化。
从**系统架构**上看,外卖系统从外卖平台接单,然后把订单推送给后面的收银系统,只需要一个应用、一个数据库、两套接口就可以支持,使用单体架构就能很好地满足外卖的接单需求。
## 小程序下单架构
接下来,随着公司业务的升级,除了提供聚合外卖服务之外,公司还提供自有小程序的下单服务。这样,消费者既可以在三方外卖平台下单,也可以在品牌自有的小程序里下单。
**不同于三方外卖订单,小程序下单平台是一个完整的业务**,它包括小程序用户注册、商品和菜单浏览、商品加购物车、在线支付等等。相应地,这里会有多个基础服务对应具体业务的处理。比如,商品服务提供前台的商品浏览功能,支付服务提供用户的支付功能,这些基础服务都是由独立的小程序服务端负责整合,然后提供接口供小程序前端访问。
当用户在小程序提交订单后,小程序前端会调用服务端的下单接口,然后服务端调用订单服务,在小程序的订单库里落地订单。现在我们已经完成了前台用户的下单,但后台的订单履行怎么处理呢?这里有两种选择:
1. 小程序订单和外卖订单的处理类似,收银系统除了对接外卖系统,同时也对接小程序的订单服务。但这样一来,收银系统需要同时对接两套订单接口,它需要做大的改造。由于这是第三方的系统,我们在实践中很难落地。
1. 我们把小程序订单当作一个特殊的外卖渠道,把小程序订单推送到外卖订单库里,最终还是由外卖系统来对接收银系统,也就是相当于小程序订单直接借用了外卖订单的履单通道。
当时由于项目上线的时间比较紧急,同时从系统稳定性的角度出发,避免对收银系统做大的改造,我们采用了**第二种方式**,小程序的订单处理就嫁接在已有的外卖系统上,整个系统架构如下图所示:
<img src="https://static001.geekbang.org/resource/image/ef/ba/ef08a388d34ab936350efd52c1549cba.jpg" alt="">
你可以看到,小程序下单平台和外卖系统相对独立,同时为了更好地解耦,小程序订单服务和外卖系统之间是通过**消息系统**同步订单数据的。
这个方案是一个比较务实的选择,通过复用外卖订单的履单通路,我们也实现了小程序订单的闭环处理。表面上看,我们节省了重新搭建系统的成本,也快速落地了小程序交易这条新业务线。
但这样的架构**实际上是一种妥协**,在后续的系统运行过程中,给我们带来了很多问题:
1. 这里有两套订单系统,一套针对小程序订单,一套针对外卖订单。我们知道,两者的字段属性和订单状态定义都有不同的地方,我们把小程序的订单硬生生地套在了外卖订单的模型里,这样限制了小程序订单能力的扩展。
1. 小程序订单处理链路过长,从小程序服务端-&gt;订单服务-&gt;小程序订单数据库-&gt;消息系统-&gt;外卖同步接口-&gt;外卖订单数据库-&gt; POS接口-&gt;收银系统一共包含了8个处理环节系统整体的性能和可用性都存在很大问题。比如取餐码已经从收银系统同步给了外卖系统但由于消息队列堵塞外卖系统不能及时同步给小程序的订单服务这样导致了小程序用户不能及时地看到取餐码。
1. 为了使两套订单系统解耦,我们使用了消息队列在两个库之间同步订单数据,这降低了系统整体的稳定性。实践中,也发生过多起消息队列故障导致的线上事故。
你可以发现,出现这些问题的根源是我们把小程序订单硬塞给外卖系统,一方面订单数据模型不匹配,另一方面由于这是两个系统的简单拼接,导致系统调用链路很长,影响了业务的扩展和系统的稳定性。
**那有没有更好的办法,能够把这两个系统有机地结合起来呢?**接下来,我们就来看下,如何通过一个统一的订单服务对两个系统进行深度的融合,从而灵活地支持多种订单业务。
## 统一订单服务架构
这里,我们把小程序订单服务提升为统一共享的订单服务,由它来落地所有类型的订单。对于这个统一的订单服务来说,外卖订单、小程序订单,或者是其他的新订单,都是它的下单来源,所有订单汇总在订单服务里,然后统一提供给收银系统进行履单。具体架构如下图所示:
<img src="https://static001.geekbang.org/resource/image/7c/b0/7c40a6fe7f17f17b28decf7b09cb0cb0.jpg" alt="">
你可以看到,系统架构经过调整,有两个大的变化:
1. 原来外卖和小程序各自有一个订单库,现在合并为了一个订单库,由这个订单服务统一对外提供订单数据的访问和状态管理。
1. 原来外卖系统的两个模块“外卖同步接口”和“POS接口”升级为了两个独立的应用。外卖同步接口变成外卖同步服务对接外卖平台POS接口变成POS服务对接门店的收银系统。它们都是通过统一订单服务存取订单数据。
**经过升级,新的架构具备了明显的层次结构,自上而下分为三层:**首先是各个渠道端包括三方外卖平台、小程序前端和POS收银系统然后每个端都有相应的服务端来对接比如外卖同步服务对接外卖平台、小程序服务端对接小程序、POS服务对接收银系统最后这些服务端都统一调用底层的订单服务。
在这个架构里如果我们要增加新的下单渠道就非常方便比如要支持App下单我们提供App服务端即可要新增加后台履单方式也非常方便比如对于新的电子卡券类订单它不需要经过收银系统可以直接由企业的OMS系统Order Management System订单管理系统处理要实现这样的业务我们只需新增加一个和OMS系统的适配应用就可以了。所以**这里就不仅仅是一个外卖订单和小程序订单的处理平台,而是升级成了一个完整的全渠道交易平台。**
同时,订单处理的链路大大缩短,从小程序服务端-&gt;订单服务-&gt;订单数据库-&gt; POS服务-&gt;收银系统只有5个节点相比之前减少了3个系统的可用性和端到端的性能得到了大幅度的提升。
最后统一订单服务实现了统一的订单属性定义、统一的订单状态管理以及订单数据的集中存储这对后续的BI分析和数据中台建设非常有帮助。它们处理数据时只需要从一个订单库拉取数据解析一个订单数据模型就可以了。
## 中台架构
上面的统一订单服务整合了外卖和小程序的订单,并且为新的下单渠道预留扩展。按照同样的思路,我们可以构建统一的商品服务,同时满足外卖和小程序上商品的管理;可以构建统一的促销服务,同时支持线上和线下的促销活动;也可以构建统一的库存服务,实现线上和线下库存的同步和共享等等。
**通过构建这样一系列的共享服务,我们就实现了各个渠道业务规则和业务数据的统一管理,最终我们落地了一个强大的业务中台,可以很方便地扩展各个业务,实现企业整体业务能力的复用。**
最后,实际项目的中台架构如下图所示:
<img src="https://static001.geekbang.org/resource/image/33/75/3319e45e76ed17eea808a6a4dcf8e575.jpg" alt="">
在这个架构中,**前端**有3个业务场景分别是小程序点单、App商城下单、外卖平台下单每个业务场景都有相应的**服务端**负责对接。在各个服务端下面,还有一些**辅助的应用**,如购物车、秒杀、拼团等等。同时这里还有一个**订单控制服务**Order Control ServiceOCS负责订单逻辑的编排以及前后台之间的状态同步你可以把它看作是基础服务之上的聚合服务。
再底下就是核心的**业务中台**它由9大服务中心组成这些中心和商户内部系统进行对接。其中商品中心和库存中心对接ERP系统会员中心对接CRM系统订单中心对接POS收银系统这里的对接分别由对应的适配插件负责。
通过这个订单业务改造落地后的中台架构,你可以看到,中台由各个通用的基础服务构成,它是相对标准的;而插件是定制的,具体和每个企业的后台系统有关。这样,通过共享服务和中台,我们就把企业内部基础设施和线上业务场景有效地打通了,从系统架构的层面,为企业的全面数字化转型打下了良好的基础。
## 总结
今天,我从一个企业的订单业务变化出发,为你介绍了为什么要落地一个统一的订单服务,以及如何落地,并通过打造一系列类似的共享服务,逐步升级系统到中台架构。
相信通过这个实际案例,你进一步理解了如何通过共享服务和中台,实现业务能力的复用,并能根据公司的业务发展阶段,选择合适的时机、合适的架构,以接地气的方式对系统进行逐步改造。
**最后,给你留一道思考题:**目前你的公司有没有落地共享服务,它是怎么逐步演变过来的呢?
欢迎你在留言区与大家分享你的答案,如果你在学习和实践的过程中,有什么问题或者思考,也欢迎给我留言,我们一起讨论。感谢阅读,我们下期再见。

View File

@@ -0,0 +1,140 @@
<audio id="audio" title="20 | 从务实的角度,给你架构设计的重点知识和学习路径" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/34/80/34c0f88a9a1f5a39df52b032e1f15c80.mp3"></audio>
你好,我是王庆友。
到目前为止,我们已经讲完了业务架构和技术架构的相关内容,相信你现在对架构有了更深入的理解。
学习架构呢,要掌握的东西有很多,你是不是开始担心自己一辈子都学不完呢?其实,我们也不需要一下子铺开学习所有的架构技能,重要的是把控好学习的节奏,在适当的时间学习适当的内容,我们可以结合实际工作,一步步地成长。所以今天这一讲,我想给你提供一些架构学习的重点方向和路径建议。
## 架构原则汇总
在技术架构篇,我针对系统的高可用、高性能、可伸缩和低成本,给你介绍了很多的架构设计原则,不同的原则对应着不同的目标,这里我把这些架构原则和目标汇总成一个表格,来帮助你更直观地了解它们。
<img src="https://static001.geekbang.org/resource/image/92/23/92ff7493c75f34d4085702dcb4a57323.jpg" alt="">
限于篇幅,这里我挑选几个原则来重点说下:
- **可回滚/可禁用**
**可回滚原则**确保了系统可以向后兼容,当系统升级出现问题的时候,我们可以回滚到旧版本,保证系统始终可用。
不过有些时候,系统回滚很困难。举个例子,如果数据库的新旧表结构差异很大,除了回滚代码,我们还要回滚数据库,这样操作起来往往需要很长时间,系统的可回滚性就比较差。所以在设计时,我们要尽量考虑数据库修改和代码的兼容性,并提前做好系统回滚的预案。
**可禁用原则**要求我们提供功能是否可用的配置,在系统出现故障时,我们能够快速下线相应的功能。比如说,新的商品推荐算法有问题,我们可以通过程序开关禁用这个功能。
- **使用成熟技术**
作为开发人员我们都很想尝试新技术但我们知道新的技术还没有经过充分的验证它往往会存在很多隐藏的Bug。
所以,作为架构师,我们在做方案设计的时候,一方面,要从系统的稳定性出发,尽量选择成熟的技术,避免因为新技术的坑而导致系统可用性出现问题;另一方面,选择成熟的技术也意味着选择了团队熟悉的技术,这样学习成本低,落地快。
- **使用同质化硬件**
我们在做硬件选型的时候,要尽量选择相同的硬件和相同的配置。
比如说对于服务器我们选择同样的CPU和内存配置以及同样的操作系统版本这样我们更容易通过统一的自动化脚本对节点进行配置对系统做水平扩展时也会更加容易。
## 架构落地过程
这些架构原则都是我们要深入理解,并且在实践中要逐渐运用和掌握的。那么下面,我就带你来了解一下架构的具体落地过程,帮助你更好地理解架构师的职责和技能要求。
简单地说架构师的职责就是负责设计架构并跟踪架构的实施过程解决过程中出现的疑难问题确保架构顺利落地。在第1讲“[架构的本质](https://time.geekbang.org/column/article/200825)”中,我和你介绍过架构师的能力模型,比如抽象思维、平衡取舍、沟通能力等等。接下来,我就结合架构的落地过程和架构师的能力模型,来具体说下架构师是如何开展工作的。
<img src="https://static001.geekbang.org/resource/image/c8/f7/c8ee9fd90ed76fd4eace41fbf074a8f7.jpg" alt="">
架构师的工作从接到项目需求或者从自己主动识别系统当前的问题开始TA的工作过程可以分为三个大阶段。
首先,架构师要和产品经理或者业务人员沟通,了解业务;和开发人员沟通,了解系统。
了解完系统和业务后,架构师接下来就要设计具体的方案,方案设计要分三步走:
- 首先,架构师针对业务需求,分解相应功能到现有的各个系统,把系统的各个部分串起来,这个第一版的方案至少要能够在表面上解决当前的问题,这样就形成一个草根的方案。
- 然后,架构师要进一步深入思考业务的本质,对现有的草根方案进行升华,比如说,通过抽象,让方案更加通用,可以解决多个类似的或潜在的业务需求,这样,草根的方案就变成了一个高大上的方案,这里很考验架构师的**透过问题看本质**和**抽象总结**的能力,
- 接下来,基于现有的各项约束,比如时间、资金和人员技术能力等因素,架构师要对方案进行简化,把高大上的方案变成一个接地气的方案,以最小的代价实现最大的价值,这里很考验架构师的**平衡取舍能力**。
方案设计好之后,最后还要进行**宣讲**,架构师需要说服相关的人员接受方案,并且在后续的方案执行中,负责跟踪架构的落地,如果过程中有疑难问题,架构师还要协助解决。
所以,我们可以看到,架构师在设计方案时,会有一个反复迭代的过程,最终才能得到一个简约而不简单的方案。并且在方案设计的前后,架构师还需要和大量的人员进行沟通,这些都需要架构师具备宽广的知识面和良好的沟通能力。
## 架构师知识结构
**那么,架构师都需要掌握哪些具体的技能呢?**这里我给你提供了一个简化的架构师技能图谱,可以帮助你循序渐进地学习这些架构技能。
<img src="https://static001.geekbang.org/resource/image/bd/05/bd542ad898f1f060fdbb120d7462b005.jpg" alt="">
首先,作为架构师,我们需要了解**计算机硬件和操作系统**的相关知识它们是负责具体干活的如果对它们有深入的了解我们就能知道系统底层是怎么执行的在做具体设计的时候我们也就可以做各种优化。比如说在设计RPC通讯框架时我们可以通过IO多路复用和内存零拷贝技术来提升服务端并发处理请求的能力。
在这之上就是**具体技术**相关的内容,从浅到深可以分为三个部分:
- 第一部分是**开发相关的基本知识**,比如数据结构和算法、具体的开发语言、常用的设计模式以及开发框架等等,这样你就具备了基本的开发能力。
- 第二部分是**各种中间件知识**,常用的中间件包括数据库、缓存、消息系统、微服务框架等等,对于这些核心中间件,我们不但要了解具体的用法,还要深入理解它们的适用场景。这样你就能写出高效健壮的代码,能够独立承担一个子系统的开发。
- 继续往下深入,你还要学习**分布式系统相关的知识**,包括底层网络和分布式通信技术,这样你就可以了解系统是怎么连接在一起的。除此之外,你还要了解一些周边的系统,比如大数据平台、运维监控系统、接入系统等等,这样,你就可以了解系统端到端的运行过程,从技术架构上保证系统的稳定可用。
掌握了这些技术能力之后,你就可以逐渐往全面的架构师发展了。比如说,你可以结合业务,来设计应用体系,包括数据模型和服务设计;你可以了解各种应用架构模型,知道它们的优缺点和适用场景,能够定义一个良好的应用依赖关系。
再往上,就是成为业务领域专家。在这个阶段,你已经知道如何通过业务拆分,实现业务之间的解耦;如何通过业务抽象,实现业务的扩展和重用。
到最后,你已经对各种架构设计的目标和架构原则都非常了解了,知道面对一个具体的问题,大致都有哪些解决的手段;然后,经过大量的实践,你能够把技术架构、应用架构、业务架构融会贯通,并针对具体情况,对架构的各个目标做良好的平衡。当然,作为架构师,你还要和一系列的人员打交道,这时候就需要你培养更多的**软技能**,能把复杂的架构问题以简单的方式表达出来。
## 架构师成长路径
现在你已经清楚了作为一个架构师TA需要具备什么样的知识结构。如果你想成为一名架构师在不同的成长阶段你还需要学习不同的内容。这里我以Java为例进一步给出学习的重点内容给你提供更具体的参考。
<img src="https://static001.geekbang.org/resource/image/36/64/36078cf0b5c469893c0be152b69d8d64.jpg" alt="">
第一个阶段是**初级开发阶段**。
在这个阶段,你需要深入学习数据结构和算法,并且一定要深入掌握单体应用的分层架构,因为这是架构设计的基础。
另外对JDK的一些核心类你不能仅仅停留在使用层面而是要深入研读源代码了解它的内部设计。这样你就知道如何开发一个高效的程序如何进行各种代码级的调优。
第二个阶段是**高级开发阶段**。
首先,你需要非常了解**设计模式**,每个设计模式都可以看做是一个小型的架构设计,这里面有很好的设计原则和抽象思维,你在做系统设计时可以借鉴它们。
然后,你需要非常了解**核心的中间件**包括DB、微服务框架、缓存和消息系统要清楚地了解它们的适用场景比如消息系统的削峰、解耦和异步知道如何对它们进行调优以及了解它们都有哪些常见的坑等等核心中间件是我们做技术选型的基础。
同时,你要深入掌握**数据库设计和服务接口设计**,了解它们的最佳设计实践,它们承载了系统核心的业务数据和业务逻辑。
最后,你需要进一步**研读源码**源码是活的教材它包含了大量实用的设计原则和技巧。这里我建议你选择一些开源的开发框架和RPC通信框架去深入了解它们内部的实现原理比如Spring和Netty。
第三个阶段是**架构师阶段**,成为技术专家。
首先你需要深入了解网络通信比如说网络分层和HTTP/TCP协议还有各种常见的RPC通讯框架了解它们的特性和适用场景这样你在设计分布式系统时就能够进行合理的技术选型。
然后是了解底层系统包括JVM、操作系统和硬件原理再往上延伸到系统的接入部分了解常见的负载均衡特性和用法这样你可以对整体的系统有个透彻的了解把各个环节可以很好地衔接起来。这里我特别建议你去读下Java和JVM的规格说明书了解Java的底层设计。
最后你需要熟练掌握各种设计工具和方法论比如领域驱动设计和UML了解常用的架构设计原则这样你就能够结合业务选择合适的应用架构和技术架构并进行落地。在这一阶段对你总的要求就是能够从**端到端的角度**进行业务分析和系统设计。
第四阶段是**大师阶段**。
在这个阶段,你需要对架构的各个目标都非常了解,除了业务系统设计,你还要对运维和监控有深入的认知。同时,你需要了解业界的架构实践,跟踪技术的发展趋势,如果出来一项新技术,你可以比较准确地对它进行定位,把它纳入到自己的能力体系当中。
另外,在这个阶段,你也已经通过大量的实践,培养了很好的软技能,比如沟通能力、项目管理能力等等。那么最后,你就能做到技术和业务的融会贯通,可以平衡各种架构目标,设计非常实用和接地气的架构,并保障它的顺利落地。
## 架构师境界
你可以发现,架构师的能力是一个逐渐提升的过程,如果从架构师的境界来看,由浅到深可以分为四层:第一层看山不是山,第二层看山是山,第三层看山不是山,第四层看山是山。
这是一个螺旋式上升的过程,那么它究竟是什么意思呢?
<img src="https://static001.geekbang.org/resource/image/8d/7f/8dee8d15ad007764e3f781a3d8cdbe7f.jpg" alt="">
- 刚接手项目的时候,你对业务还不太了解,经常会被业务方冒出的术语弄得一愣一愣的,如果把现有问题比作山,那就是横看成岭侧成峰,你根本摸不透,此时**看山不是山**。
- 经过业务梳理和深入了解系统以后,你能够设计出一个简单的方案,把各个系统串起来,能解决当前的问题,对当前的这个“山”能够看清楚全貌,此时就做到了**看山是山**。但这样的方案往往设计不够,只能解决表面问题,碰到其它类似问题或者问题稍微变形,系统还需要重新开发。
- 通过进一步抽象,你能够发现问题的本质,明白了原来这个问题是共性的,后续还会有很多类似的问题。然后你就对设计进行总结和升华,得到一个通用的方案,它不光能解决当前的问题,还可以解决潜在的问题。此时,你看到的已经是问题的本质,**看山不是山**。但这样的方案往往会过度设计,太追求通用化,会创造出过多的抽象概念,理解和实现起来都特别困难,过犹不及。
- 最后回到问题本身,你能够去除过度的抽象,给出的设计简洁明了,增之一分嫌肥,减之一分嫌瘦,既能解决当前的问题,又保留了一定的扩展能力,此时问题还是那个问题,**山还是那个山**。这样的方案在了解问题本质的基础上,同时考虑到了现状,评估了未来,不多做,不少做。
你可以对照这四个境界,来评估你当前的架构能力,不断地提升对自己的要求。
## 总结
今天,我汇总了常见的技术架构设计原则,它们都是实践的总结,你在做架构设计时,可以参考这些原则,在项目中采取相应的手段来实现架构目标。值得注意的是,在做具体的架构设计时,你需要对设计进行反复迭代,才能最终得到一个高性价比的方案。
针对架构师的成长,我也给你提供了相应的知识结构和可行的进阶之路,希望你能够一步步成长,最终实现自己的理想。
**读万卷书,行万里路。**架构师的成长尤其如此,架构没有速成之路,我们先要“读万卷书”,学习各种架构需要的技能,然后“行万里路”,通过大量的实践把架构知识变成架构能力。
**最后,给你留一道思考题:**一个架构方案从调研到设计再到落地,你觉得最困难的地方是什么?
欢迎在留言区和我互动,我会第一时间给你反馈。如果这节课对你有帮助,也欢迎你把它分享给你的朋友。感谢阅读,我们下期再见。

View File

@@ -0,0 +1,100 @@
<audio id="audio" title="结束语 | 和你聊聊我的架构心路历程" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/e6/34/e68e5ea914f52d05eadbea1ad0d28134.mp3"></audio>
你好,我是王庆友。
今天是专栏的最后一讲,我和你分享的核心内容马上就要结束了,我是感到既欣慰,又觉得如释重负。
说实话,架构的话题不好写,内容涉及面比较广,既要有理论的高度,又要有实践的细节,才能把这个话题讲得透彻。我看到很多的同学在评论里反馈课程的内容很干,都在反复地吸收和体会,这说明课程的内容对你有价值,相信你也有了很多的收获。在这里,我也很感谢你一直保持着学习的热情。
不知你有没有感觉到,整个专栏的内容既很虚又很实,这里“虚”指的是理论的高度概括,“实”指的是接地气的案例介绍。所以在这里,我想再和你简单地分享一下专栏的写作思路,帮助你更好地理解和使用这个专栏。
## 专栏的写作思路
整个专栏一共有7讲是理论篇这个也是专栏的主体内容框架如下图所示。
<img src="https://static001.geekbang.org/resource/image/d5/16/d50f83ee90b87ff3cf525f24274f9616.jpg" alt="">
<li>
第1讲“架构的本质”是专栏的总纲通过架构的本质和架构分类体系介绍让你迈进架构认知的大门。
</li>
<li>
<p>然后,针对业务架构和技术架构,分别有一讲内容深入介绍它们的定位,帮助你从源头区分这两种架构,**业务架构聚焦人脑如何理解业务**,针对的是业务性功能;**技术架构聚焦电脑具体如何干活**,针对的是系统性功能,这两者的目标以及处理手段都是不同的。<br>
(补充说明:应用架构更多的是业务架构的具体落地,在专栏中,我把应用架构和业务架构糅合,比如说[第4讲](https://time.geekbang.org/column/article/205832)“电商平台架构是如何演变的”,实际上讲的是应用架构。)</p>
</li>
<li>
最后,针对每种架构的核心目标,也都有一讲内容专门分析每个目标的实质和实现手段。
</li>
通过这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号店承担了很多大项目的设计有些是偏业务的比如针对基础业务的服务化改造有些是偏技术的比如订单水平拆库和灰度发布系统。通过这些架构实践把我很多的架构知识变成了实际的架构能力并且通过方案的整体设计让我可以把原有各个部分的能力整合在一起。
总而言之,你可以看到,我的成长经历虽然没有一个明显的主线,但还是隐隐有迹可循的,大致可以遵循以下过程。
### 打造基础能力
首先,你要对架构设计的各个要点有深入的了解,包括数据结构和算法、设计模式、数据库和服务设计、分布式通信等等。对于这些要点,你不能仅仅停留在使用层面,而是要深入理解它们的内部机制,这样你就打造了扎实的基础能力。
### 建立体系
在了解了各个设计要点的基础上,你需要对架构设计建立体系化的认知,能够从整体上认识架构,清楚架构的设计目标、设计过程和设计手段。你之前是从各个局部来考虑问题,现在要变为从整体的角度来考虑问题。在架构设计上,我们宁可要精确的模糊,也不要模糊的精确。
### 纳入体系
有了整体的认知体系以后,你就有了存放架构内容的框架。然后,你可以从**广度和深度**两个方面,来不断丰富和完善你对架构的认知。
### 实践运用
最后,通过大量的实践,你就可以把原先储备的架构知识,以及各项基础能力有效地串接起来,最终打造完整的架构能力。
架构师的能力,既涉及到业务和技术,又涵盖了它们的广度和深度。在成为架构师的过程中,你可以积累各项能力,把以往的知识和经验串起来,这其实就相当于一个银行,你可以不断地储蓄,然后进行整体输出,这是一个很好的个人成长和发挥价值的途径。
## 写在最后
最后,我想说的是,这是技术最好的时代,我们有很好的技术可以选择,有很开放的技术分享氛围,有很好的技术回报。但这也是技术最差的时代,技术太多,变化太快,我们还需要不断地学习。
想要成为信息时代的弄潮儿,除了努力,你还需要有方向,我希望这个架构专栏,可以成为你学习架构的指路明灯,帮助你更好地成长。
好了,这就是我作为一个架构老兵,想和你分享的经历和思考。专栏的正文更新到这里就要告一段落,但是更新的结束并不意味着我们之间就要切断联系,之后呢,我还会针对整个专栏的内容,给你一套系统性的结课测试题,让你可以检测一下自己的学习成果。并且,我还会继续回复你的留言,如果你对于架构有新的问题和思考,也欢迎继续和我一起交流。
在本讲的结尾,我还为你准备了一份毕业问卷,题目不多,希望你能抽出几分钟时间填写一下,我非常希望听听你对这个课程的意见和建议,欢迎你在问卷中畅所欲言,期待你的反馈!
[<img src="https://static001.geekbang.org/resource/image/e4/1b/e4db89b21afe1d2e62e7e515e96f771b.jpg" alt="">](https://jinshuju.net/f/pYpw4i)
我是王庆友,感谢你一直以来的学习和坚持,也感谢你的留言和反馈,相信对你对我,这都是一段非常有意义的成长经历,让我们一起享受架构学习的乐趣吧,我们江湖再见!

View File

@@ -0,0 +1,145 @@
<audio id="audio" title="11 | 技术架构:作为开发,你真的了解系统吗?" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/45/50/452fcf2eaeb4c6229d325cfb6e9e2f50.mp3"></audio>
你好,我是王庆友。从今天开始,我们就进入了技术架构模块,所以,这一讲,我想先跟你聊聊技术架构要解决什么问题。
对于开发人员来说,我们每天都在用技术。但你要知道,我们写的代码,其实只是系统的一小部分,我们了解的技术,也只是系统用到的一小部分。要深入掌握技术架构,我们就需要了解整体的系统。
面对一个复杂的系统,我想你可能经常会有以下困扰:
1. 不清楚系统整体的处理过程,当系统出问题时,不知道如何有针对性地去排查问题。
1. 系统设计时,经常忽视非业务性功能的需求,也不清楚如何实现这些目标,经常是付出惨痛的教训后,才去亡羊补牢。
不知你是否还记得,在[第一讲“架构的本质”](https://time.geekbang.org/column/article/200825)中,我已经说过,技术架构是从物理层面定义系统,并保障系统的稳定运行。那么今天,我会先分析下系统在物理上由哪些部分组成,让你可以从全局的角度看一个系统;然后再和你一起讨论,技术架构会面临哪些软硬件的挑战,以及它都有哪些目标,让你能够深入地了解技术架构。
## 系统的物理模型
对于大部分开发人员来说,我们主要的工作是写业务相关的代码,保证业务逻辑正确、业务数据准确,然后,这些业务代码经过打包部署后,变成实际可运行的应用。但我们写的代码只是系统的冰山一角,为了保证应用能正常运行,我们需要从**端到端系统的角度**进行分析。
我们先看下一个系统的具体组成,这里我为你提供了一个简化的系统物理模型,你可以了解一个系统大致包含哪些部分。
<img src="https://static001.geekbang.org/resource/image/85/44/8567a6fb27cb0811643c9f922601a744.jpg" alt="">
从用户请求的处理过程来看,系统主要包括五大部分。
首先是**接入系统**它负责接收用户的请求然后把用户的请求分发到某个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内打开这样用户体验比较好。
保证合理的性能分两种情况:
<li>
一种是常规的流量进来,但系统内部处理比较复杂,我们就需要运用技术手段进行优化。比如针对海量商品的检索,我们就需要构建复杂的搜索系统来支持。
</li>
<li>
第二种是高并发的流量进来,系统仍旧需要在合理的时间内提供响应,这就更强调我们做架构设计时,要保证系统的处理能力能够整体上做水平扩展,而不仅仅是对某个节点做绝对的性能优化,因为流量的提升是很难准确预计的。
</li>
### 系统的可伸缩和低成本
系统的业务量在不同的时间点,有高峰有低谷,比如餐饮行业有午高峰和晚高峰,还有电商的大促场景。我们的架构设计要保证系统在业务高峰时,要能快速地增加资源来提升系统处理能力;反之,当业务低谷时,可以快速地减少系统资源,保证系统的低成本。
高可用、高性能、可伸缩和低成本,这些技术架构的目标不是孤立的,相互之间有关联,比如说有大流量请求进来,如果系统有很好的伸缩能力,它就能通过水平扩展的方式,保证系统有高性能,同时也实现了系统的高可用。如果系统的处理能力无法快速提升,无法保证高性能,那我们还是可以通过限流、降级等措施,保证核心系统的高可用。
我在前面也提到,这些目标很多时候会冲突,或者只能部分实现,**我们在做技术架构设计时,不能不顾一切地要求达到所有目标,而是要根据业务特点,选择最关键的目标予以实现。**
比如说一个新闻阅读系统它和订单、钱没有关系即使短时间不可用对用户影响也不大。但在出现热点新闻时系统要能支持高并发的用户请求。因此这里的设计主要是考虑满足高性能而不用太过于追求4个9或5个9的可用性。
## 总结
系统比我们想象的要复杂得多,这里,我和你分享了系统的物理模型,相信你不再局限于我们自己写的代码,而是对系统的整体结构有了更清晰的认识。
你还记得吗?在前面介绍[业务架构](https://time.geekbang.org/column/article/204410)时,我和你分享的是**系统=模块+关系**,而在这里介绍技术架构时,我和你分享的是**系统的物理模型**。
因为**业务架构解决的是系统功能性问题**,我们更多的是**从人出发**,去更好地理解系统;而**技术架构解决的是系统非功能性问题**,我们在识别出业务上的性能、可用性等挑战后,更多的是**从软硬件节点的处理能力出发**,通过合理的技术选型和搭配,最终实现系统的高可用、高性能和可伸缩等目标。通过这一讲的介绍,相信你现在对技术架构的目标和常见的解决手段,已经有了更深入的理解。
当然,针对这些不同的目标,技术架构处理的原则和手段也是不一样的。后面的几讲中,我会针对每个目标,为你具体展开介绍。
**最后,给你留一道思考题:**技术架构除了我在课程中说的几个目标之外,还有哪些目标呢?
欢迎在留言区和我互动,我会第一时间给你反馈。如果这节课对你有帮助,也欢迎你把它分享给你的朋友。感谢阅读,我们下期再见。

View File

@@ -0,0 +1,170 @@
<audio id="audio" title="12 | 高可用架构:如何让你的系统不掉链子?" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/ae/26/ae9763d9d9ebf4af7cc3477c98ef0226.mp3"></audio>
你好,我是王庆友。今天我和你聊一聊,如何实现系统的高可用。
在实际工作中,我们平常更关注系统业务功能的实现,而对于系统是否会出故障,总觉得那是小概率事件,一开始不会考虑得太多。然而系统上线后,我们会发现系统其实很脆弱,每个地方都可能会出问题,处理线上事故的时间往往超过了开发功能的时间。
所以,对于系统的高可用,我想你经常会有这样的疑问:**系统的高可用真的很重要吗?如何实现系统的高可用,具体都有哪些手段呢?**
十年前我还在eBay那时候我们有几个数据来说明系统宕机对公司的影响我记得其中一个是系统每宕掉1秒公司将损失三千美金的收入现在的大型外卖平台也是如此如果就餐高峰期宕掉1小时平台至少损失几个亿的直接收入更加不用说对公司品牌的影响。
但是我们知道,系统中包含了大量的软硬件设备,要保证所有的节点都可用,不是一件容易的事。所以今天这一讲,我会从系统高可用的角度出发,和你介绍如何才能做到让系统不掉链子。
## 系统有哪些故障点?
那么一个系统,它在运行的过程中,都可能会出现哪些故障呢?我们来看一个简化的系统处理过程。
首先,客户端在远程发起请求,经过接入系统处理后,请求被转发给应用系统;应用系统调用服务完成具体的功能;在这个过程中,应用和服务还会访问各种资源,比如数据库和缓存。这里,我用红色部分,标识出了整个处理过程中可能出现的故障点,如下图所示:
<img src="https://static001.geekbang.org/resource/image/86/17/86d41e9801c949d04b42be6412bfe717.jpg" alt="">
这些故障点可以归纳为三类:
1. **资源不可用**,包括网络和服务器出故障,网络出故障表明节点连接不上,服务器出故障表明该节点本身不能正常工作。
1. **资源不足**,常规的流量进来,节点能正常工作,但在高并发的情况下,节点无法正常工作,对外表现为响应超时。
1. **节点的功能有问题**,这个主要体现在我们开发的代码上,比如它的内部业务逻辑有问题,或者是接口不兼容导致客户端调用出了问题;另外有些不够成熟的中间件,有时也会有功能性问题。
下面,我们就来看看如何才能应对这些问题,实现系统的高可用。
## 高可用策略和架构原则
系统可能出问题的地方有很多,解决的方式也不一样,在讨论具体的解决手段之前,我想先说下高可用的总体解决思路,这样你就能更好地理解具体的实现方式。
<img src="https://static001.geekbang.org/resource/image/82/09/825e9b25136970dfe41fa82317f0ca09.jpg" alt="">
要想让系统能够稳定可用,我们首先要考虑如何**避免问题的发生**。比如说我们可以通过UPSUninterruptible Power System不间断电源来避免服务器断电可以通过事先增加机器来解决硬件资源不足的问题。
然后,如果问题真的发生了,我们就要考虑怎么**转移故障**Failover。比如说我们可以通过冗余部署当一个节点发生故障时用其它正常的节点来代替问题节点。
如果故障无法以正面的方式解决,我们就要**努力降低故障带来的影响**。比如说流量太大,我们可以通过限流,来保证部分用户可以正常使用,或者通过业务降级的手段,关闭一些次要功能,保证核心功能仍旧可用。
最后是要**快速恢复系统**。我们要尽快找到问题的原因,然后修复故障节点,使系统恢复到正常状态。
这里我要强调的是,**处理线上事故的首要原则是先尽快恢复业务**,而不是先定位系统的问题,再通过解决问题来恢复系统。因为这样做往往比较耗时,这里给出的处理顺序也体现了这个原则。
那么结合前面介绍的系统故障点和高可用的解决思路,我们在做架构设计时,就可以从 **正面保障****减少损失** 两个角度来考虑具体的应对手段。下面,我就来和你分享一下高可用的设计原则。
<img src="https://static001.geekbang.org/resource/image/a1/c1/a1ccdeff03e66c86ca9e6dc891a0b7c1.jpg" alt="">
### 正面保障
**第一个设计原则是冗余无单点。**
首先,我们要保证系统的各个节点在部署时是冗余的,没有单点。比如在接入层中,我们可以实现负载均衡的双节点部署,这样在一个节点出现问题时,另一个节点可以快速接管,继续提供服务。
还有远程网络通信,它会涉及到很多节点,也很容易会出现问题,我们就可以提供多条通信线路,比如移动+电信线路,当一条线路出现问题时,系统就可以迅速切换到另一条线路。
甚至我们可以做到机房层面的冗余通过系统的异地多IDC部署解决自然灾害如地震、火灾导致的系统不可用问题。
**第二个设计原则是水平扩展。**
很多时候,系统的不可用都是因为流量引起的:在高并发的情况下,系统往往会整体瘫痪,完全不可用。
在前面的故障点介绍中,你可以看到,在应用层、服务层、资源层,它们的处理压力都是随着流量的增加而增加。[上一讲](https://time.geekbang.org/column/article/212066)中,我也提到过,由于硬件在物理上存在瓶颈,通过硬件升级(垂直扩展)一般不可行,我们需要通过增加机器数量,水平扩展这些节点的处理能力。
对于无状态的计算节点,比如应用层和服务层来说,水平扩展相对容易,我们直接增加机器就可以了;而对于有状态的节点,比如数据库,我们可以通过水平分库做水平扩展,不过这个需要应用一起配合,做比较大的改造。
### 减少损失
**第三个原则是柔性事务。**
我们知道系统的可用性经常会和数据的一致性相互矛盾。在CAP理论中系统的可用性、一致性和网络容错性三个最多只能保证两个在分布式系统的情况下我们只能在C和A中选一个。
在很多业务场景中,**系统的可用性比数据的实时一致性更重要**所以在实践中我们更多地使用BASE理论来指导系统设计。在这里我们努力实现系统的基本可用和数据的最终一致。
>
知识拓展关于BASE理论的详细信息你可以参考一下隔壁专栏《分布式协议与算法实战》的[这篇文章](https://time.geekbang.org/column/article/200717),这里就不详细展开了。
我们平时对单个数据库事务的ACID特性非常熟悉因为这里不存在P所以C和A都能得到很好地保证这是一种**刚性事务**。但在复杂的分布式场景下基于BASE理论我们通常只能实现部分的C软状态和最终一致和部分的A基本可用这是一种**柔性事务**。
柔性事务具体的实现方式有很多比如说通过异步消息在节点间同步数据。当然不同的方式对C和A的支持程度是不一样的我们在设计系统时要根据业务的特点来决定具体的方式。
**第四个原则是系统可降级。**
当系统问题无法在短时间内解决时,我们就要考虑尽快止损,为故障支付尽可能小的代价。具体的解决手段主要有以下这几种。
- **限流:**让部分用户流量进入系统处理,其它流量直接抛弃。
- **降级:**系统抛弃部分不重要的功能,比如不发送短信通知,以此确保核心功能不受影响。
- **熔断:**我们不去调用出问题的服务,让系统绕开故障点,就像电路的保险丝一样,自己熔断,切断通路,避免系统资源大量被占用。比如,用户下单时,如果积分服务出现问题,我们就先不送积分,后续再补偿。
- **功能禁用:**针对具体的功能,我们设置好功能开关,让代码根据开关设置,灵活决定是否执行这部分逻辑。比如商品搜索,在系统繁忙时,我们可以选择不进行复杂的深度搜索。
### 做好监控
**最后一个设计原则,是系统可监控。**
在实践中,系统的故障防不胜防,问题的定位和解决也非常的困难,所以,要想全面保障系统的可用性,最重要的手段就是监控。
当我们在做功能开发的时候,经常会强调功能的可测试性,我们通过测试来验证这个功能是否符合预期,而系统可监控,就像业务功能可测试一样重要。**通过监控,我们可以实时地了解系统的当前状态**,这样很多时候,业务还没出问题,我们就可以提前干预,避免事故;而当系统出现问题时,我们也可以借助监控信息,快速地定位和解决问题。
好,为了帮助你更好地理解,我对这些架构原则做个小结。
- 无单点和水平扩展是从正面的角度,直接保障系统的可用性。**无单点设计针对的是节点本身的故障,水平扩展针对的是节点处理能力的不足。**
- 柔性事务和可降级是通过提供有损服务的方式来保证系统的可用性。**柔性事务保证功能的基本可用和数据的最终一致,可降级通过损失非核心功能来保证核心功能的可用。**
- 最后,无论我们采取了多么强大的高可用措施,我们还是不能充分相信系统,还需要借助额外的监控来及时发现系统的问题并加以解决。**监控是我们的第二条保命措施。**
## 高可用手段
好了,通过前面的介绍,你应该已经了解了系统的故障点,以及高可用的设计原则。下面我们就一起来看下,在实践中都有哪些手段来保障系统的高可用。这里,我会按照系统的处理顺序来给你做详细介绍。
### 客户端-&gt;接入层
客户端到服务端通常是远程访问,所以我们首先要解决网络的可用性问题。
针对网络的高可用我们可以拉多条线路比如在企业私有的IDC机房和公有云之间同时拉移动和电信的线路让其中一条线路作为备份当主线路有问题时就切换到备份线路上。
在接入层也有很多成熟的HA方案比如说你可以选择Nginx、HAProxy、LVS等负载均衡软件它们都能很好地支持双节点+Keepalived部署。这样当一个节点出了问题另一个节点就可以自动顶上去而且两个节点对外是共享一个虚拟IP所以节点的切换对外部是透明的。
**这里,我们通过冗余和自动切换避免了单点的故障。**
### 接入层-&gt;Web应用
Web应用通常是无状态的我们可以部署多个实例很方便地通过水平扩展的方式提升系统的处理能力接入层的负载均衡设备可以通过各种算法进行多个Web实例的路由并且对它们进行健康检测如果某个实例有问题请求可以转发到另一个实例进行处理从而实现故障的自动转移。
通常情况下我们还可以在接入层做限流比如在Nginx中设置每秒多少个并发的限制超过这个并发数Nginx就直接返回错误。
**这里我们同时支持了Web节点的水平扩展、自动故障转移以及系统的可降级限流。**
### Web应用-&gt;内部服务
服务通常也是无状态的,我们也可以通过部署多个实例进行水平扩展。
有多种方式可以支持服务实例的发现和负载均衡比如说我们可以使用传统的代理服务器方式进行请求分发另外很多的微服务框架本身就支持服务的直接路由比如在Spring Cloud中我们就可以通过Eureka进行服务的自动注册和路由。
应用通常会访问多个服务,我们在这里可以做服务的隔离和熔断,避免服务之间相互影响。
比如在Spring Cloud的Hystrix组件开源熔断框架我们可以为不同服务配置不同的线程池实现资源隔离避免因为一个服务响应慢而占用所有的线程资源如果某个服务调用失败我们可以对它进行熔断操作避免无谓的超时等待影响调用方的整体性能。
在应用和服务的内部,针对具体的功能,我们还可以做一些**功能开关**。开关实际上是一个标志变量它的值可以是on/off 我们在代码中可以根据它的值来确定某一段逻辑是否要执行。开关的值可以在数据库或配置系统里定义这样我们就能够通过外部的开关值控制应用内部的行为这个在eBay有大量的落地。
**这里,我们同时支持了服务节点的水平扩展、自动故障转移以及系统的可降级(熔断和业务开关)。**
### 访问基础资源
常见的资源包括关系数据库、缓存和消息系统,我就以它们为例来介绍一下。
**关系数据库**属于有状态服务,它的水平扩展没有那么容易,但还是有很多手段能够保障数据库的可用性和处理能力。
首先,我们可以做数据库的主从部署,一方面通过读写分离,提升数据库**读**的性能减轻主库压力另一方面数据库有成熟的MHA方案支持主库故障时能够自动实现主从切换应用可以通过VIP访问数据库因此这个切换过程对应用也是透明的。
另外我们也可以通过物理的水平分库方式对数据进行分片这样就有多个主库支持写入。水平分库会涉及较多的应用改造后面会有一篇文章专门介绍1号店的订单水平分库项目到时我们再详细讨论。
再说下**缓存**。在数据读写比很高的情况下,我们可以利用缓存优化数据库的访问性能,包括进程内部缓存和分布式缓存,缓存是应对高并发的有效武器。
很多缓存方案比如Redis本身就支持集群方式它可以通过多节点支持处理能力的水平扩展通过数据的多副本来支持故障转移。
最后说下**消息系统**。消息系统有很多成熟的MQ组件比如说Kafka它可以通过多节点部署来支持处理能力的水平扩展也能通过数据的多分区实现故障的自动切换保证系统的可用性。
最后我想说的是,明天和意外你永远不知道哪个先到来,即使有了这些高可用措施,还是会有各种各样的意外等待着我们。所以,**系统的监控非常重要,只有准确地了解系统当前的状况,我们在面对问题时,才能快速响应,处理到点子上。**
## 总结
今天,我和你介绍了保障系统高可用都有哪些策略和设计原则,相信你现在对高可用的整体处理思路有了清楚的认识。
另外,我还针对典型的系统处理过程,和你介绍了各个环节都有哪些具体的高可用手段,希望你可以在工作中,结合系统的实际情况去落地它们。
接下来,我会通过几个实际的案例,来具体说明如何实现系统的高可用,你可以跟着课程继续学习,然后尝试着在实际的工作中去参考和灵活运用。
**最后,给你留一道思考题:**处理事故有三板斧的说法,你知道它们都是什么吗?你是怎么评价它们的呢?
欢迎在留言区和我互动,我会第一时间给你反馈。如果这节课对你有帮助,也欢迎你把它分享给你的朋友。感谢阅读,我们下期再见。

View File

@@ -0,0 +1,138 @@
<audio id="audio" title="13 | 高可用架构案例如何实现O2O平台日订单500万" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/f9/d7/f9c0a0fdfd61407b9fb083e4ff90c0d7.mp3"></audio>
你好,我是王庆友。在上一讲中,我和你介绍了高可用系统的设计原则和常见手段。今天呢,我会通过一个实际的案例,告诉你具体如何落地一个高可用的架构,让你能够深入理解和运用这些高可用手段。
## 项目背景介绍
先说下项目的背景。这是一个小程序点餐平台,用户在小程序上点餐并支付完成后,订单会先落到订单库,然后进一步推送到门店的收银系统;收银系统接单后,推送给后厨系统进行生产;同时返回小程序取餐码,用户可以凭取餐码去门店取餐或收取外卖。
这个项目服务于一家大型的餐饮公司公司在全国有大量的门店他们准备搞一个长期的大型线上促销活动促销的力度很大用户可以在小程序上先领取优惠券然后凭券再支付1元就可以购买价值数十元的套餐。
结合以往的经验,以及这次的促销力度,我们预计在高峰时,前端小程序请求将会达到**每秒10万QPS**,并且预计**首日的订单数量会超过500万**。在这种高并发的情况下,我们为了保证用户的体验,**系统整体的可用性要达到99.99%**。
你可以先了解一下这个点餐平台的具体架构:
<img src="https://static001.geekbang.org/resource/image/f0/de/f0767d06f06e5166687d54b2c4b7a4de.jpg" alt="">
这里呢,我具体说下系统主要的调用过程,以便于你更好地理解它:
1. 小程序前端通过Nginx网关访问小程序服务端
1. 小程序服务端会调用一系列的基础服务完成相应的请求处理包括门店服务、会员服务、商品服务、订单服务、支付服务等每个服务都有自己独立的数据库和Redis缓存
1. 订单服务接收到新订单后先在本地数据库落地订单然后通过MQ同步订单给OMS履单中心
1. 门店的收银系统通过HTTP远程访问云端的OMS履单中心拉取新订单并返回取餐码给OMSOMS再调用小程序订单服务同步取餐码
1. 小程序前端刷新页面,访问服务端获得取餐码,然后用户可以根据取餐码到门店取餐或等待外卖。
## 高可用系统改造措施
我在前面也介绍了,这次活动的促销力度很大,高峰期流量将达到平时的数十倍,这就要求系统能够在高并发的场景下,保证高可用性。
所以基于访问量、日订单量和可用性的指标我们对原有系统进行了一系列改造最终顺利地实现了首日500万订单以及在大促期间系统4个9的可用性目标。这个500万的订单量也创造了中国单商户线上交易的历史记录。
在下面的系统架构图中我标出了具体的改造点主要有10处接下来我就给你分别具体介绍一下你可以通过这些具体的改造措施来真正理解高可用系统的设计手段。
<img src="https://static001.geekbang.org/resource/image/21/4c/218719777ff902f9b26cb88a6187ba4c.jpg" alt="">
### 前端接入改造
这里的前端有两个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和数据库页面也会体现节点之间的上下游关系。**我们通过采集节点的状态数据,实时监测每个节点的健康程度,并且用红黄绿三种颜色,表示每个节点的健康状况。**这样,我们就可以非常直观地识别出,当前的哪些节点有问题。
监控的效果如下图所示,在下一讲中,我就会为你具体地介绍这个监控系统。
<img src="https://static001.geekbang.org/resource/image/ba/b0/ba4c6d94de2121ceef0e7cb579c2d8b0.jpg" alt="">
在实践中,这套监控系统也确实发挥了巨大的作用。很多时候,在系统问题还没有变得严重之前,我们就能够识别出来,并能进行主动干预。
比如说小程序服务端的部分节点有时候会假死这在Zabbix监控里往往看不出来但在我们的监控页面中这些节点就会飘红我们就可以通过重启节点来快速恢复。还有好几次系统有大面积的节点出问题了我们通过节点的上下游关系很容易地定位出了真正出现问题的地方避免所有人一窝蜂地扑上去排查问题。
除了这里我介绍的优化措施以外,我们也为系统可能出问题的地方做了各种预案。比如说,我们保留了部分虚拟机上部署的基础服务实例,这样如果容器出现了问题,基础服务可以快速切回到虚拟机上的实例。
## 系统改造小结
到这里为止,系统主要的优化措施就介绍完了,**但我们是如何知道要配置多少个节点,有没有达到预定的效果呢?**
对于这个问题,我们的做法是,**按照10万QPS和99.99%的可用指标要求,通过大量的压测来确定的。**
- 首先,我们对每个节点进行接口压测,做各种性能优化,确定好需要的机器数量;
- 然后我们利用JMeter模拟小程序前端发起混合场景的调用以此检验系统的抗压能力以及在压力下系统的可用性是否达到了预定的要求
- 最后我们在生产环境中根据压测环境按照服务器1:1的数量进行部署保证性能不打折最终这个小程序下单平台总的机器规模也达到了数百台的量级。
这里,我想结合着上一讲和你介绍的架构原则,来让你更深刻地理解这次系统可用性的改造过程。
从正面保障的角度来看,我们首先在各个环节都**避免了单点**,包括远程通信线路,这样能保证任意一个节点出了问题,都有其他实例可以顶上去;其次,我们通过节点的**垂直扩展和水平扩展**,大幅度提升了系统的处理能力,包括应用、服务和数据库的扩展;我们也有效地利用了**Redis缓存**,对高频的订单和菜单数据的读取进行了优化。
在**柔性处理**方面我们通过异步处理来优化系统的性能和避免大流量的直接冲击包括使用消息系统解耦前台下单系统和后台OMS系统以及通过及时的消息推送避免前端盲目轮询服务端。
同时,我们在**系统接入层**通过Nginx进行限流为系统的可用性进行兜底这样在流量超过预估时能够有效地避免后端系统被冲垮。
最后,我们通过**强有力的监控手段**,可以实时全面地了解系统运行状况,随时为异常情况做好准备。
## 总结
今天我与你分享了一个实际的O2O点餐平台在面对高并发流量时我们是如何对系统进行升级改造保证系统的高可用的。相信你在上一讲理论的基础上通过进一步结合实际的场景能够深入地理解如何运用各种高可用的手段了。
高可用的处理方式有很多,我这里给你介绍的也只是一部分,希望你能够在实践中,结合具体的业务场景,灵活地落地高可用的设计。
不过,无论我们采取多么周密的措施,总会有些地方我们没有考虑到,系统可能会出现各种各样的问题,这个时候对系统进行全面的监控就非常重要了。下一讲我会就如何做好系统的监控,和你做详细的介绍。
**最后,给你留一道思考题:**你当前的系统有单点吗?这个单点有没有出过问题呢?
欢迎你在留言区与大家分享你的问题和思考,我们一起讨论。如果这节课对你有帮助,也欢迎你把它分享给你的朋友。感谢阅读,我们下期再见。

View File

@@ -0,0 +1,144 @@
<audio id="audio" title="14 | 高可用架构案例(二):如何第一时间知道系统哪里有问题?" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/96/d7/962dfeb3fc84272fbf0daa65bd00e5d7.mp3"></audio>
你好,我是王庆友。
在前面两讲中,我与你介绍了系统的高可用都有哪些设计原则和具体手段。其中我也特别提到,**要想保证系统的高可用,我们还需要对系统进行全面有效的监控**。
监控是系统的眼睛,无监控,不运维。今天我们就从监控的角度来聊聊如何保证系统的高可用。
在开发软件时,我们经常强调一个业务功能的可测性,甚至有一种说法是测试驱动开发。在开发之前,我们会先设计测试用例,再去考虑如何实现功能。同样,当我们对系统作了很多加固,也是希望能保证它的稳定可用。
**但我们怎么判断系统的各个节点当前是否正常呢?**这个就对应了节点的可监控性,如果你事先想好了系统应该如何监控,如何判断每个节点是否正常,那你就会更清楚应该采取什么样的措施。很多时候,我们可以从监控的角度来倒推系统的可用性设计。
## 监控的分类
在[第11讲](https://time.geekbang.org/column/article/212066)中我和你介绍了系统的组成它包括接入层、应用系统、中间件、基础设施这几个部分那我们的监控也是针对这些部分来实施的。一般来说监控可以分为5个层次如下图所示
<img src="https://static001.geekbang.org/resource/image/07/cf/079ff9bc96e0bb11d438b965ddd751cf.jpg" alt="">
从上到下,分别为用户体验监控、业务监控、应用监控、中间件监控、基础平台监控。
1. **用户体验监控:**指的是从前端用户的访问速度出发,来监测系统的可用性,包括页面能否打开、关键接口的响应时间等等,用户体验监控一般结合前端的埋点来实现。
1. **业务监控:**它是从业务结果的角度来看,比如说订单数、交易金额等等,业务监控也是最直观的,我们知道,如果业务数据没问题,系统整体也就没有问题。对于业务监控,我们一般是从数据库里定时拉取业务数据,然后以曲线的方式展示业务指标随着时间的变化过程。除了当前的曲线,一般还有同比和环比曲线。同比是和前一天的数据进行比较,环比是和一周前的数据进行比较,两方面结合起来,我们就能知道当前的业务指标有没有问题。
1. **应用监控:**指的是对自己开发的代码进行监控比如接口在一段时间内的调用次数、响应时间、出错次数等等。更深入一点的应用监控还包含了调用链监控我们知道一个外部请求的处理过程包含了很多环节比如说网关、应用、服务、缓存和数据库我们可以通过调用链监控把这些环节串起来当系统有问题时我们可以一步步地排查。有很多APM工具可以实现调用链监控如CAT、SkyWalking等等。
1. **中间件监控:**指的是对标准中间件进行监控它是第三方开发的代码比如数据库、缓存、Tomcat等等这些组件对应的是系统的PaaS层。这些中间件往往带有配套的监控系统比如RabbitMQ就有自带的监控后台。
1. **基础平台监控:**指的是对系统底层资源进行监控如操作系统、硬件设备等等这个层次的监控对应的是系统的IaaS层。Zabbix就是典型的基础设施监控工具它可以监控CPU、内存和磁盘的使用情况。
## 监控的痛点
我们知道,一个大型的互联网平台,背后对应的是大规模的分布式系统,有大量的软硬件节点一起协作,这里的任何节点都有可能出问题,所以我们需要通过监控,及时发现和解决问题,提升系统的可用性。
但想要实现高效的监控,这不是一件容易的事情。下面,我给你举一个线上事故处理的例子,你就能理解监控面临的挑战。
首先Monitor发现订单曲线突然跌停当前的订单数量变为0于是Monitor快速拉起电话会议或者在微信群里@所有人进行排查。这时候一大堆相关的或不相关的人都开始排查自己负责的那部分系统比如说运维在Zabbix里检查网络和机器开发在ELK系统Elasticsearch+Logstash+Kibana里检查错误日志DBA检查数据库。
过了一会儿负责App服务端的开发人员在ELK里发现有大量的调用下单服务超时于是他去询问下单服务的开发人员这是怎么回事。下单服务的开发人员就去检索错误日志结果发现调用会员服务有大量的超时情况然后他就去问会员服务的开发人员这是怎么回事。会员服务的开发人员通过错误日志发现会员数据库连接不上于是他把问题反映给DBA。DBA先拉上负责网络的同事一起看发现网络没啥问题然后他再去检查会员数据库本身这时他发现有慢查询把DB给挂住了。
这样通过一系列的接力式排查问题终于找到了最后DBA把慢查询杀掉所有人都去检查自己的系统发现没有新的错误情况系统恢复了正常。而这个时候距离问题的发生已经过去了很长时间在这个期间技术被老板催老板被商户催而商户也已经被用户投诉了N次。
以上的事故处理过程还算比较顺利的,毕竟我们通过顺藤摸瓜,最后找到并解决了问题。**更多的时候,我们面对事故,就像是热锅上的蚂蚁,众说纷纭,谁也不能肯定问题出在哪里。结果呢,我们病急乱投医,胡乱干预系统,不但没能解决问题,而且往往引发了二次事故。**
你可以发现在这个例子中虽然我们有应用日志监控有Zabbix系统监控有网络和数据库监控但对于一个大规模的分布式系统来说这种分散的监控方式在实践中有一系列的弊端。
- 首先,不同的节点,它的监控的方式是不一样的,相应地,监控的结果也在不同的系统里输出。
- 同时,系统不同部分的监控都是由不同的人负责的,比如说,运维负责的是基础平台监控,开发负责的是应用系统监控。而监控信息往往专门的人才能解读,比如应用监控,它需要对应的开发人员才能判断当前的接口访问是否有问题。
- 最后,系统作为一个整体,需要上下游各个环节的人一起协作,进行大量的沟通,才能最终找到问题。
你可以看到,这种监控方式是碎片化的,对于处理线上紧急事故,它无疑是低效的,这里有很多问题。
<img src="https://static001.geekbang.org/resource/image/87/d0/872d239bc37b3b28dca238fc3f6665d0.jpg" alt="">
1. **发现问题慢:**业务监控的曲线一般1分钟更新一次有时候因为正常的业务抖动Monitor还需要把这种情况排除掉。因此他会倾向于多观察几分钟这样就导致问题的确认有很大的滞后性。
1. **定位问题慢:**系统节点多,大量的人需要介入排查,而且由于节点依赖复杂,需要反复沟通才能把信息串起来,因此很多时候,这种排查方式是串行或者说无序的。一方面,无关的人会卷入进来,造成人员的浪费;另一方面排查效率低,定位问题的时间长。
1. **解决问题慢:**当定位到问题,对系统进行调整后,验证问题是否已经得到解决,也不是一件很直观的事情,需要各个研发到相应的监控系统里去进行观察,通过滞后的业务曲线观察业务是否恢复。
**那么,我们怎么解决监控面临的这些困境,以高效的方式解决线上事故,保障系统的高可用呢?**
## 解决思路
你可以看到,前面这种监控方式,它是碎片化和人工化的,它由不同的工具负责监控系统的不同部分,并且需要大量专业的人介入,并通过反复的沟通,才能把相关的信息拼接起来,最后定位到问题。
**那我们能不能把系统所有的监控信息自动关联起来,并且以一种直观的方式展示,让所有人一看就明白是哪里出了问题,以及出问题的原因是什么呢?**
从这个思路出发,对系统的监控,我们需要做到两点:
1. 系统能够自动地判断每个节点是否正常,并直观地给出结果,不需要经过专业人员的分析。
1. 系统能够自动把各个节点的监控信息有机地串起来,从整体的角度对系统进行监控,不需要很多人反复地进行沟通。
这里,我们可以借鉴一下道路交通监控的例子。
我们经常可以在市内的高架上看到交通拥堵示意图。在下面的这张交通信息图上,你可以看到,每条道路都通过上下左右不同的方位,有机地关联在一起,形成一个整体的交通网络;同时,在交通图上,通过红黄绿三种状态,实时地反映了每条道路的拥堵情况。这样,司机就可以非常直观地了解道路是否畅通,从而提前避开拥堵路段。
<img src="https://static001.geekbang.org/resource/image/fb/13/fba62d78cffa6e8ba1d1951e133edc13.jpg" alt="">
这里有几个关键词:实时、直观、整体。下面,我们就来对照下软件系统的监控,来看看要想实现类似的监控效果,我们应该怎么做。
首先要**实时**,我们需要第一时间知道系统当前是否有问题。
然后要**直观**,节点是否有问题,我们需要很直观地就能判断出来,就像交通图上的红黄绿颜色标识一样。我们知道,在发生紧急事故时,人脑很可能会处于错乱状态,这个时候,我们一定不能指望专业的头脑或者严密的分析来判断问题,这样不但慢,而且很容易出错。所以,系统哪些部分有问题,问题是否严重,以及出问题的大致原因是什么,这些信息,监控系统都必须能够直观地给出来。
最后是**整体**,我们需要针对系统做整体监控,就像交通图一样,它是针对周边整体的道路情况进行展示,我们也需要把系统的各个节点放在一起,清晰地给出节点依赖关系。系统真正出问题的地方往往只有一个,其他地方都是连带的,如果监控系统能够给出节点的上下游依赖关系,对于定位真正的问题源是非常有用的。
所以,对照道路交通监控的思路,我们可以采取这样的监控方式:
- 首先,系统中的每个节点对应交通图的一条道路;
- 然后,节点的健康状况对应道路的拥堵情况,节点同样也有红黄绿三种不同的颜色,来展示该节点是否正常;
- 最后,节点之间的调用关系对应道路的方位关系。
这样我们就能构建一个实时的、直观的、一体化的监控系统,类似交通图一样,可以一眼就看出系统的问题所在。
好,回到刚才事故处理的例子,如果我们的监控系统按照这种方式来设计,它的监控效果会是什么样的呢?
首先所有的节点,包括服务端应用、下单服务、会员服务还有其他服务,以及它们各自用到的缓存、消息队列和数据库,这些节点的健康状态我们在一个页面里就可以看到,包括它们的依赖关系。
如果会员数据库出了问题,我们根据依赖关系倒推,会员数据库-&gt;会员服务-&gt;下单服务-&gt;服务端应用这4个节点都会爆红而其他节点不受影响保持绿色。服务端应用和下单服务节点会有错误消息提示接口调用超时而会员服务和会员数据库节点的错误消息提示的是数据库连接超时。
这样其他绿色的节点我们就不用排查了然后我们观察爆红的节点通过上下游依赖关系就知道最终的问题很可能出在会员数据库上DBA重点检查会员数据库就可以了。当数据库问题解决以后我们可以看到所有爆红的节点马上变绿立即就能确认系统恢复了正常。
## 架构方案和效果
根据前面的思路,我们设计了监控系统的整体架构,如下图所示:
<img src="https://static001.geekbang.org/resource/image/a4/d2/a469d19331860e8d4084279c422753d2.jpg" alt="">
每个被监控的节点均有对应的Agent负责采集健康数据不同的节点类型数据采集的方式也不一样Web节点通过HTTP接口调用Redis通过JredisMQ也通过对应的API接口DB则采用JDBC。
Agent每隔3s采集节点数据然后上报数据给Monitor ServiceMonitor Service负责确定节点当前的状态并保存到数据库这样就完成了节点健康状态的检测最后前端Dashboard每隔3s拉取所有节点的状态以红黄绿三种颜色在同一页面展示同时还会显示具体的出错信息。
**那我们是根据什么规则来判断节点的健康状态呢?**
这里我以DB为例简单说明一下。Agent每隔3秒会去尝试连接数据库并进行简单的表读写操作如果连接和读写都能够成功那就说明该DB当前的运行是正常的相应的在Dashboard里面这个DB节点会显示为绿色。
Redis和MQ类似我们主要也是检测组件的可用性Web应用的健康规则会相对复杂一些我们会结合Web应用接口的功能和性能来做综合判断。这个监控系统的设计我还会在下一讲里具体介绍你到时候可以深入理解其中的细节。
我们最后来看下监控的效果。
下图是某个业务系统的实际监控效果图左边是系统的部署架构最上面是两个Web应用这两个应用分别有自己的Web服务器、MQ和Redis节点。
>
提示:这里,我对细节做了模糊化处理,不过没关系,我主要的目的是让你能了解监控的效果,尽管图片模糊,但它不会影响你理解内容。
<img src="https://static001.geekbang.org/resource/image/d6/41/d67b89c71a3888068ffc2559ef659141.jpg" alt="">
以左上角的应用为例它的Web应用部署在Docker里面所以这里只显示一个节点虚拟机部署可以看到每个实例的IP但Docker容器无法看到对外表现为一个地址对于Redis我们是购买公有云的服务所以也是一个实例但MQ是集群的方式它有三个实例。
然后这两个Web应用同时依赖后端的3个基础服务这3个服务是并列的关系每个服务又分别有自己的应用、MQ和Redis。所以你可以看到在这个监控页面里节点的部署情况和依赖关系都是一目了然的。
在这个例子中有一个节点显示为黄色黄色说明它有问题但并不严重。你可以在右边的异常消息列表里看到具体的原因在最近3s内这个Web应用的接口响应时间超过了正常值的5倍每条异常消息包括了出错的节点、具体出错的接口、该接口的正常响应时间以及当前的响应时间。这样你就可以很方便地把左边的出错节点和右边的异常消息对应起来知道哪些节点有错误还有出错的原因是什么。
另外如果你在左边的图里点击某个节点会弹出新页面显示该节点的历史出错信息并且新页面里有链接可以直接跳到Zabbix、CAT和ELK系统这样你可以在这些专门的系统里做进一步的排查。
所以说,这里的监控系统提供的是整体的监控信息,可以帮助你快速定位问题的根源,在很多情况下,我们通过这里给出的错误信息,就可以知道出错的原因。当然,如果碰到特别复杂的情况,你还是可以在这里快速关联到各个专业的监控系统去收集更深入的信息。
## 总结
今天,我与你介绍了一下监控的分类,你现在应该对监控有了比较深入的了解,知道一个完整的监控体系都包含了哪些内容。
此外,我也结合线上事故处理的例子,和你说明了碎片化的监控带来的一些问题,并给出了整体化的解决思路以及具体的落地方案。在实践中,这套监控系统也确实发挥了巨大的价值,让我们可以高效地应对线上事故,提升系统的可用性,希望你能够深入地领悟和掌握。
在下一讲中,我还会和你介绍这个方案的实现细节,这样,你也可以尝试着去落地类似的监控系统。
**最后,给你留一道思考题:** 你的公司都有哪些监控手段,当处理线上事故时,你遇到的最大的挑战是什么?
欢迎你在留言区与大家分享你的答案,如果你在学习和实践的过程中,有什么问题或者思考,也欢迎给我留言,我们一起讨论。感谢阅读,我们下期再见。

View File

@@ -0,0 +1,176 @@
<audio id="audio" title="15 | 高可用架构案例(三):如何打造一体化的监控系统?" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/29/81/29812685b70f4fa0f2af764496c11a81.mp3"></audio>
你好,我是王庆友。
上一讲,我与你介绍了整体化监控系统的设计方案,今天我就带你深入它的内部设计,让你可以了解它具体是如何落地的。
这个监控系统主要分为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
返回信息:
&quot;status&quot;:“warning&quot;,
&quot;avg_time&quot;:“583.0&quot;,
&quot;call_count&quot;:&quot;10&quot;,
&quot;error_count&quot;:&quot;0&quot;,
&quot;error_info&quot;:&quot; orderListGet: current average time= 583.0, total average time =109.84, 调用次数= 10&quot;
```
Web节点会预先提供一个HTTP接口Agent通过调用这个接口返回当前Web实例最近3s的健康状态。
这里最主要的就是 **status字段**它表明了Web节点最近3s是否健康如果是“error”或者“warning”返回的结果还会包含 **error_info字段**,它负责给出具体的错误信息。
Agent在获取了这4类节点的健康状态后会调用Monitor Service进行数据上报如果节点有问题上报内容还包括具体的错误消息。
总体的架构如下图所示:
<img src="https://static001.geekbang.org/resource/image/23/92/23c7549bd553bab3b142aec6d5525792.jpg" alt="">
**你要注意的是**Agent本身是一个独立的应用它不需要和节点部署在一起如果节点数量少我们部署一个Agent实例就可以如果节点的数量比较多我们可以部署多个Agent实例比如给每类节点部署一个实例。总的要求就是让Agent能够在3s内完成所有节点的健康信息收集就可以了。
另外节点的连接信息事先是配置在数据库里的比如数据库节点的IP端口、账号和密码等等当Agent启动的时候它会通过Monitor Service获取节点配置信息Agent在运行过程中也会定期刷新这个配置。
## 接入监控系统
好,说完了节点信息的采集,下面我们来看下,这些节点要接入监控系统,都需要做些什么。
对于Redis、MQ、DB这三类节点接入监控系统只需要提供配置信息就可以了无需额外的开发。
而对于Web应用接入监控我们需要对应用代码做些改造
1. 针对每次接口调用,应用程序需要在接口代码中记录本次调用的耗时以及出错状况;
1. 应用程序需要汇总最近3秒的接口调用情况根据规则给出节点的健康状态
1. 应用程序提供一个对外的HTTP接口供Agent来获取上一步给出的健康状态。
为了方便Web应用的接入监控系统开发团队提供了SDK它内置了接口调用信息的统计和健康计算规则。应用程序借助SDK就可以给Agent提供最终的健康结果也就是说SDK帮助应用完成了最复杂的第二步工作。
所以,对应用来说,它接入监控系统是非常简单的。
首先在每个应用接口中调用SDK提供的 **logHeahthInfo方法**,这个方法的输入包括了接口名字、本次接口调用耗时和错误信息,这和我们平常接入日志系统是很类似的。
```
try{
result = service.invoke(request)
HealthUtil.logHealthInfo(&quot;xxx_method&quot;,
(System.currentTimeMillis() - start)null);
}catch (Exception e){
HealthUtil.logHealthInfo(&quot;xxx_method&quot;,
(System.currentTimeMillis() - start)
e.getMessage());}
```
然后应用提供一个额外的HTTP接口在接口中直接调用SDK内置的 **healthCheck**方法给Agent提供最终的健康信息。这些就是应用接入监控系统要做的全部事情。
```
@RequestMapping(value = &quot;/agent/check&quot;)
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
这里的分组实际上是对应用进行归类,比如说,共享服务是一个分组,它内部包含多个服务,这些服务是并列的关系。这样,我们通过分组在页面里的位置关系,来体现应用之间的上下游依赖关系。
如下图所示,红色圈里的是各个分组,蓝色圈里是各个应用。我们可以很清晰地看到,“应用层”分组里的会员应用,会调用“依赖服务”分组里的四个服务。
<img src="https://static001.geekbang.org/resource/image/66/4e/663f7ce00874ec02826e7f9d12c9844e.jpg" alt="">
这里你可以发现“应用层”分组里只有1个应用它采取了1行1列的布局而“依赖服务”分组里有四个服务它采用的是2行2列的布局。**那么这个布局是怎么实现的呢?**
首先布局是在后台定义的保存在数据库里。我们为每个系统预先设定好布局类似HTML里的Table布局语法行用TR表示列用TD表示。我们根据页面显示要求提前确定好分组和应用会占用多少行多少列。前端通过Monitor Service的接口获取页面的布局信息然后根据布局信息进行动态展示如果系统的部署有变化我们在管理后台调整布局就可以了非常灵活。
这样我们通过类似Table方式的布局前端通过一套代码就可以满足所有系统的节点展示需求并且能够比较好地体现应用之间的上下游依赖关系当系统有问题时我们就可以很直观地判断出问题的根源在哪里。
在前面,我说的是一个页面代表一个系统,其实我们也可以对所有系统的节点做一个整体的**大盘监控**,这样我们只需要看一个大盘页面,就可以监控所有的节点,如下图所示:
<img src="https://static001.geekbang.org/resource/image/54/01/5491914f7cf8f8a62d5087315a437701.jpg" alt="">
大盘监控具体的实现方式是这样的:
- 首先,前端页面读取所有节点的健康状态,按照节点分类展示有问题的节点,并标识出相应的颜色;
- 然后,节点的具体出错信息也可以在大盘中展示;
- 最后,我们根据每个系统内部节点的健康状况,按照一定的规则,算出各个系统的总体健康状态,在页面展示系统的健康状态。
比如说一个系统,如果它下面有一个节点是错误状态,对应的系统状态就是红色的;超过两个节点是警告状态,对应系统状态就是黄色的。如果我们点击相应的系统节点,就会跳转到具体系统的监控页面中,我们可以进一步了解该系统内部各个节点的详细状态信息。
通过这个大盘监控,我们就能在一个页面里,知道当前哪些节点有问题、哪些系统有问题、具体出错信息是什么,我们平常监控这一个页面就可以了。
## 库表设计
最后我简单介绍下监控系统的数据库表设计主要的表有3张
<img src="https://static001.geekbang.org/resource/image/ea/23/ea3537c06a2bc48f3ca9f95679da9023.jpg" alt="">
1. **系统信息表**用来定义监控体系里有哪些系统其中Layout布局定义了该系统前端的布局方式。
1. **节点信息表**用来定义节点的配置信息其中节点类型可选的值有Web应用、Redis、MQ、DB等等节点类型决定了节点健康信息的获取方式。其他字段用于Agent如何去连接节点还有邮箱和手机用于节点出错时相应的人可以接收报警信息。
1. **节点监控状态表**用来记录每个节点的最新健康状态用于Dashboard显示。
到这里为止,我给你介绍完了整个系统的核心设计。从监控的层次来看,这个监控系统可以分为大盘级别监控-&gt;系统级别监控-&gt;节点级别监控你甚至还可以快速关联到每个节点的专门监控系统比如Zabbix的硬件监控、CAT的应用监控、ELK的日志监控等等实现最粗粒度到最细粒度监控的一体化。
相比较各个专门的监控系统我们这里不求对各类节点的监控做得多深入而是大致上能反映节点的健康状况即可如果我们要对组件做更深入的监控组件的API也可以为我们提供非常详细的信息。我们更强调的是要把系统的所有节点串起来直观地反映它们的健康状况避免监控系统的碎片化和专业化。
总而言之这个监控系统就相当于是一个全身体检不同于对某个器官的深入检查它是把系统的各个部位都做了初步检查并且给出了一个很容易阅读的结果报告。这个系统实现起来很简单但非常实用我们相当于用20%的成本实现了80%的监控效果。
## 总结
今天,我与你分享了一体化监控系统具体的设计细节,相信你已经非常清楚了它的内部实现机制,如果有需要,你也可以在实践中尝试落地类似的监控系统。
这里,我讲得比较细,不仅仅是为了让你理解这个监控系统是怎么设计的,而是想和你分享做架构设计时,我们要做全面深入的考虑,要简化开发的对接工作,要简化用户的使用,这样的架构设计才能顺利落地,实现预期的价值。
比如在这里我们为Web应用提供了SDK这降低了开发者的接入成本我们通过页面的动态布局设计避免了前端开发工作的定制化我们通过大盘监控以及和现有监控系统进行打通进一步方便了用户的使用全面提升监控系统的价值。
**最后,给你留一道思考题:** 你觉得在做架构设计时,最大的挑战是什么?
欢迎你在留言区与大家分享你的答案,如果你在学习和实践的过程中,有什么问题或者思考,也欢迎给我留言,我们一起讨论。感谢阅读,我们下期再见。

View File

@@ -0,0 +1,137 @@
<audio id="audio" title="16 | 高性能和可伸缩架构:业务增长,能不能加台机器就搞定?" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/39/3b/39bf384157fe8735071dba40db77573b.mp3"></audio>
你好,我是王庆友,今天我来和你聊一聊如何打造高性能和可伸缩的系统。
在课程的[第11讲](https://time.geekbang.org/column/article/212066),我和你介绍了,技术架构除了要保证系统的高可用,还要保证系统的高性能和可伸缩,并且能以低成本的方式落地。在实践中呢,高性能、可伸缩和低成本紧密相关,处理的手段也比较类似,这里我就放在一起来给你讲解。
在实际的工作当中,我们一般会比较关注业务功能的实现,而很少关注系统的性能,所以我们经常会面临以下这些挑战:
- **系统的TPS很低只要流量一大系统就挂加机器也没用**
- **机器的资源利用率很低,造成资源严重浪费。**
我曾经就统计过公司云服务器的资源利用率结果让我非常意外有相当比例的服务器它们的CPU和内存平均利用率长期不到1%但与此同时这些系统整体的TPS只有个位数。这里你可以发现资源利用率低和系统性能低的现象同时并存很显然系统没有充分利用硬件资源它的性能有很大的优化空间。
所以今天,我就先来给你介绍一下常用的性能数据,让你建立起对性能的基本概念;然后,我会和你具体讲解实现系统高性能和可伸缩的策略,让你能在实践中灵活运用。
## 常用的性能数据
对于服务器来说1ms的时间其实不算短它可以做很多事情我在这里列了几个基础的性能数据你可以把它们看做是系统性能的基线。
<img src="https://static001.geekbang.org/resource/image/7f/83/7fb77d02fc24085534617eab95e2e283.jpg" alt="">
你可以看到内存的数据读取是SSD磁盘的10倍SSD磁盘又是普通磁盘的10倍一个远程调用的网络耗时是机房内部调用的1000倍一个分布式缓存访问相对于数据库访问性能也有数十倍的提升。
了解了这些常用的性能数据你就能对性能建立一个直观的认识有些时候我们采取一些简单的手段就能提升系统的性能。比如说如果磁盘的IO访问是瓶颈我们只要用SSD磁盘来代替机械硬盘就能够大幅度地提升系统的性能。
## 高性能的策略和手段
那么对于一个实际的业务系统来说情况就会复杂很多。一个外部请求进来需要经过内部很多的软硬件节点处理用户请求的处理时间就等于所有节点的处理时间相加。只要某个节点性能有问题比如数据库或者某项资源不足比如网络带宽系统整体的TPS就上不去。这也是在实践中很多系统TPS只有个位数的原因。
不同类型的节点,提升性能的方法是不一样的,概括起来,总体上可以分为三类。
### 加快单个请求处理
这个其实很好理解。简单来说,就是当一个外部请求进来,我们要让系统在最短的时间内完成请求的处理,这样在单位时间内,系统就可以处理更多的请求。具体的做法主要有两种:
1. **优化处理路径上每个节点的处理速度。**比如说,我们可以在代码中使用更好的算法和数据结构,来降低算法的时间和空间复杂度;可以通过索引,来优化数据库查询;也可以在高读写比的场景下,通过缓存来代替数据库访问等等。
1. **并行处理单个请求。**我们把一个请求分解为多个子请求内部使用多个节点同时处理子请求然后对结果进行合并。典型的例子就是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个节点的处理能力是匹配的。你可以参考下面的这张交易单元化的示意图
<img src="https://static001.geekbang.org/resource/image/d6/54/d655e5fd2ff1c4c72dfae0779aa9ca54.jpg" alt="">
**通过单元化处理,我们把相关的节点绑定在一起,同进同退,更容易实现系统的可伸缩。**
而如果我们把单元扩大到系统的所有节点,这就是一个**虚拟机房**的概念。我们可以在一个物理机房部署多个虚拟机房,也可以在不同的物理机房部署多个虚拟机房,这样,部署系统就像部署一个应用一样,系统的可伸缩性自然就更好。
## 高性能和可伸缩架构原则
说完了高性能和可伸缩的策略,接下来,我再说下具体的架构设计原则,让你能够在实践中更好地落地。
- 可水平拆分和无状态
这意味着节点支持多实例部署,我们可以通过水平扩展,线性地提升节点的处理能力,保证良好的伸缩性以及低成本。
- 短事务和柔性事务
短事务意味着资源锁定的时间短,系统能够更好地支持并发处理;柔性事务意味着系统只需要保证状态的最终一致,这样我们就有更多的灵活手段来支持系统的高性能,比如说通过异步消息等等。
- 数据可缓存
缓存是系统性能优化的利器如果数据能够缓存我们就可以在内存里拿到数据而不是通过磁盘IO这样可以大大减少数据库的压力相对于数据库的成本缓存的成本显然也更低。
- 计算可并行
如果计算可并行我们就可以通过增加机器节点加快单次请求的速度提高性能。Hadoop对大数据的处理就是一个很好的例子。
- 可异步处理
异步处理给系统的处理增加了弹性空间,我们可以利用更多的处理时间,来降低系统对资源的实时需求,在保证系统处理能力的同时,降低系统的成本。
- 虚拟化和容器化
虚拟化和容器化是指对基础资源进行了抽象这意味着我们不需要再依赖具体的硬件对节点的移植和扩容也就更加方便。同时虚拟化和容器化对系统的资源切分得更细也就说明对资源的利用率更高系统的成本也就更低。举个例子我们可以为单个Docker容器分配0.1个CPU当容器的处理能力不足时我们可以给它分配更多的CPU或者增加Docker容器的数量从而实现系统的弹性扩容。
实现了系统的高性能和可伸缩,就表明我们已经最大限度地利用了机器资源,那么低成本就是自然的结果了。
## 总结
在课程的开始,我给出了一些基础的性能指标,在具体的业务场景中,你就可以参考这些指标,来评估你当前系统的性能。
另外,我还分别针对系统的高性能和可伸缩目标,介绍了它们的实现策略和设计原则,在工作中,你可以根据具体的业务,由易到难,采取合适的手段来实现这些目标。
在实践中呢,实现高可用和可伸缩的手段也是多种多样的,接下来的课程中,我还会通过实际的案例,来具体说明实现这些目标的有效手段,帮助你更好地落地。
**最后,给你留一道思考题**:在工作中,你都采取过哪些手段保证了系统的高性能和可伸缩呢?
欢迎在留言区和我互动,我会第一时间给你反馈。如果这节课对你有帮助,也欢迎你把它分享给你的朋友。感谢阅读,我们下期再见。

View File

@@ -0,0 +1,100 @@
<audio id="audio" title="17 | 高性能架构案例:如何设计一个秒杀系统?" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/d3/d4/d395a62a8d50780234331c6fe5877cd4.mp3"></audio>
你好,我是王庆友。
在上一讲中我和你详细介绍了打造一个高性能系统的应对策略和架构手段。那么今天我就以1号店的秒杀系统为例来具体说明如何实现一个高性能的系统。
## 背景和问题
先说下背景。在2014年的时候1号店作为网上超市类电商经常在线上举行各种大促活动。比如进口牛奶促销活动每次促销的牛奶有几十万盒促销价格非常优惠一般这样的促销活动会在某个整点的时间进行开卖如上午10点。对于这种质高价优并且是刚需的商品会有大量的用户来抢购俗话说“手快有手慢无”往往短短几分钟内所有牛奶就能售卖完毕。
这本质上是一种秒杀活动,但商品数量非常大,一瞬间会有大量的用户流量涌入,流量可以高达平时的几十倍。而且和少量商品的秒杀不同,这些都是有效流量,最终会生成订单。
而在正常情况下系统因为资源有限只能处理10%的流量无法处理剩下的90%流量,瞬间高并发的流量涌入,很大程度上会引起后台系统超时报错,导致用户下单不成功。这样一来,用户就会反复刷新页面,多次尝试下单,不但用户的体验不好,而且系统的压力会更大。
**最终的结果就是,系统往往由于过载,整体处理能力下降,甚至瘫痪,导致所有用户都无法购买。**就像下图表示的一样,在秒杀场景下,系统会面临这样的困境:
<img src="https://static001.geekbang.org/resource/image/e9/31/e933d73f5e41c6ef89080cf56be90031.jpg" alt="">
在这种情况下,对于用户来说,能不能买到商品,拼的是体力和人品,由于体验不好,用户会逐渐对活动失去兴趣;而对于系统来说,我们需要拼命地加机器来满足峰值流量。
每次1号店要进行大促的时候在活动开始前运营和技术人员会坐在一起大家一起来预估活动的峰值流量然后技术人员做评估系统的哪些节点需要加机器以及要加多少机器。但这样的做法其实存在几个问题
- 首先,我们对峰值流量的预估以及要加多少机器都是拍脑袋的,和实际出入往往很大,一旦估计少了,系统同样会面临过载的风险;
- 其次,为了短暂的几分钟促销,我们需要增加大量的机器,事先要做很多的运维准备工作,不但浪费资源,而且效率很低;
- 最为关键的是,有些处理节点,系统不是通过加机器就能扩展处理能力的,比如商品库存数据库,下单时,我们需要扣库存,而为了防止库存更新冲突,我们需要锁定库存记录,导致系统的并发处理能力有限,这个问题单靠加机器是解决不了的。
## 总体方案
对于这种高并发情况,看来让系统单纯地通过加机器去硬扛,是不可行的。**那么,我们有没有更好的办法,既保证用户体验,又保证系统能够轻松地应对流量挑战呢?**
我们先来深入分析下业务场景。这个秒杀活动的特点是在短期的1~2分钟内用户流量很大但只要促销的商品卖完流量马上恢复常态。所以对于前端短期内这么大的下单请求后端如果实时处理压力会非常大但如果把这个处理时间延长到10分钟后端是可以完成下单的。那对用户来说商品优惠的力度这么大他们关心的是能否买到所以会愿意多等一段时间而不是在页面上一次次点击下单每次系统都提示下单失败。
当然,如果我们把订单处理的时间延长了,只要我们在前台告诉用户,系统已经接受了他们的订单,并且不断同步用户订单处理的进度,用户体验的问题其实也不大。
基于这个分析,我们就可以利用**异步处理**的思路来应对秒杀活动。
我们先在前端接收用户所有的下单请求但不在后端实时生成订单而是放在队列里然后系统根据后端订单中心的实际处理能力从队列里获取订单请求再交给订单中心生成实际的订单。同时系统告诉用户当前的处理进度有多少订单排在TA的前面TA还要等多久。
这样对于用户来说,在前台下单一次就可以了,然后等系统慢慢处理,这也符合先到先得的原则,非常公平合理。对系统来说,只要根据大促的商品总量,一定程度上增强系统处理能力,保证下单请求从进来到最后处理完成,这个时间相对合理就可以了。
比如说有20万件的商品每人限购一件预计用户会在2分钟内完成下单但用户能够接受系统在20分钟内完成订单处理。这样系统只要保证每分钟能处理1万订单就行而如果不采取排队的方式系统就需要每分钟处理10万订单它的压力就会提升一个数量级。
基于排队的思路,系统总体架构设计如下图所示:
<img src="https://static001.geekbang.org/resource/image/6a/7f/6a809c9da997868271c2b493cb4f397f.jpg" alt="">
在这个架构中,我们**在前台和后台下单系统之间,新增了排队系统,它包括排队区和处理区两个部分。**系统整体的处理过程是这样的:
1. 用户在商品详情页提交订单后,这个订单会作为预订单进入排队区,同时排队系统会返回用户一个排队编号,这个编号用于跟踪后续的订单处理进度;
1. 用户被引导到一个等待页,这个页面会根据排队号,定时地查询排队系统,排队系统会返回预订单在队列中的位置信息,包括它前面还有多少未处理的预订单,以及后台系统大概还要多久会处理这个预订单,这样用户就不会焦虑;
1. 在排队系统的处理区,有很多消费者,它们依次从排队区的队列里获取预订单,然后调用后台下单系统生成实际的订单;
1. 随着预订单变成正式的订单,队列里的预订单会逐渐变少,如果当前的预订单已经从队列里被移除了,用户的等待页就会检测到这个情况,页面自动跳转到订单完成页,这就和常规的购物流程一样了,用户进行最后的支付,最终完成整个前台下单过程。
这里,你可以看到,**前台**的预订单有瞬时的大流量,但我们只是把它们放到队列里,这个处理起来很快,排队系统可以轻松应对;而**后台**生成实际的订单是匀速的,并且最大化地发挥了下单系统的处理能力。另一方面,对于用户体验来说,用户可以选择在等待页等候,实时获取订单处理进度的反馈,也可以选择离开,然后在用户中心的“待支付订单”里完成支付。通过这样的设计,排队系统既保证了系统处理订单的能力,也保证了用户良好的体验。
下面是一张用户等待页的效果图,你可以直观地了解秒杀系统的用户体验。
<img src="https://static001.geekbang.org/resource/image/c8/30/c8e4840b1e3064ecf84b6c77d38b9630.jpg" alt="">
现在,你已经了解了秒杀系统的总体设计。接下来,我深入介绍下这个排队系统的内部设计细节,帮助你更好地理解它。
## 内部设计
首先,**针对队列的技术选型**,排队系统使用的是**Redis**而不是MQ。因为相对于MQ来说Redis更轻量级性能更好它内置了队列数据结构除了和MQ一样支持消息的先进先出以外我们还可以获取队列的长度以及通过排队号获取消息在队列中的位置这样我们就可以给前端反馈预订单的处理进度。
对于秒杀场景来说,一个订单只能包含一个商品,这里我们**为每个秒杀商品提供一个单独的队列**这样就可以分散数据在Redis中的存取多个队列可以提供更好的性能。
**关于队列的调度问题**,也就是消费者优先从哪个队列里拿预订单,排队系统会结合下单时间和队列的长度来确定,以保证用户合理的时间体验。比如说,某个秒杀商品的队列很长,消费者会优先从这个队列拿预订单,从而避免用户等待太长的时间。
**关于队列长度**为了保证用户能够买到商品我们并不是把所有前台的下单请求都会放到队列里而是根据参与活动的秒杀商品数量按照1:1的比例设置队列初始长度这样就保证了进入队列的请求最终都能生成订单。
这个可用队列长度会随着预订单进入队列不断地减少当数值变为0时下单前台会拒绝接受新请求进入队列直接反馈用户下单失败。当然如果后台订单生成异常或用户取消订单后可用队列长度会增加前台会重新开放预订单进入队列。
## 更多优化:建立活动库存
除了秒杀流程,系统还有**常规的购物流程**,这两个购物方式都是从详情页开始,到订单完成页结束。不同的地方是,常规购物流程走的是购物车和结算页,系统是同步处理的,这样可以有更好的用户体验。
在这里,我们在系统设计上,可以很好地同时支持秒杀流程和常规购物流程。
如果运营人员在后台上架商品的时候,设置这是一个秒杀商品,那么从详情页开始,系统就会引导用户走秒杀流程,否则就走常规购物流程。特别是在早期秒杀系统刚落地的时候,如果发现秒杀流程有问题,我们还可以快速切回到常规的购物流程,实现了一定程度上的系统互备。
<img src="https://static001.geekbang.org/resource/image/74/75/74d1a5c896d5f21f42c202f4f3d3bc75.jpg" alt="">
此外,对于秒杀活动来说,参与活动的商品种类是有限的,但这些商品库存的扣减非常频繁,因此我们建立了**活动库存**的概念,把少量参与促销的商品种类单独放在一个库里,避免和大量常规的商品放在一起,这样也大幅度地提高了库存数据库的读写性能。
好了,通过这个秒杀系统的架构设计,你可以看到,我们巧妙地通过请求的异步化处理,对流量进行削峰,从而保证了系统的高性能。这里我们不需要增加太多的机器,在系统落地时,我们通过排队系统对前后台解耦,后台下单系统基本上也不需要修改,系统整体改造的工作量不大,整个落地过程也非常顺利。
不过值得注意的是,**这种方式比较适合瞬时有高并发流量的场景**,比如这里说的秒杀场景。如果订单高峰会持续一段较长的时间,而用户对订单处理又有比较高的时间要求,那就不适合采用这种异步削峰的方式。
举个例子,外卖订单的午高峰通常会持续两个小时,而用户普遍期待订单半小时能够送达。对于这种情况,我们就需要正面应对高峰流量,比如通过水平扩展各个节点,提升系统的处理能力。这也要求系统能够做到弹性伸缩,高效地支持资源的缩容或扩容,节省成本。
## 总结
今天我针对1号店的大促业务挑战与你分享了一个秒杀系统的具体设计对照我在上一讲中介绍的高性能应对策略秒杀系统主要使用了**异步化处理**的方式,这也符合实际的业务场景。
通过今天的分享,相信你对如何保障系统的高性能有了更深入的体会,如果你也有类似的瞬时高并发的场景,你也可以在实践中参考这里的做法。
**最后,给你留一道思考题:** 你的公司业务上有高并发的场景吗,系统是如何应对的呢?
欢迎给我留言,我会及时给你反馈。如果这节课对你有帮助,也欢迎你把它分享给你的朋友。感谢你的阅读,我们下期再见。

View File

@@ -0,0 +1,185 @@
<audio id="audio" title="18 | 可伸缩架构案例:数据太多,如何无限扩展你的数据库?" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/62/80/62dee8d0c7359f3ce1d21c34c5184180.mp3"></audio>
你好,我是王庆友。在[第16讲](https://time.geekbang.org/column/article/217152)中我和你介绍了很多可伸缩的架构策略和原则。那么今天我会通过1号店订单水平分库的实际案例和你具体介绍如何实现系统的可伸缩。
## 问题和解决思路
2013年随着1号店业务的发展每日的订单量接近100万。这个时候订单库已有上亿条记录订单表有上百个字段这些数据存储在一个Oracle数据库里。当时我们已经实现了订单的服务化改造只有订单服务才能访问这个订单数据库但随着单量的增长以及在线促销的常态化单一数据库的存储容量和访问性能都已经不能满足业务需求了订单数据库已成为系统的瓶颈。所以对这个数据库的拆分势在必行。
数据库拆分一般有两种做法,一个是垂直分库,还有一个是水平分库。
- **垂直分库**
简单来说,垂直分库就是数据库里的表太多,我们把它们分散到多个数据库,一般是根据业务进行划分,把关系密切的表放在同一个数据库里,这个改造相对比较简单。
- **水平分库**
某些表太大,单个数据库存储不下,或者数据库的读写性能有压力。通过水平分库,我们把一张表拆成多张表,每张表存放部分记录,分别保存在不同的数据库里,水平分库需要对应用做比较大的改造。
<img src="https://static001.geekbang.org/resource/image/6a/81/6a414d387a08a6dc291c3a3a9e763c81.jpg" alt="">
当时1号店已经通过服务化实现了订单库的**垂直拆分**它的订单库主要包括订单基本信息表、订单商品明细表、订单扩展表。这里的问题不是表的数量太多而是单表的数据量太大读写性能差。所以1号店通过**水平分库**把这3张表的记录分到多个数据库当中从而分散了数据库的存储和性能压力。
水平分库后,应用通过订单服务来访问多个订单数据库,具体的方式如下图所示:
<img src="https://static001.geekbang.org/resource/image/7c/9f/7cf1df5c241cd515d5e89456d2a7f39f.jpg" alt="">
原来的一个Oracle库被现在的多个MySQL库给取代了每个MySQL数据库包括了1主1备2从都支持读写分离主备之间通过自带的同步机制来实现数据同步。所以你可以发现**这个项目实际包含了水平分库和去Oracle两大改造目标。**
## 分库策略
我们先来讨论一下水平分库的具体策略,包括要选择哪个分库维度,数据记录如何划分,以及要分为几个数据库。
### 分库维度怎么定?
首先,我们需要考虑根据哪个字段来作为分库的维度。
这个字段选择的标准是尽量避免应用代码和SQL性能受到影响。具体地说就是现有的SQL在分库后它的访问尽量落在单个数据库里否则原来的单库访问就变成了多库扫描不但SQL的性能会受到影响而且相应的代码也需要进行改造。
具体到订单数据库的拆分,你可能首先会想到按照**用户ID**来进行拆分。这个结论是没错,但我们最好还是要有量化的数据支持,不能拍脑袋。
这里最好的做法是先收集所有SQL挑选出WHERE语句中最常出现的过滤字段比如说这里有三个候选对象分别是用户ID、订单ID和商家ID每个字段在SQL中都会出现三种情况
1. 单ID过滤比如说“用户ID=?”;
1. 多ID过滤比如“用户ID IN(?,?,?)”;
1. 该ID不出现。
最后我们分别统计这三个字段的使用情况假设共有500个SQL访问订单库3个候选字段出现的情况如下
<img src="https://static001.geekbang.org/resource/image/26/9a/26480ace17c1629c24f5881f65f2fa9a.jpg" alt="">
从这张表来看结论非常明显我们应该选择用户ID来进行分库。
不过,等一等,这**只是静态分析**。我们知道每个SQL访问的频率是不一样的所以我们还要分析每个SQL的实际访问量。
在项目中我们分析了Top15执行次数最多的SQL 它们占总执行次数85%具有足够代表性按照执行的次数如果使用用户ID进行分库这些SQL 85%会落到单个数据库13%落到多个数据库只有2%需要遍历所有的数据库。所以说,**从SQL动态执行次数的角度来看**用户ID分库也明显优于使用其他两个ID进行分库。
这样通过前面的量化分析我们知道按照用户ID分库是最优的选择同时也大致知道了分库对现有系统会造成多大影响。比如在这个例子中85%的SQL会落到单个数据库那么这部分的数据访问相对于不分库来说执行性能会得到一定的优化这样也解决了我们之前对分库是否有效果的疑问坚定了分库的信心。
### 数据怎么分?
好,分库维度确定了以后,我们如何把记录分到各个库里呢?
一般有两种数据分法:
1. **根据ID范围进行分库**比如把用户ID为1 ~ 999的记录分到第一个库1000 ~ 1999的分到第二个库以此类推。
1. **根据ID取模进行分库**比如把用户ID mod 10余数为0的记录放到第一个库余数为1的放到第二个库以此类推。
这两种分法,各自存在优缺点,如下表所示:
<img src="https://static001.geekbang.org/resource/image/3b/81/3bf9fb5fb9e1569cf4478c493c02f081.jpg" alt="">
在实践中为了运维方便选择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页数据则需要从每个库里取前1000100*10条记录汇总后共有8000条记录然后我们对这8000条记录进行二次排序后取第900到1000之间的记录。
你可以看到在分库情况下对于每个数据库我们要取更多的记录并且汇总后还要在应用里做二次排序越是靠后的分页系统要耗费更多的内存和执行时间。而在不分库的情况下无论取哪一页我们只要从单个DB里取100条记录即可也无需在应用内部做二次排序非常简单。
**那么,我们如何解决分库情况下的分页问题呢?**这需要具体情况具体分析:
- 如果是为前台应用提供分页我们可以限定用户只能看到前面n页这个限制在业务上也是合理的一般看后面的分页意义不大如果一定要看可以要求用户缩小范围重新查询
- 如果是后台批处理任务要求分批获取数据我们可以加大分页的大小比如设定每次获取5000条记录这样可以有效减少分页的访问次数
- 分库设计时,一般还有配套的大数据平台负责汇总所有分库的记录,所以有些分页查询,我们可以考虑走大数据平台。
### 分库字段映射
分库字段只有一个比如这里我们用的是用户ID如果给定用户ID这个查询会落到具体的某个库。但我们知道在订单服务里根据**订单ID**查询的场景也很多见不过由于订单ID不是分库字段如果不对它做特殊处理系统会盲目查询所有分库从而带来不必要的资源开销。
所以,这里我们**为订单ID和用户ID创建映射保存在Lookup表里**我们就可以根据订单ID找到相应的用户ID从而实现单库定位。
Lookup表的记录数和订单库记录总数相等但它只有2个字段所以存储和查询性能都不是问题这个表在单独的数据库里存放。在实际使用时我们可以通过**分布式缓存**来优化Lookup表的查询性能。此外对于新增的订单除了写订单表我们同时还要写Lookup表。
## 整体架构
通过以上分析最终的1号店订单水平分库的总体技术架构如下图所示
<img src="https://static001.geekbang.org/resource/image/3a/9c/3ae46ab0d2d5430f03b436c87247d59c.jpg" alt="">
- **上层应用**通过订单服务访问数据库;
- **分库代理**实现了分库相关的功能包括聚合运算、订单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个我们可以分三步走
1. 增加6个MySQL实例把现有6个库的数据同步到新的库比如说0号库同步到6号库1号库同步到7号库等等
1. 在配置文件里把分库的取模从6变成12
1. 通过数据库脚本每个库删掉一半数据比如对于0号库删掉用户ID%12=6的记录对于6号库删掉用户ID%12=0的记录。
你可以看到,通过这样的分库方式,整个数据库扩展是非常容易的,不涉及复杂的数据跨库迁移工作。
订单的水平分库是一项系统性工作,需要大胆设计,谨慎实施。**你需要把握住这几个要点:**
- 首先,你需要在分库策略的指导下,结合实际情况,在每个方面做出最合适的选择;
- 其次,对于特殊场景,如分页查询,你需要具体问题具体解决;
- 最后,你要总体规划,控制好落地步骤,包括对系统改造、性能测试、数据迁移、上线实施等各个环节做好衔接,保证业务不出问题。
## 总结
今天我和你分享了1号店订单水平分库的实际案例并给出了具体的做法和原因相信你已经掌握了如何通过对数据库的水平拆分来保证系统的高性能和可伸缩。
**水平分库是针对有状态的存储节点进行水平扩展**,相对于无状态的节点,系统改造的复杂性比较高,要考虑的点也比较多。通过今天的分享,希望你以后在设计一个复杂方案时,能够更全面地思考相关的细节,提升架构设计能力。
**最后,给你留一道思考题**:你公司的数据库有什么瓶颈吗,你计划对它做什么样的改造呢?
欢迎在留言区和我互动,我会第一时间给你反馈。如果这节课对你有帮助,也欢迎你把它分享给你的朋友。感谢阅读,我们下期再见。

View File

@@ -0,0 +1,112 @@
<audio id="audio" title="19 | 综合案例:电商平台技术架构是如何演变的?" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/f8/35/f8fc29ff4a50970918b5dc849c7f7635.mp3"></audio>
你好,我是王庆友。
在前面的几讲中,我分别和你介绍了技术架构的高可用、高性能、可伸缩等目标,并通过实际的案例说明了如何实现这些目标。今天呢,我会通过一个综合案例,来说明电商平台的技术架构是如何演变的,让你可以全面地理解如何实现这些目标。
一个实际的电商系统很复杂,在案例介绍中,为了简化,我用比较有代表性的**交易系统**和**账户系统**来代表整体的电商系统,并具体分析这两个系统在电商平台发展过程中,它们都碰到了什么瓶颈,以及我们在技术架构上是如何解决的。
这一讲会包含很多架构图,每一张图都代表了不同时期的架构设计,为了方便你更好地理解它们,在每张架构图中,我都用红色方框圈出了当前架构存在的问题,用绿色实体部分代表了上一个架构所存在问题的解决办法,希望你听完今天的讲解,能够结合这些架构图,加深对技术架构的理解。
## 单体系统
<img src="https://static001.geekbang.org/resource/image/cf/cf/cf1a7a16f3a09eb85c7dd4c1c172c6cf.jpg" alt="">
第一代的电商系统是一个单体架构,所有的代码都打包在一个应用里,部署的时候会有多个实例,我们通过**负载均衡**,把用户请求分发到具体的实例中。这个时候,所有的数据表还在一个数据库里。
**这里的问题是**,单体应用的所有代码都放在一起,代码编译需要很长时间,应用启动也需要很长时间,并且代码相互依赖,开发效率低,并行开发困难。随着单体应用的体量越变越大,这些问题也越来越突出。
## SOA架构
<img src="https://static001.geekbang.org/resource/image/84/c9/84da834d04bd838a6c76b7a535340bc9.jpg" alt="">
**针对单体应用体量过大带来的问题**,我们**对系统进行拆分**,把整体系统分为多个子系统。比如在图中,我们把系统拆分为交易系统和账户系统,这两个系统(或者说服务)通过内部的负载均衡进行相互调用,这个时候,底层数据库还没有拆分,两个系统还是访问同一个数据库。
通过拆分系统整体就变成了SOA架构这样我们减少了代码冲突系统的开发也变得更加高效部署的时候我们也更容易针对各个系统的处理能力进行水平扩展。
**但这里的问题是**,内部服务通过中心化的负载均衡进行访问,中心化的负载均衡增加了服务的调用时间。此外,在电商场景下,内部的服务很多,服务调用的频率很高,每秒可能有上百万次,导致了负载均衡的连接能力不够。而且负载均衡是单点,如果它出了问题,很容易引发系统整体的可用性问题(即使负载均衡是多实例,当系统流量很大时,也会因为某台负载有问题,导致其他节点压力增大而引起雪崩效应)。
## 服务调用去中心化
<img src="https://static001.geekbang.org/resource/image/5c/a8/5cc8cddc0c25a847fce084ff890db1a8.jpg" alt="">
**针对内部服务路由中心化的问题**,我们去掉了内部的负载均衡,加入了**服务注册中心**比如ZooKeeper。
当服务实例启动或退出时,它们会自动在注册中心进行注册或销毁,服务的客户端和注册中心保持长连接,可以实时地获取可用的服务列表;然后在客户端,根据相应的算法选择服务实例,直接调用服务。每次调用无需经过注册中心,如果注册中心有问题,也只是新的服务实例无法注册,或者是已有的服务实例无法注销,这对客户端调用服务的影响是非常有限的。
当然,通过注册中心和更体系化的微服务框架,我们还可以实现完善的**服务治理**,包括服务隔离、熔断、降级,这些都是对原来简单的负载均衡方式的加强,能够进一步提升服务的可用性。
现在,我们解决了服务调用的问题,但随着业务量逐渐变大,数据表越来越多,数据量也越来越大,**单个数据库比如Oracle的性能和储存容量已经无法满足需求了**。这个时候,我们就需要对数据库进行改造,提升它的处理能力。
## 垂直分库
<img src="https://static001.geekbang.org/resource/image/84/18/8440763ab5e043a4099c1424ba595018.jpg" alt="">
**对于单个数据库性能和容量瓶颈**,解决的办法就是,我们对数据库进行**垂直拆分**,按照业务拆分为交易数据库和账户数据库,这样就可以满足它们各自的容量和性能需求,同时也避免了不同业务数据表之间的相互耦合。
你可以认为垂直分库是系统拆分的第二阶段,这样,通过第一阶段的应用代码拆分和这里的数据库表拆分,交易系统和账户系统就可以独立发展。
**不过,新的问题又来了**,垂直分库后,每个数据库都是单实例。随着业务的发展,和原来系统只有单个数据库类似,现在交易系统也只有一个数据库,它的性能和容量还是有问题,并且数据库单实例也带来了可用性的问题,如果数据库挂了,相应的系统也就不可用。
## 水平分库及高可用部署
<img src="https://static001.geekbang.org/resource/image/d6/70/d6b81b2d5317e872632e1d09ce2ac170.jpg" alt="">
**针对单个数据库的可用性问题**,我们可以采用 **MHA高可用**Master High Availability方式部署。比如数据库部署一主多从通过MHA机制我们可以实时检测主库的可用性如果主库有问题系统会自动Failover故障转移到最新的从库。另一方面我们还可以利用多个从库支持**读写分离**,减轻主库的访问压力。
**针对单个数据库的性能和容量问题**,首先我们可以**引入缓存**,在高读写比的场景下,让应用先访问缓存,大大减轻对底层数据库的压力。然后,我们可以对数据库按照某个维度(比如用户维度),进行**水平拆分**,把数据记录分布到多个实例中,最终分散主库的写压力以及数据存储的瓶颈(在[上一讲](https://time.geekbang.org/column/article/218385)中,我已经具体介绍过了,你可以点击链接去回顾内容)。
在实践中,我们还可以提供**多套水平分库**。比如说,针对交易数据,我们可以同时按照用户维度和商户维度进行水平分库,用户维度的库用于前台用户下单的场景,商户维度的库用于后台商家履单的场景。这里,只有用户维度的分库会支持**写**,我们通过数据同步机制,把用户维度分库的更新同步到商户维度的分库里。
**当系统体量发展到了一定程度,我们又碰到了新的问题**:单个机房的服务器不够用,无法在同一个机房找到更多的机器部署交易系统和账户系统。
## 多机房部署
<img src="https://static001.geekbang.org/resource/image/d6/cf/d618f33b2ac8799bf6bab3949ed344cf.jpg" alt="">
**对于单机房服务器不够的问题**,我们可以在新的机房部署交易系统和账户系统,为了落地方便,所有服务还是注册到旧机房的注册中心,数据还是存放在旧机房的交易数据库和账户数据库。 这样,我们通过在新机房部署应用,对应用节点进行水平扩展,从而解决了单机房机器不足的问题。
**但这里产生了跨机房访问的问题**:首先,我们只有一个服务注册中心,服务实例一部分部署在老机房,一部分部署在新机房,对于服务调用者来说,它会同时访问新旧机房的服务实例;其次,数据库部署在老机房,新机房的应用会访问旧机房的数据库。
这两种情况,都会产生大量的跨机房访问,我们知道,根据机房物理距离的不同,跨机房访问的网络延时在数十毫秒到数百毫秒之间,是机房内部通信耗时的上千倍,这会对**应用的性能**产生很大影响,而且跨机房的**网络可用性**也经常是一个问题。
## 服务调用本地化
<img src="https://static001.geekbang.org/resource/image/bf/07/bfbf070729b23e11308dc8ed19cbc607.jpg" alt="">
为了避免服务的跨机房访问,我们**在新机房也单独部署了服务注册中心**,让每个机房的服务注册到同机房的注册中心。这样,客户端的服务调用会路由到同机房的服务端,实现了服务调用的本地化,大大降低了跨机房通信带来的延时和不可用性问题。
这时,**随着业务越来越复杂,新的问题又来了**:交易系统会依赖很多周边服务。比如下单后,我们需要给用户送积分,交易系统会同步调用积分服务。但是同步调用积分服务,一方面会影响下单的性能,另一方面如果积分服务不可用,会导致核心的下单功能失败。
## 依赖分级管理
<img src="https://static001.geekbang.org/resource/image/f0/91/f010a6bb35627923566a0f3484447091.jpg" alt="">
**对于外部服务依赖的可用性问题**,我们的解决办法是,针对这些外部依赖进行**分级管理**,根据依赖功能的重要性不同,把它们分为强依赖和弱依赖。
- 对于强依赖,我们**实时同步调用**,比如在用户下单时调用库存服务,由于库存非常重要,必须实时扣减,如果调用库存服务失败,下单也失败。
- 对于大量的弱依赖,我们以**异步消息**的方式进行信息同步,比如对于积分服务,可以通过柔性事务来保证数据的最终一致性,这样大大提升了核心系统的性能和可用性。
**不过,这里存在的问题是**,新机房的交易系统和账户系统都在访问老机房的数据库,有跨机房数据库访问的性能问题,以及老机房整体故障带来的可用性问题。比如说,机房断电,通信光纤有问题或者发生自然灾害,导致老机房整体不可用,这就会导致所有系统都不可用。
## 多机房独立部署
<img src="https://static001.geekbang.org/resource/image/63/ca/6301edac111c2f9f4dbd5492cbf0aaca.jpg" alt="">
**针对机房整体不可用的问题**,解决方案是,我们**在多个机房做对等的部署**,这样每个机房的系统可以形成内部闭环,包括服务、注册中心和数据库,机房之间不产生直接的相互依赖,从而实现了机房级别的水平部署。
如果系统的单元化做得完善,我们还可以进一步支持**虚拟机房**的概念,一个物理机房可以部署多个虚拟机房,每个虚拟机房包含了一个完整的系统。通过多机房独立部署,我们极大地提升了系统的可用性、处理能力和可伸缩性,可以应对系统面临的各种异常情况。
另外,最近几年,容器化技术的发展很快,原来很多的电商平台都是基于**虚拟机**部署,现在也纷纷改造为用**Docker+K8s**的方式部署,这大大提升了资源的利用率、系统的弹性伸缩能力。在面临资源瓶颈时,你可以考虑用这种方式来优化系统的部署。
## 总结
今天,我基于一个简化的电商系统模型,与你分享了电商平台的技术架构发展过程,我们是如何通过一步步的架构升级,解决系统各个阶段出现的高可用、高性能和可伸缩问题的,相信你现在对技术架构如何应对各种系统性挑战,有了更深入的认识。
**值得注意的是**,系统的技术架构变化不一定要完全遵循这个过程,不同的业务、不同的发展阶段,对系统的要求都是不一样的,这里我给出的只是典型的问题和解决手段,希望你在工作中,能够具体情况具体分析,灵活地运用这些手段。
业务在不断发展,新的问题会不断出现,但技术也在不断地进步,解决的手段层出不穷,我们需要不断学习,找到新的手段来解决问题。
**最后,给你留一道思考题**:你的公司当前的系统架构处于哪个阶段,面临什么样的问题呢?
欢迎在留言区和我互动,我会第一时间给你反馈。如果这节课对你有帮助,也欢迎你把它分享给你的朋友。感谢阅读,我们下期再见。

View File

@@ -0,0 +1,145 @@
<audio id="audio" title="01 | 架构的本质:如何打造一个有序的系统?" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/39/53/397d92d9df0fdc074c25e95196669d53.mp3"></audio>
你好,我是王庆友,今天是专栏的第一讲,我想先和你聊聊架构的本质。
我们知道,现在的软件系统越来越复杂,当然相应地,架构的作用也越来越明显。作为开发人员,我们每天都在和架构打交道,在这个过程中,对于架构也经常会产生各种各样的问题:
- 什么是架构?架构都有哪些分类,分别解决什么问题呢?
- 怎样才是一个好的架构设计?我怎么才能成长为一名优秀的架构师呢?
这些问题涉及我们对架构的认识,也是学习和运用架构的开始。所以,今天,我们就来深入地分析架构的实质,让你能够透彻地理解它。
作为专栏的第一讲,我希望先和你讨论架构中理念性的部分,就是所谓架构的道,这样可以指导你学习后续的实操层面的内容,也就是架构的术。
接下来,我们就正式开始吧,先说下我对架构本质的理解。
## 架构的本质
物理学中有个很著名的“熵增定律”:一个封闭系统,都是从有序到无序,也就是它的熵(即混乱程度)会不断地增加,最终系统会彻底变得无序。
这个理论放在软件系统的演化上,也是非常适用的。
一方面,随着业务需求的增加,我们会往系统里不停地添加业务功能;另一方面,随着访问量的不断增加,我们会不断通过技术手段来加强系统非业务性功能。如果事先不做良好的设计,随着时间的推进,整个系统野蛮生长,就会逐渐碎片化,越来越无序,最终被推倒重来。
不过,自然界中的生物可以通过和外界交互,主动进行新陈代谢,制造“负熵”,也就是降低混乱程度,来保证自身的有序性,继续生存。比如,植物通过光合作用,把光能、二氧化碳和水合成有机物,以此滋养自己,延续生命。对于软件系统,我们也可以主动地调整系统各个部分的关系,保证系统整体的有序性,来更好地适应不断增长的业务和技术变化。这种系统内部关系的调整就是通过架构实现的,所以,架构的本质就是:
>
**通过合理的内部编排,保证系统高度有序,能够不断扩展,满足业务和技术的变化。**
这里包含两层意思,我们具体展开说下:
**首先,架构的出发点是业务和技术在不断复杂化,引起系统混乱,需要通过架构来保证有序**。我们知道架构这个词来源于建筑行业,那为什么建筑行业需要“架构”呢?
搭一个草房子很简单可以直接上手盖一个2层楼房稍微复杂一些但在工匠的经验指导下问题也不大而盖一座高楼复杂性就大不一样了我们需要考虑内部结构、承重、采光、排水、防雷抗震等这就需要专业人员事先做好整体的架构设计并严格地按照设计来施工。
这里,你可以看到,建筑里的架构不是天然就有的,而是因为建筑越来越复杂,我们需要通过架构来管理这种复杂性,避免建造过程的失控。
软件系统也是如此,从简单的桌面应用发展到现在的大型互联网平台,这个过程中,系统规模越来越大,业务和技术也越来越复杂。我们同样需要通过架构设计,消化复杂性带来的混乱,使系统始终处于一个有序状态,能够应对现有和将来的需求变化。
**其次,架构实现从无序到有序,是通过合理的内部编排实现的,基本的手段,就是“分”与“合”,先把系统打散,然后将它们重新组合,形成更合理的关系。**
<img src="https://static001.geekbang.org/resource/image/3d/ae/3dd1ae4724e696a488210a75a8bce4ae.jpg" alt="">
具体地说,**“分”就是把系统拆分为各个子系统、模块、组件**。拆分的时候,首先要解决每个部分的定位问题,然后根据定位,划分彼此的边界,最后实现合理的拆分,我们比较熟悉的微服务架构,就是一种典型的拆分做法。
**“合”就是基于业务流程和技术手段,把各个组件有机整合在一起**。比如说在微服务架构中,拆分为具体微服务后,我们需要对这些服务进行归类和分层,有些属于底层基础服务,有些属于上层聚合服务,还要尽可能地实现服务的平台化,比如我们最近说的中台,这些都是合的思想体现。
这个分与合的过程将系统的复杂性分解为两个层次:
- 首先,各个子系统承担独立的职责,内部包含了自身的复杂性。子系统的复杂性对外部是透明的,外部不用关心。
- 其次,子系统通过封装后,简化为职责明确的一个点,因此,我们只需要在合的过程中,解决各个点之间的依赖关系,这样就可以定义出系统整体。
举个例子我们都知道GoF的23个设计模式在Builder模式中它的主逻辑只需要给出各个部件的组装关系即可它不关心创建某个具体部件的内部逻辑这个可以交给工厂模式去实现。这里Builder模式负责粗粒度的组装逻辑它承担的是合的部分工厂模式负责细粒度的构造逻辑承担的是分的部分大家各自管理自己的复杂性。
**通过合理的“分”与“合”,系统不是回到了原点,而是把原先铁板一块的系统变成一个富有弹性的结构化系统。这样,系统的复杂性有效地分解了,系统的有序度大幅度地提升了。**
当然,系统的复杂性是多方面的,有技术上和业务上的,架构也是一个体系,会有多种架构一起来应对这些复杂性挑战。那么接下来,我们就来具体看下。
## 架构的分类
按照不同的角度,架构可以有很多分类,但一般来说,主要分为**业务架构**、**应用架构**和**技术架构**。那么,这些架构分别为谁服务,解决什么问题,相互之间是什么关系呢?
回答这些问题前,我们先来看下系统的落地过程。
系统首先由人来开发,然后由机器来运行,人和机器共同参与一个系统的落地。
<img src="https://static001.geekbang.org/resource/image/95/64/95790773cbc59bedbb01a07894e94f64.jpg" alt="">
对于负责开发的人来说,比较头疼的是,业务太复杂,脑子想不清楚,即使当前勉强把业务逻辑转化为代码,系统后续的维护也是问题。因此,开发人员的要求是系统概念清晰,业务逻辑容易理解,可以直观地进行代码开发。
对于负责运行的机器来说,比较头疼的是,外部请求并发量太大,导致机器扛不住,有的时候,硬件还会出问题。因此,它的要求是系统能够水平扩展,支持硬件容错,保证系统的高性能和高可用。
这里,**开发的痛点主要由业务架构和应用架构来解决,机器的痛点主要由技术架构来解决。**
为什么这么说呢?我们看下,这些架构具体都是做什么用的。
简单来说,业务架构就是讲清楚核心业务的处理过程,定义各个业务模块的相互关系,它从概念层面帮助我们理解系统面临哪些问题以及如何处理;而应用架构就是讲清楚系统内部是怎么组织的,有哪些应用,相互间是怎么调用的,它从逻辑层面帮助我们理解系统内部是如何分工与协作的。
技术架构就是讲清楚系统由哪些硬件、操作系统和中间件组成,它们是如何和我们开发的应用一起配合,应对各种异常情况,保持系统的稳定可用。所以,技术架构从物理层面帮助我们理解系统是如何构造的,以及如何解决稳定性的问题。
这里你可以看到业务架构、应用架构和技术架构分别从概念、逻辑和物理层面定义一个系统。业务架构给出了业务模块的划分和依赖关系这也大致决定了应用系统如何分工和协作当然这不需要严格地一一对应比如一个商品业务可能对应3个应用一个前台商品展示应用、一个后台商品管理应用以及一个商品基础服务但这不影响我们从逻辑上理解一个业务场景有哪些应用参与并且它们是如何协作的。
而技术架构呢,通过保障应用的稳定运行,最终保证业务不出问题。比如在大促的时候,多个应用可能会受大流量冲击,技术架构就要考虑怎么通过技术手段,保障相关的应用能够处理高并发,从而保证大促顺利进行。
这里,我举个拍电影的例子,来帮助你更直观地理解这三种架构的关系:业务架构定义了这个电影的故事情节和场景安排;应用架构进一步定义有哪些角色,每个角色有哪些职责,并且在每个场景中,这些角色是如何互动的;技术架构最后确定这些角色由谁来表演,物理场景上是怎么布置的,以此保证整个拍摄能够顺利完成。
最后,我想强调一下:系统是人的系统,架构首先是为人服务的。因此,业务概念清晰、应用分工合理、人好理解是第一位的。然后,我们再考虑技术选型的问题,保证系统非功能性目标的实现。**所以做架构设计时,一般是先考虑业务架构,再应用架构,最后是技术架构。**
## 什么是好的架构?
从上面的内容,我们不难看出,一个好的架构必须满足两方面挑战:业务复杂性和技术复杂性。
### 1. 业务复杂性
系统首先要满足当前的业务需求,在此基础上,还要满足将来的业务需求,因此系统要能不断地扩展变化,包括调整现有功能,以及增加新功能。
而且,系统的功能变化不能影响现有业务,不要一修改,就牵一发动全身,到处出问题。因此,在架构设计上,要做到系统的柔性可扩展,能够根据业务变化做灵活的调整。
此外,市场不等人,上新业务要快,之前花了半年上了个业务,这回再上个类似的新业务,需要短时间就能落地。因此,架构设计上,还要做到系统功能的可重用,这样才能通过快速复用,实现业务敏捷和创新。
### 2. 技术复杂性
要保证一个业务能正常运行,除了满足业务功能之外,还要保证这个系统稳定可用。
一个复杂系统是由很多部分组成的,如应用程序、服务器、数据库、网络、中间件等,都可能会出问题。那怎么在出问题时,能够快速恢复系统或者让备用系统顶上去呢?
还有流量问题,平时流量不大,少量机器就可以处理,但在大促的时候,大量流量进来,系统是不是能够通过简单地加机器方式就能支持呢?
此外还有低成本的问题系统能否做到使用廉价设备而不是高大上的IOE设备使用免费的开源组件而不是昂贵的商业套件使用虚拟化技术而不是物理机并且在流量低谷和高峰的不同时期让系统能够弹性缩容和扩容呢
这些都属于技术性的挑战,解决的是系统的非业务性功能,也都是架构设计要支持的。
**因此,一个好的架构设计既要满足业务的可扩展、可复用;也要满足系统的高可用、高性能和可伸缩,并尽量采用低成本的方式落地。所以,对架构设计来说,技术和业务两手都要抓,两手都要硬。**
那么,一个优秀的架构师需要具备什么样的能力,才能设计一个好的架构呢?
## 什么是好的架构师?
一个优秀的架构师,应具备很强的综合能力,要内外兼修,“下得厨房,上得厅堂”,下面我来通过典型的架构方式,来介绍一名优秀架构师应该具备的能力:
<img src="https://static001.geekbang.org/resource/image/a1/9c/a177795d73884d912c4a5ae7fcea7e9c.jpg" alt="">
一个驾校教练必定开车技术好一个游泳教练必定游泳水平好因为这些都是实践性很强的工作。架构师也是一样TA必定是一个**出色的程序员**,写的一手好代码。
在此基础上,**架构师要有技术的广度(多领域知识)和深度(技术前瞻)**。对主流公司的系统设计非常了解,知道优劣长短,碰到实际问题,很快就能提供多种方案供评估。
此外,架构师还需要有**思维的高度,具备抽象思维能力**。抽象思维是架构师最重要的能力架构师要善于把实物概念化并归类。比如面对一个大型的B2C网站能够迅速抽象为采购-&gt;运营-&gt;前台搜索-&gt;下单-&gt;履单这几大模块,对系统分而治之。
架构师还需要有**思维的深度,能够透过问题看本质**。透过问题看本质是由事物的表象到实质往深层次挖掘。比如看到一段Java代码知道它在JVMJava Virtual MachineJava虚拟机中如何执行一个跨网络调用知道数据是如何通过各种介质比如网卡端口到达目标位置。透过问题看本质可以使架构师能够敏锐地发现底层的真实情况以端到端闭环的方式去思考问题能够识别系统的短板并解决它。
还有很重要的一点,能落地的架构才是好架构,所以架构师还需要具备**良好的沟通能力(感性)**,能确保各方对架构达成共识,愿意采取一致的行动;而**良好的平衡取舍能力(理性)**,可以确保架构在现有资源约束下是最合理的,能让理想最终照进现实。
## 总结
我今天和你分享了架构的本质,架构的终极目标是保证系统的有序,通过拆分和整合,使系统具有柔性,能够进化,从而可以满足现有的和将来的各种变化。
如果你能深入地理解架构的这些本质和手段,就可以不用照搬某某大厂的方案了,而是能够根据实际情况,以最合理的方式来解决系统面临的问题。
这里呢,我也分享了架构的三种典型分类,包括它们各自的定位和相互关系,相信你现在对架构整体有了一个简明的框架,知道架构设计都要做哪些事情了。
最后,我还为你提供了高标准的架构师能力模型,这样,你能比较清楚自己的努力方向是什么,这些要求很高,但你也不要有任何的畏难情绪,你可以在架构实践中,逐步地往这个目标上靠近,通过本专栏后续的学习,相信你也可以更快地达到这个目标。
**最后,给你留一道思考题:**除了本文提到的三种架构,你还知道有哪些架构分类,它们分别做什么用?
我是王庆友,欢迎在留言区和我互动,我会第一时间给你反馈。如果这节课对你有帮助,也欢迎你把它分享给你的朋友。感谢阅读,我们下期再见。

View File

@@ -0,0 +1,84 @@
<audio id="audio" title="开篇词 | 想吃透架构?你得看看真实、接地气的架构案例" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/36/39/3671dd3b09dc9e0132487c2fa1558939.mp3"></audio>
你好我是王庆友前1号店首席架构师很高兴能在这里和你一起聊聊架构。
架构的内容,相信你也看过很多了,博客、书籍、技术大会等等,都会有大量架构方面的分享。但一方面,这些内容比较碎片化,比如这一次讲的是技术的高并发处理,下一次讲的是老业务的改造。表面上看,你脑子里塞得满满的,但实际上,你很难循序渐进、系统地去学习架构。
另一方面,这些内容大多比较含糊,看似说的要点很全,但缺乏关键的实现细节,这样的话,说的和做的联系不起来,结果就是你可能理解起来都有困难,更不用说在实际工作中落地了。因此,我想了想,要能为你提供全新的架构学习体验,那就是让理论和实战结合起来,理论讲透彻,实战提供大量接地气的案例,让你能够知行合一地学架构。
我先简单介绍下自己吧,我从事软件开发已有二十年,架构设计也超过十年了,所以也经历了软件发展的各个阶段。
一开始呢我从事的是企业级软件开发后来转到了大型互联网电商平台最近我奋战在新零售领域帮助传统的大型连锁餐饮企业搭建O2O平台打通他们的线上和线下系统。
从1号店开始我负责了许多大项目的架构设计积累了很多架构方面的经验。我是一个爱琢磨的人喜欢总结做过的东西形成一个体系化的认知。因此在架构这个话题上我有很多东西想和你分享。我希望这些总结过的经验可以帮你在成为架构师的道路上少走一些弯路也希望通过这个专栏能够抛砖引玉和你一起讨论一起成长。
## 为什么开发人员要学习架构?
这其实可以从系统和个人发展这两方面来看。
### 一、无架构,不系统
现在的软件系统规模越来越大,业务上和技术上都非常地复杂,大一点的互联网公司,技术人员都有几千号人。那么,如何开发这么复杂的系统?如何有效地组织他们的工作呢?
在这里,一个好的架构设计无疑是至关重要的,无论你是有一定经验的开发人员,还是已经开始从事系统设计的架构师,深入学习和理解架构都是必不可少的,掌握好架构设计,可以让你轻松应对技术和业务的挑战。但是很多技术人员,由于个人项目经验有限,又缺乏很好的学习途径,对架构设计一知半解。在实际工作中,不能把握好架构设计的度,要么设计不足,要么过度设计,导致系统变来变去,严重影响开发效率和质量。
### 二、拓展你的职业发展空间
此外,对于技术人员来说,公司通常会提供两个职业发展通道供你选择:管理路线和技术路线。
现实中,大部分同学应该都是走技术路线的,很多程序员的职业发展目标,也都是想要成为一名优秀的架构师。这不仅仅意味着更优渥的薪水和更持久的职业生涯,更因为在架构师这个舞台上,你可以凭借个人出色的架构能力,为项目的落地发挥巨大的作用,你会有更大的成就感。
所以说,无论从软件发展的趋势,还是从个人职业发展方向上考虑,你都应该拥抱架构,主动学习,尽快成长为一个能力全面的架构师。
## 如何学习架构?
但是,成为一名优秀的架构师,却不是一件容易的事情,你需要克服很多挑战。
- 首先,你需要跳出当前的小模块,站在系统整体的角度来考虑问题。
- 其次,你不仅要从技术的角度考虑问题,也要学会从业务的角度来考虑问题,深入理解系统的挑战在哪里,不要在错误的地方发力。
- 最后,你需要做好各方面的平衡,能在现有的各项资源约束下,寻求一个最优解。
而这些,无疑都是非常考验人,也非常锻炼人的,需要你能够快速成长起来。如果你在走向架构师这条路上,完全靠自己摸索,找不到正确的方向,不断地犯错,你很可能会半途而废。
那么,如何找到一个好的学习方式呢?
我们知道,一门新的语言,有很多书籍教你如何从入门到精通;一个新的开源框架,也有全面的资料教你如何深入掌握。但**在架构学习这方面,并没有十分系统的理论指导,教你如何一步步进阶。**
有时候,一些架构的术语和概念甚至都没统一,需要靠你自己去分辨。有时候,你可以从各种渠道找到一些项目的架构介绍,但不知是作者出于保密的目的,还是自己也没有深度参与项目,内容往往避实就虚,缺乏具体的东西。这样的架构介绍,参考性就很有限,你看过了,也就忘了,就像是中国的山水画,写意不写实,内行可以看门道,外行只能看热闹,无助于你实际能力的提升。
**此外,架构设计的实践性很强**,如果公司的业务比较简单,对架构设计的要求不高,你就未必有很好的实践机会;如果你只是抽象地了解了一些架构原则,不去具体地实践它,那么你很难去学透架构,让它变成自己的能力。
而在设计这个专栏的时候,我就有针对性地考虑到了这些问题,并给出了解决方案:
- 首先,我不会在这里罗列所有架构相关的知识点,而是选择实践中最有用的部分,形成一个精简的体系,让你可以快速掌握架构的核心内容,抓住学习的重点。
- **此外,本专栏的核心内容全部来自于本人实际项目的总结**。我会基于这些实战后的思考,和你深入分享我的经验和看法,让你能够透过现象看本质,对架构的认知快速到位。我想说的是,这将会是一个有态度的专栏,而不是架构知识的搬运工。
- **更为重要的是,每讲课程,我会根据不同的架构原则,给你提供很多完整且非常接地气的实际案例**。比如说通过1号店App服务端架构改造的案例你将会深入地理解系统从单体架构到分布式再到服务化演变的背后原因通过1号店订单水平分库的案例你将会了解大型互联网公司是如何解决单个数据库的性能和容量瓶颈的。这些案例都很典型不但让你能够理论结合实践深入理解和运用各种架构原则而且在很多情况下你甚至可以直接参考它们在公司落地类似的架构改造。
这个专栏,它有深度、有细节、实战性强。希望通过这个专栏,能让你对架构形成一个清晰的认知框架,并对各个架构要点形成自己的理解,同时,通过结合着大量实际案例的学习,希望最终你可以把这些知识变成实际的能力。
## 我是如何规划这个课程的?
首先,我会和你分享架构的本质,让你对架构形成一个体系化的认知。
接下来,我将整体内容划分为**业务架构**和**技术架构**两大部分,先理论后案例,带你深入理解每种架构要解决的问题,以及如何解决。
- **业务架构篇**:重点针对系统的扩展性和复用性两大目标展开。首先,我会介绍这两大目标的内涵和实现手段,让你明白如何通过业务的拆分和组合,搭建一个柔性可变的系统;然后,每部分都有三个实际案例,让你理解如何以服务化和平台化的方式升级改造系统,最终实现扩展性和复用性的目标。
- **技术架构篇**:重点针对系统高可用和高性能/可伸缩的目标展开。首先我会介绍实现这些目标的策略和架构原则比如说高可用的策略有避免事故、降低影响、快速恢复等高可用的架构原则有无单点、可监控、水平扩展等这样你可以总体了解实现这些目标都有哪些手段然后每个目标都有3个实际案例让你理解如何在项目中灵活应用这些策略和原则。
最后,我还会通过一篇架构总结,汇总架构原则,以及它们的适用场景,并就如何成长为优秀的架构师,为你提供一个可行的方向指导。
## 写在最后
那么在最后,我想说的是,架构对我们的工作是如此的重要,如果你想深入学习架构,除了有好的学习方法,你一定还要多思考,多交流,多实践。
在课程设计中,我在每一节课后都留了思考题,希望你能够结合每篇文章的知识点,给出自己的思考。
**这次当然也不例外,你可以给我留言,说说你对学习架构都有哪些困惑。**
另外,你也要大胆地抛出问题,我一定会尽我所能及时给你反馈。很多问题都是共性的,我们一起讨论和解决,成长一定会更快。
书上学来终觉浅,梅花香自苦寒来。好的指导方法加上你自身的努力实践,相信你的架构之路会越走越顺,越走越快。
希望这个专栏,能够帮你开启架构师的进阶之路,在接下来的两个月里,让我们在架构之路上一起成长吧。
我是王庆友,欢迎你把这个课程分享给你的朋友,我们一起学架构。感谢你的阅读,我们第一讲再见。

View File

@@ -0,0 +1,11 @@
<audio id="audio" title="结课测试 | “架构实战案例解析”100分试卷等你来挑战" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/08/e6/08a1ef6669365908653e3232a5e9fde6.mp3"></audio>
你好,我是王庆友。
到这里,《架构实战案例解析》这门课程就已经全部结束了。为了帮助你检验自己的学习效果,我给你准备了一个结课小测试。
这套测试题共有 10 道题目,包括 5 道单选题和 5 道多选题,满分 100 分,系统会自动评分。在答完题之后,你还可以去回顾试卷内容,也可以看到我为你附上的参考答案和题目解析,希望能帮助到你。
好了,点击下面按钮开始测试吧,祝你学有所成!
[<img src="https://static001.geekbang.org/resource/image/28/a4/28d1be62669b4f3cc01c36466bf811a4.png" alt="">](http://time.geekbang.org/quiz/intro?act_id=96&exam_id=198)