MySQL与HBase说到最核心的点,是一种数据存储方案。方案本身没有对错、没有好坏,只有合适与否。相信多数公司都与MySQL有着不解之缘,部分学校的课程甚至直接以SQL语言作为数据库讲解。我想借自身经历,先来谈谈MySQL应用的演化。
只有MySQL
笔者之前曾在一家O2O创业公司工作,公司所有数据都存储在同一个MySQL里,而且没有任何主备方案。相信这是很多初创公司会用到的一个典型解决办法,当时这台MySQL为用户、订单、物流服务,同时也为线下分析服务。
单实例的问题:
主从方案
随着业务增加,单个DB是无法承载这么多请求的。于是就有了主从复制、读写分离的解决方案。
master只负责写请求,slave同步master用来服务读请求:
为了扩展读能力可以增加多个slave;
允许slave同步有一定的延迟;
一致性要求严格的,可以指定读主库。
主从功能的问题:
需要增加管理Proxy层,分配写请求、读请求;
节点故障:其它节点应该快速接管故障节点的功能。
垂直拆分
业务继续增长,master甚至无法承载所有的写请求,数据库需要按业务拆分。
垂直拆分的问题:
水平拆分
业务继续增长,订单表有大量的并发写入,而且已经有了几千万行数据。
参考:大众点评订单系统分库分表实践
https://zhuanlan.zhihu.com/p/24036067
水平分库/分表带来的问题:
MySQL的主要瓶颈,单机单进程。CPU有限、内存/磁盘功能、连接数有限、网卡吞吐有限……
集群的限制点:
关系型数据库,纵向的外键相互join;
范式参考链接:https://zhuanlan.zhihu.com/p/20028672
数据库事务性,基于单机的锁机制,无法扩展到集群中使用;
全局有序列性基于B+树,数据有序聚合存储,集群化后无法保证;
数据本地存储,扩容需要迁移数据。
集群的方案:
放弃部分功能,辅助索引检索、join、全局事务性、聚合函数等;
水平拆分:存储KV化,用机械的map思路实现集群;
扩容方案:手动导数据,开发数据迁移脚本;
事务性:两阶段事务、paxos、单库事务……
备份容灾:从节点同步主节点,但有一定的数据延迟;
服务稳定性:主节点挂了,Proxy会将从节点升级为主节点;从节点挂了会被其它从节点替换。
水平拆分:
单个region过大,RegionServer会将region均分为两个(自动、手工)。然后更新.META.表。
扩容方案:
RegionServer向HMaster汇报状态。HMaster为RegionServer负载均衡,调整其负责的region 。
增/删RegionServer后,会为重新调整region的分配方式。
服务稳定性:
RegionServer只是计算单元,挂掉后Hmaster可以随便再找一个节点代替坏节点服务。
事务性:
HBase只保证行级事务,单行数据肯定存在同一台机器(单机事务很好做)。
备份容灾:
HBase:
Client会通过Zookeeper定位到 .META. 表;
根据 .META. 查找需要服务的RegionServer,连接RegionServer进行读写;
Client会缓存 .META. 表信息,下次可以直接连到RegionServer 。
MySQL:
Rquest的路由流程,MySQL与HBase基本一致,那么RegionServer与MySQL的性能差异如何呢?
新增
为什么MySQL建议自增主键?(MySQL随机插入的代价)
辅助索引,插入基本是随机的:
HBase可以随机插入:
HBase的所有插入只是写入内存memstore,只保证内存数据的有序即可(很快、很容易);
为防止数据丢失写入memstore前,先写入wal(可以关闭,速度更快);
HBase没有辅助索引需要维护;
memstore写满了,申请一块新的内存,旧的memstore被后台线程刷盘,存入HFile。
修改
MySQL数据变化引起存储变动:
HBase直接将变化写入到memstore,没有其它开销。
删除
MySQL数据删除:
直接操作B+树的节点,肯定需要刷新磁盘;
如果引起树结构变化,甚至可能需要多次刷新磁盘。
HBase只是在memstore记录删除标记,没有其它开销。
HBase写入内存+后台刷盘(最多是WAL,磁盘顺序写);MySQL需要维护B+树,大量的磁盘随机读写。
MySQL要求尽量追加写(自增 ID),速度较慢;HBase可以随机插入,速度很快。
MySQL读得快
MySQL数据是本地存储的,HBase是基于HDFS,有可能数据不在本地。
B+ 树天然的全局有序
HBase只有局部信息,没有辅助索引
查询会优先查找memstore,如果没有会查找Hfile(存储结构类似B+树)。如果第一个Hfile中没有所需的信息,则需要去第二、第三个Hfile中查询;如果查询的数据恰好在memstore,第一个Hfile,HBase会优于MySQL;平均下来,HBase读性能一般。减少Hfile数据以提速,小的HFile合并成大的HFile文件。这种存储结构叫LSM树(Log-structured merge-tree);
如果需要检索特定的列,可能需要遍历所有Hfile,成本巨高。
MySQL成也B+,败也B+;HBase成也LSM,败也LSM。
B+ 树
查询“值为25”的节点,只需要2次定位即可。
LSM树
查询“值为25”的节点,只需要4次定位即可。
异步化
后台线程将memstore写入Hfile;
后台线程完成Hfile合并;
wal异步写入(数据有丢失的风险)。
数据就近
blockcache,缓存常用数据块:读请求先到memstore中查数据,查不到就到blockcache中查,再查不到就会到磁盘上读,把最近读的信息放入blockcache,基于LRU淘汰,可以减少磁盘读写,提高性能;
本地化,如果Region Server恰好是HDFS的data node,Hfile会将其中一个副本放在本地;
就近原则,如果数据没在本地,Region Server会取最近的data node中数据。
快速检索
基于bloomfilter过滤:
基于timestamp过滤:
HFile存储结构:
HFile存储格式
参考链接:
https://link.zhihu.com/?target=https%3A//blog.csdn.net/yangbutao/article/details/8394149
Trailer存储整个Hfile的定位信息;
DataIndex存储Data块的索引信息:Data存储为一组磁盘块,存储数据信息;DataIndex功能类似于B+树的非叶子节点;Data每个磁盘块中的数据按key有序,加载到内存后可以用二分查找定位;Key按行 + 列族 + 列 + 时间戳生成,按字典序排序(最佳查询方式:最左匹配);
MetaIndex存储Meta的索引信息,Meta存储一系列元信息;MetaIndex功能类似于B+树的非叶子节点;Meta存储bloomfiler等辅助信息。
查询缓存
将SQL执行结果放入缓存。
缓存B+高层节点
一千万行的大表,一般只需要一棵3层的B+树,其中索引节点 (非叶子节点) 的大小约20MB。完全可以考虑将大部叶子节点缓存,基于主键查询只需要一次IO。
减少随机写——缓冲:延迟写/批量写
上节提到,B+树通过自增主键大量减少随机插入。由于辅助索引的存在,插入、修改、删除操作,辅助索引可能引起大量的随机IO。
减少随机读——MRR
SELECT * FROM t WHERE key_part1 >= 1000 AND key_part1 < 2000 AND key_part2 = 10000;
# 普通操作分解:
key_part1= 1000, key_part2=1000, id = 1
select * from t where id=1
key_part1= 1001, key_part2=1000, id = 10
select * from t where id=10
...
# MRR 操作分解:
SELECT * FROM t WHERE key_part1 >= 1000 AND key_part1 < 2000 AND key_part2 = 10000;
key_part1= 1000, key_part2=1000, id = 1
buffer.append(1)
key_part1= 1000, key_part2=1000, id = 10
buffer.append(10)
...
sort(buffer)
select * from t where id in (buffer)
索引下推
InnoDB的辅助索引
B+树全局有序,叶子节点存储的是主键。基于辅助索引定位主键,再用主键定位数据。MySQL水平切分后,没办法跨库维持建立全局有序索引:
HBase相同问题