架构就是解决问题给出的整体技术方案,既要掌握整体,也要知道局部瓶颈能够解决具体业务的方案。
架构师,是一个既需要掌控整体又需要洞悉局部瓶颈并依据具体的业务场景给出解决方案的团队领导型人物。
架构
在软件工程和企业信息系统领域,又有很多细分,如所谓的系统架构师(System Architect)、应用架构师(Application Architect)、企业架构师(Enterprise Architect)以及基础设施架构师(Infrastructure Architect)等等。
- 应用架构师负责构建一个以解决特定问题为目标的软件应用的内部结合结构,一般以满足各种功能性需求以及维护性需求为设计考虑目标;
- 系统架构师则提供运营支撑软件应用的信息系统的结构设计,一般以满足各种非功能性需求或运营性需求为设计目标(如安全性、可伸缩性、可互操作性等等);
- 企业架构师,就不光只顾IT系统的架构了,他应以企业的持续经营目标为考虑要素来构建企业所需要的内在结构设计;
- 基础设施架构师主要是负责基础平台的设计。
架构设计
其实就是设计模式中的七大原则
1.开闭原则
2.依赖倒置原则
3.单一职责原则
4.接口隔离原则
5.迪米特法则(最小知道原则)
6.里氏替换原则
7.合成/聚合复用原则
对我实践过程中总结来说:
1、抽象,封装,可扩展,使用接口实现
定义对外调用的接口,实现这些方法的结构体就是实现了接口,不需要关心具体的实现,只需要调用就好,其实也就是设计模式中的开闭原则和依赖倒置原则,接口隔离的原则,迪米特法则,实现依赖于接口,接口尽量细化,不互相依赖。
2、尽量设计原子功能
每个服务职责单一,可以服务化,不相互依赖,解耦,同时组合功能就是尽量模块清晰,只实现原子的功能,想实现大的功能就是将原子的功能组合起来,也就是合成复用原则
3、关注性能、可用性、伸缩性、扩展性、安全性这5个架构要素的实现
架构要素
在架构设计中一些常规的思想是我们设计的核心要素,我们必须拥有解决的能力,掌握其常规方案,结合实际场景有具体设计的能力。
公共服务服务化
其实就是一种共享服务的抽象,可以实现架构和业务上的解耦,解决各种依赖问题,可以提高共同部分的优化效率和管控等等好处,没有具体的实现方案,视具体情况进行抽象设计。架构解耦还有很多方案。
高可用,容错机制
高可用HA(High Availability)是分布式系统架构设计中必须考虑的因素之一,它通常是指,通过设计减少系统不能提供服务的时间。
高可用保证是通过冗余+自动故障转移来保证系统的高可用特性。
我们先来看一下正常的服务架构
常见互联网分布式架构如上,分为:
- (1)客户端层:典型调用方是浏览器browser或者手机应用APP
- (2)反向代理层:系统入口,反向代理
- (3)站点应用层:实现核心应用逻辑,返回html或者json
- (4)服务层:如果实现了服务化,就有这一层
- (5)数据-缓存层:缓存加速访问存储
- (6)数据-数据库层:数据库固化数据存储
整个系统的高可用,又是通过每一层的冗余+自动故障转移来综合实现的。
1、【客户端层->反向代理层】的高可用
【客户端层】到【反向代理层】的高可用,是通过反向代理层的冗余来实现的。以nginx为例:有两台nginx,一台对线上提供服务,另一台冗余以保证高可用,常见的实践是keepalived存活探测,相同virtual IP提供服务。
自动故障转移:当nginx挂了的时候,keepalived能够探测到,会自动的进行故障转移,将流量自动迁移到shadow-nginx,由于使用的是相同的virtual IP,这个切换过程对调用方是透明的。
2、【反向代理层->站点层】的高可用
【反向代理层】到【站点层】的高可用,是通过站点层的冗余来实现的。假设反向代理层是nginx,nginx.conf里能够配置多个web后端,并且nginx能够探测到多个后端的存活性。
自动故障转移:当web-server挂了的时候,nginx能够探测到,会自动的进行故障转移,将流量自动迁移到其他的web-server,整个过程由nginx自动完成,对调用方是透明的。
3、【站点层->服务层】的高可用
【站点层】到【服务层】的高可用,是通过服务层的冗余来实现的。“服务连接池”会建立与下游服务多个连接,每次请求会“随机”选取连接来访问下游服务。
自动故障转移:当service挂了的时候,service-connection-pool能够探测到,会自动的进行故障转移,将流量自动迁移到其他的service,整个过程由连接池自动完成,对调用方是透明的(所以说RPC-client中的服务连接池是很重要的基础组件)。
4、【服务层>缓存层】的高可用
【服务层】到【缓存层】的高可用,是通过缓存数据的冗余来实现的。
缓存层的数据冗余又有几种方式:第一种是利用客户端的封装,service对cache进行双读或者双写。
缓存层也可以通过支持主从同步的缓存集群来解决缓存层的高可用问题。以redis为例,redis天然支持主从同步,redis官方也有sentinel哨兵机制,来做redis的存活性检测。
自动故障转移:当redis主挂了的时候,sentinel能够探测到,会通知调用方访问新的redis,整个过程由sentinel和redis集群配合完成,对调用方是透明的。
5、【服务层>数据库层】的高可用
大部分互联网技术,数据库层都用了“主从同步,读写分离”架构,所以数据库层的高可用,又分为“读库高可用”与“写库高可用”两类。
【服务层】到【数据库读】的高可用,是通过读库的冗余来实现的。既然冗余了读库,一般来说就至少有2个从库,“数据库连接池”会建立与读库多个连接,每次请求会路由到这些读库。
自动故障转移:当读库挂了的时候,db-connection-pool能够探测到,会自动的进行故障转移,将流量自动迁移到其他的读库,整个过程由连接池自动完成,对调用方是透明的(所以说DAO中的数据库连接池是很重要的基础组件)。
【服务层】到【数据库写】的高可用,是通过写库的冗余来实现的。以mysql为例,可以设置两个mysql双主同步,一台对线上提供服务,另一台冗余以保证高可用,常见的实践是keepalived存活探测,相同virtual IP提供服务。
自动故障转移:当写库挂了的时候,keepalived能够探测到,会自动的进行故障转移,将流量自动迁移到shadow-db-master,由于使用的是相同的virtual IP,这个切换过程对调用方是透明的。
通过整体架构的分析,我们可以看到高可用的思想就是通过冗余(多实例,集群化)和在调度(最常见的就是负载均衡)的基础上实现故障自动转移来完成的。
负载均衡
提到高可用,不得不提负载均衡,其实高可用的实现还是依赖于负载均衡的,是在负载均衡的基础上实现的故障自动转移,没有冗余节点也即是多实例的负载均衡,也谈不上故障自动转移。但是负载均衡的重点还是在调度均衡,不光在 最前端的nginx,还有后端数据库连接池都存在这个均衡的问题。
常见互联网分布式架构如上,分为客户端层、反向代理nginx层、站点层、服务层、数据层。可以看到,每一个下游都有多个上游调用,只需要做到,每一个上游都均匀访问每一个下游,就能实现“将请求/数据【均匀】分摊到多个操作单元上执行”。
1、【客户端层->反向代理层】的负载均衡
【客户端层】到【反向代理层】的负载均衡,是通过“DNS轮询”实现的:DNS-server对于一个域名配置了多个解析ip,每次DNS解析请求来访问DNS-server,会轮询返回这些ip,保证每个ip的解析概率是相同的。这些ip就是nginx的外网ip,以做到每台nginx的请求分配也是均衡的。
2、【反向代理层->站点层】的负载均衡
【反向代理层】到【站点层】的负载均衡,是通过“nginx”实现的。通过修改nginx.conf,可以实现多种负载均衡策略:轮询,最少连接路由,IPhash等
3、【站点层->服务层】的负载均衡
【站点层】到【服务层】的负载均衡,是通过“服务连接池”实现的。连接池需要涉及负载均衡、故障转移、超时处理的相关机制。
4、【数据层】的负载均衡
在数据量很大的情况下,由于数据层(db,cache)涉及数据的水平切分,所以数据层的负载均衡更为复杂一些,它分为“数据的均衡”,与“请求的均衡”。业内常见的水平切分方式有这么几种:按照range水平切分,按照id哈希水平切分
提到负载均衡我们需要了解一些专业名称
- 1)nginx:一个高性能的web-server和实施反向代理的软件
- 2)lvs:Linux Virtual Server,使用集群技术,实现在linux操作系统层面的一个高性能、高可用、负载均衡服务器
- 3)keepalived:一款用来检测服务状态存活性的软件,常用来做高可用
- 4)f5:一个高性能、高可用、负载均衡的硬件设备(听上去和lvs功能差不多?)
- 5)DNS轮询:通过在DNS-server上对一个域名设置多个ip解析,来扩充web-server性能及实施负载均衡的技术
我们来看一下负载均衡随着量的扩大不断演进的过程
1、单机架构
浏览器通过DNS-server,域名解析到ip,直接访问webserver,这种情况下的缺点显而易见
- 1)非高可用,web-server挂了整个系统就挂了
- 2)扩展性差,当吞吐量达到web-server上限时,无法扩容
2、DNS轮询
通过dns轮询将流量分到不同的webserver。这种情况下
- 1)非高可用:DNS-server只负责域名解析ip,这个ip对应的服务是否可用,DNS-server是不保证的,假设有一个web-server挂了,部分服务会受到影响
- 2)扩容非实时:DNS解析有一个生效周期
- 3)暴露了太多的外网ip
3、nginx
这个方案在站点层与浏览器层之间加入了一个反向代理层,利用高性能的nginx来做反向代理,利用nginx来实现流量分流,但是nginx也是单点。
4、nginx+keepalived
为了解决nginx高可用的问题,keepalived出场了,当nginx挂了,另一个nginx就顶上来了
这种情况下,仍会存在问题
- 1)资源利用率只有50%
- 2)nginx仍然是接入单点,如果接入吞吐量超过的nginx的性能上限怎么办
4、lvs/f5
nginx毕竟是软件,性能比tomcat好,但总有个上限,超出了上限,还是扛不住。lvs就不一样了,它实施在操作系统层面;f5的性能又更好了,它实施在硬件层面;它们性能比nginx好很多,例如每秒可以抗10w,这样可以利用他们来扩容,常见的架构图如下:
基本上公司到这一步基本就能解决接入层高可用、扩展性、负载均衡的问题。当然如果业务量继续扩大,我们还可以进行扩展
5、DNS轮询
facebook,google,baidu的PV是不是超过80亿呢,它们的域名只对应一个ip么,终点又是起点,还是得通过DNS轮询来进行扩容:
架构图可见,在lvs上再新增了水平扩展,能力更加强大了,其实在上面也可以使用这种方式,来水平扩展nginx,所以总体的思路就是水平扩展,垂直提升。
前面将的都是相同服务实例的均衡,其实也存在异构服务器,也就是实例能力不相同的情况,也就是负载均衡的另一种体现,后端的service有可能部署在硬件条件不同的服务器上:
- 1)如果对标最低配的服务器“均匀”分摊负载,高配的服务器的利用率不足;
- 2)如果对标最高配的服务器“均匀”分摊负载,低配的服务器可能会扛不住;
需要做到根据能力来实现负载均衡,其实在nginx中就有这个概念,我们通过权重来给服务器来实现分配
1、静态权重
为每个下游service设置一个“权重”,代表service的处理能力,来调整访问到每个service的概率,使用nginx做反向代理与负载均衡,就有类似的机制。
这个方案的优点是:简单,能够快速的实现异构服务器的负载均衡。
缺点也很明显:这个权重是固定的,无法自适应动态调整,而很多时候,服务器的处理能力是很难用一个固定的数值量化。
2、动态权重
- 1)用一个动态权重来标识每个service的处理能力,默认初始处理能力相同,即分配给每个service的概率相等;
- 2)每当service成功处理一个请求,认为service处理能力足够,权重动态+1;
- 3)每当service超时处理一个请求,认为service处理能力可能要跟不上了,权重动态-10(权重下降会更快);
- 4)为了方便权重的处理,可以把权重的范围限定为[0, 100],把权重的初始值设为60分。
这种情况下,我们需要使用过载保护,就是在服务器承受压力到瓶颈的时候,就能够维持在这个能力,而不是继续增加将其压垮,最简易的方式,服务端设定一个负载阈值,超过这个阈值的请求压过来,全部抛弃。这个方式不是特别优雅。
我们在动态权重中,
- 1)如果某一个service的连接上,连续3个请求都超时,即连续-10分三次,客户端就可以认为,服务器慢慢的要处理不过来了,得给这个service缓一小口气,于是设定策略:接下来的若干时间内,例如1秒(或者接下来的若干个请求),请求不再分配给这个service;防止其处理能力变为0
- 2)如果某一个service的动态权重,降为了0(像连续10个请求超时,中间休息了3次还超时),客户端就可以认为,服务器完全处理不过来了,得给这个service喘一大口气,于是设定策略:接下来的若干时间内,例如1分钟(为什么是1分钟,根据经验,此时service一般在发生fullGC,差不多1分钟能回过神来),请求不再分配给这个service;但是要进行回复就要继续给他分配。
- 3)可以有更复杂的保护策略…
单点系统
单点系统并不是一种架构思想,但是提到高可用,肯定就要知道单点系统,并不是所有的系统都能实现高可用,单点系统一般来说存在两个很大的问题:
- 非高可用:既然是单点,master一旦发生故障,服务就会受到影响
- 性能瓶颈:既然是单点,不具备良好的扩展性,服务性能总有一个上限,这个单点的性能上限往往就是整个系统的性能上限
比如我们常用的架构中,也有避免不了的单点,在这个互联网架构中,站点层、服务层、数据库的从库都可以通过冗余的方式来保证高可用,但至少
- nginx层是一个潜在的单点
- 数据库写库master也是一个潜在的单点
再比如GFS
GFS的系统架构里主要有这么几种角色:
- (1)client,就是发起文件读写的调用端
- (2)master,这是一个单点服务,它有全局事业,掌握文件元信息
- (3)chunk-server,实际存储文件额服务器
这个系统里,master也是一个单点的服务,Map-reduce系统里也有类似的全局协调的master单点角色。系统架构设计中,像nginx,db-master,gfs-master这样的单点服务是存在的,也是我们需要解决的
1、shadow-master解决单点高可用问题
“影子master”,顾名思义,服务正常时,它只是单点master的一个影子,在master出现故障时,shadow-master会自动变成master,继续提供服务。
shadow-master它能够解决高可用的问题,并且故障的转移是自动的,不需要人工介入,但不足是它使服务资源的利用率降为了50%,业内经常使用keepalived+vip的方式实现这类单点的高可用。
以GFS的master为例,master正常时:
- (1)client会连接正常的master,shadow-master不对外提供服务
- (2)master与shadow-master之间有一种存活探测机制
- (3)master与shadow-master有相同的虚IP(virtual-IP)
当发现master异常时:shadow-master会自动顶上成为master,虚IP机制可以保证这个过程对调用方是透明的,比如我们k8s的master的三主机制,比如数据库的双master机制都是使用了这种方案,只不过数据库还需要实现双主的数据同步,实现一致性的要求。
2、减少与单点的交互,是存在单点的系统优化的核心方向
既然知道单点存在性能上限,单点的性能(例如GFS中的master)有可能成为系统的瓶颈,那么,减少与单点的交互,便成了存在单点的系统优化的核心方向。怎么来减少与单点的交互,这里提两种常见的方法。
批量写
批量写是一种常见的提升单点性能的方式。比如利用数据库写单点生成做“ID生成器”,常规方法就是利用数据库写单点的auto increament id来生成和返回ID,这样生成ID的并发上限,取决于单点数据库的写性能上限。如果我们使用批量的方式
- (1)中间加一个服务,每次从数据库拿出100个id
- (2)业务方需要ID
- (3)服务直接返回100个id中的1个,100个分配完,再访问数据库
这样一来,每分配100个才会写数据库一次,分配id的性能可以认为提升了100倍。
客户端缓存
客户端缓存也是一种降低与单点交互次数,提升系统整体性能的方法。比如上面的GFS的master使用了客户端缓存减少了与master的交互
- (1)GFS的调用客户端client要访问shenjian.txt,先查询本地缓存,miss了
- (2)client访问master问说文件在哪里,master告诉client在chunk3上
- (3)client把shenjian.txt存放在chunk3上记录到本地的缓存,然后进行文件的读写操作
- (4)未来client要访问文件,从本地缓存中查找到对应的记录,就不用再请求master了,可以直接访问chunk-server。如果文件发生了转移,chunk3返回client说“文件不在我这儿了”,client再访问master,询问文件所在的服务器。
根据经验,这类缓存的命中非常非常高,可能在99.9%以上(因为文件的自动迁移是小概率事件),这样与master的交互次数就降低了1000倍。
3、“DNS轮询”技术支持DNS-server返回不同的nginx外网IP,实现nginx负载均衡层的水平扩展。
DNS-server部分,一个域名可以配置多个IP,每次DNS解析请求,轮询返回不同的IP,就能实现nginx的水平扩展,扩充负载均衡层的整体性能。
数据库单点写库也是同样的道理,在数据量很大的情况下,可以通过水平拆分,来提升写入性能。
遗憾的是,并不是所有的业务场景都可以水平拆分,例如秒杀业务,商品的条数可能不多,数据库的数据量不大,就不能通过水平拆分来提升秒杀系统的整体写性能
高并发,高性能,扩展性
高并发(High Concurrency)是互联网分布式系统架构设计中必须考虑的因素之一,互联网分布式架构设计,提高系统并发能力的方式,方法论上主要有两种:垂直扩展(Scale Up)与水平扩展(Scale Out)。
- 垂直扩展
- 机器配置升级,这种最原始的方式,也是提高性能最快的方式。所以是初期互联网业务发展非常迅猛的最推荐的方式,如果预算不是问题。
- 架构完善,组件优化,其实也是属于垂直扩展,通过优化架构,能够提高单机的支持的能力,这个就需要大量的实践经验,调优经验,如:使用Cache来减少IO次数,使用异步来增加单服务吞吐量,使用无锁数据结构来减少响应时间;
- 水平扩展,不管是提升单机硬件性能,还是提升单机架构性能,都有一个致命的不足:单机性能总是有极限的。所以互联网分布式架构设计高并发终极解决方案还是水平扩展:只要增加服务器数量,就能线性扩充系统性能。水平扩展在现在最直观的就是分布式集群的应用。
高并发是我们架构设计中最常见的,很多细节实现可以看这里。
技术思想
1、技术和业务:技术和业务是相辅相成的,任何技术的初衷都是为了服务业务,发展技术的目的是为了更好的服务业务,一旦脱离业务,技术就失去了意义。