一个软件系统随着功能越来越多,调用量急剧增长,整个系统逐渐碎片化,越来越无序,最
终无法维护和扩展,所以系统在一段时间的野蛮生长后,也需要及时干预,避免越来越无序。架构的本质就是对系统进行有序化重构,使系统不断进化
那架构是如何实现无序到有序的呢? 基本的手段就是分和合,先把系统打散,然后重新组合。分的过程是把系统拆分为各个子系统 / 模块 / 组件,拆的时候,首先要解决每个组件的定位问题,然后才能划分彼此的边界,实现合理的拆分。合就是根据最终要求,把各个分离的组件有机整合在一起,相对来说,第一步的拆分更难。
拆分的结果使开发人员能够做到业务聚焦、技能聚焦,实现开发敏捷,合的结果是系统变得柔性,可以因需而变,实现业务敏捷
架构的分类
架构一般可分业务架构、应用架构、技术架构
1. 业务架构从概念层面帮助开发人员更好的理解系统,比如业务流程、业务模块、输入输出、业务域
2. 应用架构从逻辑层面帮助开发落地系统,如数据交互关系、应用形式、交互方式,是的整
个系统逻辑上更容易理解,步入大家熟知的 SOA 就属于应用架构的范畴
3. 技术架构主要解决技术平台选型、如操作系统、中间件、设备、多机房、水平扩展、高可用等问题
需要注意的是,系统或者架构首先都是为人服务的,系统的有序度高,用用逻辑合理,业务概念清晰是第一位。现在大家讨论更多的是技术架构,如高并发设计,分布式事务处理等,只是因为这个不需要业务上下文背景,比较好相互沟通。具体架构设计时,首先要关注业务架构和应用架构,这个架构新手要特别注意。也是面试时候的痛点!
大型网站的架构演进
ps : 以下内容部分图片参考自《大型网站系统与 JAVA 中间件实战.pdf》
从一个电商网站开始
为了更好的理解,我们用电商网站来举例,作为一个交易类型的网站,一定会具备
用户(用户注册、用户管理)、商品(商品展示、商品管理)、交易(下单、支付)这些功能假如我们只需要支持这几个基本功能,那么我们最开始的架构应该可能是这样的
这个地方要注意的是,各个功能模块之间是通过 JVM 内部的方法调用来进行交互的,而应用和数据库之间是通过 JDBC 进行访问。
单机负载告警,数据库与应用分离
随着网站的开放,访问量不断增大,那么这个时候服务器的负载势必会持续升高,必须要才需一些办法来应付。这里先不考虑更换机器和各种软件层面的优化,先从架构的结构上来做一些调整。我们可以把数据库与应用从一台机器分到两台机器
变化:
网站从一台变成了 2 台,这个变化对我们来说影响非常小。单机的情况下,我们应用采用 JDBC 的方式来和数据库进行连接,现在数据库与应用分开了,我们只需要在配置文件中把数据库的地址从本机改成数据库服务器的 ip 地址就行。
对于开发、测试、部署都没有影响调整以后我们能够缓解当前的系统压力,不过随着时间的退役,访问量继续增大的话,我们的系统还是需要做改造
为什么这么分呢?从计算机本身的角度来考虑的话,一个请求的访问到处理最终到返回,性 能瓶颈只会是:CPU、文件 IO、网络 IO、内存、等因素。而一台计算机中这些纬度是有性 能瓶颈的,如果某个资源消耗过多,通常会造成系统的响应速度较慢,所以增加一台机器, 使得数据库的 IO 和 CPU 资源独占一台机器从而增加性能。 这个地方插入一点题外话,就是简单说一下各个资源的消耗原因。 CPU/IO/内存: 1. 主要是上下文的切换,因为每个 CPU 核心在同一时刻只能执行一个线程,而 CPU 的调 度有几种方式,比如抢占式和轮询等,以抢占式为例,每个线程会分配一定的执行时间, 当达到执行时间、线程中有 IO 阻塞或者有高优先级的线程要执行时。CPU 会切换执行其 他线程。而在切换的过程中,需要存储当前线程的执行状态并恢复要执行的线程状态,这 个过程就是上下文切换。比如 IO、锁等待等场景下也会触发上下文切换,当上下文切换 过多时会造成内核占用比较多的 CPU。 2. 文件 IO,比如频繁的日志写入,磁盘本身的处理速度较慢、都会造成 IO 性能问题 3. 网络 IO,带宽不够 4. 内存,包括内存溢出、内存泄漏、内存不足 实际上不管是应用层的调优也好,还是硬件的升级也好。其实无非就是这几个因素的调整。 |
应用服务器复杂告警,如何让应用服务器走向集群
假如说这个时候应用服务器的压力变大了,根据对应用的检测结果,可以针对性的对性能压力大的地方进行优化。我们这里考虑通过水平扩容来进行优化,把单机变为集群
应用服务器从一台变为两台,这两个应用服务器之间没有直接的交互,他们都依赖数据
库对外提供服务,那么这个时候会抛出两个问题
1. 最终用户对应两个应用服务器访问的选择对于这个问题,可以采用 DNS 解决,也可以通过负载均衡设备来解决
2. session 的问题?
水平和垂直扩容
对于大型的分布式架构而言,我们一直在追求一种简单、优雅的方式来应对访问量和数据量的增长。而这种方式通常指的是不需要改动软件程序,仅仅通过硬件升级或者增加机器就可以解决。而这种就是分布式架构下的伸缩设计
伸缩分为垂直伸缩和水平伸缩两种
垂直伸缩:表示通过升级或者增加单台机器的硬件来支撑访问量以及数据量增长的方式,垂直伸缩的好处在于技术难度比较低,运营和改动成本也相对较低。但是缺点是机器性能是有瓶颈的,同时升级高性能的小型机或者大型机,成本是非常大的。这也是阿里去 IOE 的一个原因之一
增加 CPU 核心数:增加 CPU 后系统的服务能力能够得到大的增长,比如响应速度、同时可以处理的线程数。但是引入 CPU 后也会带来一些显著的问题
1. 锁竞争加剧;多个线程同时运行访问某个共享数据,那么就涉及到锁竞争,锁竞争激烈时
会导致很多线程都在等待锁,所以即时增加 CPU 也无法让线程得到更快的处理。当然这里是有调优手段的,可以通过调优手段来降低锁竞争
2. 支撑并发请求的线程数是固定的,那么即时增加 CPU,系统的服务能力也不会得到提升3. 对于单线程任务,多核心 CPU 是没有太大的作用的
增加内存:增加内存可以直接提成系统的响应速度,当然,也有可能达不到效果,就是如果JVM 堆内存是固定的。
水平伸缩:通过增加机器来支撑访问量及数据量增长的方式,成为水平伸缩,水平伸缩理论上来说没有瓶颈,但是缺点是技术要求比较高,同时给运维带来了更大的挑战
垂直伸缩和水平伸缩都有各自的有点,我们在实际使用过程中都会对两者做结合,一方面要考虑硬件升级的成本,一方面要考虑软件改造的成本。
引入负载均衡设备
服务路由,基于负载均衡设备来实现
引入负载均衡器以后,会带来 session 相 关的问题
负载均衡算法
轮询(Round Robin)法
将请求按顺序轮流分配到后台服务器上,均衡的对待每一台服务器,而不关心服务器实际的连接数和当前的系统负载
缺点:当集群中服务器硬件配置不同、性能差别大时,无法区别对待
随机法
通过系统随机函数,根据后台服务器列表的大小值来随机选取其中一台进行访问。随着调用量的增大,其实际效果越来越接近于平均分配流量到后台的每一台服务器,也就是轮询法的效果
优点:简单使用,不需要额外的配置和算法。
缺点:随机数的特点是在数据量大到一定量时才能保证均衡,所以如果请求量有限的话,可能会达不到均衡负载的要求。
源地址哈希法
根据服务消费者请求客户端的 IP 地址,通过哈希函数计算得到一个哈希值,将这个哈希值和服务器列表的大小进行取模运算,得到的结果便是要访问的服务器地址的序号。采用源地址哈希法进行负载均衡,相同的 IP 客户端,如果服务器列表不变,将映射到同一个后台服务器进行访问。
加权轮询(Weight Round Robin)法
不同的后台服务器可能机器的配置和当前系统的负载并不相同,因此它们的抗压能力也不一样。跟配置高、负载低的机器分配更高的权重,使其能处理更多的请求,而配置低、负载高的机器,则给其分配较低的权重,降低其系统负载,加权轮询很好的处理了这一问题,并将请求按照顺序且根据权重分配给后端
最小连接数法
前面几种方式都是通过对请求次数的合理分配最大可能提高服务器的利用率,但是实际上,请求次数的均衡并不能代表负载的均衡。所以,引入了最小连接数法。它正是根据后端服务器当前的连接情况,动态的选取其中当前积压连接数最少的一台服务器来处理当前请求,尽可能的提高后台服务器利用率,将负载合理的分流到每一台服务器。