高性能架构模式

1 高性能数据库集群

1.1 读写分离

读写分离的基本原理是将数据库读写操作分散到不同的节点上。下面是其基本架构图。

读写分离

1.2.1 基本原理实现

读写分离的基本实现是:

  • 数据库服务器搭建主从集群,一主一从、一主多从都可以。
  • 数据库主机负责读写操作,从机只负责读操作。
  • 数据库主机通过复制将数据同步到从机,每台数据库服务器都存储了所有的业务数据。
  • 业务服务器将写操作发给数据库主机,将读操作发给数据库从机。

1.2.2 设计复杂度

主从复制延迟分配机制

  1. 主从复制延迟
    以 MySQL 为例,主从复制延迟可能达到 1 秒,如果有大量数据同步,延迟 1 分钟也是有可能的。主从复制延迟会带来一个问题:如果业务服务器将数据写入到数据库主服务器后立刻(1 秒内)进行读取,此时读操作访问的是从机,主机还没有将数据复制过来,到从机读取数据是读不到最新数据的,业务上就可能出现问题。例如,用户刚注册完后立刻登录,业务服务器会提示他“你还没有注册”,而用户明明刚才已经注册成功了。
    解决思路:

    • 写操作后的读操作指定发给数据库主服务器
    • 读从机失败后再读一次主机
    • 关键业务读写操作全部指向主机,非关键业务采用读写分离
  2. 分配机制
    将读写操作区分开来,然后访问不同的数据库服务器,一般有两种方式:程序代码封装中间件封装

    • 程序代码封装
      程序代码封装架构

    程序代码封装的方式具备几个特点:
    - 实现简单,而且可以根据业务做较多定制化的功能。
    - 每个编程语言都需要自己实现一次,无法通用,如果一个业务包含多个编程语言写的多个子系统,则重复开发的工作量比较大。
    - 故障情况下,如果主从发生切换,则可能需要所有系统都修改配置并重启。

    • 中间件封装
      中间件封装指的是独立一套系统出来,实现读写操作分离和数据库服务器连接的管理。中间件对业务服务器提供 SQL 兼容的协议,业务服务器无须自己进行读写分离。对于业务服务器来说,访问中间件和访问数据库没有区别,事实上在业务服务器看来,中间件就是一个数据库服务器。其基本架构是:
      中间件封装
      数据库中间件的方式具备的特点是:

      • 能够支持多种编程语言,因为数据库中间件对业务服务器提供的是标准 SQL 接口。

      • 数据库中间件要支持完整的 SQL 语法和数据库服务器的协议(例如,MySQL 客户端和服务器的连接协议),实现比较复杂,细节特别多,很容易出现 bug,需要较长的时间才能稳定。

      • 数据库中间件自己不执行真正的读写操作,但所有的数据库操作请求都要经过中间件,中间件的性能要求也很高。

      • 数据库主从切换对业务服务器无感知,数据库中间件可以探测数据库服务器的主从状态。例如,向某个测试表写入一条数据,成功的就是主机,失败的就是从机。

1.2 分库分表

  1. 业务分库:业务分库指的是按照业务模块将数据分散到不同的数据库服务器。
  2. 单表数据拆分有两种方式:垂直分表和水平分表。示意图如下:
    单表拆分
    • 垂直分表
    • 水平分表

2 高性能NoSQL

关系数据库经过几十年的发展后已经非常成熟,强大的 SQL 功能和 ACID 的属性,使得关系数据库广泛应用于各式各样的系统中,但这并不意味着关系数据库是完美的,关系数据库存在如下缺点。关系数据库缺点:

  • 关系数据库存储的是行记录,无法存储数据结构
  • 关系数据库的 schema 扩展很不方便
  • 关系数据库在大数据场景下 I/O 较高
  • 关系数据库的全文搜索功能比较弱

常见的NoSQL方案

  • K-V 存储:解决关系数据库无法存储数据结构的问题,以 Redis 为代表。
  • 文档数据库:解决关系数据库强 schema 约束的问题,以 MongoDB 为代表。
  • 列式数据库:解决关系数据库大数据场景下的 I/O 问题,以 HBase 为代表。
  • 全文搜索引擎:解决关系数据库的全文搜索性能问题,以 Elasticsearch 为代表。

3 高性能缓存

