# 几种常见的架构模式

分享一些工作中会用到的一些架构方面的设计模式。总体而言，共有八种，分别是：

1. **单库单应用模式**：最简单的，可能大家都见过
2. **内容分发模式**：目前用的比较多
3. **查询分离模式**：对于大并发的查询、业务
4. **微服务模式**：适用于复杂的业务模式的拆解
5. **多级缓存模式**：可以把缓存玩的很好
6. **分库分表模式**：解决单机数据库瓶颈
7. **弹性伸缩模式**：解决波峰波谷业务流量不均匀的方法之一
8. **多机房模式**：解决高可用、高性能的一种方法

### **三、单库单应用模式**

这是最简单的一种设计模式，我们的大部分本科毕业设计、一些小的应用，基本上都是这种模式，这种模式的一般设计见下图：

![](https://p1-jj.byteimg.com/tos-cn-i-t2oaga2asx/gold-user-assets/2019/11/29/16eb61a5f8e57770~tplv-t2oaga2asx-zoom-in-crop-mark:4536:0:0:0.image)



如上图所示，这种模式一般只有一个数据库，一个业务应用层，一个后台管理系统，所有的业务都是用过业务层完成的，所有的数据也都是存储在一个数据库中的，好一点会有数据库的同步。虽然简单，但是**也并不是一无是处**。

- **优点**：结构简单、开发速度快、实现简单，可用于产品的第一版等有原型验证需求、用户少的设计。
- **缺点**：性能差、基本没有高可用、扩展性差，不适用于大规模部署、应用等生产环境。

### **四、内容分发模式**

基本上所有的大型的网站都有或多或少的采用这一种设计模式，常见的应用场景是使用CDN技术把网页、图片、CSS、JS等这些静态资源分发到离用户最近的服务器。这种模式的一般设计见下图：

![](https://p1-jj.byteimg.com/tos-cn-i-t2oaga2asx/gold-user-assets/2019/11/29/16eb61a5f8ff4662~tplv-t2oaga2asx-zoom-in-crop-mark:4536:0:0:0.image)

如上图所示，这种模式较单库单应用模式多了一个CDN、一个云存储OSS(七牛、又拍等雷同)。一个典型的应用流程(以用户上传、查看图片需求为例)如下：

1. 上传的时候，用户选择本地机器上的一个图片进行上传
2. 程序会把这个图片上传到云存储OSS上，并返回该图片的一个URL
3. 程序把这个URL字符串存储在业务数据库中，上传完成。
4. 查看的时候，程序从业务数据库得到该图片的URL
5. 程序通过DNS查询这个URL的图片服务器
6. **智能DNS会解析这个URL，得到与用户最近的服务器(或集群)的地址A**
7. 然后把服务器A上的图片返回给程序
8. 程序显示该图片，查看完成。

由上可知，这个模式的关键是智能DNS，它能够解析出离用户最近的服务器。运行原理大致是：根据请求者的IP得到请求地点B，然后通过计算或者配置得到与B最近或通讯时间最短的服务器C，然后把C的IP地址返回给请求者。这种模式的优缺点如下：

- **优点**：资源下载快、无需过多的开发与配置，同时也减轻了后端服务器对资源的存储压力，减少带宽的使用。
- **缺点**：目前来说OSS，CDN的价格还是稍微有些贵(虽然已经降价好几次了)，只适用于中小规模的应用，另外由于网络传输的延迟、CDN的同步策略等，会有一些一致性、更新慢方面的问题。

### **五、查询分离模式**

这种模式主要解决单机数据库压力过大，从而导致业务缓慢甚至超时，查询响应时间变长的问题，也包括需要大量数据库服务器计算资源的查询请求。这个可以说是单库单应用模式的升级版本，也是技术架构迭代演进过程中的必经之路。 这种模式的一般设计见下图：

![](https://p1-jj.byteimg.com/tos-cn-i-t2oaga2asx/gold-user-assets/2019/11/29/16eb61a5fcca9706~tplv-t2oaga2asx-zoom-in-crop-mark:4536:0:0:0.image)

如上图所示，这种模式较单库单应用模式与内容分发模式多了几个部分，一个是**业务数据库的主从分离**，一个是**引入了ES**，为什么要这样？都解决了哪些痛点，下面具体结合业务需求场景进行叙述。

**场景一：全文关键词检索**

我想这个需求，绝大多数应用都会有，如果使用传统的数据库技术，大部分可能都会使用like这种SQL语句，高级一点可能是先分词，然后通过分词index相关的记录。SQL语句的性能问题与全表扫描机制导致了非常严重的性能问题，现在基本上很少见到。 这里的ES是ElasticSearch的缩写，是一种查询引擎，类似的还有Solr等，都差不多的技术，ES较Solr配置简单、使用方便，所以这里选用了它。另外，ES支持横向扩展，理论上没有性能的瓶颈。同时，还支持各种插件、自定义分词器等，可扩展性较强。在这里，使用ES不仅可以替代数据库完成全文检索功能，还可以实现诸如分页、排序、分组、分面等功能。具体的，请同学们自行学习之。那怎么使用呢？一个一般的流程是这样的：

1. 服务端把一条业务数据落库
2. 服务端异步把该条数据发送到ES
3. ES把该条记录按照规则、配置**放入自己的索引库**
4. 客户端查询的时候，由服务端把这个**请求发送到ES**，得到数据后，根据需求拼装、组合数据，返回给客户端

实际中怎么用，还请同学们根据实际情况做组合、取舍。

**场景二：大量的普通查询**

这个场景是指我们的业务中的大部分辅助性的查询，如：取钱的时候先查询一下余额，根据用户的ID查询用户的记录，取得该用户最新的一条取钱记录等。我们肯定是要天天要用的，而且用的还非常多。同时呢，我们的写入请求也是非常多的，导致**大量的写入、查询操作压向同一数据库**，然后，数据库挂了，系统挂了，领导生气了，被开除了，还不起房贷了，露宿街头了，老婆跟别人跑了，......

不敢想，所以要求我们必须分散数据库的压力，一个业界较成熟的方案就是数据库的读写分离，**写的时候入主库，读的时候读从库**。这样就把压力分散到不同的数据库了，如果一个读库性能不行，扛不住的话，可以一主多从，横向扩展。可谓是一剂良药啊！那怎么使用呢？一个一般的流程是这样的：

1. 服务端把一条业务数据落库
2. 数据库同步或异步或半同步把该条数据**复制**到从库
3. 服务端读数据的时候直接去**从库读**相应的数据

比较简单吧，一些聪明的、爱思考的、上进的同学可能发现问题了，也包括上面介绍的场景一，就是延迟问题，如：数据还没有到从库，我就马上读，那么是读不到的，会发生问题的。 对于这个问题，各家公司解决的思路不一样，方法不尽相同。一个普遍的解决方案是：**读不到就读主库**，当然这么说也是有前提条件的，但具体的方案这里就不一一展开了，我可能会在接下来的分享中详解各种方案。 另外，关于数据库的复制模式，还请同学们自行学习，太多了，这里说不清。该总结一下这种模式的优缺点的了，如下：

- **优点**：减少数据库的压力，理论上提供无限高的读性能，间接提高业务(写)的性能，专用的查询、索引、全文(分词)解决方案。
- **缺点**：数据延迟，数据一致性的保证。

### **六、微服务模式**

上面的模式看似不错，解决了性能问题，我可以不用露宿街头了、老婆还是我的，哈哈。但是

软件系统天生的复杂性决定了，除了性能，还有其他诸如高可用、健壮性等大量问题等待我们解决，再加上各个部门间的撕逼、扯皮，更让我们码农雪上加霜，所以

继续吧......

微服务模式可以说是最近的热点，花花绿绿、大大小小、国内国外的公司都在鼓吹，实践这个模式，可是大部分都没有弄清楚**为什么**要这么做，也并不知道这么做有什么**好处、坏处**，在这里，我将以我自己的亲身实践说一下我对这个模式的看法，不喜勿喷！随着业务与人员的增加，遇到了如下的问题：

1. 单机数据库写请求量大量增加，导致数据库压力变大
2. 数据库一旦挂了，那么整个业务都挂了
3. 业务代码越来越多，都在一个GIT里，越来越难以维护
4. 代码腐化严重、臭味越来越浓
5. 上线越来越频繁，经常是一个小功能的修改，就要整个大项目要重新编译
6. 部门越来越多，该哪个部门改动大项目中的哪个东西，撕逼的厉害
7. 其他一些外围系统直接连接数据库，导致一旦数据库结构发生变化，所有的相关系统都要通知，甚至对修改不敏感的系统也要通知
8. 每个应用服务器需要开通所有的权限、网络、FTP、各种各样的，因为每个服务器部署的应用都是一样的
9. 作为架构师，我已经失去了对这个系统的把控......

为了解决上述问题，我司使用了微服务模式，这种模式的一般设计见下图：

![](https://p1-jj.byteimg.com/tos-cn-i-t2oaga2asx/gold-user-assets/2019/11/29/16eb61a5fc90d54b~tplv-t2oaga2asx-zoom-in-crop-mark:4536:0:0:0.image)



如上图所示，我把**业务分块，做了垂直切分，切成一个个独立的系统，每个系统各自衍化，有自己的库、缓存、ES等辅助系统，系统之间的实时交互通过RPC，异步交互通过MQ，通过这种组合，共同完成整个系统功能。** 那么，这么做是否真的解决上述问题了呢？不玩虚的，一个个来说。对于问题一，由于拆分成了多个子系统，系统的压力被分散了，而各个子系统都有自己的数据库实例，所以数据库的压力变小。

对于问题二，一个子系统A的数据库挂了，只是影响到系统A和使用系统A的那些功能，不会所有的功能不可用，从而解决一个数据库挂了，导致所有功能不可用的问题。

问题三、四，也因为拆分得到了解决，各个子系统有自己独立的GIT代码库，不会相互影响。通用的模块可通过库、服务、平台的形式解决。

问题五，子系统A发生改变，需要上线，那么我只需要编译A，然后上线就可以了，不需要其他系统做同样的事情。

问题六，顺应了**康威定律**，我部门该干什么事、输出什么，也通过服务的形式暴露出来，我部只管把我部的职责、软件功能做好就可以。

问题七，所有需要我部数据的需求，都通过接口的形式发布出去，客户通过接口获取数据，从而屏蔽了底层数据库结构，甚至数据来源，我部只需保证我部的接口契约没有发生变化即可，新的需求增加新的接口，不会影响老的接口。

问题八，不同的子系统需要不同的权限，这个问题也优雅的解决了。

问题九，暂时控制住了复杂性，我只需控制好大的方面，定义好系统边界、接口、大的流程，然后再分而治之、逐个击破、合纵连横。

目前来说，所有问题得到解决！bingo! 但是，还有许多其他的副作用会随之产生，如RPC、MQ的超高稳定性、超高性能，网络延迟，数据一致性等问题，这里就不展开来讲了，太多了，一本书都讲不完。

另外，对于这个模式来说，最难把握的是**度**，切记**不要切分过细**，我见过一个功能一个子系统，上百个方法分成上百个子系统的，真的是太过度了。实践中，一个较为可行的方法是：**能不分就不分，除非有非常必要的理由**！。

- **优点**：相对高性能，可扩展性强，高可用，适合于中等以上规模公司架构。
- **缺点**：复杂、度不好把握。指不仅需要一个能在高层把控大方向、大流程、总体技术的人，还需要能够针对各个子系统有针对性的开发。把握不好度或者滥用的话，这个模式适得其反！

### **七、多级缓存模式**

这个模式可以说是应对超高查询压力的一种普遍采用的策略，基本的思想就是在所有链路的地方，能加缓存就加缓存，如下图所示：

![](https://p1-jj.byteimg.com/tos-cn-i-t2oaga2asx/gold-user-assets/2019/11/29/16eb61a5fe721312~tplv-t2oaga2asx-zoom-in-crop-mark:4536:0:0:0.image)



如上图所示，一般在三个地方加入缓存，一个是客户端处，一个是API网关处，一个是具体的后端业务处，下面分别介绍。

**客户端处缓存**：这个地方加缓存可以说是效果最好的---无延迟。因为不用经过长长的网络链条去后端业务处获取数据，从而导致加载时间过长，客户流失等损失。虽然有CDN的支持，但是从客户端到CDN还是有网络延迟的，虽然不大。具体的技术依据不同的客户端而定，对于WEB来讲，有浏览器本地缓存、Cookie、Storage、缓存策略等技术；对于APP来讲，有本地数据库、本地文件、本地内存、进程内缓存支持。以上提到的各种技术有兴趣的同学可以继续展开来学习。如果客户端缓存没有命中，那么就会去后端业务拿数据，一般来讲，都会有个API网关，在这里加缓存也是非常有必要的。

**API网关处缓存**：这个地方加缓存的好处是不用把请求发送到后方，直接在这里就处理了，然后返回给请求者。常见的技术，如http请求，API网关用的基本都是nginx，可以使用nginx本身的缓存模块，也可以使用Lua+Redis技术定制化。其他的也都大同小异。

**后端业务处**：这个我想就不用多说了，大家应该差不多都知道，什么Redis,Memcache,Jvm内等等，不熬述了。

实践中，要结合具体的实际情况，综合利用各级缓存技术，使得各种请求最大程度的在到达后端业务之前就被解决掉，从而减少后端服务压力、减少占用带宽、增强用户体验。至于是否只有这三个地方加缓存，我觉得要活学活用，**心法比剑法重要！**总结一下这个模式的优缺点：

- **优点**：抗住大量读请求，减少后端压力。
- **缺点**：数据一致性问题较突出，容易发生雪崩，即：如果客户端缓存失效、API网关缓存失效，那么所有的大量请求瞬间压向后端业务系统，后果可想而知。

### **八、分库分表模式**

这种模式主要解决单表写入、读取、存储压力过大，从而导致业务缓慢甚至超时，交易失败，容量不够的问题。一般有水平切分和垂直切分两种，这里主要介绍水平切分。这个模式也是技术架构迭代演进过程中的必经之路。 这种模式的一般设计见下图：

![](https://p1-jj.byteimg.com/tos-cn-i-t2oaga2asx/gold-user-assets/2019/11/29/16eb61a717cfbdbf~tplv-t2oaga2asx-zoom-in-crop-mark:4536:0:0:0.image)



如上图所示红色部分，把一张表分到了几个不同的库中，从而分担压力。是不是很笼统？哈哈，那我们接下来就详细的讲解一下。首先澄清几个概念，如下： **主机**：硬件，指一台物理机，或者虚拟机，有自己的CPU，内存，硬盘等。 **实例**：数据库实例，如一个MySQL服务进程。一个主机可以有多个实例，不同的实例有不同的进程，监听不同的端口。 **库**：指表的集合，如学校库，可能包含教师表、学生表、食堂表等等，这些表在一个库中。一个实例中可以有多个库。库与库之间用库名来区分。 **表**：库中的表，不必多说，不懂的就不用往下看了，不解释。

那么怎么把单表分散呢？到底怎么个分发呢？分发到哪里呢？以下是几个工作中的实践，分享一下： **主机**：这是最主要的也是最重要的点，本质上分库分表是因为计算与存储资源不够导致的，而这种资源主要是由物理机，主机提供的，所以在这里分是最基本的，毕竟没有可用的计算资源，怎么分效果都不是太好的。 实例：实例控制着连接数，同时受OS限制，CPU、内存、硬盘、网络IO也会受间接影响。会出现热实例的现象，即：有些实例特别忙，有些实例非常的空闲。一个典型的现象是：由于单表反应慢，导致连接池被打满，所有其他的业务都受影响了。这时候，把表分到不同的实例是有一些效果的。 库：一般是由于单库中最大单表数量的限制，才采取分库。 表：单表压力过大，索引量大，容量大，单表的锁。据以上，把单表水平切分成不同的表。

大型应用中，都是一台主机上只有一个实例，一个实例中只有一个库，**库==实例==主机，所以才有了分库分表**这个简称。

既然知道了基本理论，那么具体是怎么做的呢？逻辑是怎么跑的呢？接下来以一个例子来讲解一下。 这个需求很简单，用户表(user)，单表数据量1亿，查询、插入、存储都出现了问题，怎么办呢？

首先，分析问题，这个明显是由于数据量太大了而导致的问题。 其次，设计方案，可以分为10个库，这样每个库的数据量就降到了1KW，单表1KW数据量还是有些大，而且不利于以后量的增长，所以每个库再分100个表，这个每个单表数据量就为10W了，对于查询、索引更新、单表文件大小、打开速度，都有一些益处。接下来，给IT部门打电话，要10台物理机，扩展数据库...... 最后，逻辑实现，这里应该是最有学问的地方。首先是写入数据，需要知道写到哪个分库分表中，读也是一样的，所以，需要有个请求**路由层**，负责把请求分发、转换到不同的库表中，一般有路由规则的概念。

怎么样，简单吧？哈哈，too 那义务。说说这个模式的问题，主要是带来了事务上的问题，因为分库分表，事务完成不了，而**分布式事务**又太笨重，所以这里需要有一定的策略，保证在这种情况下事务能够完成。采取的策略如：最终一致性、复制、特殊设计等。再有就是业务代码的改造，一些关联查询要改造，一些单表orderBy的问题需要特殊处理，也包括groupBy语句，如何解决这些副作用不是一句两句能说清楚的，以后有时间，我单独讲讲这些。

该总结一下这种模式的优缺点的了，如下：

- **优点**：减少数据库单表的压力。
- **缺点**：事务保证困难、业务逻辑需要做大量改造。

### **九、弹性伸缩模式**

这种模式主要解决突发流量的到来，导致无法横向扩展或者横向扩展太慢，进而影响业务，全站崩溃的问题。这个模式是一种相对来说比较高级的技术，也是各个大公司目前都在研究、试用的技术。截至今日，有这种思想的架构师就已经是很不错了，能够拿到较高薪资，更别提那些已经实践过的，甚至实现了底层系统的那些，所以，你懂得...... 这种模式的一般设计见下图：

![](https://p1-jj.byteimg.com/tos-cn-i-t2oaga2asx/gold-user-assets/2019/11/29/16eb61a719a46586~tplv-t2oaga2asx-zoom-in-crop-mark:4536:0:0:0.image)



如上图所示，多了一个弹性伸缩服务，用来动态的增加、减少实例。原理上非常简单，但是这个模式到底解决什么问题呢？先说说由来和意义。

每年的双11、六一八或者一些大促到来之前，我们都会为大流量的到来做以下几个方面的工作: 提前准备10倍甚至更多的机器，即使用不上也要放在那里备着，以防万一。这样浪费了大量的资源。 每台机器配置、调试、引流，以便让所有的机器都可用。这样浪费了大量的人力、物力，更容易出错。 如果机器准备不充分，那么还要加班加点的重复上面的工作。这样做特别容易出错，引来领导的不满，没时间回家陪老婆，然后你的老婆就......(自己想)

在双十一之后，我们还要人工做缩容，非常的辛苦。一般一年中会有多次促销，那么我们就会一直这样，实在是烦！

最严重的，**突然间的大流量爆发，会让我们触不及防**，半夜起来扩容是在正常不过的事情，为此，我们偷懒起来，要更多的机器备着，也就出现了大量的cpu利用率为1%的机器。

我相信，如果你是老板一定很**震惊**吧！！！ 哈哈，那么如何改变这种情况呢？请接着看

为此，首先把所有的计算资源整合成资源池的概念，然后通过一些策略、监控、服务，动态的从资源池中获取资源，用完后在放回到池子中，供其他系统使用。 具体实现上比较成熟的两种资源池方案是VM、docker，每个都有着自己强大的生态。监控的点有CPU、内存、硬盘、网络IO、服务质量等，根据这些，在配合一些预留、扩张、收缩策略，就可以简单的实现自动伸缩。怎么样？是不是很神奇？深入的内容我们会在的码农原创的公众号文章中详细介绍。

该总结一下这种模式的优缺点的了，如下：

- **优点**：弹性、随需计算，充分优化企业计算资源。
- **缺点**：应用要从架构层做到可横向扩展化改造、依赖的底层配套比较多，对技术水平、实力、应用规模要求较高。

### **十、多机房模式**

这种模式主要解决**不同地区高性能、高可用**的问题。

随着应用用户不断的增加，用户群体分布在全球各地，如果把服务器部署在一个地方，一个机房，比如北京，那么美国的用户使用应用的时候就会特别慢，因为每一个请求都需要通过海底光缆走上个那么一秒钟(预估)左右，这样对用户体验及其不好。怎么办？使用多机房部署。

这种模式的一般设计见下图：

![](https://p1-jj.byteimg.com/tos-cn-i-t2oaga2asx/gold-user-assets/2019/11/29/16eb61a7198c976d~tplv-t2oaga2asx-zoom-in-crop-mark:4536:0:0:0.image)



如上图所示，一个典型的用户请求流程如下：

用户请求一个链接A 通过DNS智能解析到离用户最近的机房B 使用B机房服务链接A

是不是觉得很简单，没啥？其实这里面的问题没有表面这么简单，下面一一道来。 首先是**数据同步**问题，在中国产生的数据要同步到美国，美国的也一样，数据同步就会涉及数据版本、一致性、更新丢弃、删除等问题。 其次是一地多机房的请求**路由**问题，典型的是如上图，中国的北京机房和杭州机房，如果北京机房挂了，那么要能够通过路由把所有发往北京机房的请求转发到杭州机房。异地也存在这个问题。

所以，多机房模式，也就是异地多活并不是那么的简单，这里只是起了个头，具体的有哪些坑，会在另一篇文章中介绍。

该总结一下这种模式的优缺点的了，如下：

- **优点**：高可用、高性能、异地多活。
- **缺点**：数据同步、数据一致性、请求路由。

至此，整个关于**八种架构设计模式及其优缺点概述**就介绍完了，大约1W字左右。最后，我想说的是没有银弹、灵活运用，共勉！





> 参考链接：https://juejin.cn/post/6844904007438172167