流量包业务模型与数据量预估
简介:梳理账号服务里流量包的业务模型,预估数据量,引出分库分表的需求
-
业务模型
- 创建短链要消耗流量包,流量包是对外售卖的商品,可以叠加购买
- 一个用户会有多条流量包记录,类似订单记录
-
流量包商品
| 商品 | 每天可创建 | 有效期 |
|---|---|---|
| 流量包一 | 5 次 | 1 个月 |
| 流量包二 | 10 次 | 6 个月 |
| 流量包三 | 50 次 | 12 个月 |
- 用户行为与流量包记录
| 用户行为 | 生成的流量包记录 |
|---|---|
| 刚注册 | 免费:每天 2 次,不过期 |
| 购买商品一 | 付费:每天 5 次,1 个月过期 |
| 购买商品二 × 3 份 | 付费:每天 30 次(10 × 3),6 个月过期 |
- 表结构
CREATE TABLE `traffic` (
`id` bigint unsigned NOT NULL AUTO_INCREMENT,
`day_limit` int DEFAULT NULL COMMENT '每天限制多少条,短链',
`day_used` int DEFAULT NULL COMMENT '当天用了多少条,短链',
`total_limit` int DEFAULT NULL COMMENT '总次数,活码才用',
`account_no` bigint DEFAULT NULL COMMENT '账号',
`out_trade_no` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin DEFAULT NULL COMMENT '订单号',
`level` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin DEFAULT NULL COMMENT '产品层级:FIRST青铜、SECOND黄金、THIRD钻石',
`expired_date` date DEFAULT NULL COMMENT '过期日期',
`plugin_type` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin DEFAULT NULL COMMENT '插件类型',
`product_id` bigint DEFAULT NULL COMMENT '商品主键',
`gmt_create` datetime DEFAULT CURRENT_TIMESTAMP,
`gmt_modified` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
UNIQUE KEY `uk_trade_no` (`out_trade_no`,`account_no`) USING BTREE,
KEY `idx_account_no` (`account_no`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
-
数据量预估
- 用户量由产品 / 运营预估:按免费流量(软文、内容平台推广)和付费流量(广告平台投放)估算每月新增,再乘以月数
- 未来 2 年累计 500 万用户,每个用户每年约 10 条记录,总量约 5000 万条
- 单表数据量最好不超过 1000 万,所以至少分 5 张表
- 进一步按水平分表的思路,表数量取 2、4、8、16 张
- 实战里业务逻辑复杂,先分 2 张表
- 哈希取模方式下,分 2 张和 4 张的编码复杂度差别不大,但表越多调试越麻烦
-
注意点
- 容量尽量一次性预估好,数据入库后再扩容要做数据迁移,成本大
- 估不准时可以多分几张表,多几张表影响不大
数据库性能优化思路(面试题)
简介:单表 1000 万数据,未来 1 年再增长 500 万,查询变慢,说出优化思路
- 回答顺序
- 千万不要一上来就说分库分表
- 1000 万 ~ 2000 万的数据量并不大,靠软硬优化足够解决,涨到 3000 万也能撑住
- 先反问业务场景,再从“不分库分表”和“分库分表”两个角度回答
- 千万不要一上来就说分库分表
-
图中绿色是优先做的不分库分表方案,红色是最后才考虑的分库分表
-
不分库分表
| 类型 | 做法 |
|---|---|
| 软优化 | 数据库参数调优(缓存、连接池等) |
| 软优化 | 分析慢查询 SQL 和执行计划,改写 SQL 和程序 |
| 软优化 | 优化索引结构、优化表结构 |
| 软优化 | 引入 NoSQL、调整程序架构 |
| 硬优化 | 提升硬件:带宽、CPU 核数、内存、机械硬盘换固态硬盘 |
-
软优化里最关键的一项:引入 NoSQL 和程序架构调整
- 读写分离
- 多数业务读多写少,一主多从,读请求走从库,写请求走主库
- 问题:主从复制有延迟,要看业务能否接受
- 引入 NoSQL
- 把数据同步到 Elasticsearch 做宽表,复杂的关联查询直接查宽表
- 同样有同步延迟
- 读写分离
-
分库分表
- 没有通用策略,根据业务场景选择(外卖、物流、电商都不一样)
- 先看只分表能否满足业务需求和未来增长
- 分表解决单表数据量大时的查询效率问题
- 分表仍在同一个库上操作,CPU、内存、IO 没变,提升不了并发
- 只分表满足不了,再分库和分表一起做
-
注意点
- 分片策略选错会产生数据热点
- 外卖按城市分库:一线城市的库数据量和访问量远大于小城市,瓶颈集中在少数库,其余库浪费资源
- 按时间范围分库:产品爆发期注册的活跃用户都集中在同一个库
- 要问清楚“慢”是 SQL 响应慢还是并发上不去,只是单表数据量大就先分表
- 分片策略选错会产生数据热点
-
结论
- 数据量和访问压力不是特别大:优先考虑缓存、读写分离、索引等方案
- 数据量极大且业务持续快速增长:再考虑分库分表
分库分表解决的问题
简介:分库分表能突破数据库自身的瓶颈,以及服务器 IO、CPU 的瓶颈
-
图中红色是分库后的库,绿色是每个库内再分出的表
-
解决数据库本身的瓶颈
- 连接数不够:连接过多时报
too many connections- 原因是访问量太大,或数据库设置的最大连接数太小
- 多个库共用一个 MySQL 实例时共享连接数,每个服务节点的连接池又各占几十个连接,多启动几个节点就占满
- 最大连接数可以调大,但调得过大同样有瓶颈
- 分表解决单表海量数据的查询性能问题,分库解决单台数据库的并发访问压力问题
- 例:用户表 1000 万数据分成 4 张表,每张 250 万,单次查询面对的数据量大幅下降
- 连接数不够:连接过多时报
-
解决系统本身的 IO、CPU 瓶颈
| 瓶颈 | 表现 |
|---|---|
| 磁盘读写 IO | 热点数据太多,即使用了数据库自身的缓存,仍有大量 IO,SQL 执行慢 |
| 网络 IO | 请求的数据多、传输量大,带宽不够,链路响应时间变长 |
| CPU | 单机做复杂 SQL 计算(多表关联)时 CPU 使用率高,还有扫描行数大、锁冲突、锁等待 |
-
分表还能缩小锁的影响范围
- 单表 1000 万数据时,一次锁表影响所有请求
- 分成 4 张表后,只影响落到被锁那张表的请求
-
注意点
- 分库后每个库放到不同服务器,才能获得更多的 CPU、内存、带宽;前期为了节省服务器可以先放在同一台
分库分表带来的新问题
简介:分库分表不是万能方案,拆分后会多出 6 类问题
| 问题 | 说明 |
|---|---|
| 跨节点 Join 和多维度查询 | 拆分前多表关联用 SQL join 就能实现,拆分后数据分布在不同节点,join 很麻烦 |
| 分布式事务 | 一次操作的内容分布在不同库,不可避免出现跨库事务;例:商品库扣库存成功、订单库生成订单失败,怎么回滚 |
| 排序、翻页、函数计算 | 跨节点多库查询时,limit 分页、order by 排序都会出问题 |
| 全局主键重复 | 自增 id 在不同库表中各自增长,多张表都会有 id = 1 的记录 |
| 二次扩容 | 首次预估很难覆盖未来 3 ~ 5 年,业务发展快就满足不了存储,需要多次扩容 |
| 技术选型 | 分库分表中间件较多,各有优势和短板 |
- 多维度查询的例子
-
图中绿色的查询只落到一个库,橙色的查询要把所有库查一遍
-
不同维度查数据,用到的分片键(partition key)不一样
- 订单表的分片键是
user_id:用户下单、查自己的订单列表,都固定落到同一个库 - 商家查自己店铺的订单列表很麻烦:订单分布在不同的数据节点
- 下单用户的
user_id各不相同,只能把所有库都查一遍,库越多、商家越多越撑不住
- 下单用户的
- 订单表的分片键是
-
排序分页为什么更复杂
- 排序字段不是分片字段时,要先在各个分片节点排序并返回,再把结果集汇总后二次排序
- 例:查前 10 条,每个分片都要先取 10 条,汇总后再排序取前 10 条
- 带来更多的 CPU、IO 消耗
- 排序字段不是分片字段时,要先在各个分片节点排序并返回,再把结果集汇总后二次排序
垂直分表与垂直分库
简介:垂直拆分按“列”和“业务”拆,解决字段过多和单库资源瓶颈
- 垂直分表
- 需求:商品表字段太多,每个字段访问频次不一样,浪费 IO 资源
- 大表拆小表,基于列字段进行
- 访问频次低、字段大的商品描述信息放一张表
- 访问频次高的商品基本信息放一张表
- 拆分原则
- 不常用的字段单独放一张表
text、blob等大字段拆出来放在附表- 业务上经常组合查询的列放在同一张表
- 例子:商品列表页只展示标题、封面、价格等基本信息,进入详情页才加载课前须知、富文本详情,所以商品拆成主表和附表
- 拆分前这些字段都在
product一张表里
- 拆分前这些字段都在
-- 拆分后:主表,访问频次高的基本信息
CREATE TABLE `product` (
`id` int(11) unsigned NOT NULL AUTO_INCREMENT,
`title` varchar(524) DEFAULT NULL COMMENT '视频标题',
`cover_img` varchar(524) DEFAULT NULL COMMENT '封面图',
`price` int(11) DEFAULT NULL COMMENT '价格,分',
`total` int(10) DEFAULT '0' COMMENT '总库存',
`left_num` int(10) DEFAULT '0' COMMENT '剩余',
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;
-- 拆分后:附表,访问频次低的大字段
CREATE TABLE `product_detail` (
`id` int(11) unsigned NOT NULL AUTO_INCREMENT,
`product_id` int(11) DEFAULT NULL COMMENT '产品主键',
`learn_base` text COMMENT '课前须知,学习基础',
`learn_result` text COMMENT '达到水平',
`summary` varchar(1026) DEFAULT NULL COMMENT '概述',
`detail` text COMMENT '视频商品详情',
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;
- 垂直分库
- 需求:C 端项目里,单个数据库的 CPU、内存长期处于 90% 以上,数据库连接经常不够
- 按业务拆分:一个系统中的不同业务各用各的库
- 拆分前全部落在单一的库上,单库处理能力是瓶颈,还受磁盘空间、内存、TPS 等限制
- 拆分后不同库不再竞争同一台物理机的 CPU、内存、网络 IO、磁盘
- 高并发场景下,一定程度上能突破 IO、连接数和单机硬件资源的瓶颈
- 解决业务层面的耦合,业务清晰,方便管理和维护
- 单体项目升级改造为微服务项目,就是垂直分库
-
图中红色是各个微服务独立的库
-
注意点
- 垂直分库分表可以提高并发,但没有解决单表数据量过大的问题
水平分表与水平分库
简介:水平拆分按“行”拆,表结构不变,解决单表数据量过大和单库资源瓶颈
- 垂直与水平的区别
- 都是大表拆小表
- 垂直分表:拆表结构
- 水平分表:拆数据
- 垂直是竖着切(切列),水平是横着切(切行)
-
图中绿色是拆分后的表和库,结构相同、数据不同
-
水平分表
- 需求:一张表的数据达到几千万,查询一次耗时长
- 把一张表的数据分到同一个库的多张表中,每张表只有部分数据
- 每张表结构一样、数据不一样,所有表的数据合起来就是全部数据
- 针对数据量巨大的单表(如订单表),按某种规则(RANGE、HASH 取模等)切分到多张表
- 减少锁表时间:没分表前执行 DDL(如添加一列)会锁表,期间所有读写只能等待
- 分表后改其中一张表,其他表不受影响
- 局限:这些表仍在同一个库,单库操作还是有 IO 瓶颈,主要解决单表数据量过大的问题
-
水平分库
- 需求:高并发项目中,水平分表后仍在单个库上,一个库的 CPU、内存、带宽限制导致响应慢
- 把同一个表的数据按一定规则分到不同的数据库,数据库在不同的服务器上
- 是对数据行的拆分,不影响表结构
- 每个库的结构都一样,数据都不一样,没有交集,所有库的并集就是全量数据
- 水平分库的粒度比水平分表更大
- 可以和水平分表一起用(每个库里再分表),同时解决单机瓶颈和单表数据量过大的问题
-
四种拆分方式对比
| 方式 | 拆分依据 | 解决的问题 | 局限 |
|---|---|---|---|
| 垂直分表 | 按列(字段冷热、大小) | 字段多、大字段浪费 IO | 单表行数没变 |
| 垂直分库 | 按业务 | 单库连接数、CPU、内存、IO 瓶颈和业务耦合 | 单表数据量过大没解决 |
| 水平分表 | 按行(RANGE、HASH 取模) | 单表数据量过大 | 仍在同一个库,有单库 IO 瓶颈 |
| 水平分库 | 按行,分到不同服务器的库 | 单库 CPU、内存、带宽瓶颈 | 引入跨库查询、分布式事务等问题 |
面试/考试记忆点
- 数据库优化不要一上来就分库分表:先软优化、硬优化,再分表,最后才分库分表
- 数据量和访问压力不大时,优先考虑缓存、读写分离、索引
- 分表解决单表数据量大的查询性能问题,分库解决单库的并发访问压力问题
- 分库分表没有通用策略,分片策略选错会造成数据热点
- 分库分表带来 6 类问题:跨节点 Join 和多维度查询、分布式事务、排序分页、全局主键、二次扩容、技术选型
- 垂直分表按列拆(冷热字段分离),垂直分库按业务拆(单体改微服务)
- 水平拆分按行拆:结构相同、数据不同、合起来是全量
- 垂直拆分不解决单表数据量过大,水平分表不解决单库资源瓶颈
- 容量一次预估到位,单表数据量不超过 1000 万