首页 > 技术文章 > MySQL、HBase、ES的特点和区别

hanease 2022-02-28 15:43 原文

MySQL:关系型数据库,主要面向OLTP,支持事务,支持二级索引,支持sql,支持主从、Group Replication架构模型(本文全部以Innodb为例,不涉及别的存储引擎)。

HBase:基于HDFS,支持海量数据读写(尤其是写),支持上亿行、上百万列的,面向列的分布式NoSql数据库。天然分布式,主从架构,不支持事务,不支持二级索引,不支持sql。

ElasticSearch:ES是一款分布式的全文检索框架,底层基于Lucene实现,虽然ES也提供存储,检索功能,但我一直不认为ES是一款数据库,但是随着ES功能越来越强大,与数据库的界限也越来越模糊。天然分布式,p2p架构,不支持事务,采用倒排索引提供全文检索。

 

数据存储方式

假设有这样一张人员信息表:

MySQL中要提前定义表结构,也就是说表共有多少列(属性)需要提前定义好,并且同时需要定义好每个列所占用的存储空间。数据以行为单位组织在一起的,假如某一行的某一列没有数据,也需要占用存储空间。

HBase则是以列为单位存储数据,每一列就是一个key-value,HBase的表列(属性)不用提前定义,而且列可以动态扩展,比如人员信息表中需要添加一个新的“address”字段,MySQL需要提前alter表,HBase的话直接插入即可。

ES比较灵活,索引中的field类型可以提前定义(定义mapping),也可以不定义,如果不定义,会有一个默认类型,不过出于可控性考虑,关键字段最好提前定义好。(Solr中必须提前定义好schema.xml文件)

上图简单的展示了数据在MySQL和HBase中存储差异(和真实的情况还有差距),可以看到即使第二条记录的sex字段为空,MySQL依然会为该字段保留空间,因为后续有可能会有update语句来更新该记录,补上sex内容。而HBase则是把每一列都看做是一条记录,row+列名作为key,data作为value,依次存放。假如某一行的某一个列没有数据,则直接跳过该列。对于稀疏矩阵的大表,HBase能节省空间。

看到这里,大家是否会有一个疑问:使用HBase存储时,假如此时需要添加第二行的sex内容,如何实现呢,数据是否连续?后面介绍读写流程会解释。

不一样的ES

说完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默认对所有字段都建了索引,所以比较适合复杂的检索或全文检索。

容灾


数据库系统,数据的完整性和一致性是非常重要的问题,数据库进程挂了,可以恢复,但是数据丢了,就再也找不回来了。下面说说各个系统的容灾方式。

MySQL


单节点:

现在的数据库普遍采用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:


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:


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,MySQL的Innodb中的数据是按主键的顺序依次存放,主键即为聚簇索引(对聚簇索引和非聚簇索引不了解同学可以看看这篇文章),索引采用B+树结构进行组织。

从图中可以看出,数据是按聚簇索引顺序依次存放,假设下面一些场景:

1.查询

Innodb中主键即为聚簇索引,假如根据主键查询,聚簇索引的叶子节点存放就是真正的数据,可以直接查到相应的记录。

假如是二级索引查询,那么需要先通过二级索引找到该记录的主键,然后根据主键通过聚簇索引找到对应的记录,这里多了一个索引查找的过程。

2.插入

顺序插入:因为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的功能。

3.更新 & 删除

update和delete如果不是顺序的话,也会包含大量的随机io,当然MySQL都针对随机io都进行了一些优化,尽量减少随机io带来的性能损失。

HBase


HBase不支持二级索引,它只有一个主键索引,采用LSM树(LSM可以参考这篇博客)。

 
 

HBase是一个分布式系统,这点跟MySQL不同,它的数据是分散不同的server上,每个table由一个或多个region组成,region分散在集群中的server上,一个server可以负责多个region。

这里有一点需要特别注意:table中各个region的存放数据的rowkey(主键)范围是不会重叠的,可以认为region上数据基于rowkey全局有序,每个region负责它自己的那一部分的数据。

1.查询

假如我们要查询rowkey=150的这条记录,首先从zk中获取hbase:meta表(存放region和key的对应关系的元数据表)的位置,通过查询meta表得知rowkey=150的数据在哪个server的哪个region上。

2.插入

上图粗略的展示了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合并成一个,减少检索时打开的文件数。

3.更新 & 删除

HBase将更新和删除也全部看做插入操作,用timestamp和delete marker来区分该记录是否是最新记录、是否需要删除。也正是因为这样,除了查询,其他的操作统一转换成了顺序写,保证了HBase高效的写性能。

ES


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。

1.插入

先来说说写入吧,由于有多个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慢,但是我花样多呀^ ^)

2.读取

每个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的功能越来越丰富,不仅仅包含全文检索的功能,而且还有统计分析等功能,说它是全文检索框架吧,它比全文检索功能要丰富,说它是数据库吧,但是它不支持事务,只能说现在各个框架之间的界限越来越模糊了。

