数据库:以MySQL、HBase、ElasticSearchInnodb为例进行比较,不涉及其他存储引擎)。HBase:基于HDFS,支持海量数据读写(尤其是写入),支持亿行、百万列,是面向列的分布式NoSql数据库。自然分布式,主从架构,不支持事务,不支持二级索引,不支持SQL。ElasticSearch:简称ES,是一个分布式全文搜索框架,底层基于Lucene技术。虽然ES也提供了存储和检索功能,但我从来没有想过ES是数据库,但是随着ES功能越来越强大,与数据库的界限也越来越模糊。分布式、P2P架构,但不支持事务,使用倒排索引提供全文检索。2、数据存储方式假设有这样一张人员信息表:MySQL数据库需要预先定义表结构,需要预先定义数据表的列数(属性),同时时间,需要定义每一列占用的存储空间。数据以行为单位进行组织。如果一行的某一列没有数据,则需要存储空间。HBase将数据存储在列中,每一列都是一个键值。HBase表的列(属性)不需要预先定义,可以动态扩展列。比如人员信息表需要增加一个新的“地址”字段,MySQL需要提前在alter表中增加字段,HBase可以直接插入。ES更灵活。索引中的字段类型可以预先定义(定义映射),也可以不定义。如果未定义,将有一个默认类型。但为了可控性,建议提前定义关键字段。(schema.xml文件必须在Solr中提前定义好)上图展示了MySQL和HBase中数据存储的区别(与真实情况还是有差距的)。可以看到,即使第二条记录的sex字段为空,MySQL仍然会为该字段预留空间,因为后面可能会有update语句更新记录,添加sex内容。而HBase则是把每一列看成一条记录,以行+列名为key,以data为value,顺序存储。如果某行某一列没有数据,则直接跳过该列。对于矩阵稀疏的大表,HBase可以大大节省存储空间。看到这里,你是不是有一个疑问:在使用HBase存储的时候,如果此时需要添加第二行的性内容,如何实现,数据是否连续?后面会解释读写过程。不同ESES的存储方式与上述两种不同。MySQL和HBase以不同的方式存储数据。不管怎样,他们还是存储数据,而ES存储的是倒排索引。我们先来了解一下什么是倒排索引,为什么需要倒排索引(InvertedIndex):我们肯定会有这样的经历:偶然看到一段不错的文字,却不知道出处。这时候,我去了图书馆。一一寻找,无疑是大海捞针。这时候就有了全文搜索技术,其核心就是倒排索引。如果有如下文档:我们想知道哪些文档包含关键字you,我们可以先建立一个倒排索引,格式如下:前面的部分叫做字典(dictionary),里面的每个词叫做term,而下面的文档列表叫做psoting-list,列表中记录了所有包含该词条的文档id,两者结合就是一个完整的倒排索引(InvertedIndex)。可以看出,如果需要搜索包含“you”的文档,可以根据字典找到对应的posting-list。在全文检索中,创建倒排索引是最关键、最耗时的过程,而真正的倒排索引结构远比图中所示复杂。不仅需要对文档进行分词(ES中的中文可以自定义分词器),还需要计算TF-IDF,方便打分排序(找你的时候,打分决定先显示哪个doc,也就是所谓的搜索排名)、压缩等操作。每次接收到文档时,ES都会在倒排索引中更新其信息。可以看出,ES、MySQL、HBase的存储还是有很大区别的。而且ES不仅包含了倒排索引,还默认存储了文档doc,所以我们在使用ES的时候也可以获得完整的文档信息,所以某种程度上感觉就像是在使用数据库一样,而且还可以配置为不存储文档信息。此时只能根据查询条件获取到文档id,无法获取到完整的文档内容。总结:MySQL:行存储方式更适合OLTP业务。HBase:列存的方式更适合OLAP业务,HBase采用列族的方式平衡OLTP和OLAP,支持横向扩展。如果数据量比较大,性能要求不是那么高,对事务没有要求,HBase可以考虑。ES:ES默认对所有字段都有索引,所以比较适合复杂检索或者全文检索。3、容灾对比3.1MySQL单节点:目前的数据库普遍采用writeaheadlog策略来避免数据丢失。wal机制的简单解释是:在提交一个CUD操作,将数据写入内存的同时,也应该将一份副本写入到日志文件中,并保证日志数据成功落盘,操作即可返回给客户。如果此时数据库宕机,已经提交到内存的数据还没有来得及刷新回磁盘。重启数据库后,可以通过回放.log中的日志文件数据来恢复内存。问题又来了:写日志会对性能有很大的影响吗?其实还是有一点影响的,只是日志文件是顺序写的。相对来说,为了保证数据的完整性,这种性能损失还是可以接受的。.在独立服务器的情况下,MySQL的InnoDB使用重做日志和检查点机制来确保数据完整性。因为我怕日志会越来越大,占用磁盘太多,而且当日志特别大的时候,恢复起来也需要时间。Checkpoint的出现就是为了解决这些问题。检查点机制保证之前的日志数据一定已经刷回磁盘。当数据库宕机时,只需要重放checkpoint之后的log,数据库会定时checkpoint,保证了数据库恢复的效率。但是考虑到硬件故障时机器无法启动,或者磁盘故障时数据无法恢复,checkpoint+redolog的方案行不通。为了防止此类故障,MySQL还提供了主从和组复制集群级别的容灾计划。Master-Slave架构的主要思想是:master负责业务的读写请求,然后通过binlog复制到slave节点,这样如果因为master库不能恢复不可抗拒因素,从库可以提供服务。这里我们使用“复制”这个词,而不是“同步”,因为基于binlog复制的方案无法实现主从数据的强一致性。这种主从同步方式会导致从库在主库挂掉后丢失少量数据。正是因为主从架构存在数据不一致的问题,MySQL5.7才有了MysqlGroupReplication的解决方案。mgr使用Paxos协议实现数据节点的强同步,保证所有节点都可以写入数据,所有节点都可以读取数据,同时也是最新的数据。3.2HBaseHBase的容灾有点类似于MySQL的单机容灾,但具体实现还是有自己的特点。在介绍HBase容灾之前,我们先了解一下HBase和HDFS的关系:HBase中的数据存储在HDFS上。可以简单理解为HBase分为两层:一层是NoSql服务(即提供分布式检索服务)第一层是分布式文件系统(实际存储数据的地方,目前使用的是HDFS)。在HBase中,区域分布在不同的区域服务器上。client端通过meta表定位数据所在regionserver的region,然后获取数据。但是,数据可能不一定存储在区域服务器本地。每个region都知道自己对应的HDFS数据在哪些数据块上,最后通过访问HDFS获取数据,尤其是当HBase和HDFS部署在不同的集群上时,数据的读写完全是通过RPC实现的,为了减少RPC的开销,保证服务稳定,HBase和HDFS经常部署在同一个集群中。同样,当一个regionserver挂了,region可以快速切换到另一个regionserver,因为只涉及到重放日志,并没有移动已经放在磁盘上的数据,而且HBase也会控制日志的大小来减少恢复时间。HBase还使用日志写入来防止数据丢失。数据写入内存的同时,也写入了HLog。HLog也存储在HDFS上。数据写入HLog后才算成功。一个regionserver挂掉后,master将故障机器上的region调度给其他regionserver。regionserver通过回放HLog来恢复region的数据。恢复成功后,区域重新上线。由于日志直接写在HDFS上,不用担心单个节点挂掉时日志数据丢失。问题。问题来了:回放HLog时,恢复的region会短暂不可用,直到HLog回放成功。HBase1.0版本增加了regionreplicas功能,即提供了slaveregion。当masterregion挂掉后,仍然可以通过slavereplicas读取数据,但是slave不提供write,slavereplicas和primaryregion没有强同步。是的,你不一定能一直读到最新的数据,所以你在启用这个功能的时候,也要考虑你的业务是否一定需要强一致性。HBase也提供集群复制,用于机房级别的容灾。老大说集群复制功能还有一些bug,目前正在积极优化改进中。相信以后集群复制会越来越完善。3.3ES:ES的容灾也是采用写日志的方式。与HBase不同,ES节点保存自己的日志。这类似于MySQL。日志存储在本地,和MySQL有同样的问题,如果机器宕机或者硬盘故障,日志数据也会丢失,所以索引的每个分片也有主备。默认配置是主分片和副本分片。当然也可以配置多个副本。默认:primaryshard先接收client发送的数据,再将数据同步到replicashard。当replicashard也写入成功后,会通知client数据已经正确写入,从而防止数据写入尚未进入replicashard时,primary挂掉,数据丢失。又到了提问的时候了。如果副本节点出现问题,比如网络故障无法写入,那岂不是数据写入失败了?所以ES的master维护了一个in-syncset,里面保存了当前存活的,以及和primary同步的replicaset,只要set中的replica同步完成,就认为数据写入成功。考虑一种情况:由于网络故障导致所有replica都下线,此时in-syncset为空,primary中只保留了一份数据,可能会因为primary故障导致数据丢失,所以ES增加了设置了wait_for_active_shards参数,只有当存活副本数大于该参数时,才能正常写入,不满足则停止写入服务。(这是5.X版本的实现,因为ES版本更新太快,所以和2.X之前的版本有些区别,5.X里面的in-syncset方法和kafka的容灾模式很像,但是和Kafka有点区别:ES的primary负责写服务,但是primary和replica都可以提供读服务,而Kafka只提供primary分区上的读写服务,replica只做同步primary上的数据,不提供读取。4.读写方式4.1MysqlMySQL的Innodb中的数据是按照主键的顺序存储的,主键是聚簇索引(有相关知识的可以看这篇文章不知道聚簇索引和非聚簇索引)。索引是以B+树结构组织的。从图中可以看出,数据是按照聚簇索引的顺序存储的。假设有以下场景:1.查询主InnoDB中的键是聚簇索引。如果查询是基于主键的,那么聚集索引的叶子节点存储的才是真正的数据。可以直接找到对应的记录。如果是二级索引查询,需要先通过二级索引找到记录的主键,再根据主键通过聚簇索引找到对应的记录。这是一个额外的索引搜索过程。2.Insertionorder插入:因为Innodb数据是按照聚簇索引的顺序存储的,如果按照主键索引的顺序插入,也就是插入数据的主键是连续的,因为是顺序的io,所以插入效率会更高。随机插入:如果每次插入的数据主键不连续,mysql需要取出每条记录对应的物理块,会造成大量的随机io,随机io操作和顺序io的性能差距非常大,尤其是机械盘。(Kafka官网提到机械盘顺序写可以达到600M/s,而随机写可能只有100k/s。因此,在具有六个7200rpmSATARAID-5阵列的JBOD配置上,线性写入的性能约为600MB/秒,而随机写入的性能仅为约100k/秒——相差超过6000倍。这就是为什么HBase和ES把所有的insert、update、delete操作都看做是顺序写操作,避免随机io)注:这就是为什么MySQL的主键通常定义为自增id,它确实不涉及业务逻辑,这样新的数据就可以插入到顺序io中。另外,为了提高随机io的性能,MySQL提供了insertbuffer的功能。3.Update&Delete如果update和delete不是顺序的,也会包含大量的随机io。当然MySQL对randomio做了一些优化,尽量减少randomio带来的性能损失。4.2HBaseHBase不支持二级索引,只有一个主键索引,使用LSM树。HBase是一个分布式系统,它不同于MySQL。它的数据分散在不同的服务器上。每个表由一个或多个区域组成。区域分散在集群中的服务器上。一台服务器可以负责多个区域。.这里有一点需要特别注意:表中每个区域存储的数据的rowkey(主键)范围不会重叠。可以认为region上的数据是根据rowkey全局排序的,每个region负责自己的部分数据。1、查询如果我们要查询rowkey=150的记录,首先从zk中获取hbase:meta表(存储region和key对应关系的元数据表)的位置,通过查询meta表得到rowkey=150是哪个数据所在服务器的区域。2.Insert上图大致展示了HBase的region的结构。region不仅仅是一个文件,它是由一个memstore和多个storeFiles组成的(storeFile的上限是可以配置的)。插入数据时,首先将数据写入memstore,当memstore的大小达到一定阈值时,将memstore刷入硬盘,成为一个新的storeFile。flushing时会对memstore中的数据进行排序、压缩等操作。可以看出,单个storeFile中的数据是有序的,但是region中storeFiles之间的数据并不是全局有序的。这样做的好处是不管主键是否连续,所有的插入都会变成顺序写入,大大提高了写入性能。看到这里,你可能会有一个疑问:这种写法导致一条记录如果不是一次性插入,会分散在不同的storeFiles中。在查询区域中的记录时,如何知道要查找哪个storeFile?什么?答案是:所有查询。HBase会使用多路合并的方式查询region上的所有storeFiles,直到找到符合条件的记录。所以HBase写性能好,读性能差。当然,HBase也做了很多优化,比如每个storeFile都有自己的index,bloomfilter进行过滤,compaction:多个storeFile以可配置的方式合并为一个,减少检索时打开文件的数量。3、Update&deleteHBase将update和delete视为插入操作,通过timestamp和deletemarker来区分记录是否为最新记录,是否需要删除。也正是因为如此,除了查询之外,其他操作都统一转化为顺序写入,保证了HBase高效的写入性能。4.3ESES也是一个分布式系统。和ES类似的还有一个项目叫做Solr,它是基于Lucene的分布式全文搜索框架。有兴趣的可以去Lucene官网了解一下,这里就不做对比了。上面的例子展示了ES和传统数据库的概念对比。在下面的介绍中,index对应DB中的table,doc对应table中的record,field对应row中的column。一个ES集群由一个或多个节点组成,一个节点就是一个ES服务进程。一个索引由多个分片组成,分片分散在各个节点上。每个分片使用Lucene创建倒排索引并维护自己的索引数据。图中的一个小盒子就是碎片。出于灾难恢复的考虑,每个分片都有多个副本。份数可以配置。默认是2。绿色的是primaryshard,灰色的是replicashard。1.插入先说写吧。既然有多个分片,那么请求进来怎么判断写到哪个分片呢?ES中的每个文档都有一个唯一的id。默认情况下,id将采用哈希值。根据分片的数量是根据相应的分片建模的。默认情况下,shard中的数据id不是全局排序的,这一点与Mysql和HBase有很大不同。ES的写法有点类似于HBase。它还将所有写入操作转换为顺序写入。也是先把数据写入内存,过一段时间再把内存数据刷到磁盘。磁盘上的索引文件会定期合并,保证索引文件不会过大影响检索性能。另外,数据存储在ES中后,不能立即取回,这与MySQL和HBase完全不同,也与数据库系统不同。主要原因是倒排索引由于结构复杂,需要专门的indexReader来查询数据,而indexReader是以快照的形式打开的索引,这意味着indexReader看不到新的数据之后那。所以ES提供了一个refresh的功能,refresh会重新打开indexReader,让它读取到最新的数据。默认的刷新间隔是1s,所以ES号称是近实时的检索功能。说到顺序写入,这时候你可能会想:ES的写入速度和HBase差不多吧?嗯,不是,不仅差还差一点点,因为ES还有两个比较关键的步骤:建立索引和刷新索引!这两个过程非常耗时:建索引时需要分词、计算权重等复杂操作(如果你对倒排索引的创建和检索感兴趣,请参考《信息检索导论》)。刷新会重新打开索引,这两个过程结合在一起会导致ES接收文件率低(可以使用bulk方式加快数据导入)。但也正是因为有这些过程,ES才有了强大的检索功能。(虽然我插入的慢,但是我有很多技巧^^)2.读取每个节点都能收到读取请求,然后节点将请求分发到包含该索引的分片的节点,对应的节点将查询,并计算满足条件的文档,排序后的结果聚合到分发请求的节点(所以查询请求默认会依次发送到各个节点,防止所有请求都命中一个节点),以及节点会将数据返回给客户端。(ES也支持指定shard查询,默认情况下路由是根据documentid的,相当于主键查询,但是如果不能确定数据在哪个shard上,还是需要查询所有shard)这里,我要强调的是,由于ES支持全文搜索,根据倒排索引的特点,大多数情况下,一个关键字对应很多文档,如果全部返回,数据量很大,这样会给集群带来很大的压力,所以ES默认只返回权重最高的前20条记录(可以配置),也可以通过scroll函数获取所有数据。类似的场景和我们平时使用baidu和google是一样的。我们在使用搜索引擎的时候,往往希望得到最相关的前N个文档,而不关心文档有多少。这就是为什么我们需要计算权重。.现在ES的功能越来越多,不仅有全文检索的功能,还有统计分析等功能。假设它是一个全文搜索框架。它比全文搜索功能更丰富。说它是数据库,但是它不支持事务,只能说现在各种框架的界限越来越模糊了。3.Update&deleteES的update和delete和HBase类似,都是看做insert操作,通过timestamp和deletemarker区分。问题又来了:D:既然这种将更新和删除统一为顺序写入的方式可以提高写入性能,那么这里面没有坏处吗?答案是肯定的,这种方法可以有效提高写入性能。但是有个很大的问题就是后台经常需要merge,merge是一个很耗资源的过程。对于一些对稳定性要求高的业务,这是不能接受的,但是如果不合并,又会降低查询性能(小文件太多影响查询性能)。目前一般的做法是尽量选择业务低峰期进行合并操作。5、使用场景说了这么多,还是希望能对比一下MySQL、HBase、ES各自的实现,以便我们根据业务特点选择最合适的存储和检索方案。说一下笔者的工作经历:5.1MySQLMySQL是三种模型中最成熟的,并且支持事务,支持二级索引,容灾备份方案也是最成熟的,所以线上核心业务Mysql是bestchoice(当然如果你不缺钱,oracle也挺好的,遇到自己解决不了的问题,直接打电话手动眯)。5.2HBase由于其强大的写入能力和水平扩展能力,HBase更适合存储日志、用户行为等数据量比较大的数据。这类数据一般不涉及事务级别的读写,对二级索引的要求不是很高。高的。而且,与Mysql不同的是,HBase的主键往往涉及到业务逻辑。如果查询条件单一,可以直接将要查询的字段作为主键的一部分,类似于MySQL的联合索引,提供检索功能。5.3ESES现在不仅提供了全文检索,还提供了统计功能,而且它提供的Restful接口非常好用。有了Kibana,还可以图形化展示,第三方插件也很丰富。ES虽然可以横向扩展,但是考虑到ES的大部分搜索都会检索到索引的所有分片,如果单个索引的数据量过大,会对性能造成一定的影响,所以单个索引的大小最好控制在一定的范围内,比如存储用户行为日志的索引可以每隔一段时间归档一次,可以创建一个新的索引来分离冷热。此外,ES还可以作为MySQL或HBase的索引。Mysql虽然也有索引功能,但是过多的索引往往会拖累MySQL的性能,而线上的MySQL数据库一般不允许执行统计SQL。这时候可以使用ES辅助实现统计,而HBase因为只有主键检索,所以需要二级索引的功能。给大家举一个笔者以前所在公司一起使用的场景:trace系统的日志数据主要存储在HBase中,最近三个月的数据也保存一份在ES中,主要用于完成各种复杂的搜索和统计。但是数据同步需要业务自己来实现。当然trace业务对一致性没有那么高的要求,这个问题也可以忽略。tip:在向ES同步数据库数据时,由于网络延迟等问题,可能会出现到达顺序乱序的情况。此时,旧数据可能会覆盖新数据。ES提供了一个版本函数,可以将数据时间戳转换为版本值,防止旧版本的数据覆盖新版本的数据。综上所述,传统关系型数据库具有强大的事务处理能力,可以满足大部分在线业务需求,但横向扩展一直是个让人头疼的问题。NoSql数据库虽然解决了横向扩展的问题,但是其功能过于单一,现在越来越多的公司开始研究新一代的NewSQL数据库,它结合了关系型数据库的优点,具有横向扩展的能力,比如如淘宝的Oceanbase,PingCAP的TiDB,还有国外的CockroachDB。让我们准备好拥抱NewSQL。
