web应用架构的演变

现在很多公司都使用微服务架构,如果时间倒流回10年前,可能主流的架构还是单体架构,那么为什么现在单体架构已经逐渐被微服务架构所取代呢?架构的演化过程又是怎样的呢?今天一起学习下~

web应用架构的演变

系统架构的演变一定是问题驱动:出现问题->引入新技术解决问题->引入新技术差生新的问题->解决新的问题

架构的分类

架构一般可分业务架构、应用架构、技术架构

  1. 业务架构从概念层面帮助开发人员更好的理解系统,比如业务流程、业务模块、输入输出、业务域
  2. 应用架构从逻辑层面帮助开发落地系统,如数据交互关系、应用形式、交互方式,是的整
    个系统逻辑上更容易理解,步入大家熟知的 SOA 就属于应用架构的范畴
  3. 技术架构主要解决技术平台选型、如操作系统、中间件、设备、多机房、水平扩展、高可
    用等问题

需要注意的是,系统或者架构首先都是为人服务的,系统的有序度高,逻辑合理,业务
概念清晰是第一位。具体架构设计时,首先要关注业务架构和应用架构,这个架构新手要特别注意。

影响系统性能的主要原因

CPU/IO/内存:

  1. 上下文切换,因为每个 CPU 核心在同一时刻只能执行一个线程,而 CPU 的调度有几种方式,比如抢占式和轮询等,以抢占式为例,每个线程会分配一定的执行时间,当达到执行时间、线程中有 IO 阻塞或者有高优先级的线程要执行时。CPU 会切换执行其他线程。而在切换的过程中,需要存储当前线程的执行状态并恢复要执行的线程状态,这个过程就是上下文切换。比如 IO、锁等待等场景下也会触发上下文切换,当上下文切换 过多时会造成内核占用比较多的 CPU。
  2. 文件 IO,比如频繁的日志写入,磁盘本身的处理速度较慢、都会造成 IO 性能问题
  3. 网络 IO,带宽不够
  4. 内存,包括内存溢出、内存泄漏、内存不足

实际上不管是应用层的调优也好,还是硬件的升级也好。其实无非就是这几个因素的调整

水平拆分和垂直拆分

互联网时代谈论最多的话题就是拆分。拆分一般分为水平拆分和垂直拆分,这并不单指对数据库或者缓存的拆分,主要是表达一种分而治之的思想和逻辑。

  • 水平拆分
    水平拆分是指由于单一节点无法满足需求,需要扩展为多个节点,多个节点具有一致的功能,组成一个服务池,一个节点服务一部分请求量,所有节点共同处理大规模高并发的请求量。
  • 垂直拆分
    垂直拆分指按照功能进行拆分,秉着“专业的人干专业的事”的原则,把一个复杂的功能拆分为多个单一、简单的功能,不同单一简单功能组合在一起,和未拆分前完成的功能是一样的。由于每个功能职责单一、简单,使得维护和变更都变得更简单、容易、安全,所以更易于产品版本的迭代,还能够快速的进行敏捷发布和上线。
水平和垂直扩容

对于大型的分布式架构而言,我们一直在追求一种简单、优雅的方式来应对访问量和数据量的增长。而这种方式通常指的是不需要改动软件程序,仅仅通过硬件升级或者增加机器就可以解决。而这种就是分布式架构下的伸缩设计
伸缩分为垂直伸缩和水平伸缩两种

  • 垂直伸缩:表示通过升级或者增加单台机器的硬件来支撑访问量以及数据量增长的方式,垂 直伸缩的好处在于技术难度比较低,运营和改动成本也相对较低。但是缺点是机器性能是有瓶颈的,同时升级高性能的小型机或者大型机,成本是非常大的。这也是阿里去 IOE 的一个原因之一
    在这里插入图片描述

增加 CPU 核心数:增加 CPU 后系统的服务能力能够得到大的增长,比如响应速度、同时可 以处理的线程数。但是引入 CPU 后也会带来一些显著的问题

  1. 锁竞争加剧;多个线程同时运行访问某个共享数据,那么就涉及到锁竞争,锁竞争激烈时
    会导致很多线程都在等待锁,所以即时增加 CPU 也无法让线程得到更快的处理。当然这里
    是有调优手段的,可以通过调优手段来降低锁竞争
  2. 支撑并发请求的线程数是固定的,那么即时增加 CPU,系统的服务能力也不会得到提升
  3. 对于单线程任务,多核心 CPU 是没有太大的作用的
    增加内存:增加内存可以直接提成系统的响应速度,当然,也有可能达不到效果,就是如果 JVM 堆内存是固定的。
  • 水平伸缩:
    通过增加机器来支撑访问量及数据量增长的方式,成为水平伸缩,水平伸缩理论上来说没有瓶颈,但是缺点是技术要求比较高,同时给运维带来了更大的挑战。垂直伸缩和水平伸缩都有各自的优点,我们在实际使用过程中都会对两者做结合,一方面要考虑硬件升级的成本,一方面要考虑软件改造的成本。