3.更新 &删除

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的数据。

MongoDB

MongoDB是当今最火爆的NoSQL数据库。MongoDB最早在09年发布,算得上是早期大数据时代的数据库代表作了。随着MongoDB的火爆,研发MongoDB的团队还专门成立了MongoDB公司来对MongoDB进行维护和推广,现在这个公司已经在纳斯达克上市,市值达到十几亿美元,算得上是技术变现的典范了。

MongoDB最大的特点是表结构灵活可变,字段类型可以随时修改。MongoDB中的每一行数据只是简单的被转化成Json格式后存储,因此MongoDB中压根没有MySQL中表结构这样的概念,你可以直接简单粗暴的将任意结构的数据塞入同一个表中,压根不必考虑表结构的限制,更不必像MySQL一样因为要修改数据表结构而大费周折。简而言之,往MySQL写数据像是在做填空题,你写入的数据必须与最早定义的表结构一致,而往MongoDB写数据就像是在做问答题,想怎么写就怎么写,这灵活度不要爽太多。

说完MongoDB的优点也该说一说它的缺点了。MongoDB不需要定义表结构这个特点给表结构的修改带来了极大的方便,但是也给多表查询、复杂事务等高级操作带来了阻碍。因此,如果你的数据的逻辑结构非常复杂,经常需要进行复杂的多表查询或者事务操作,那显然还是MySQL这类关系型数据库更合适。

得益于MongoDB的这些特点,MongoDB很适合那些表结构经常改变,数据的逻辑结构没又没那么复杂不需要多表查询操作,数据量又比较大的应用场景。例如,有一个游戏应用,需要存储每个用户的信息,用户分为法师、战士等具有不同属性的角色,还有装备、技能等很多结构复杂的信息,游戏每次更新还可能会引入很多新的用户属性,这时如果你使用MySQL,那么你可能需要建立很多个表,定义很多个表结构,并且游戏的每次更新也可能会给你带来重定义表结构等一堆麻烦事,而如果使用MongoDB则这些麻烦统统不存在,因为你可以定义只一张表便可以容纳所有的信息,而且可以随时根据新的需求增减字段。

Redis

Redis是现在最热门的key-value数据库。它与MongoDB同在2009年发布,也同样是早期大数据时代的数据库代表作。

Redis的最大特点当然就是key-value存储所带来的简单和高性能了。所谓key-value存储,就是每一条记录只包含一个用于查询数据的Key,以及与之对应的存储数据的value,就如同现实生活中的门牌号与住户,而没有诸如表、字段这些常规数据库中必需有的复杂概念,所有的查询都仅仅依赖于key值。因此,key-value数据库可谓是数据库中数据结构最简单的一种,也得益于这种简单的结构,再加上Redis会把所有数据加载到内存中的,Redis能得到远高于MongoDB这类常规数据库的读写性能。当然,Redis的功能还不止key-value存储这么简单,相较它的key-value前辈Memcached,Redis还支持数据持久化,list、set等多种数据结构,主从复制备份等一些列功能,因此Redis绝对称得上是key-value数据库中功能最全面、最简单易用的款。

Redis的key-valule存储带来了性能这个优势,但是也给复杂查询带来了很多局限。由于阉割掉了数据表、字段这样的重要特性,且所有的查询都依赖key,因此Redis无法提供常规数据库所具备的多列查询、区段查询等复杂查询功能。同时,由于Redis需要把数据存在内存中,这也大大限制了Redis可存储的数据量,这也决定了Redis难以用在数据规模很大的应用场景中。

Redis牺牲了常规数据库中的数据表、复杂查询等功能,换来了很大的性能提升,特别适合那些对读写性能要求极高,且数据表结构简单(key-value、list、set之类)、查询条件也同样简单的应用场景。如果你的数据表结构还挺复杂,你还经常需要做一些复杂查询操作,那你最好还是老老实实用MongoDB或者SQL吧。

ElasticSearch

ES的特点,正如其名,那就是搜索。严格的说,ES不是一个数据库,而是一个搜索引擎,ES的方方面面也都是围绕搜索设计的。ES支持全文搜索,这里简单解释下什么是全文搜索:对于“我在北京的一家互联网公司工作”这样的数据,如果你搜索“北京”、“互联网”、“工作”这些关键词都能命中这条数据的话,这就是全文搜索,你每天都在用的百度、Google都属于全文搜索。值得一提的是,ES的全文搜索对中文也有很好的支持(单是中文分词器就有很多种),绝对能够满足国内大多数人的全文搜索需求。除了搜索之外,ES还会自动的替你对所有字段建立索引,以实现高性能的复杂聚合查询,因此只要是存入ES的数据,无论再复杂的聚合查询也可以得到不错的性能,而且你再也不用为如何建立各种复杂索引而头痛了。

