⼋种架构设计模式及其优缺点概述
1. 什么是架构
我想这个问题,⼗个⼈回答得有⼗⼀个答案,因为另外的那⼀个是⼤家妥协的结果。哈哈,我理解,架构就是⾻架,如下图所⽰:⼈类的⾝体的⽀撑是主要由⾻架来承担的,然后是其上的肌⾁、神经、⽪肤。架构对于软件的重要性不亚于⾻架对⼈类⾝体的重要性。
2. 什么是设计模式
这个问题我问过的⾯试者不下于数⼗次,回答五花⼋门,在我看来,模式就是经验,设计模式就是设计经验,有了这些经验,我们就能在特定情况下使⽤特定的设计、组合设计,这样可以⼤⼤节省我们的设计时间,提⾼⼯作效率。
作为⼀个⼯作10年以上的⽼码农,经历的系统架构设计也算不少,接下来,我会把⼯作中⽤到的⼀些架构⽅⾯的设计模式分享给⼤家,望⼤家少⾛弯路。总体⽽⾔,共有⼋种,分别是:
1. 单库单应⽤模式:最简单的,可能⼤家都见过
2. 内容分发模式:⽬前⽤的⽐较多
3. 查询分离模式:对于⼤并发的查询、业务
4. 微服务模式:适⽤于复杂的业务模式的拆解
5. 多级缓存模式:可以把缓存玩的很好
6. 分库分表模式:解决单机数据库瓶颈
7. 弹性伸缩模式:解决波峰波⾕业务流量不均匀的⽅法之⼀
8. 多机房模式:解决⾼可⽤、⾼性能的⼀种⽅法
⼀. 单库单应⽤模式
这是最简单的⼀种设计模式,我们的⼤部分本科毕业设计、⼀些⼩的应⽤,基本上都是这种模式,这种模式的⼀般设计见下图:如上图所⽰,这种模式⼀般只有⼀个数据库,⼀个业务应⽤层,⼀个后台管理系统,所有的业务都是⽤过业务层完成的,所有的数据也都是存储在⼀个数据库中的,好⼀点会有数据库的同步。虽然简单,但是也并不是⼀⽆是处。
优点:结构简单、开发速度快、实现简单,可⽤于产品的第⼀版等有原型验证需求、⽤户少的设计。
缺点:性能差、基本没有⾼可⽤、扩展性差,不适⽤于⼤规模部署、应⽤等⽣产环境。
⼆. 内容分发模式
基本上所有的⼤型的⽹站都有或多或少的采⽤这⼀种设计模式,常见的应⽤场景是使⽤CDN技术把⽹页、图⽚、CSS、JS等这些静态资源分发到离⽤户最近的服务器。
这种模式较单库单应⽤模式多了⼀个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的同步策略等,会有⼀些⼀致性、更新慢⽅⾯的问题。
三. 查询分离模式
这种模式主要解决单机数据库压⼒过⼤,从⽽导致业务缓慢甚⾄超时,查询响应时间变长的问题,也包括需要⼤量数据库服务器计算资源的查询请求。这个可以说是单库单应⽤模式的升级版本,也是技术架构迭代演进过程中的必经之路。
这种模式的⼀般设计见下图:
如上图所⽰,这种模式较单库单应⽤模式与内容分发模式多了⼏个部分,⼀个是业务数据库的主从分离,⼀个是引⼊了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. 作为架构师,我已经失去了对这个系统的把控......
为了解决上述问题,我司使⽤了微服务模式,这种模式的⼀般设计见下图:
如上图所⽰,我把业务分块,做了垂直切分,切成⼀个个独⽴的系统,每个系统各⾃衍化,有⾃⼰的库、缓存、ES等辅助系统,系统之间的实时交互通过RPC,异步交互通过MQ,通过这种组合,共同完成整个系统功能。
那么,这么做是否真的解决上述问题了呢?不玩虚的,⼀个个来说。对于问题⼀,由于拆分成了多个⼦系统,系统的压⼒被分散了,⽽各个⼦系统都有⾃⼰的数据库实例,所以数据库的压⼒变⼩。
对于问题⼆,⼀个⼦系统A的数据库挂了,只是影响到系统A和使⽤系统A的那些功能,不会所有的功能不可⽤,从⽽解决⼀个数据库挂了,导致所有功能不可⽤的问题。
问题三、四,也因为拆分得到了解决,各个⼦系统有⾃⼰独⽴的GIT代码库,不会相互影响。通⽤的模块可通过库、服务、平台的形式解决。
问题五,⼦系统A发⽣改变,需要上线,那么我只需要编译A,然后上线就可以了,不需要其他系统做同样的事情。
问题六,顺应了康威定律,我部门该⼲什么事、输出什么,也通过服务的形式暴露出来,我部只管把我部的职责、软件功能做好就可以。
问题七,所有需要我部数据的需求,都通过接⼝的形式发布出去,客户通过接⼝获取数据,从⽽屏蔽了
底层数据库结构,甚⾄数据来源,我部只需保证我部的接⼝契约没有发⽣变化即可,新的需求增加新的接⼝,不会影响⽼的接⼝。
问题⼋,不同的⼦系统需要不同的权限,这个问题也优雅的解决了。
问题九,暂时控制住了复杂性,我只需控制好⼤的⽅⾯,定义好系统边界、接⼝、⼤的流程,然后再分⽽治之、逐个击破、合纵连横。
⽬前来说,所有问题得到解决!bingo!
但是,还有许多其他的副作⽤会随之产⽣,如RPC、MQ的超⾼稳定性、超⾼性能,⽹络延迟,数据⼀致性等问题,这⾥就不展开来讲了,太多了,⼀本书都讲不完。
另外,对于这个模式来说,最难把握的是度,切记不要切分过细,我见过⼀个功能⼀个⼦系统,上百个⽅法分成上百个⼦系统的,真的是太过度了。实践中,⼀个较为可⾏的⽅法是:能不分就不分,除⾮有⾮常必要的理由!。
优点:相对⾼性能,可扩展性强,⾼可⽤,适合于中等以上规模公司架构。
缺点:复杂、度不好把握。指不仅需要⼀个能在⾼层把控⼤⽅向、⼤流程、总体技术的⼈,还需要能够针对各个⼦系统有针对性的开发。把握不好度或者滥⽤的话,这个模式适得其反!
五.多级缓存模式
这个模式可以说是应对超⾼查询压⼒的⼀种普遍采⽤的策略,基本的思想就是在所有链路的地⽅,能加缓存就加缓存,如下图所⽰:如上图所⽰,⼀般在三个地⽅加⼊缓存,⼀个是客户端处,⼀个是API⽹关处,⼀个是具体的后端业务处,下⾯分别介绍。
客户端处缓存:这个地⽅加缓存可以说是效果最好的---⽆延迟。因为不⽤经过长长的⽹络链条去后端业务处获取数据,从⽽导致加载时间过长,客户流失等损失。虽然有CDN的⽀持,但是从客户端到CDN还是有⽹络延迟的,虽然不⼤。具体的技术依据不同的客户端⽽定,对于WEB来讲,有浏览器本地缓存、Cookie、Storage、缓存策略等技术;对于APP来讲,有本地数据库、本地⽂件、本地内存、进程内缓存⽀持。以上提到的各种技术有兴趣的同学可以继续展开来学习。如果客户端缓存没有命中,那么就会去后端业务拿数据,⼀般来讲,都会有个API⽹关,在这⾥加缓存也是⾮常有必要的。
API⽹关处缓存:这个地⽅加缓存的好处是不⽤把请求发送到后⽅,直接在这⾥就处理了,然后返回给请求者。常见的技术,如http请求,API⽹关⽤的基本都是nginx,可以使⽤nginx本⾝的缓存模块,也可以使⽤Lua+Redis技术定制化。其他的也都⼤同⼩异。
后端业务处:这个我想就不⽤多说了,⼤家应该差不多都知道,什么Redis,Memcache,Jvm内等等,不熬述了。
实践中,要结合具体的实际情况,综合利⽤各级缓存技术,使得各种请求最⼤程度的在到达后端业务之前就被解决掉,从⽽减少后端服务压⼒、减少占⽤带宽、增强⽤户体验。⾄于是否只有这三个地⽅加缓存,我觉得要活学活⽤,⼼法⽐剑法重要!总结⼀下这个模式的优缺点:
优点:抗住⼤量读请求,减少后端压⼒。
缺点:数据⼀致性问题较突出,容易发⽣雪崩,即:如果客户端缓存失效、API⽹关缓存失效,那么所有的⼤量请求瞬间压向后端业务系统,后果可想⽽知。
六. 分库分表模式
这种模式主要解决单表写⼊、读取、存储压⼒过⼤,从⽽导致业务缓慢甚⾄超时,交易失败,容量不够的问题。⼀般有⽔平切分和垂直切分两种,这⾥主要介绍⽔平切分。这个模式也是技术架构迭代演进过程中的必经之路。
这种模式的⼀般设计见下图:
如上图所⽰红⾊部分,把⼀张表分到了⼏个不同的库中,从⽽分担压⼒。是不是很笼统?哈哈,那我们接下来就详细的讲解⼀下。⾸先澄清⼏个概念,如下:
主机:硬件,指⼀台物理机,或者虚拟机,有⾃⼰的CPU,内存,硬盘等。
实例:数据库实例,如⼀个MySQL服务进程。⼀个主机可以有多个实例,不同的实例有不同的进程,监听不同的端⼝。
库:指表的集合,如学校库,可能包含教师表、学⽣表、⾷堂表等等,这些表在⼀个库中。⼀个实例中可以有多个库。库与库之间⽤库名来区分。
表:库中的表,不必多说,不懂的就不⽤往下看了,不解释。
那么怎么把单表分散呢?到底怎么个分发呢?分发到哪⾥呢?以下是⼏个⼯作中的实践,分享⼀下:
主机:这是最主要的也是最重要的点,本质上分库分表是因为计算与存储资源不够导致的,⽽这种资源主要是由物理机,主机提供的,所以在这⾥分是最基本的,毕竟没有可⽤的计算资源,怎么分效果都不是太好的。
实例:实例控制着连接数,同时受OS限制,CPU、内存、硬盘、⽹络IO也会受间接影响。会出现热实例的现象,即:有些实例特别忙,有些实例⾮常的空闲。⼀个典型的现象是:由于单表反应慢,导致连接池被打满,所有其他的业务都受影响了。这时候,把表分到不同的实例是有⼀些效果的。

版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。