数据库集群:读写分离

  • 原理:将数据库读写操作分散到不同的节点上。

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

    • 解决方式1:写操作后的读操作指定发给数据库主服务器

      • 存在问题:和业务强绑定,对业务的侵入和影响较大
    • 解决方式2:读从机失败后再读一次主机

      • 优点:与业务无绑定,只需要在底层访问数据API接口做封装
      • 存在问题:要读两次,增加主机的访问压力
    • 解决方式3:关键业务读写操作全部指向主机,非关键业务采用读写分离

      • 存在问题:还是和业务强绑定,即要对业务做分级,关键业务不走读写分离
  • 具体实现:分配机制

    • 实现1:程序代码封装

      • 在代码里面抽出一个数据访问层,实现读写操作分离和数据库服务器连接的管理。
      • 优点:实现简单,且可以做定制。
      • 缺点:每个编程语言都要实现一次;如果要做主从切换,则需要改代码或者配置,然后重启
      • 已有产品:TDDL
    • 实现2:中间件封装

      • 独立一套系统出来,实现读写操作分离和数据库服务器连接的管理。
      • 优点:支持多编程语言;主从切换等操作,业务服务器无感知
      • 缺点:实现复杂,容易出BUG;所有请求都走中间件系统,性能要求高;
      • 已有产品:MySQL Router(官方)、Atlas(360)

数据库集群:分库分表

  • 业务分库

    • 定义:指的是按照业务模块将数据分散到不同的数据库服务器
    • 存在问题

      • join操作问题——无法使用join,只能业务层面分别查询
      • 事务问题——无法支持本地事务,而分布式事务性能又太差
      • 成本问题——需增加数据库机器
  • 分表

    • 垂直分表

      • 定义:表记录数不变,但按照列切成多张表(例如原有A、B、C三列,切成A、B和A、C两张表)
      • 适用于将不常用的列单独切出去存储
      • 缺点:可以会涉及多次读写操作
    • 水平分表

      • 定义:列不变,将数据切成多份放在不同表里面
      • 路由算法

        • 范围路由:选取有序的数据列(例如整形)作为路由的条件,不同分段分散到不同的数据库表中

          • 优点:可以随着数据的增加平滑地扩充新的表
          • 不足:可能数据分布不均匀
        • Hash路由:将某一列或多列,按照hash分布在不同分表内

          • 优点:数据分布均匀
          • 不足:扩充新表麻烦,需要数据重新分布
        • 配置路由:新增一张路由表,记录原表每行数据对应的分表id

          • 优点:设计简单,需要扩充的时候只要迁移数据再修改路由表数据即可
          • 不足:需要多查询一次,切路由表本身会成为性能瓶颈
      • 分表后问题

        • join操作、count、order by等,都需要业务层面代码支持
    • 实现方式

      • 程序代码封装、中间件封装

高性能NoSQL

  • 关系数据库存在的不足

    • 存储的是行记录,无法存储数据结构
    • schema扩展不方便
    • 在大数据场景下I/O高
    • 全文搜索能力弱
  • K-V存储

    • 解决关系数据库无法存储数据结构的问题,以 Redis 为代表
  • 文档数据库

    • 解决关系数据库强 schema 约束的问题,以 MongoDB 为代表
    • 目前绝大部分文档数据库存储的数据格式是 JSON
    • 优点:新增字段简单、历史数据不会出错、很容易存储复杂数据
    • 不足:不支持事务、无法实现join操作
  • 列式数据库

    • 解决关系数据库大数据场景下的 I/O 问题,以 HBase 为代表
    • 按照列来存储数据的数据库,与之对应的传统关系数据库被称为“行式数据库”
    • 优点:节省I/O、存储压缩比更大
    • 不足:频率更新多个列性能较差
  • 全文搜索引擎

    • 解决关系数据库的全文搜索性能问题,以 Elasticsearch 为代表
    • 基本原理:倒排索引
    • 使用方式:关系型数据库的数据,转换成JSON,再输入全文搜索引擎做索引