缓存就是为了弥补存储系统在这些复杂业务场景下的不足,其基本原理是将可能重复使用的数据放到内存中,一次生成、多次使用,避免每次使用都去访问存储系统。
缓存架构
出现的问题:

  1. 缓存穿透
    缓存穿透是指缓存没有发挥作用,业务系统虽然去缓存查询数据,但缓存中没有数据,业务系统需要再次去存储系统查询数据。通常情况下有两种情况:
    • 存储数据不存在:这种情况的解决办法比较简单,如果查询存储系统的数据没有找到,则直接设置一个默认值(可以是空值,也可以是具体的值)存到缓存中,这样第二次读取缓存时就会获取到默认值,而不会继续访问存储系统。
    • 缓存数据生成耗费大量时间或者资源
  2. 缓存雪崩
    缓存雪崩是指当缓存失效(过期)后引起系统性能急剧下降的情况。当缓存过期被清除后,业务系统需要重新生成缓存,因此需要再次访问存储系统,再次进行运算,这个处理步骤耗时几十毫秒甚至上百毫秒。而对于一个高并发的业务系统来说,几百毫秒内可能会接到几百上千个请求。由于旧的缓存已经被清除,新的缓存还未生成,并且处理这些请求的线程都不知道另外有一个线程正在生成缓存,因此所有的请求都会去重新生成缓存,都会去访问存储系统,从而对存储系统造成巨大的性能压力。这些压力又会拖慢整个系统,严重的会造成数据库宕机,从而形成一系列连锁反应,造成整个系统崩溃。
    • 更新锁机制:
      • 对缓存更新操作进行加锁保护,保证只有一个线程能够进行缓存更新,未能获取更新锁的线程要么等待锁释放后重新读取缓存,要么就返回空值或者默认值。
      • 对于采用分布式集群的业务系统,由于存在几十上百台服务器,即使单台服务器只有一个线程更新缓存,但几十上百台服务器一起算下来也会有几十上百个线程同时来更新缓存,同样存在雪崩的问题。因此分布式集群的业务系统要实现更新锁机制,需要用到分布式锁,如Zookkeeper。
    • 后台更新机制:由后台线程来更新缓存,而不是由业务线程来更新缓存,缓存本身的有效期设置为永久,后台线程定时更新缓存。后台定时机制需要考虑一种特殊的场景,当缓存系统内存不够时,会“踢掉”一些缓存数据,从缓存被“踢掉”到下一次定时更新缓存的这段时间内,业务线程读取缓存返回空值,而业务线程本身又不会去更新缓存,因此业务上看到的现象就是数据丢了。解决的方式有两种:
      • 后台线程除了定时更新缓存,还要频繁地去读取缓存(例如,1 秒或者 100 毫秒读取一次),如果发现缓存被“踢了”就立刻更新缓存,这种方式实现简单,但读取时间间隔不能设置太长,因为如果缓存被踢了,缓存读取间隔时间又太长,这段时间内业务访问都拿不到真正的数据而是一个空的缓存值,用户体验一般。
      • 业务线程发现缓存失效后,通过消息队列发送一条消息通知后台线程更新缓存。可能会出现多个业务线程都发送了缓存更新消息,但其实对后台线程没有影响,后台线程收到消息后更新缓存前可以判断缓存是否存在,存在就不执行更新操作。这种方式实现依赖消息队列,复杂度会高一些,但缓存更新更及时,用户体验更好。
  3. 缓存热点
    虽然缓存系统本身的性能比较高,但对于一些特别热点的数据,如果大部分甚至所有的业务请求都命中同一份缓存数据,则这份数据所在的缓存服务器的压力也很大。
    缓存热点的解决方案就是复制多份缓存副本,将请求分散到多个缓存服务器上,减轻缓存热点导致的单台缓存服务器压力。

4 单服务器高性能模式:Reactor与Proactor

4.1 Reactor 模式

I/O 多路复用结合线程池,Reactor 会根据事件类型来调用相应的代码进行处理。Reactor 模式也叫 Dispatcher 模式(在很多开源的系统里面会看到这个名称的类,其实就是实现 Reactor 模式的),更加贴近模式本身的含义,即 I/O 多路复用统一监听事件,收到事件后分配(Dispatch)给某个进程。Reactor 模式的核心组成部分包括 Reactor 和处理资源池(进程池或线程池),其中 Reactor 负责监听和分配事件,处理资源池负责处理事件。

4.1.1 1. 单 Reactor 单进程 / 线程

