高并发网站中的数据库设计

介绍

数据库其实就是一个普通的计算机系统,为我们提供某种服务。
我们对数据库功能的抽闲可以将数据库功能抽象成三个部分

  • 用户API
  • 关系代数和事物引擎
  • K-V存储

关系型数据库底层也是K-V模型的,但是他为我们提供了更高层的服务,这些服务牺牲了运算速度实现了原子性,一致性,隔离性。
但是有些时候我们需要效率,关系型数据库成了系统的瓶颈。我们需要把速度拿回来,所以一些K-V数据库就产生了,其实就是去掉了关系代数和事物引擎将K-V存储和关于K-V的API暴露给了用户,这就是Nosql,Nosql放弃了事物提高了速度。
但是人们发现最简单的K-V存储好像什么事情都干不了,需要我们处理的事情太多了,就产生了newSql,为我们处理了关系代数模型和事物引擎的一部分功能。

Key-Value存储

本质就是映射,按照key找value。

映射的关键特性 扩展

  • 是否支持范围查找
  • 是否能够处理更新
  • 读写性能指标
  • 是否面向磁盘结构
  • 并行指标
  • 内存占用
  • Etc…

常见的数据结构

  • 排序数组
  • 排序链表
  • 跳表
  • B tree
  • LSM tree
  • HashMap
  • Fractal Tree
  • Red-Black-tree
  • COLA
  • Etc…

关系代数

primary key:id user_id name

按照key找value, Primary Key:id 按照id查找其他数据。

但是如果按照user_id查看记录,则需要遍历所有记录。效率为O(n)

二级索引

二级索引就是同样也是个K-V映射表 key为user_id,value为主键id。
如果二级索引为Hash结构,效率就下降为0(1),如果是二分查找 Tree,效率为O(logN)
这样查询 先按user_id查找到id,在通过id查找到数据。

如果按照多条件查询 where user_id = 1 and name = ‘tom’
如果按照刚才的索引查找,但是如果user_id为1的记录有一万条,我们就需要遍历一万条记录。

组合索引

扩展阅读
扩展阅读
使用 user_id和name作为key,主键id作为value。

事物简介

事物的核心是锁和并发

  • 优势:方便理解
  • 劣势:性能较低

事物其实就是帮助程序员人脑来理解程序到底是怎么运转的。人脑永远是串行的。把一个逻辑整理成第一步,第二步,第三步…
但是这要的程序只能在单机单线程中执行。
而现实生活中很多事情都是有先后的,例如我们从bob账户中取钱,我们查询账户余额为100,但我们真的去做减的时候,bob账户中的钱已经被别人取走了。
人脑不能很好的理解并发的概念,所以就设计了一种模式,就是事物,让我们方便理解并发,但是降低了性能。

容易理解的模型性能都不好,性能好的模型都不容易理解 – 这就是生活。

事物单元

其实数据库的DDL操作都可以拆分成两个操作,insert 是先查询再插入,update 先查询再更新, delete 先查询再删除,只有select是一个查询操作。
每次查询的时候都把数据锁上,锁的概念:在线程上面维持一个对这个访问的独占权,其他线程不能再访问它了。
事物所覆盖的所有数据都会被这个线程独占。
但是如果都用这么重量级的锁,那性能就太低了,所以事物为我们提供了四种隔离界别

  • 未提交读
  • 提交读
  • 可重复读
  • 序列化

这些也是单独的事物单元

  • 商品要建立一个基于GMT_Modified的索引
  • 从数据库中读取一行记录
  • 向数据库中写入一行记录,同事更新这行记录的所有索引
  • 删除整张表
  • Etc…

一组事物单元,是顺序执行的,第一个事物单元结束后,第二个事物单元才开始执行。
如果两个事物单元没有任何关系的话,A事物操作bob账户,B事物操作tom账户,两个事物是可以并行的。
如果两个事物共享同一份数据,事物没有办法并行。

事物产生原因

扩展阅读

事物单元之间的Happen-before关系(数据的四种冲突)

  • 读写
  • 写度
  • 读读
  • 写写

问题

  • 如何能以最快的速度完成
  • 又能保证上面四种操作的逻辑顺序

mvcc Multi-Version Concurrency Control 多版本并发控制

SQL引擎


现在主流的数据库都在使用CBO(基于成本的优化)进行系统的操作。

分布式存储

多级K-V存储

关键特性

  • 可运维
  • 高性能
  • 可以比较容易的扩容
  • 核心数据结构还是hash和树,部分case针对多机做了一点优化。