高性能缓存

  • 适用场景

    • 需要经过复杂运算后得出的数据
    • 读多写少的数据
  • 缓存存在的问题

    • 缓存穿透

      • 定义:是指缓存没有发挥作用,业务系统虽然去缓存查询数据,但缓存中没有数据,业务系统需要再次去存储系统查询数据
      • 出现该问题的两种情况

        • 存储数据确实不存在。解决方式是若不存在,也提供一个默认值存在缓存当中
        • 缓存数据生成耗费大量时间或者资源。当缓存失效时,由于生成缓存数据的时间较长,因此这段时间内的请求都会打到存储系统上。
    • 缓存雪崩

      • 定义:是指当缓存失效(过期)后引起系统性能急剧下降的情况。在生成新缓存的这段时间内,所有请求仍然会打到存储系统上。
      • 解决方式

        • 更新锁机制。对缓存更新操作进行加分布式锁保护,保证只有一个线程能够进行缓存更新,未能获取更新锁的线程要么等待锁释放后重新读取缓存,要么就返回空值或者默认值。
        • 后台更新机制。缓存本身的有效期设置为永久,后台线程定时更新缓存。

          • 注意存在内存不够,缓存数据被踢出的问题。此时两种解决方式:要么更新缓存的线程还要频繁读缓存,若发现被踢了则再写进去;或者业务线程发现缓存数据不存在,发消息通知,然后更新缓存线程收到消息来重新写缓存。
    • 缓存热点

      • 定义:对于一些特别热点的数据,如果大部分甚至所有的业务请求都命中同一份缓存数据,则这份数据所在的缓存服务器的压力也很大
      • 解决方式:复制多份缓存副本,将请求分散到多个缓存服务器上,减轻缓存热点导致的单台缓存服务器压力。

单服务器高性能模式:PPC与TPC

  • PPC(Process Per Connection)

    • 定义:每次有新的连接就新建一个进程去专门处理这个连接的请求
    • 父进程流程:socket->bind->listen->accept->fork->close
    • 子进程流程:read->业务处理->write->close
    • 适用场景:服务器的访问量和并发量并没有那么大
    • 存在问题

      • fork代价高(要创建进程)、父子进程通信复杂(适用IPC等方案通信)、支持的并发连接数量有限(一般最大几百个)
  • prefork

    • 定义:prefork 就是提前创建进程(pre-fork)。系统在启动的时候就预先创建好进程,然后才开始接受用户的请求,当有新的连接进来的时候,就可以省去 fork 进程的操作
    • 父进程流程:socket->bind->listen->fork
    • 子进程流程:accept->read->业务处理->write->close
    • 实现关键就是多个子进程都accept同一个socket,当有新连接进入时,保证只有一个进程能最后accept成功
  • TPC(Thread Per Connection)

    • 定义:每次有新的连接就新建一个线程去专门处理这个连接的请求
    • 优势:创建线程的消耗比进程要少得多;线程通信相比进程通信更简单
    • 主进程流程:socket->bind->listen->accept->pthread
    • 子线程流程:read->业务处理->write->close
    • 存在问题

      • 创建线程仍然有代价、可能出现死锁、多线程互相影响可能导致整个进程退出
  • prethread

    • 定义:预先创建线程,然后才开始接受用户的请求,当有新的连接进来的时候,就可以省去创建线程的操作
    • 主进程流程:socket->bind->listen->pthread
    • 子线程流程:accept->read->业务处理->write->close

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

  • Reactor

    • 使用I/O多路复用技术

      • 当多条连接共用一个阻塞对象后,进程只需要在一个阻塞对象上等待,而无须再轮询所有连接
      • 当某条连接有新的数据可以处理时,操作系统会通知进程,进程从阻塞状态返回,开始进行业务处理
    • 核心组成:Reactor(负责监听和分配事件)、处理资源池(进程池/线程池)(负责处理事件)
    • 具体实现方式

      • 单Reactor单进程/线程

        • Reactor 对象通过 select 监控连接事件,收到事件后通过 dispatch 进行分发。
        • 如果是连接建立的事件,则由 Acceptor 处理,Acceptor 通过 accept 接受连接,并创建一个 Handler 来处理连接后续的各种事件。
        • 如果不是连接建立事件,则 Reactor 会调用连接对应的 Handler(第 2 步中创建的 Handler)来进行响应。
        • Handler 会完成 read-> 业务处理 ->send 的完整业务流程。
      • 单Reactor多线程

        • 主线程中,Reactor 对象通过 select 监控连接事件,收到事件后通过 dispatch 进行分发
        • 如果是连接建立的事件,则由 Acceptor 处理,Acceptor 通过 accept 接受连接,并创建一个 Handler 来处理连接后续的各种事件
        • 如果不是连接建立事件,则 Reactor 会调用连接对应的 Handler(第 2 步中创建的 Handler)来进行响应
        • Handler 只负责响应事件,不进行业务处理;Handler 通过 read 读取到数据后,会发给 Processor 进行业务处理
        • Processor 会在独立的子线程中完成真正的业务处理,然后将响应结果发给主进程的 Handler 处理;Handler 收到响应后通过 send 将响应结果返回给 client
      • 多Reactor多进程/线程

        • 父进程中 mainReactor 对象通过 select 监控连接建立事件,收到事件后通过 Acceptor 接收,将新的连接分配给某个子进程
        • 子进程的 subReactor 将 mainReactor 分配的连接加入连接队列进行监听,并创建一个 Handler 用于处理连接的各种事件
        • 当有新的事件发生时,subReactor 会调用连接对应的 Handler(即第 2 步中创建的 Handler)来进行响应
        • Handler 完成 read→业务处理→send 的完整业务流程
  • Proactor

    • Proactor Initiator 负责创建 Proactor 和 Handler,并将 Proactor 和 Handler 都通过 Asynchronous Operation Processor 注册到内核
    • Asynchronous Operation Processor 负责处理注册请求,并完成 I/O 操作
    • Asynchronous Operation Processor 完成 I/O 操作后通知 Proactor
    • Proactor 根据不同的事件类型回调不同的 Handler 进行业务处理
    • Handler 完成业务处理,Handler 也可以注册新的 Handler 到内核进程