单体架构

在这里插入图片描述

这里服务器做了一个集群(水平拆分),以防止单点故障(如果只有一台服务器,那么这台服务器挂掉后,项目就挂掉了),但是做了集群之后,虽然并发量提升了,但是会产生新的问题:1. 用户对应两个应用服务器访问的选择(用nginx解决)2. session 共享的问题(用redis解决)。所以还是那句话,问题驱动技术。

负载均衡算法
  • 轮询(Round Robin)法
    将请求按顺序轮流分配到后台服务器上,均衡的对待每一台服务器,而不关心服务器实际的连接数和当前的系统负载
    **缺点:**当集群中服务器硬件配置不同、性能差别大时,无法区别对待

  • 随机法:
    通过系统随机函数,根据后台服务器列表的大小值来随机选取其中一台进行访问。随着调用量的增大,其实际效果越来越接近于平均分配流量到后台的每一台服务器,也就是轮询法的效果
    优点: 简单使用,不需要额外的配置和算法。
    缺点: 随机数的特点是在数据量大到一定量时才能保证均衡,所以如果请求量有限的话,可能会达不到均衡负载的要求。

  • 源地址哈希法
    根据服务消费者请求客户端的 IP 地址,通过哈希函数计算得到一个哈希值,将这个哈希值和 服务器列表的大小进行取模运算,得到的结果便是要访问的服务器地址的序号。采用源地址 哈希法进行负载均衡,相同的 IP 客户端,如果服务器列表不变,将映射到同一个后台服务器进行访问。

  • 加权轮询(Weight Round Robin)法
    不同的后台服务器可能机器的配置和当前系统的负载并不相同,因此它们的抗压能力也不一样。跟配置高、负载低的机器分配更高的权重,使其能处理更多的请求,而配置低、负载高的机器,则给其分配较低的权重,降低其系统负载,加权轮询很好的处理了这一问题,并将请求按照顺序且根据权重分配给后端

  • 最小连接数法
    前面几种方式都是通过对请求次数的合理分配最大可能提高服务器的利用率,但是实际上,请求次数的均衡并不能代表负载的均衡。所以,引入了最小连接数法。它正是根据后端服务器当前的连接情况,动态的选取其中当前积压连接数最少的一台服务器来处理当前请求,尽可能的提高后台服务器利用率,将负载合理的分流到每一台服务器。

session 问题

我们打开一个网页,基本上需要浏览器和 web 服务器进行多次交互,我们都知道 Http 协议 本身是无状态的,这也是 http 协议设计的初衷,客户端只需要简单的向服务器请求下载某些 文件,无论是客户端还是服务器都没必要记录彼此过去的行为,每一次请求之间是独立的, 好比一个顾客和一个自动售货机之间的关系一样. 而实际上,我们很多的场景都需要带有状态的特性,因此聪明的我们引入了 session+cookie 机制来记住每次请求的会话。 在会话开始时,给当前会话分配一个唯一的会话标识(sessionid),然后通过 cookie 把这个 标识告诉浏览器,以后在每次请求的时候,浏览器都会带上这个会话标识来告诉 web 服务器 请求属于哪个会话。在 web 服务器上,各个会话有独立的存储,保存不同会话的信息。 如果遇到禁用 cookie 的情况,一般的做法就是把这个会话标识放到 URL 的参数中。

而我们应用服务器从一台变成两台后,就会遇到 session 问题

分布式环境下的 session 共享
Session 共享在当前这个互联网背景下,已经不是一个新鲜的话题了,而且如何解决 session共享其实也有很多非常成熟的方案。服务器实现的 session 复制或 session 共享,这类型的共享 session 是和服务器紧密相关的

我们在 Web 服务器之间增加了会话数据的同步,通过同步就保证了不同 Web 服务器之间 Session 数据的一致。一般应用容器都支持 Session Replication 方式

存在问题:

  1. 同步 Session 数据造成了网络带宽的开销。只要 Session 数据有变化,就需要将数据同步到所有其他机器上,机器越多,同步带来的网络带宽开销就越大。
  2. 每台 Web 服务器都要保存所有 Session 数据,如果整个集群的 Session 数据很多(很多
    人同时访问网站)的话,每台机器用于保存 Session 数据的内容占用会很严重。 这个方案是靠应用容器来完成 Session 的复制从而解决 Session 的问题的,应用本身并不关 心这个事情。这个方案不适合集群机器数多的场景。

