Skip to content

结构设计

DB 与 APP 的不同

有无状态

无状态应用,每个实例提供的服务都是等价、对等的。APP 应用为无状态应用,DB应用为有状态应用。

数据库正是因为有状态,所以维护起来更有挑战。

APP 在面对大量高并发请求时可以无所顾及的增加实例,加机器进行扩容。处理能里也会将得到线性提升。简单粗暴又有效。

DB 面对同样的压力挑战时正因为其有状态,扩容起来就没有那么从容。因为当前的请求携带的信息需要与已有的数据进行融合累积。

状态不仅要考虑当前状态,还需要考虑历史状态。因为数据是累积的。

  • 当前状态
  • 历史状态

如交易订单,当前订单信息,累积账单信息

同样面临的挑战还有比如高可用(当前状态),迁移(历史状态)。在线扩容、不停机迁移,升级,维护(当前状态+历史状态)。

比如电脑新装系统、新购置手机,有一个顾虑就是里面的数据需要拷贝到新系统里。搬家头疼的也是东西太多了。

综上所述,由于DB应用具有当前状态、历史状态属性,DB在高压下面临的真正挑战,

可归结为吞吐量(QPS)挑战,存储量(SIZE)挑战。

认清DB真正面对的挑战 QPS(QPS+TPS) + SIZE (历史累积 + 增长速度)。要缓解数据库压力,下面将从qps、size两个方面来进一步思考解决之道。

进一步思考,QPS 与 SIZE 之间亦相互影响。

tps 加速SIZE 增长

SIZE 增大QPS下降

取舍策略: 时间换空间,空间换时间。

多Master方案

多master,即可多写。能够解决以上两个问题吗?

tps 表面来看将写的压力分散到多个实例来处理,分担了总体压力。但是要保持多个实例数据的一致性。强一致性下多个实例都要处理完成才返回结果。

tps的角度来分析,单个实例的处理量并没有减少,反而可能产生相互等待。即使是最终一致性,tps总量也没有没减少。

可以降低的是单机所承担的qps。

可能多个实例之间由协议来完成实例间的数据同步,但是对tps性能来说影响也是负面的。对size来说也没带来好处。

多master带来的优势更多的是高可用,或类似CDN多机房本地优先处理。

总结: 多master方案 在tps和size 两个方面都不能做到缓解服务压力的作用

伪命题。随着机器增加复杂难度指数上升。mysql 最新8.0 多master方案官方不建议生产环境中使用。

现有方案: bucardo 同步通过触发器来记录变化 、 自身逻辑复制。

注意问题,多写造成多实例之间的写循环。

读写分离

读写分离的核心是将读请求与写请求分开来处理,请求=qps+tps。master只处理写请求,由slave来处理读请求。

通常在现实的TP生产环境中,读请求往往是写请求的数倍或数十倍。这样通过一主多从的方式可以非常有效的将请求分散到多个实例,增加从库也比较容易实现。

将数据库的读请求分离开来对写的影响也会产生积极的作用,因为读写都会占有IO资源,CPU资源。将读请求分到其他实例,资源完全交给写处理,写的性能进而会得到极大的提升。

总结: 读写分离解决的是并发请求量qps,对SIZE方面的问题没有得到解决

现有方案: 数据库流复制,应用层通常框架自带读写路由功能。 如jdbc不仅有路由功能,还可以自动识别主从

注意问题: 主从之间的同步,延迟问题。从库对长事务对主库gc影响,主库wal日志保留策略等。

业务剥离

DB 的核心业务能力是ACID处理。通过前面APP与DB的对比可知,DB的维护成本,复杂程度通常要高于其他应用。如果业务的需要没有ACID要求尽量不使用DB来处理,做到且用且珍惜!

  • 日志类 没有事务要求
  • 临时状态类 没有持久性要求(ttl)
  • 频繁更新 类似临时状态
  • 计算类 思考问题不要用脚,app比数据库更灵活更高效
  • 约束类 数据库提供约束功能,但是只作为兜底保障,外键是一定要去掉的。验证尽力靠前,如在前端页面验证优于app验证优于DB验证。

总结: 将非必要业务不由DB来处理是最简单有效的,无论是qps还是size