Reactor单线程

  • ** 描述**
  1. Reactor 对象通过 select 监控连接事件,收到事件后通过 dispatch 进行分发。
  2. 如果是连接建立的事件,则由 Acceptor 处理,Acceptor 通过 accept 接受连接,并创建一个 Handler 来处理连接后续的各种事件。
  3. 如果不是连接建立事件,则 Reactor 会调用连接对应的 Handler(第 2 步中创建的 Handler)来进行响应。
  4. Handler 会完成 read-> 业务处理 ->send 的完整业务流程。
  • ** 优点**
    单 Reactor 单进程的模式优点就是很简单,没有进程间通信,没有进程竞争,全部都在同一个进程内完成。

  • ** 缺点**

  1. 只有一个进程,无法发挥多核 CPU 的性能;只能采取部署多个系统来利用多核 CPU,但这样会带来运维复杂度,本来只要维护一个系统,用这种方式需要在一台机器上维护多套系统。
  2. Handler 在处理某个连接上的业务时,整个进程无法处理其他连接的事件,很容易导致性能瓶颈。
  • 使用场景
    只适用于业务处理非常快速的场景,目前比较著名的开源软件中使用单 Reactor 单进程的是 Redis。

4.1.2 单 Reactor 多线程

单 Reactor 多线程

  • 描述
    1. 主线程中,Reactor 对象通过 select 监控连接事件,收到事件后通过 dispatch 进行分发。
    2. 如果是连接建立的事件,则由 Acceptor 处理,Acceptor 通过 accept 接受连接,并创建一个 Handler 来处理连接后续的各种事件。
    3. 如果不是连接建立事件,则 Reactor 会调用连接对应的 Handler(第 2 步中创建的 Handler)来进行响应。
    4. Handler 只负责响应事件,不进行业务处理;Handler 通过 read 读取到数据后,会发给 Processor 进行业务处理。
    5. Processor 会在独立的子线程中完成真正的业务处理,然后将响应结果发给主进程的 Handler 处理;Handler 收到响应后通过 send 将响应结果返回给 client。
  • 优点
    单 Reator 多线程方案能够充分利用多核多 CPU 的处理能力
  • 缺点
    1. 多线程数据共享和访问比较复杂。例如,子线程完成业务处理后,要把结果传递给主线程的 Reactor 进行发送,这里涉及共享数据的互斥和保护机制。以 Java 的 NIO 为例,Selector 是线程安全的,但是通过 Selector.selectKeys() 返回的键的集合是非线程安全的,对 selected keys 的处理必须单线程处理或者采取同步措施进行保护。
    2. Reactor 承担所有事件的监听和响应,只在主线程中运行,瞬间高并发时会成为性能瓶颈。
  • 适用场景

4.1.3 多 Reactor 多进程 / 线程

为了解决单 Reactor 多线程的问题,最直观的方法就是将单 Reactor 改为多 Reactor,这就产生了第 3 个方案:多 Reactor 多进程 / 线程。
多 Reactor 多进程 / 线程

  • 描述
  1. 父进程中 mainReactor 对象通过 select 监控连接建立事件,收到事件后通过 Acceptor 接收,将新的连接分配给某个子进程。

  2. 子进程的 subReactor 将 mainReactor 分配的连接加入连接队列进行监听,并创建一个 Handler 用于处理连接的各种事件。

  3. 当有新的事件发生时,subReactor 会调用连接对应的 Handler(即第 2 步中创建的 Handler)来进行响应。

  4. Handler 完成 read→业务处理→send 的完整业务流程。

  • 优点
  1. 父进程和子进程的职责非常明确,父进程只负责接收新连接,子进程负责完成后续的业务处理。
  2. 父进程和子进程的交互很简单,父进程只需要把新连接传给子进程,子进程无须返回数据。
  3. 子进程之间是互相独立的,无须同步共享之类的处理(这里仅限于网络模型相关的 select、read、send 等无须同步共享,“业务处理”还是有可能需要同步共享的)。
  • 缺点
  • 适用场景
    目前著名的开源系统 Nginx 采用的是多 Reactor 多进程,采用多 Reactor 多线程的实现有 Memcache 和 Netty。

4.2 Proactor

Reactor 是非阻塞同步网络模型,因为真正的 read 和 send 操作都需要用户进程同步操作。这里的“同步”指用户进程在执行 read 和 send 这类 I/O 操作的时候是同步的,如果把 I/O 操作改为异步就能够进一步提升性能,这就是异步网络模型 Proactor。
在这里插入图片描述

  • 描述
  1. Proactor Initiator 负责创建 Proactor 和 Handler,并将 Proactor 和 Handler 都通过 Asynchronous Operation Processor 注册到内核。
  2. Asynchronous Operation Processor 负责处理注册请求,并完成 I/O 操作。
  3. Asynchronous Operation Processor 完成 I/O 操作后通知 Proactor。
  4. Proactor 根据不同的事件类型回调不同的 Handler 进行业务处理。
  5. Handler 完成业务处理,Handler 也可以注册新的 Handler 到内核进程。
  • 优点
  • 缺点
posted @ 2021-10-05 23:45  lihaihui199102  阅读(54)  评论(0编辑  收藏  举报