mysql-hbase存储引擎插件实现大容量数据存储

最近把hbase-storage-plugin代码分享到github 上,为了记录笔者当时的思路,所以写了这篇文章。

1、初衷:做一个集中的大容量存储引擎

1.1、起因

自从进入公司运维部以后,虽然一直在做开发的工作,但是跟DBA同学可以“亲密”接触,从而可以体会到个中的各种酸甜苦辣。在我们这边,DBA同学遇到的很多告警是磁盘空间告警,半夜起来处理这种故障实在是让人狼心。
在处理这种磁盘故障的过程中,发现很多业务库中存储了日志型的数据,定期就需要删除,近期的数据访问就不是很频繁,至于很多历史数据,就更是很少访问了。
考虑存在冷热数据的不同,一直琢磨在MySQL基础上实现一个大容量存储引擎。热数据用Innodb存储,等它变成冷数据,就改成大容量引擎。

1.2 第一次尝试

刚开始笔者考虑基于HDFS上做一个MySQL存储引擎,由于HDFS文件不能修改。正好利用LevelDB的存储特性,只会生成文件,而不会修改文件,于是改造了LevelDB的代码,让LevelDB运行在HDFS之上,然后基于LevelDB做了一个MySQL存储引擎。
这样在开发环境可以跑起来了,但是实际运行中,经常出现内存问题,因为HDFS的C-API是基于JVM的,没有纯C的库,内存问题无法解决,最后只能放弃。
因为hbase提供了一个thrift服务,可以支持c++语言,而且hbase天然有索引特性,这样我们在实现主键功能时会非常简单,所以最后敲定了hbase。

1.3、打算解决的问题

如果我们有了hbase这样一个海量的MySQL存储引擎,我们就可以解决以下几个难题。

1、冷热数据采用不同引擎

如下图所示:把近期的热数据先用Innodb引擎存储,随着时间的推移,逐步把一些老数据表,通过alter table 表名 engine hbase改成用Hbase来存储。

code-hot-data.png

通过这种方式,可以在数据的高效访问与数据保存周期上达到双赢,重复利用了Innodb的性能和hbase海量容量的特性。

2、主从库采用不同引擎
主从库采用不同的引擎,在主库中采用Innodb,并且只保留近期数据。从库中用hbase引擎存储所有数据,历史数据从主库删除的时候,不删除从库中的表。
这样也可以达到数据长期保存的效果,而且还可以防止因hbase引擎代理问题,影响线上业务。

master-slave.png

3、集中存储,数据共享

一套Hbase存储多套业务数据,甚至于,可以让不同业务访问相同的Hbase的表。一个业务的表也轻松的转移到另外一个业务中来。

sharingstorage.png

2、hbase存储引擎的开发

2.1 主数据存储格式

首先,每一张MySQL表,对应在Hbase中建立一张对应的表,所以在MySQL的增删改查都会对应到Hbase表中的操作。
Bbase只有一个rowkey用来定位数据,而MySQL的键可以有多个字段组成,为了实现键查询和键 前缀查询,笔者首先按照MySQL主键字段顺序逐个组织成一个字节数组,也就是最后要存储到Hbase中的RowKey。

rowkey.png

MySQL中主键的字段类型,这里只列了整数型和字符串型,开发Hbase存储引擎的时候,笔者只支持以下的数据类型成为主键:

MYSQL_TYPE_LONG
MYSQL_TYPE_LONGLONG
MYSQL_TYPE_TINY
MYSQL_TYPE_SHORT
MYSQL_TYPE_INT24
MYSQL_TYPE_TIME
MYSQL_TYPE_DATETIME
MYSQL_TYPE_TIMESTAMP
MYSQL_TYPE_VAR_STRING
MYSQL_TYPE_VARCHAR
MYSQL_TYPE_BIT
MYSQL_TYPE_STRING

这些字段类型除了后面的4个是字符串以外,前面的都可以转换成为整数型数据。按照图中的格式存储主键,主要是为了实现键字段数据还原、键顺序查询(order by)等功能。

由于hbase支持字段,所以数据字段就按照hbase的字段来存储。

由于Hbase天然具有顺序,所以笔者按照主键存储在Rowkey,数据字段存储在hbase的列中,这样主数据存储了根据主键定位数据的能力,所以Hbase引擎表是一种列簇表。从代码中我们就可以看出来:

virtual bool primary_key_is_clustered() { return TRUE; }

2.2 第二索引功能