中间层代理

有的业务必须要落盘到DB,但是又存在其特殊性。

读特别频繁,写不是很大。可采用预加载,缓存等将读请求拦截在DB前。因为这种缓存类中间件的性能往往要高于DB数个等级,因为不需要频繁IO,不需要考虑事务,锁等。

对数据实时可靠性要求不高的大并发写,可采用缓存合并多次写,最终一起落盘。

业务峰谷特征明显,峰时实时要求可延迟。通常会考虑使用队列削峰填谷、解耦。也可以结合采用消费端多次写合并的方式进一步缓解写请求。

总结:替代大部分qps,缓解tps

多机方案

数据的拆分,将数据分散到多个主机上。降低单主机承担的qps、size

数据的组成

  • 单实例多项目
  • 一个项目多个独立模块
  • 一个模块多个表
  • 单表中多列、行

接下来按照数据的组成结构从上至下开始拆

单实例多个项目,由于项目初期或数据轻量级项目。多个database使用一个DB实例。当某个项目数据量增到可以独立拥有一个实例的时候将这个项目从中剥离出来,与其他项目不在相互打扰。

多个项目之间访问是通过API来实现,项目在数据库层没有直接的依赖。拆分很容易

一个项目多个模块之间类似于多个项目,但是之间的相关性更强,耦合度相对要高。但在数据层并不一定存在join多表关联,拆分起来问题也不是很大。

以上的拆分的关键词是相关性。不同的数据没有直接的联系可天然的进行拆分。在路由层即可完成。如不同的model路由到不同的DB实例。类似于app的拆分,微服务之类。

以下的拆分的关键词是亲和性,主要是涉及到多表的联合join查询,多表之间直接依赖。为满足事务要求多个表必须要落在一个DB实例中。

亲和性的理解就是相关的数据放到一起。具体与业务逻辑密切相关,如A用户的订单与B用户的订单通常没有直接的强耦合,那么A,B数据就可存放在不同的DB实例中。这就是所谓的多租户应用。

表的拆分

  • 垂直拆分 (按列拆分,不同的列集划分为一个处理单元)
  • 水平拆分 (range,hash,list)(按行拆分,不同的行集划分为一个处理单元)

垂直拆分主要根据数据库表设计范式的要求

水平拆分主要应用表之间的继承关系,如根据时间划分,则可进行历史数据归档等。解决size问题

表的拆分更多的属于表设计范畴。还有个就是索引的设计。合理的表设计直接影响接下来的表分片。

表拆分代价及注意事项, 外键,全局唯一性,联合查询,甚至跨库事务等特性的支持,查询条件下推。

冷热分离

冷热分离作为表(时间维度range)拆分后的一个具体应用。根据数据访问热度进行划分。

通常按时间维度进行划分,比如最近N个月之内数据存放在性能较优的ssd,历史数据放在普通大容量sata盘。

进一步可对历史数据压缩,归档

表的分片

都到表的分片(hash,list拆分)这步了,尽量要求表的结构能不动就别折腾了,包括索引等任何ddl

分片的关键词分布键,分布键的选取至关重要。

如何尽量将数据打散,均匀的分散到多个实例中,避免数据倾斜

亲和性的要求,多个相关表的分布键需要一致。

表分片的本质就是在表级别将数据分散到多实例中,不仅缓解了单机的qps也缓解的单机的size压力。

现有方案: pgxl,pgxc, citus

注意问题: 分布键的选择,多实例之间网络的延迟。

总结

降低单实例的压力主要是从qps,size两个维度来考虑。

主要手段就是拆、拆、拆

以上从开始到最后的拆分,越向下拆分难度越大,维护难度增加,反而得到的收益越不明显。

如果前面的方案能够解决尽量不采用后面的方案。

化繁为简的思路优于具体的技术。务要以业务为核心。技术围绕业务需求,切莫本末倒置。换句来说业务能解决的不用技术来解决。

扩展 分布式数据库

  • 共享存储
  • no sharing 2pc 比如greenplum
  • gmt 集中事务比如 pgxc
  • fdw 外存储

  • 分布式计算存储

  • 分布式计算
  • 分布式存储
  • 分布式事务