每个优秀的程序员和架构师都应该掌握分库分表, 这是我的观点.
移动互联网时代, 海量的用户每天产生海量的数量, 比如:
用户表
订单表
交易流水表
以支付宝用户为例, 8 亿; 微信用户更是 10 亿. 订单表更夸张, 比如美团外卖, 每天都是几千万的订单. 淘宝的历史订单总量应该百亿, 甚至千亿级别, 这些海量数据远不是一张表能 Hold 住的. 事实上 MySQL 单表可以存储 10 亿级数据, 只是这时候性能比较差, 业界公认 MySQL 单表容量在 1KW 以下是最佳状态, 因为这时它的 BTREE 索引树高在 3~5 之间.
既然一张表无法搞定, 那么就想办法将数据放到多个地方, 目前比较普遍的方案有 3 个:
分区;
分库分表;
NoSQL/NewSQL;
说明: 只分库, 或者只分表, 或者分库分表融合方案都统一认为是分库分表方案, 因为分库, 或者分表只是一种特殊的分库分表而已. NoSQL 比较具有代表性的是 MongoDB,es.NewSQL 比较具有代表性的是 TiDB.
Why Not NoSQL/NewSQL?
首先, 为什么不选择第三种方案 NoSQL/NewSQL, 我认为主要是 RDBMS 有以下几个优点: - RDBMS 生态完善; - RDBMS 绝对稳定; - RDBMS 的事务特性;
NoSQL/NewSQL 作为新生儿, 在我们把可靠性当做首要考察对象时, 它是无法与 RDBMS 相提并论的. RDBMS 发展几十年, 只要有软件的地方, 它都是核心存储的首选.
目前绝大部分公司的核心数据都是: 以 RDBMS 存储为主, NoSQL/NewSQL 存储为辅! 互联网公司又以 MySQL 为主, 国企 & 银行等不差钱的企业以 Oracle/DB2 为主! NoSQL/NewSQL 宣传的无论多牛逼, 就现在各大公司对它的定位, 都是 RDBMS 的补充, 而不是取而代之!
Why Not 分区?
我们再看分区表方案. 了解这个方案之前, 先了解它的原理:
分区表是由多个相关的底层表实现, 这些底层表也是由句柄对象表示, 所以我们也可以直接访问各个分区, 存储引擎管理分区的各个底层表和管理普通表一样 (所有的底层表都必须使用相同的存储引擎), 分区表的索引只是在各个底层表上各自加上一个相同的索引, 从存储引擎的角度来看, 底层表和一个普通表没有任何不同, 存储引擎也无须知道这是一个普通表还是一个分区表的一部分.
事实上, 这个方案也不错, 它对用户屏蔽了 sharding 的细节, 即使查询条件没有 sharding column, 它也能正常工作 (只是这时候性能一般). 不过它的缺点很明显: 很多的资源都受到单机的限制, 例如连接数, 网络吞吐等! 虽然每个分区可以独立存储, 但是分区表的总入口还是一个 MySQL 示例. 从而导致它的并发能力非常一般, 远远达不到互联网高并发的要求!
至于网上提到的一些其他缺点比如: 无法使用外键, 不支持全文索引. 我认为这都不算缺点, 21 世纪的项目如果还是使用外键和数据库的全文索引, 我都懒得吐槽了!
所以, 如果使用分区表, 你的业务应该具备如下两个特点:
数据不是海量 (分区数有限, 存储能力就有限);
并发能力要求不高;
Why 分库分表?
最后要介绍的就是目前互联网行业处理海量数据的通用方法: 分库分表.
虽然大家都是采用分库分表方案来处理海量核心数据, 但是还没有一个一统江湖的中间件, 笔者这里列举一些有一定知名度的分库分表中间件:
阿里的 TDDL,DRDS 和 cobar,
开源社区的 sharding-jdbc(3.x 已经更名为 sharding-sphere);
民间组织的 MyCAT;
360 的 Atlas;
美团的 zebra;
备注: sharding-jdbc 的作者张亮大神原来在当当, 现在在京东金融. 但是 sharding-jdbc 的版权属于开源社区, 不是公司的, 也不是张亮个人的!
其他比如网易, 58, 京东等公司都有自研的中间件. 总之各自为战, 也可以说是百花齐放.
但是这么多的分库分表中间件全部可以归结为两大类型:
CLIENT 模式;
PROXY 模式;
CLIENT 模式代表有阿里的 TDDL, 开源社区的 sharding-jdbc(sharding-jdbc 的 3.x 版本即 sharding-sphere 已经支持了 proxy 模式). 架构如下:
client arch
PROXY 模式代表有阿里的 cobar, 民间组织的 MyCAT. 架构如下:
proxy arch
但是, 无论是 CLIENT 模式, 还是 PROXY 模式. 几个核心的步骤是一样的: SQL 解析, 重写, 路由, 执行, 结果归并.
笔者比较倾向于 CLIENT 模式, 架构简单, 性能损耗较小, 运维成本低.
接下来, 以几个常见的大表为案例, 说明分库分表如何落地!
分库分表第一步也是最重要的一步, 即 sharding column 的选取, sharding column 选择的好坏将直接决定整个分库分表方案最终是否成功. 而 sharding column 的选取跟业务强相关, 笔者认为选择 sharding column 的方法最主要分析你的 API 流量, 优先考虑流量大的 API, 将流量比较大的 API 对应的 SQL 提取出来, 将这些 SQL 共同的条件作为 sharding column. 例如一般的 OLTP 系统都是对用户提供服务, 这些 API 对应的 SQL 都有条件用户 ID, 那么, 用户 ID 就是非常好的 sharding column.
这里列举分库分表的几种主要处理思路:
只选取一个 sharding column 进行分库分表 ;
多个 sharding column 多个分库分表;
sharding column 分库分表 + es;
再以几张实际表为例, 说明如何分库分表.
订单表
订单表几个核心字段一般如下:
订单表
以阿里订单系统为例 (参考《企业 IT 架构转型之道: 阿里巴巴中台战略思想与架构实现》), 它选择了三个 column 作为三个独立的 sharding column, 即: order_id,user_id,merchant_code.user_id 和 merchant_code 就是买家 ID 和卖家 ID, 因为阿里的订单系统中买家和卖家的查询流量都比较大, 并且查询对实时性要求都很高. 而根据 order_id 进行分库分表, 应该是根据 order_id 的查询也比较多.
这里还有一点需要提及, 多个 sharding-column 的分库分表是冗余全量还是只冗余关系索引表, 需要我们自己权衡.
冗余全量的情况如下 -- 每个 sharding 列对应的表的数据都是全量的, 这样做的优点是不需要二次查询, 性能更好, 缺点是比较浪费存储空间 (浅绿色字段就是 sharding-column):
冗余全量
冗余关系索引表的情况如下 -- 只有一个 sharding column 的分库分表的数据是全量的, 其他分库分表只是与这个 sharding column 的关系表, 这样做的优点是节省空间, 缺点是除了第一个 sharding column 的查询, 其他 sharding column 的查询都需要二次查询, 这三张表的关系如下图所示 (浅绿色字段就是 sharding column):
表之间的关系图
冗余全量表 PK. 冗余关系表
速度对比: 冗余全量表速度更快, 冗余关系表需要二次查询, 即使有引入缓存, 还是多一次网络开销;
存储成本: 冗余全量表需要几倍于冗余关系表的存储成本;
维护代价: 冗余全量表维护代价更大, 涉及到数据变更时, 多张表都要进行修改.
总结: 选择冗余全量表还是索引关系表, 这是一种架构上的 trade off, 两者的优缺点明显, 阿里的订单表是冗余全量表.
用户表
用户表几个核心字段一般如下:
用户表
一般用户登录场景既可以通过 mobile_no, 又可以通过 email, 还可以通过 username 进行登录. 但是一些用户相关的 API, 又都包含 user_id, 那么可能需要根据这 4 个 column 都进行分库分表, 即 4 个列都是 sharding-column.
账户表
账户表几个核心字段一般如下:
账户表
与账户表相关的 API, 一般条件都有 account_no, 所以以 account_no 作为 sharding-column 即可.
复杂查询
上面提到的都是条件中有 sharding column 的 SQL 执行. 但是, 总有一些查询条件是不包含 sharding column 的, 同时, 我们也不可能为了这些请求量并不高的查询, 无限制的冗余分库分表. 那么这些条件中没有 sharding column 的 SQL 怎么处理? 以 sharding-jdbc 为例, 有多少个分库分表, 就要并发路由到多少个分库分表中执行, 然后对结果进行合并. 具体如何合并, 可以看笔者 sharding-jdbc 系列文章, 有分析源码讲解合并原理.
这种条件查询相对于有 sharding column 的条件查询性能很明显会下降很多. 如果有几十个, 甚至上百个分库分表, 只要某个表的执行由于某些因素变慢, 就会导致整个 SQL 的执行响应变慢, 这非常符合木桶理论.
更有甚者, 那些运营系统中的模糊条件查询, 或者上十个条件筛选. 这种情况下, 即使单表都不好创建索引, 更不要说分库分表的情况下. 那么怎么办呢? 这个时候大名鼎鼎的 Elasticsearch, 即 es 就派上用场了. 将分库分表所有数据全量冗余到 es 中, 将那些复杂的查询交给 es 处理.
淘宝我的所有订单页面如下, 筛选条件有多个, 且商品标题可以模糊匹配, 这即使是单表都解决不了的问题 (索引满足不了这种场景), 更不要说分库分表了:
条件筛选
所以, 以订单表为例, 整个架构如下:
archeitecture
具体情况具体分析: 多 sharding column 不到万不得已的情况下最好不要使用, 成本较大, 上面提到的用户表笔者就不太建议使用. 因为用户表有一个很大的特点就是它的上限是肯定的, 即使全球 70 亿人全是你的用户, 这点数据量也不大, 所以笔者更建议采用单 sharding column + es 的模式简化架构.
es+HBase 简要
这里需要提前说明的是, Solr+HBase 结合的方案在社区中出现的频率可能更高, 本篇文章为了保持一致性, 所有全文索引方案选型都是 es. 至于 es+HBase 和 Solr+HBase 孰优孰劣, 或者说 es 和 Solr 孰优孰劣, 不是本文需要讨论的范畴, 事实上也没有太多讨论的意义. es 和 Solr 本就是两个非常优秀且旗鼓相当的中间件. 最近几年 es 更火爆:
es V.S. Solr
如果抛开选型过程中所有历史包袱, 单论 es+HBase 和 Solr+HBase 的优劣, 很明显后者是更好的选择. Solr+HBase 高度集成, 引入索引服务后我们最关心, 也是最重要的索引一致性问题, Solr+HBase 已经有了非常成熟的解决方案一一 Lily HBase Indexer.
来源: https://juejin.im/entry/5bf751b1e51d4541902f5f4f