利用成熟的技术做 session 复制,比如 12306 使用的 gemfire,比如常见的内存数据库如 Redis
在这里插入图片描述
Session 数据不保存到本机而且存放到一个集中存储的地方,修改 Session 也是发生在集中 存储的地方。Web 服务器使用 Session 从集中存储的地方读取。这样保证了不同 Web 服务 器读取到的 Session 数据都是一样的。存储 Session 的具体方式可以是数据库

存在问题:

  1. 读写 Session 数据引入了网络操作,这相对于本机的数据读取来说,问题就在于存在时延
    和不稳定性,不过我们的通讯基本都是发生在内网,问题不大。
  2. 如果集中存储 Session 的机器或者集群有问题,就会影响到我们的应用。

相对于 Session Replication,当 Web 服务器数量比较大、Session 数比较多的时候,这个集 中存储方案的优势是非常明显的。

将 session 维护在客户端

很容易想到就是利用 cookie,但是客户端存在风险,数据不安全,而且可以存放的数据量比 较小,所以将 session 维护在客户端还要对 session 中的信息加密。

我们的 Session 数据放到 Cookie 中,然后在 Web 服务器上从 Cookie 中生成对应的 Session 数据。这就好比我们每次都把自己的碗筷带在身上,这样去那家饭店就可以随意选择了。相 对前面的集中存储方案,不会依赖外部的存储系统,也就不存在从外部系统获取、写入 Session 数据的网络时延、不稳定性了。

存在问题:
安全性。Session 数据本来都是服务端数据,而这个方案是让这些服务端数据到了外部网络及 客户端,因此存在安全性上的问题。我们可以对写入的 Cookie 的 Session 数据做加密,不过 对于安全来说,物理上不能接触才是安全的。

单体架构垂直拆分

在这里插入图片描述

项目经过一段时间的运行,用户越来越多,业务逻辑也越来越多,为了解耦,将系统架构进行垂直拆分,不同的子系统都有自己独立的域名,登录一个系统后,不用登录其他系统就可以访问,这里用到sso(单点登录)

但是这样做有缺点:用户系统需要用到用户库、订单库、商品库,所以crud的代码都写在了用户系统中,订单系统需要用到订单库和用户库,对这两个库的crud也写到了订单系统中,这样就增加了查询订单库的重复代码,并且,如果订单库做修改时,那么用户系统和订单系统都需要改代码,这显然是不符合常理的。所以就有了下面的架构。

面向服务的架构(SOA)

在这里插入图片描述

在SOA服务中,将垂直架构进行了改造,将原先的子系统直接查库改造为通过专一的服务对数据库进行操作,各服务之间使用RPC远程通信。经过这样改造后,即使数据库改变,那么只需要修改对应的通用服务即可。无需更改子系统代码,降低了系统耦合性。

微服务架构

在这里插入图片描述

完整版微服务架构

在这里插入图片描述

在这里插入图片描述

什么是分布式架构

分布式架构的定义
简单来说,分布式系统是指位于网络计算机上的组件仅通过传递消息通信和协调目标系统。这里面有两个重要因素

  1. 组件是分布在网络计算机上
  2. 组件之间仅仅通过消息传递来通信并协调行动。分布式系统其实也可以认为是一种去中心化的实现思路,对于用户来说是无感知的

分布式架构的意义
从单机单用户到单机多用户,再到现在的网络时代,应用系统发生了很多的变化,为什么要用分布式系统呢?

  1. 升级单机硬件的性价比越来越低
  2. 单机处理能力存在瓶颈
  3. 对于稳定性和可用性的要求
分布式架构带来的问题

假如订单服务有三台服务器集群,在某一个时刻,只有1间总统套房可以预定,这时有三个人同时预定这一间总统套房,那么就有可能会有爆房(超卖)的问题,如果是在单体服务器中,可以通过加JVM的锁解决,但是现在是分布式架构中,由于Web服务1~3在三个不同的JVM进程里,而无论是synchronized关键字还是JUC包里的Lock锁,都无法保证不同JVM内共用一个锁对象,因此上面的代码在高并发场景下是一定会出现超卖问题的(使用压测工具JMeter测试即可发现)。这时就不得不使用分布式锁。