高性能负载均衡:分类及架构

  • 高性能集群的复杂性主要体现在需要增加一个任务分配器,以及为任务选择一个合适的任务分配算法
  • 负载均衡不只是为了计算单元的负载达到均衡状态,可以基于性能、负载、业务来考虑
  • 负载均衡分类

    • DNS负载均衡

      • 优点:简单、成本低;就近访问,提升访问速度。
      • 不足:更新不及时,有DNS缓存;扩展性差,控制权在域名商;分配策略比较简单
    • 硬件负载均衡

      • 通过单独的硬件设备来实现负载均衡功能,这类设备和路由器、交换机类似
      • 典型的硬件产品:F5、A10
      • 优点:功能强大;性能强大;稳定性高;支持安全防护
      • 不足:贵;扩展性差
    • 软件负载均衡

      • 通过在普通服务器上运行负载均衡软件来实现负载均衡功能
      • 典型产品:Nginx(7层负载均衡)、LVS(4层负载均衡)
      • 优点:简单;便宜(用普通Linux服务器即可);灵活可扩展
      • 不足:性能一般;功能没有硬件负载均衡强大;一般不具备防火墙和防 DDoS 攻击等安全功能
  • 三种负载均衡可以同时使用

    • 基本原则:DNS 负载均衡用于实现地理级别的负载均衡;硬件负载均衡用于实现集群级别的负载均衡;软件负载均衡用于实现机器级别的负载均衡

高性能负载均衡:算法

  • 算法分类

    • 任务平分类。将收到的任务平均分配给服务器进行处理(绝对平均或加权平均)
    • 负载均衡类。根据服务器的负载来进行分配(CPU load、连接数、I/O使用率等)
    • 性能最优类。根据服务器的响应时间来进行任务分配,优先将新任务分配给响应最快的服务器
    • Hash类。根据任务中的某些关键信息进行 Hash 运算,将相同 Hash 值的请求分配到同一台服务器上
  • 具体算法

    • 轮询

      • 优点:最简单
      • 不足:不关注服务器本身状态(只要没宕机或断线);不关心服务器之间性能差异(加权轮询解决了该问题)
    • 负载最低优先

      • 将任务分配给当前负载最低的服务器。具体负载指标可以有多个(CPU load、连接数等)
      • 优点:感知服务器本身状态
      • 不足:复杂
    • 性能最优

      • 性能最优优先类算法是站在客户端的角度来进行分配的,优先将任务分配给处理速度最快的服务器
      • 优点:同样感知服务器本身状态(通过响应时间)
      • 不足:复杂(收集和统计响应时间本身性能有损耗,采样率和统计周期的设置都有讲究)
    • Hash类

      • 常见两种场景:按源地址Hash,按请求ID Hash

标签: none

添加新评论