代表性组件

  • mongoDB -> mongos服务器
  • Hbase -> region server + client jar包
  • DRDS(TDDL) -> tddl规则引擎组件

分布式结构可以理解成map套map的过程,先查询数据在哪个单机,再做单机查询。

路由

规则引擎:有状态的数据应该按照什么规则进行写入和读取
例如:对pk % 3 —> DB0,DB1,DB2

本质来说还是个查找的过程

  • hash
    • O(1)效率
    • 不支持范围查询(按时间这样的查询条件就比较困难)
    • 不需要频繁调整数据分布
  • Tree
    • 主要是 B Tree
    • O(logN)效率
    • 支持范围查询
    • 需要频繁分裂和合并
  • Hash
    • Id%n
    • 最普通的hash
    • 如果id%3 -> id%4 总共会走80%的数据发生移动,最好的情况是倍分id%3 -> id%6,这时候会有50%的数据发生移动
    • 数据移动本身就是个要了亲命的事
  • hash
    • 一致性hash 只要解决数据的扩容和缩容的问题
    • 虚拟节点
      • 解决热点问题,只需要调整对应关系疾苦
      • 解决n->n+1问题,规则可以规定只移动需要移动的数据
      • 方案相对复杂
      • 一般推荐使用简单方案开始,使用n->2n方案扩容
      • 只有需要的情况下,在考虑平滑的扩展到虚拟节点方案。
  • B-Tree
    • Hbase使用的切分方法
      • 支持范围查询
      • 对于发部分场景来说,引导列都是pk.userid一类的单值查询,用树相对复杂。
      • 需要频繁的进行切分和合并操作 –region server的恶梦
      • 固定节点情况下,跨度相对较大,查询效率可能会进一步降低

一致性

寻求一种能够保证,在给定多台计算机,并且他们相互之间由网络相互连通,中间的数据没有拜占庭将军问题(数据不会被伪造)的前提下(P Partition tolerance 分区容忍性)能够做到一下两种特性的方法:

  • 数据的每次成功写入,数据不会丢失,并且按照写入的顺序排列 (C Consistency 一致性)
  • 给定安全级别,保证服务的可用性,并尽可能减少机器的消耗(A Availability 可用性)
    • 数据的可用性
    • 服务的可用性 (响应时间,例如500ms)

无主机方案

  • Dynamo/cassandra/Paxos:gossip ,W+R>N (W>N-R) N(node) w(write) r(read)
  • 所有节点可写,不存在单点故障
  • 读数据的最新版本,需要将所有节点的数据都读出来合并一次

有主机方案(Raft)

主从配置

  • Mysql MongoDB Oracle+fibreChannel
  • 只有一个节点可写,切换时存在短暂leader election过程,会出现短暂不可写。
  • 数据一致性比较好控制,读最新数据只需要读主机就可以,一致性读性能较好。

实际系统需要考虑的问题

  • 有些机器负担写任务,因此读压力可能不均衡,因此必须有权重设置。
  • 单个节点挂掉的时候,TCP超时会导致业务APP的线程花费更多的时间来处理单个请求,这样会降低APP的处理能力,导致雪崩。
  • 因为突发情况,导致数据库请求书增加,数据库相应变慢,导致雪崩。

数据库实践

单机优化原则

  • 二分查找效率>全表遍历
    • 选择合适的索引
  • 内存读写>SSD读写>磁盘读写
    • 将物理读(磁盘读)换成逻辑读(内存读)
  • 减少锁冲突
    • 尽可能通过业务设计,将更新变成插入,来减少加锁去锁的操作
  • 减少临时表使用
    • 减少多维度排序

分布式系统优化原则

  • 减少跨机网络交互
    • 尽可能带sharding key
    • 分页优化(google一下)
  • 减少数据读写热点
    • 切分颗粒度尽可能细(用户颗粒度好于省份)
  • 减少锁开销
    • 尽可能规避分布式事物

总结

沈询所有资源的索引(会不断更新)

  • 尽一切可能利用单机资源
    • 单机事物
    • 单机join
  • 好的存储模型
    • 尽可能走内存
    • 尽可能将以此要查询到的数据物理的放在一起
    • 通过合理的数据冗余,减少走网络的次数
    • 合理并行提升响应时间
    • 读取数据瓶颈,可以通过加slave节点解决
    • 写入瓶颈,用规则sharding和扩容来解决

警惕 InnoDB 和 MyISAM 创建 Hash 索引陷阱

今天最好的表现,是明天最低的要求。