MySQL:关系型数据库,主要面向OLTP,支持事务,支持二级索引,支持sql,支持主从、Group Replication架构模型(本文所有以Innodb为例,不涉及别的存储引擎)。javascript
HBase:基于HDFS,支持海量数据读写(尤为是写),支持上亿行、上百万列的,面向列的分布式NoSql数据库。自然分布式,主从架构,不支持事务,不支持二级索引,不支持sql。html
ElasticSearch:ES是一款分布式的全文检索框架,底层基于Lucene实现,虽然ES也提供存储,检索功能,但我一直不认为ES是一款数据库,可是随着ES功能愈来愈强大,与数据库的界限也愈来愈模糊。自然分布式,p2p架构,不支持事务,采用倒排索引提供全文检索。java
假设有这样一张人员信息表:node
MySQL中要提早定义表结构,也就是说表共有多少列(属性)须要提早定义好,而且同时须要定义好每一个列所占用的存储空间。数据以行为单位组织在一块儿的,假如某一行的某一列没有数据,也须要占用存储空间。git
HBase则是以列为单位存储数据,每一列就是一个key-value,HBase的表列(属性)不用提早定义,并且列能够动态扩展,好比人员信息表中须要添加一个新的“address”字段,MySQL须要提早alter表,HBase的话直接插入便可。web
ES比较灵活,索引中的field类型能够提早定义(定义mapping),也能够不定义,若是不定义,会有一个默认类型,不过出于可控性考虑,关键字段最好提早定义好。(Solr中必须提早定义好schema.xml文件)redis
上图简单的展现了数据在MySQL和HBase中存储差别(和真实的状况还有差距),能够看到即便第二条记录的sex字段为空,MySQL依然会为该字段保留空间,由于后续有可能会有update语句来更新该记录,补上sex内容。而HBase则是把每一列都看作是一条记录,row+列名做为key,data做为value,依次存放。假如某一行的某一个列没有数据,则直接跳过该列。对于稀疏矩阵的大表,HBase能节省空间。sql
看到这里,你们是否会有一个疑问:使用HBase存储时,假如此时须要添加第二行的sex内容,如何实现呢,数据是否连续?后面介绍读写流程会解释。shell
说完MySQL、HBase,这里要重点说一下ES,ES的存储方式和上面两个都不同,MySQL和HBase是将数据按不一样的方式进行存储,好歹它们存的仍是数据,而ES则存的是倒排索引。咱们先来了解一下什么是倒排索引,以及为何须要倒排索引(Inverted Index):数据库
咱们确定都会这样的经历:偶然看到一段很好的文字,可是殊不知道出处,这时候去图书馆,一个一个翻找,无疑是大海捞针,这个时候肿么办呢,因而便有了全文检索这项技术,而它最核心的就是倒排索引。假若有以下文档:
咱们想要知道有哪些文档含有you这个关键字,首先能够建立一个倒排索引,格式以下:
咱们把前面的部分叫作dictionary(字典),里面的每一个单词叫作term,后面的文档列表叫作psoting-list,list中记录了全部含有该term的文档id,两个组合起来就是一个完成的倒排索引(Inverted Index)。可以看出,假如须要查找含有“you”的文档时,根据dictionary而后找到对应的posting-list便可。
而全文检索中,建立Inverted Index是最关键也是最耗时的过程,并且真正的Inverted Index结构也远比图中展现的复杂,不只须要对文档进行分词(ES里中文能够自定义分词器),还要计算TF-IDF,方便评分排序(当查找you时,评分决定哪一个doc显示在前面,也就是所谓的搜索排名),压缩等操做。每接收一个document,ES就会将其信息更新在倒排索引中。
从这里咱们就能够看出ES和MySQL、HBase的存储仍是有很大的区别。并且ES不只包含倒排索引,默认同时还会把文档doc存储起来,因此当咱们使用ES时,也能拿到完整的文档信息,因此某种程度上,感受就像在使用数据库同样,可是也能够配置不存储文档信息,这时只能根据查询条件获得文档id,并不能拿到完整的文档内容。
总结:MySQL行存储的方式比较适合OLTP业务。列存储的方式比较适合OLAP业务,而HBase采用了列族的方式平衡了OLTP和OLAP,支持水平扩展,若是数据量比较大、对性能要求没有那么高、而且对事务没有要求的话,HBase也是个不错的考虑。ES默认对全部字段都建了索引,因此比较适合复杂的检索或全文检索。
数据库系统,数据的完整性和一致性是很是重要的问题,数据库进程挂了,能够恢复,可是数据丢了,就再也找不回来了。下面说说各个系统的容灾方式。
单节点:
如今的数据库广泛采用write ahead log策略来避免数据丢失,wal机制简单的解释就是:在提交CUD操做,数据写入内存的同时,也要写一份到log文件中,并且要保证log数据落盘成功后才能向client返回操做成功,假如此时数据库宕机,已经提交到内存的数据还没来得及刷回磁盘,重启数据库后能够经过回放log文件来恢复内存中的数据。
问题又来了:写log的话,对性能影响会不会很大?其实多少仍是有点影响的,不过log文件是顺序写入,相对来讲为了保证数据完整性,这点性能损失仍是能够接受的。
单机状况下,MySQL的innodb经过redo log和checkpoint机制来保证数据的完整性。由于怕log越写越大,占用过多磁盘,并且当log特别大的时候,恢复起来也比较耗时。而checkpoint的出现就是为了解决这些问题。
checkpoint机制保证了以前的log数据必定已经刷回磁盘,当数据库宕机时,只须要将checkpoint以后的log回放便可,数据库会定时作checkpoint,这样就保证了数据库恢复的效率。
可是考虑到若是硬件故障时机器没法启动,或者磁盘故障时数据没法恢复,checkpoint+redo log方案也就不起做用了,为了防止这种故障,MySQL还提供了master-slave和group replication 集群级别的容灾方案。
Master-Slave架构主要思路是:master负责业务的读写请求,而后经过binlog复制到slave节点,这样若是主库由于不可抗拒因素没法恢复时,从库能够提供服务,这里咱们用了“复制“这个词,而不是”同步“,由于基于binlog复制的方案并不能作到主从数据强一致,这种主从同步方式会致使主库挂掉以后从库有可能丢失少许的数据。
正是由于主从架构存在数据不一致的问题,因此MySQL5.7出现了Mysql Group Replication方案,mgr采用paxos协议实现了数据节点的强同步,保证了全部节点均可以写数据,而且全部节点读到的也是最新的数据。(原谅本人水平有限,说不清楚主从架构为何会丢数据,也讲不清楚mgr是怎么实现的,可是这里强烈推荐一本前司同事的书:《MySQL运维内参》,里面详细解释了Master-Slave和Group Replication 的架构,是深刻理解Mysql的不二之选,听说本书的出现拉低了DBA的门槛,没有任何打广告的嫌疑^ ^)
HBase的容灾和MySQL的单机容灾有些相似,但具体实现上仍是颇有本身的特色。在介绍HBase容灾前,咱们先来了解一下HBase和HDFS的关系:HBase中的数据都是存放在HDFS上,能够简单理解HBase分为两层:一层为NoSql service(即提供分布式检索服务),一层是分布式文件系统(数据真正存放的位置,目前采用HDFS)。HBase中region分布在不一样的regionserver上,client端经过meta表来定位数据在在哪一个regionserver的region上,而后获取数据,可是数据有可能并不必定在该regionserver本地保存,每一个region都知道本身对应的数据在HDFS的哪些数据块上,最后经过访问HDFS来获取数据,尤为当HBase和HDFS部署在不一样的集群上时,数据的读写彻底是经过RPC来实现,为了减小RPC的开销,保证服务稳定,每每会将HBase和HDFS部署在同一个集群。同理,当一个regionserver挂了,region能够快速切换到别的regionserver上,由于只涉及到回放Log,并不会移动已经落盘的数据,并且HBase也会控制log的大小,来减小恢复时间。
HBase也是采用写log的方式防止数据丢失,数据写内存的同时,同时也会写入HLog,HLog也是存储在HDFS上,写入HLog后才会认为数据写成功,某个regionserver挂掉以后,master将故障机器上的regions调度到别的regionserver上,regionserver经过回放HLog来恢复region的数据,恢复成功后,region从新上线,因为log是直接写在HDFS上,因此不用担忧单个节点挂掉log数据丢失的问题。
这里引出一个问题:回放HLog的时候,正在被恢复的region会短期不可用,直到HLog回放成功。HBase1.0版本中加入了region replicas功能,也就是提供一个slave region,当主region挂掉的时候,依然能够经过slave replicas来读数据,可是slave不提供write,并且slave replicas和primary region并非强同步的,并不必定总能读到最新的数据,因此开启该功能时,也要考虑本身业务是否必需要求强一致。
HBase也提供了cluster replication,目的是为了作机房级的容灾,boss说如今cluster replication功能还有些bug,目前也在积极优化改进,相信之后会cluster replication会愈来愈完善。
ES的容灾也是采用写log的方式,与HBase不一样的是,ES的节点保存各自的log,这点跟MySQL相似,log是存放在本地的,这也就存在和MySQL同样的问题,假如机器宕机或硬盘故障,log数据也会丢失,因此index每一个shard也有主备,默认配置是一个primary shard,一个replica shard,固然也能够配置多个replica。
默认状况下:primary shard首先接收client端发送过来的数据,而后将数据同步到replica shard中,当replica shard也写入成功后,才会告知client数据已正确写入,这样就防止数据还没写入replica shard时,primary挂掉致使的数据丢失。
又到了提问环节,若是有一个replica节点出了问题,好比网络故障没法写入,那岂不是数据一直写入不成功了?因此ES的master维护了一个in-sync set,里面保存了目前存活、且与primary同步的replica集合,只要set中的replica同步完成即认为数据写入成功。考虑到一种状况:全部的replica由于网络故障都下线了,in-sync set此时为空,数据只在primary中保留一份,颇有可能因primary故障而致使丢数据,因此ES新增了wait_for_active_shards参数,只有当存活的replica数大于该参数时,才能正常写入,若不知足,则中止写服务。
(这是5.X版本的实现,因为ES版本更新过快,这和2.X以前的版本有些差别,5.X中in-sync set的方式和Kafka的容灾模式很是相似,但和Kafka有一点区别:ES的primary负责写服务,可是primary和replica均可以提供读服务,而Kafka只有primary partition提供读写服务,replica只是同步primary上的数据,并不提供读。具体为何Kafka不用replica提供读服务,你们能够思考一下哈。而ES 2.X以前版本的容灾更像ZK,采用quorum的方式,若是不对请指正)
存储方式和读写方式很大程度上决定了系统的吞吐,本节主要介绍MySQL、HBase、ES各自是如何读写数据的。
先说说MySQL,MySQL的Innodb中的数据是按主键的顺序依次存放,主键即为聚簇索引(对聚簇索引和非聚簇索引不了解同窗能够看看这篇文章),索引采用B+树结构进行组织。
从图中能够看出,数据是按聚簇索引顺序依次存放,假设下面一些场景:
Innodb中主键即为聚簇索引,假如根据主键查询,聚簇索引的叶子节点存放就是真正的数据,能够直接查到相应的记录。
假如是二级索引查询,那么须要先经过二级索引找到该记录的主键,而后根据主键经过聚簇索引找到对应的记录,这里多了一个索引查找的过程。
顺序插入:由于Innodb的数据是按聚簇索引的顺序依次存放的,若是是根据主键索引的顺序插入,即插入的数据的主键是连续的,由于是顺序io,因此插入效率会较高。
随机插入:假如每次插入的数据主键是不连续的,MySQL须要取出每条记录对应的物理block,会引发大量的随机io,随机io操做和顺序io的性能差距很大,尤为是机械盘。
(Kafka官网提到一个机械盘的顺序写能达到600M/s,而随机写可能只有100k/s。As a result the performance of linear writes on aJBODconfiguration with six 7200rpm SATA RAID-5 array is about 600MB/sec but the performance of random writes is only about 100k/sec—a difference of over 6000X.这也是为何HBase、ES将全部的insert、update、delete操做都统一当作顺序写操做,避免随机io)
note:这也是为何MySQL的主键一般定义为自增id,不涉及业务逻辑,这样新数据插入时能保证是顺序io。另外MySQL为了提升随机io的性能,提供了insert buffer的功能。
update和delete若是不是顺序的话,也会包含大量的随机io,固然MySQL都针对随机io都进行了一些优化,尽可能减小随机io带来的性能损失。
HBase不支持二级索引,它只有一个主键索引,采用LSM树(LSM能够参考这篇博客)。
HBase是一个分布式系统,这点跟MySQL不一样,它的数据是分散不一样的server上,每一个table由一个或多个region组成,region分散在集群中的server上,一个server能够负责多个region。
这里有一点须要特别注意:table中各个region的存放数据的rowkey(主键)范围是不会重叠的,能够认为region上数据基于rowkey全局有序,每一个region负责它本身的那一部分的数据。
假如咱们要查询rowkey=150的这条记录,首先从zk中获取hbase:meta表(存放region和key的对应关系的元数据表)的位置,经过查询meta表得知rowkey=150的数据在哪一个server的哪一个region上。
上图粗略的展现了HBase的region的结构,region不仅仅是一个文件,它是由一个memstore和多个storeFile组成(storeFile上的上限能够配置)。插入数据时首先将数据写入memstore,当memstore大小达到必定阈值,将memstore flush到硬盘,变成一个新的storeFile。flush的时候会对memstore中的数据进行排序,压缩等操做。能够看到单个storeFile中的数据是有序的,可是region中的storeFile间的数据不是全局有序的。
这样有的好处就是:无论主键是否连续,全部的插入一概变成顺序写,大大提升了写入性能。
看到这里你们可能会有一个疑问:这种写入方式致使了一条记录若是不是一次性插入,极可能分散在不一样的storeFile中,那在该region上面查询一条记录时,怎么知道去找哪一个storeFile呢?答案就是:所有查询。HBase会采用多路归并的方式,对该region上的全部storeFile进行查询,直到找到符合条件的记录。因此HBase的拥有很好的写入性能,可是读性能较差。
固然HBase也作了不少优化,好比每一个storeFile都有本身的index、用于过滤的bloom filter、compaction:按可配置的方式将多个storeFile合并成一个,减小检索时打开的文件数。
HBase将更新和删除也所有看作插入操做,用timestamp和delete marker来区分该记录是不是最新记录、是否须要删除。也正是由于这样,除了查询,其余的操做统一转换成了顺序写,保证了HBase高效的写性能。
ES的也是一个分布式系统,与ES相似的还有一个叫Solr的项目,都是基于Lucene的全文检索分布式框架,有兴趣的能够去Lucene官网了解,这里就不作对比了。
上如展现了ES和传统数据库的概念对比。下面的介绍中,统一使用index对应DB中table,doc对应table中的记录,field对应row中的一列。
ES集群由一个或多个node组成,一个node即为一个ES服务进程。一个index由多个分片shard组成,shard分散在各个node上面,每一个shard都采用Lucene来建立倒排索引,维护各自的索引数据。
图中的一个小方框即为一个shard,出于容灾考虑,每一个shard都会有多副本,副本个数能够配置,默认为2,绿色的即为primary shard,灰色的即为replica shard。
先来讲说写入吧,因为有多个shard,请求过来时,如何判断写入到哪一个shard呢,ES中每一个doc都会有一个惟一id,默认会对id取hash值,根据shard的个数mode到对应的shard上,默认状况下shard中的数据id不是全局有序的,这点和Mysql、HBase有很大区别。
ES的写入和HBase有些相似,也是将全部的写操做变成顺序写,也是先将数据写入内存,而后一段时间后会将内存数据flush到磁盘,磁盘的索引文件会定时进行merge,保证索引文件不会过多而影响检索性能。
另外提一点,数据存入ES后并非立马就能检索到,这点跟MySQL和HBase,或者说跟数据库系统是彻底不同的。主要是由于因为Inverted Index结构的复杂,须要一个专门的indexReader来查询数据,可是indexReader是以snapshot的方式打开的索引,也就是说indexReader看不到以后的新数据。因此ES提供了一个refresh功能,refresh会从新打开indexReader,使其可以读到最新的数据。默认refresh的间隔是1s,因此ES自称是近实时检索功能。
说到顺序写,这时候你们可能会想:那ES的写入速度和HBase差很少喽?那,其实不是的,不止不如并且差的还不是一点点,由于ES多了两个最关键的步骤:build index和refresh index!这两个过程是很耗时的: build index时须要分词、计算权重等复杂的操做(对inverted index建立,检索感兴趣的,能够参考《信息检索导论》)。而refresh会从新打开index,这两个过程加起来致使ES接收文档的速率并不高(能够经过bulk方式来加快数据导入)。但也正是由于这些过程才使ES有强大的检索功能。(虽然我insert慢,可是我花样多呀^ ^)
每一个node均可以接收读request,而后该node会把request分发到含有该index的shard的节点上,对应的节点会查询、并计算出符合条件的文档,排序后结果汇聚到分发request的node(因此查询请求默认会轮循的将发送到各个节点上,防止请求所有打到一个节点),由该node将数据返回给client。(ES也支持指定shard查询,默认是根据文档id进行路由,至关于主键查询,可是假如不能肯定数据在哪一个shard上时,仍是须要查询全部shard)
这里要强调一下,因为ES支持全文检索,根据Inverted Index的特性,大部分状况下,一个关键字对应了不少的doc,若是所有返回,数据量较大,会对集群形成较大压力,因此ES默认只返回权重最高的前20条记录(可配置),也能够经过scroll功能获取所有数据。相似的场景跟咱们平时使用baidu、google是同样的,咱们使用搜索引擎时,每每是但愿获得关联性最强的top N文档,并不关心所有文档有多少个,这也是为何要计算权重的缘由。
如今的ES的功能愈来愈丰富,不只仅包含全文检索的功能,并且还有统计分析等功能,说它是全文检索框架吧,它比全文检索功能要丰富,说它是数据库吧,可是它不支持事务,只能说如今各个框架之间的界限愈来愈模糊了。
ES的更新和删除和HBase相似,也是所有看作是插入操做,经过timestamp和delete marker来区分。
又到了问题环节 :D :既然这种将更新删除统一变成顺序写的方式可以提升写性能,那它难道没有什么坏处吗?
答案是确定有的呀,这种方式可以有效的提高写性能,可是存在一个很大的问题就是后台常常会须要merge,而merge是一个很是耗资源的过程,对于某些稳定性要求较高的业务来讲,这是不能接受的,可是不merge的话,又会下降查询性能(过多的小文件影响查询性能)。目前通用的作法是尽可能选择业务低峰期进行merge操做。
说了这么多,其实仍是但愿对MySQL,HBase,ES各自的实现作下对比,方便咱们根据业务特色选择最合适的存储、检索方案。下面说一下笔者在工做中使用的经验:
MySQL在三款中最为成熟,并且支持事务,支持二级索引,容灾备份方案也最为成熟,因此线上核心业务Mysql是不二之选(固然若是不差钱,Oracle也挺不错,并且出问题本身解决不了的话,打电话就能够了,手动斜眼)。
HBase由于其强大的写入能力和水平扩展能力,比较适合存储日志,用户行为等数据量比较大的数据,这种数据通常不涉及事务级别的读写,对二级索引的需求也不是很高。并且HBase的主键不像Mysql,每每是涉及到业务逻辑的,若是查询条件单一的话,能够把直接把须要查询的字段做为主键的一部分,相似MySQL的联合索引,来提供检索功能。
ES如今不只提供全文检索,还提供统计功能,而且提供的Restful接口很是好用,配上Kibana还能够进行图形化展现,第三方插件也很丰富。虽然ES能够水平扩展,可是考虑到ES的大部分检索都会检索该index的全部shard,若是单个index数据过大,性能多少也会受到影响,因此单个index的大小最好控制在必定的范围,好比存储用户行为日志的index,能够每隔一段时间归一次档,建立新的index,作到冷热分离。并且ES也能够做为MySQL或HBase的索引来使用,虽然Mysql也有索引功能,可是过多的索引每每会拖累MySQL的性能,而且线上MySQL数据库通常也不容许执行统计类的sql,这时能够用ES辅助实现统计,HBase由于只有主键检索,因此更须要二级索引的功能。
举一个笔者前司组合使用的场景:trace系统的log数据以HBase做为主要存储,同时最近三个月的数据也在ES里面保留一份,ES主要用来完成各类复杂检索、统计。但数据同步须要业务本身实现,固然trace业务对一致性要求不那么高,也能够忽略这个问题。
tip:将数据库的数据向ES中同步的时候,由于网络延迟等问题,到达的顺序可能会乱序,这时老数据有可能会覆盖新的数据,ES提供了一个version功能,能够将数据的timestamp做为version值,防止旧version的数据覆盖新version的数据。
传统的关系型数据库有着强大的事物处理能力,知足了大部分线上业务需求,可是水平扩展性一直是一个头疼的问题,NoSql数据库虽然解决了水平扩展问题,可是功能太单一,如今愈来愈多的公司开始着手研究新一代NewSQL数据库,结合了关系型数据库的优势外还拥有水平扩展能力,好比淘宝的Oceanbase,PingCAP的TiDB,国外的CockroachDB,让咱们作好拥抱NewSQL的准备吧。
-------
Best used: For rapidly changing data with a foreseeable database size (should fit mostly in memory).
For example: To store real-time stock prices. Real-time analytics. Leaderboards. Real-time communication. And wherever you used memcached before.
Best used: When you need to store data so huge that it doesn't fit on server, but still want a friendly familiar interface to it.
For example: Web analytics, to count hits by hour, by browser, by IP, etc. Transaction logging. Data collection from huge sensor arrays.
Best used: If you need dynamic queries. If you prefer to define indexes, not map/reduce functions. If you need good performance on a big DB. If you wanted CouchDB, but your data changes too much, filling up disks.
For example: For most things that you would do with MySQL or PostgreSQL, but having predefined columns really holds you back.
Best used: When you have objects with (flexible) fields, and you need "advanced search" functionality.
For example: A dating service that handles age difference, geographic location, tastes and dislikes, etc. Or a leaderboard system that depends on many variables.
Best used: For accumulating, occasionally changing data, on which pre-defined queries are to be run. Places where versioning is important.
For example: CRM, CMS systems. Master-master replication is an especially interesting feature, allowing easy multi-site deployments.
Best used: If you need to restict access on the cell level.
For example: Same as HBase, since it's basically a replacement: Search engines. Analysing log data. Any place where scanning huge, two-dimensional join-less tables are a requirement.
Best used: Hadoop is probably still the best way to run Map/Reduce jobs on huge datasets. Best if you use the Hadoop/HDFS stack already.
For example: Search engines. Analysing log data. Any place where scanning huge, two-dimensional join-less tables are a requirement.
Best used: If you need a better HBase.
For example: Same as HBase, since it's basically a replacement: Search engines. Analysing log data. Any place where scanning huge, two-dimensional join-less tables are a requirement.
Best used: For graph-style, rich or complex, interconnected data.
For example: For searching routes in social relations, public transport links, road maps, or network topologies.
Best used: For graph-style, rich or complex, interconnected data.
For example: For searching routes in social relations, public transport links, road maps, or network topologies.
Best used: Any application where low-latency data access, high concurrency support and high availability is a requirement.
For example: Low-latency use-cases like ad targeting or highly-concurrent web apps like online gaming (e.g. Zynga).
Best used: If you like Erlang and wanted to use Mnesia or DETS or ETS, but you need something that is accessible from more languages (and scales much better than ETS or DETS).
For example: In an Erlang-based system when you want to give access to the DB to Python, Ruby or Java programmers.
Best used: Any application where low-latency data access, high concurrency support and high availability is a requirement.
For example: Storing massive amounts of profile data in online advertising or retail Web sites.
Best used: Applications where you need constant real-time upates.
For example: Displaying sports scores on various displays and/or online. Monitoring systems. Fast workflow applications.