说了这么多ES的优点,你是不是觉得ES简直万能了?可惜不是的,ES也有很多的短处,最明显的就是字段类型无法修改、写入性能较低和高硬件资源消耗。前边讲到ES会自动的替你建立索引,尽管这能给全文搜索以及聚合查询带来很多好处还能替你省了建索引这一麻烦事,但是这个特性也会带来一堆问题。ES需要在创建字段前要预先建立Mapping,Mapping中包含每个字段的类型信息,ES需要根据Mapping为字段建立合适的索引。由于这个Mapping的存在,ES中的字段一但建立就不能再修改类型了。(例如,你建的数据表的某个字段忘了加全文搜索,你想临时加上,但是表已经建好并且已经有很多数据了,这时候该怎么办呢?不好意思,你只能把整个数据表删了再重建一遍!)因此,ES在数据结构灵活度上高于MySQL但远不如MongoDB。ES的缺点还不止这些,自动建立索引使得ES的写入性能也收到了影响,要明显低于MongoDB。对于同样的数据ES占用的存储空间也要明显大于MongoDB(建那么多索引能不占空间吗?),对硬件资源的消耗也是非常厉害,大数据量下64G内存+SSD基本是标配,算得上是数据库中的贵族服务了,因此如果你的老板很小气,对于ES的选用可要慎重喽!

ES的全文搜索特性使它成为构建搜索引擎的利器。除此之外,ES很好的支持了复杂聚合查询这一特点还使得ES非常适合拿来作数据分析使用。其实,ES还专门做了与自己配套的ELK套装,给你提供从日志收集到数据可视化分析的一条龙服务,绝对是构建高大上数据分析平台的利器。但是,ES的高成本和低写入性能这些缺点也注定了它不适合用在那些数据价值不高、对写入性能有要求、数据量大而成本受限的场景中。

HBase

HBase是Hadoop项目的一部分,而且是当年谷歌大数据三驾马车之一的BigTable方案的实现,因此绝对算得上是大数据时代最有代表性的技术之一了。

作为Hadoop系列产品之一,HBase也继承了Hadoop项目的最大优点,那就是对海量数据的支持,以及极强的横向(存储容量)扩展能力。和Redis类似,HBase也需要为每一行数据定义一个key,之后所有的查询都依赖这个key进行。但是不同的地方在于,HBase中的一行数据还可以有非常多的列项(类似MongoDB字段),数据会按照列进行分组和存储,同一列的数据存储在同一个地方,这也是HBase被称为列式存储数据库的原因。其实从本质上来说,HBase相当于是把逻辑上的一张大表按照列族分拆成若干张小表分别进行存储,不仅是列,数据的行数到达一定数量后表也会再被拆分。因此,HBase能够把巨大的表分布到很多台机器上,从而容纳规模近乎无限的数据。同时,对HBase进行横向扩展也非常方便,你基本只需要添加新的机器,而不用对数据做任何改动,就可以实现数据库容量线性的增长,这在其他SQL数据库中是难以做到的(尽管其他数据库也有诸如MongoDB分片集群之类的功能帮助你进行数据规模横向扩展,但是无论是在实施的难度上还是在对数据的影响方面这些都无法跟HBase相提并论。)

HBase的列式存储特性带来了海量数据规模的支持和极强的扩展能力,但是也给数据的读取带来很大的局限。由于只有同一列族的数据才会被存放在一起,而且所有的查询都必须要依赖Key,这就使得很多复杂查询难以进行。例如,如果你的查询条件涉及多个列项,或者你无法获取要查询数据的key,那么查询效率将会非常低下。因此,HBase仅仅适合。

HBase的列式存储特点带来了对海量数据的容纳能力,因此非常适合数据量极大,查询条件简单,列与列之间联系不大的轻查询应用场景。最典型的比如搜索引擎所使用的网页数据库。HBase不适合数据结构复杂,且需要复杂查询的应用场景。另外值得一提的是,HBase是很重的一款产品,需要依赖很多的Hadoop组件,因此如果你的数据规模不大,那就完全没必要杀鸡用牛刀,MongoDB这类产品完全可以更好的满足你的需求。

总结

以上四种数据库是当今NoSQL中最火爆的几款,掌握了它们,你基本就能cover住互联网开发中的绝大多数数据存储需求。这里还想强调的一点是,如同买衣服一样,没有最好的数据库,只有最适合你的应用场景的数据库,因此选用一款数据库前一定要想清楚自己的应用场景是否合适。再给大家总结下这些数据库的适用场景:

如果你对数据的读写要求极高,并且你的数据规模不大,也不需要长期存储,选redis;

如果你的数据规模较大,对数据的读性能要求很高,数据表的结构需要经常变,有时还需要做一些聚合查询,选MongoDB;

如果你需要构造一个搜索引擎或者你想搞一个看着高大上的数据可视化平台,并且你的数据有一定的分析价值或者你的老板是土豪,选ElasticSearch;

如果你需要存储海量数据,连你自己都不知道你的数据规模将来会增长多么大,那么选HBase。

 转载自:https://zhuanlan.zhihu.com/p/37964096

 

 

 

推荐阅读