第二索引功能的实现有赖于Hbase对一个表有批量写操作的支持,下面我们先看一下Hbase支持的批量写操作API。

/**
* Performs multiple mutations atomically on a single row. Currently
* {@link Put} and {@link Delete} are supported.
*
* @param rm object that specifies the set of mutations to perform atomically
* @throws IOException
*/
void mutateRow(final RowMutations rm) throws IOException;

这个API可以保证这些变更操作的原子性,基于这个保证,笔者就能够轻易的实现第二索引功能了。

2.2.1 第二索引存储格式

为了保证操作的原子性,笔者把第二索引的存储也存储在主数据对应的这张Hbase表中,格式为:
RowKey:格式是有组成键值的字段按照顺序组成
entry:key 字段存储了主键的数据。

2.2.2 第二索引数据变更

在增删改查时,和主数据一起生成一批Mutation,在Hbase中一次性对表进行操作,从而保证了原子性。

2.3.2 TODOList

开源的代码中实现了唯一性的第二索引,对于非唯一的第二索引,可以考虑把重复的键值存放在相同的第二索引Rowkey下。

2.3 批量数据插入

MySQL存储引擎提供了很多优化的操作能力,譬如批量数据插入,当我们load数据、批量插入或者做一些表变更(如:更换存储引擎)的时候,会用到批量数据操作。
批量数据操作会先缓存一些数据行,当达到缓存大小时,把这些数据一次性的写入底层存储中,这里也利用了Hbase的批量操作能力。

2.4 基于主键的查询优化

当一条SQL语句中,指定了所有的主键字段的情况下, 这时候,是可以避免采用范围查询,而是直接采用基于rowkey的定位查询功能的。笔者实现了下面的函数:
virtual int index_read_idx_map(uchar * buf, uint index, const uchar * key,
key_part_map keypart_map, enum ha_rkey_function find_flag);
在这个函数中,是直接调用了ScannerID HbaseClient::scannerOpenWithScan(const Text& tableName, const TScan& scan, const std::map<Text, Text> & attributes)函数来快速定位到主键的。

2.5 其他

由于MySQL实例访问Hbase是通过网络来访问的,所以这里做一些底层的优化处理,如:连接池、连接重建等,还有很多优化的空间。

3、改造thrift server

开发完引擎以后,与hbase一起联调,一旦建立几个连接,后续的连接请求就无法服务了,主要原因是thrift server才用了传统的半同步半异步设计模式,每个新的连接,会启动一个独立的线程来为它服务,一旦线程用完就无法再为后续的连接请求服务了。

如何解决这个问题呢,可以把这种模式改造成反应器设计模式,就能够提供高并发的服务了。
于是基于swift重新实现了hbase的thrift server,swift是一套基于netty实现的thrift服务框架,开发的步骤主要是:
1)基于thrift协议文件,生成服务框架:

java -jar .\swift-generator-cli-0.19.3-standalone.jar -override_package org.apache.hadoop.hbase.swift.generated -use_java_namespace org\apache\hadoop\hbase\thrift\Hbase.thrift -out ..\java

2)在生成的框架中实现hbase的访问逻辑。
3)重写thrift server之后,还有一个好处是我们可以扩展thrift server的能力,笔者在原有的API的基础上添加了几个API,如下图所示:

thrift-api-change.png

有了这些api,我们就可以利用它们来实现一些额外的功能,如:更改引擎,truncate table语法等。

有兴趣研究swift的可以看一下笔者很早以前记录的一篇文章(今天放到简书):http://www.jianshu.com/p/49c619d33307

4、总结

笔者在公司内部没有采用这个方案,最终选择了mariadb来解决这种日志型存储的问题,日志性的表可以选择tokudb引擎,一般能达到4倍以上的压缩比,好的情况下可以达到10倍。在公司现有业务场景下基本上能解决绝大多数问题了。毕竟Mariadb的成熟度高,使用广,稳定性好。当然仍然无法解决海量的存储问题。

后来笔者基于思路完成了大部分代码,近期把它开源了放在了github上:
https://github.com/herry2038/mysql-hbase-storage-plugin
主要是笔者觉得hbase这个思路不错,一方面交流学习,另一方面希望有机会能继续完善项目。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,163评论 6 498
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,301评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,089评论 0 352
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,093评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,110评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,079评论 1 295
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,005评论 3 417
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,840评论 0 273
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,278评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,497评论 2 332
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,667评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,394评论 5 343
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,980评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,628评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,796评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,649评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,548评论 2 352

推荐阅读更多精彩内容