分库分表项目实战

169 阅读6分钟

这是我参与2022首次更文挑战的第16天,活动详情查看:2022首次更文挑战

背景

  • 业务数据比较多,而且历史数据不能够直接删除
    • 数据审查需要
    • 业务场景可能会用到较为旧的数据(至少为1年)
    • 数据较为敏感,尽可能地不接入公司内的其他数据平台
    • 目前数据已经达到了单库的上限
  • 业务场景分时严重
    • 月末的时候产生、使用大量数据
    • 峰值时,给服务以及数据库带来很大压力
      • 数据库压力过大,出现invalid connection
      • 业务大量重试给到数据库更大的压力
  • 冷热数据分布明显
    • 当月数据会大量访问,历史数据访问量很少
    • 尽管已经按照月份的形式进行分表,但是对于冷热数据问题并没有很好的解决
      • 依旧会集中打当前月表

因此我们需要一个方案来解决以上问题

  • 数据量问题
    • 需要更大的容量,并且可以随着业务发展、在业务无感知的情况下进行扩容
  • 第二点和第三点都是数据访问上的问题,会给数据库带来比较大的读写压力
    • 多点的方式分担数据读写压力

方案选型

存储方案选型

小结

通过上述比较,使用分片库能够带来的提升最大

  • 两者虽然看上去都是无限容量的
    • 但是冷热库增加的库对于业务并不是无感知的
      • 每次增加新的冷热库都需要业务方手动加上新的库
      • 业务需要自己实现路由到不同库的逻辑,当库发生变动的时候,需要自己进行路由适配
    • 分片库在数据库侧做了封装
      • 只要带上分库键,其实现的路由策略会自动路由到对应的库,当数据库进行扩容的时候对业务侧无感知
      • 初次接入的业务方也有一定的改造量
        • 需要确保查询语句带上了分库键以保证最高性能
        • 更新、创建语句大多都已经带上了分库键
      • 一旦数据写入,不允许修改分库键的值
      • 部分问题以及限制在第二部分讲
  • 性能方面
    • 分片库由于是多库,在方案合理的情况下可以提升较多性能
      • 不同库同表的并发写入
      • 读也会分发到多个库,降低同库同表的读写锁冲突
  • 两者都不需要对原有的mysql driver进行更新

从结论上来说选择分库方案

  • 更好的可扩展性
  • 更好的性能
  • 可接受的修改量

分片库方案设计

限制

从增删改查来说:

增:

  • 单记录
    • 需带上分库键
  • 多记录
    • 分片库之间没有实现分布式事务,不提供原子性,需要业务上来保证,使得批量新增失败后的结果是符合预期的
    • 不支持跨分片库的prepare多行插入,支持同个分片库内的prepare多行插入
      • 什么是prepare插入
    • 不同分片库内的主键id并不能保证全局唯一,不同库同表的id会重复

删:

  • 单记录
    • 可不带上分库键,带上会有更好的性能

改:

  • 单记录
    • 不可修改分库键的值
  • 多记录
    • 同样不能保证跨库的事务,业务上需要自己实现对应的原子性保证逻辑

查:

  • 单记录
    • 不支持跨分片库的distinct操作
    • 查询需要带上分库键
  • 多记录
    • 由于查询需要带上分库键,in查询需要拆分每次查询的分库键,由in变成==

总结

  • 若一个SQL本身就只会在一个分片库内执行,且带上了分库键,那么不需要任何修改
  • 跨多个库的操作得不到保障,需要业务上做处理
  • 自增id在不同库同表中并不能保障全局唯一性
  • 需要带上分库键

分库键设计

现状

可以看到目前的存储结构为

  • 按月分表
  • 当月数据操作量较大
  • 每个表中的数据可以按活动维度进行划分
    • 活动有多条数据
    • 同活动内数据同时使用的概率较大,较少出现跨活动使用的情况

分库方案

按月分库

  • 可以解决扩容问题
  • 无法解决冷热数据访问问题
  • 由于现在是按月分表,表字段中不存在月份字段,需要新增一个月份字段,用于分库
    • 历史数据需要新增该字段,并刷上值
    • 所有数据库操作均带上月份字段

按活动分库

  • 可以解决扩容问题
  • 通过hash活动ID的方式,将同表的不同活动数据分散到不同的库
    • 流量更加均匀的打到不同库上,可以实现负载均衡
      • 锁开销降低
      • 读写吞吐增大
      • 提高数据库缓存命中率
    • 表需新增活动ID列
      • 需要刷上新的列值
    • 所有的操作需要带上分库键
  • 同表的主键ID不能保证唯一性
    • 不会影响业务

小结

使用按活动ID进行分库会更好

代码修改

上线以及回滚操作

部署过程

  1. 需要先在单库上新增活动ID字段
  2. 使用工具给活动ID字段刷值
  3. 申请分片库实例
  4. 分片库建表
  5. 使用数据同步工具全量+增量模式将单库数据全量同步到
  6. 将单库设置为只读
    1. 需要单库停写,需要和业务方对齐
  7. 调整代码数据库连接配置,部署服务
  8. 观察数据库读写流量
    1. 读写流量都在分片库

能否去除第6步?或者去除停机过程?这一步的目的是什么?

回滚方案

需要注意的是分库不能保证同表不同库的主键ID全局唯一,在回滚、同步分片库到单库的时候会出现主键ID冲突的问题,需要自己使用分布式ID生成工具生成ID,插入数据时显示声明ID值

  • 使用分布式ID生成工具生成ID
  • 配置分片库到单库的同步工具、同步规则
  • 需要回滚时,直接回滚线上实例到上一个版本即可,重置为连接单库