分布式锁的四种实现方式
  • MySQL实现分布式锁
    核心思想:在数据库中创建一个表,表中包含方法名等字段,并在方法名字段上创建唯一索引,想要执行某个方法,就使用这个方法名向表中插入数据,成功插入则获取锁,执行完成后删除对应的行数据释放锁。
    基于数据库实现分布式锁会出现的问题:

    1. 因为是基于数据库实现的,数据库的可用性和性能将直接影响分布式锁的可用性及性能,所以,数据库需要双机部署、数据同步、主备切换;
    2. 不具备可重入的特性,因为同一个线程在释放锁之前,行数据一直存在,无法再次成功插入数据,所以,需要在表中新增一列,用于记录当前获取到锁的机器和线程信息,在再次获取锁的时候,先查询表中机器和线程信息是否和当前机器和线程相同,若相同则直接获取锁;
    3. 没有锁失效机制,因为有可能出现成功插入数据后,服务器宕机了,对应的数据没有被删除,当服务恢复后一直获取不到锁,所以,需要在表中新增一列,用于记录失效时间,并且需要有定时任务清除这些失效的数据;
    4. 不具备阻塞锁特性,获取不到锁直接返回失败,所以需要优化获取逻辑,循环多次去获取。
  • Redis实现分布式锁
    主要应用以下两个命令:
    SETNX:是【SET if Not eXists】的简写
    格式:setnx key value
    将key的值设为value,当且仅当key不存在。若给定的key已经存在,则SETNX不做任何动作。
    EXPIRE:设置过期时间
    expire key timeout:为key设置一个超时时间,单位为second,超过这个时间锁会自动释放,避免死锁。
    核心原理在于每个线程都尝试创建一个锁的KEY,如果能创建成功,则获取锁,否则阻塞等待
    Redission框架实现分布式锁:主要流程:当一个线程获取锁之后,处理业务流程,另一个后台线程每隔10秒检测获得锁的线程是否还持有锁,如果持有,就延长锁的时间。剩余没有获得锁的线程自旋

  • Zookeeper实现分布式锁

三种分布式锁的对比

MySQL的实现最为简单,但效率低且可靠性差;
Redis保证AP(可用性+分区容错性),主从异步执行;
Zookeeper保证CP(一致性+分区容错性),保证一半以上Follower同步成功,一定能拿到正确数据。
用Zookeeper实现分布式锁的性能不如Redis,但更可靠。

分布式锁优点缺点
Zookeeper有等待锁的队列,大大提高抢锁的效率添加和删除节点的效率较低
RedisSETNX和DEL的效率高需要客户端的自旋获取锁,且主从架构可能出现数据丢失的问题

最好的方式还是实现无锁化

数据库架构的演变

读写分离

随着业务的继续增长,数据量和访问量持续增加。单体数据库已经有点力不从心了。对于大型网站来说,有不少业务是读多写少,这个情况也会直接反馈到数据库上。那么对于这种情况来说,我们可以考虑采用读写分离的方式来优化数据库的压力
在这里插入图片描述

写库为主库,读库是从库,从库用于分担主库的读压力

这个结构的变化会带来两个问题

  1. 数据如何同步
    我们希望通过读库来分担主库上读的压力,那么首先需要解决的是怎么复制到读库的问题。数据库系统一般都提供了数据复制的功能,我们可以直接使用数据库系统自身的机制。不同的数据库系统有不同的支持 比如 Mysql 支持 Master+slave 的结构提供数据复制机制
  2. 应用对数据源如何路由
    对于应用来说,增加一个读库对结构变化产生了一定的影响,也就是我们的应用需要根据不同的情况来选择不同的数据库源

读写分离后,数据库又遇到瓶颈
通过读写分离以及在某些场景用分布式存储系统替换关系型数据库的方式,能够降低主库的压力,解决数据存储方面的问题,不过随着业务的发展,我们的主库也会遇到瓶颈。推演到现在,我们的网站各个模块:交易、商品、用户数据都还是存储在一个数据库。尽管增加了缓存、读写分离的方式,但是数据库的压力仍然在持续增加,因此我们可以对数据垂直拆分水平拆分来解决数据库压力问题

  • 垂直拆分:专库专用,数据垂直拆分。垂直拆分的意思是把数据库中不同的业务数据拆分到不同的数据库中,那么根据我们推演的例子, 把用户、交易、商品的数据分开

    不同业务的数据从原来的一个数据库拆分到了多个数据库中,那么就需要考虑到如何处理原来单机跨业务的事务

  1. 使用分布式事务解决
  2. 去掉事务或者不追求强事务的支持

对数据进行垂直拆分后,解决了把 所有业务数据放在一个数据库中 的压力问题,并且也可以根据不同 业务的特点进行更多的优化

  • 水平拆分:数据水平拆分就是把同一个表的数据拆分到两个数据库中,产生数据水平拆分的原因是某个业务的数据表的数据量或者更新量达到了单个数据库的瓶颈,这个时候就可以把表拆到两个或者多个数据库中。

    数据水平拆分与读写分离的区别是,读写分离解决的是读压力大的问题,对于数据量大或者更新量大的情况并不起作用。

    数据水平拆分与数据垂直拆分的区别是,垂直拆分是把不同的表拆分到不同的数据库,而水平拆分是把同一个表拆分到不同的数据库中
    我们可以进一步把用户表拆分到两个数据库中,它们拥有结构一模一样的用户表,而且每个库中的用户表都只涵盖了一部分的用户,两个数据库的用户和在一起就相当于没有拆分之前的用户表

水平拆分带来的影响

  1. sql 路由问题,需要根据一个条件来决定当前请求发到那个数据库中
  2. 主键的处理,不能采用自增 id,需要全局 id
  3. 由于同一个业务的数据被拆分到不同的数据库,因此涉及到一些查询需要跨两个数据库获
    取,如果数据量太大并且需要分页,就比较难处理了

搜索引擎其实是一个读库
搜索引擎其实可以理解成一个读库,我们的商品存储在数据库中,而网站需要提供用户实时检索的功能,尤其是在商品搜索这块。对于这样的读请求,如果全部走读库,其实性能也会存在一个瓶颈。而使用搜索引擎,不仅仅能大大提高检索速度。还能减轻读数据库的压力。而搜索引擎最重要的工作,就是需要根据被搜索的数据来构建索引,而随着被搜索的数据的变化,索引也需要相应变化。

搜索集群的使用方式和读库的使用方式是一样的,只是构建索引的过程基本都是需要我们自己来实现。可以从两个纬度对搜索引擎构建索引的方式进行规划, 一个是按照全量/增量划分。一种是按照实时/非实时划分。

  • 全量方式用于第一次建立索引,可能是新建,也可能是重建。而增量的方式是在全量的基础上持续更新索引。
  • 实时和非实时提现在索引更新的时间上,实时是最好的,非实时主要考虑到对数据源头的保护

总的来说,搜索引擎技术解决了站内搜索时某些场景下的读的问题,提供了更好的查询效率。

其他提高服务响应的数据层面的方法

加速数据读取的利器-缓存及分布式存储
在大型网站中,基本上就是在解决存储计算的问题,所以存储是一个很重要的支撑系统。 网站建设初期我们都是从关系型数据库开始的,而且很多时候为了方便,我们会把一些业务逻辑放在数据库里面去做,比如触发器、存储过程。虽然在前期能够很方便的解决问题,但是在未来的发展过程中会带来很多的麻烦,比如数据量大了以后,要做分库分表操作等. 同 时,业务发展到一定的体量以后,对存储的需求不能完全通过关系型数据库来满足

分布式文件系统
对一些图片、大文本,使用数据库就不合适了,所以我们会采用分布式文件系统来实现文件存储,分布式文件系统有很多产品、比如淘宝的 TFS、google 的 GFS。还有开源的 HDFS

NoSQL
NoSQL 我们可以理解成 Not Only SQL、或者是 No SQL。 两种意思都是为了表达在大型网站中,关系型数据库可以解决大部分问题,但是对于不同内容的特征、访问特征、事务特征等对存储的要求是不一样的。NoSQL 是定位于是文件系统和 SQL 关系型数据库之间的范畴。

数据缓存
大型网站内部都会用到一些数据缓存,主要用于分担数据库的读的压力,缓存系统一般是用来保存和查询键值对的。应用系统中一般会把热点数据放入到缓存,而缓存的填充也应该是由应用系统完成。如果数据不存在,则从数据库独处数据后放入缓存。随着时间的推移,当缓存容量不够需要清除数据时,最近不被访问的数据就会被清理掉。还有一种方式就是在数据库的数据发生变化后,主动把数据放入到缓存系统中,这样的好处是数据变化时能够及时更新缓存的数据,不会造成读取失效。

页面缓存
除了数据缓存外,我们还可以对页面做缓存,数据缓存可以加速应用在响应请求时的数据读取数度,但是最终展示给用户的还是页面,有些动态产生的页面或者访问量特别高的页面,我们会对页面或者内容做一些缓存。

分布式存储
我们应用最多的主要还是关系型数据库,但是在有些场景中,关系型数据库不是很合适。所以我们会引入分布式存储系统,比如 redis、mongoDB、cassandra、HBase 等。 根据不同的场景和数据结构类型,选择合适的分布式存储系统可以极大提高性能。分布式系统通过集群提供一个高容量、高并发访问、数据冗余融债的支持。

  • 3
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值