先从公司的最近的一个大数据项目谈起。最近公司有一个项目,设计到大量的数据,设计到数据的存储查询同时还需要保证高并发,那现在如何解决存储和负载均衡的问题?下面的文章将该问题结合目前的数据库,谈谈它们在存储和负载方面有哪些解决方案。
首先是为什么要分表分库?试想假如我们是早期的创业团队,主要以软件开发功能为主,数据量不大,主要是一些客户业务方面的数据,大概10w至1000w,早期简单,我们将数据存储在mysql,postgres中,查询读写速度都没问题。随着业绩的好转数据越来越多从原来的1000w狂涨到几千w甚至上亿,此时查询数据表越来越卡,硬盘容量也越来越小,有时甚至查询1分钟至几分钟才能查询出结果,这时将考虑如何将数据分散到不同机器上去以应对大量数据查询和存储要求,这个时候,我们通常会做如下设计:
ok,我们将客户表拆成一个和客户相关的子表,将业务表拆成同样一个一个小的子表,拆表拆库后通过在用户和数据库之间插入一个应用插件解决了,解决了单台机器存储和查询数据的问题。但是过了几个月,公司业绩很好,很快我们发现,我们这个设计又有问题了,因为我们的业务需求表增长速度非常快,几乎每天就有好几万的业务数据量,此时如果我们继续对业务需求表细分,需要仔细考虑全局数据的情况,而且细分也非常麻烦,此时我们这种架构已经不适合了。那又没有这样的数据库能不要拆表的情况下,将单表数据保存到多台机器呢?这样是不是就解决了我们存储和大量查询计算所带来的性能。
先来看下hbase,hbase是google bigtable的山寨版,它的原理图是这样的:
hbase作为列族式数据库,将数据表的按rowkey进行拆分,按照业务的规则,将不同的rowkey路由到不同的机器上去,同时还按列族拆成一个个store.需要注意的是hbase中rowkey的保存顺序是按字典顺序保存的,因此如果要保证业务数据按不同规则拆分需要手动对rowkey“改造“,像hash,前缀、时间倒排等,当然这涉及性能,不在本节讨论范围。将rowkey按分区段进行拆分后,首先是将负载分散,假如有10000个请求,现在将表拆分成了100份,这样单台机器的负载就降低至了100 Qps;其次对于数据表的横向扩展非常的方便,假如新增加了一台机器,那只要将新增数据rowkey id号加载到新的机器上就行了。这里留个问题,那数据查询时如何能找到数据所在表位置?
如何用过mongo分布式数据库的人肯定会说这和HBase的架构没区别啊,下面看下mongo如何分表设计的:
对于一个读写操作,mongos知道应该将其路由到哪个复制集上(数据的元数据保存至mongos集群),mongos通过将片键空间划分为若干个区间,计算出一个操作的片键的所属区间对应的复制集来实现路由,这样mongo将表的rowkey划分成多个分片来保存到不同服务器上,在如上图,是不是和前面的hbase非常的相似。通常一个shard分片还用一个以上备份,副本机制能有效解决大数据的并行读写的问题,试想一下对于一秒上千万甚至亿级别的读写,一个备份的一台主机读写的情况下立刻就宕机了,如果我们我们采用多台主机,其中一台可读可写,其他主机是该主机的备份,在高并发下,因为多台主机保存了同样的数据,这样就能将负载分摊到多台机器上,这样就能在数据层保证高并发,保证了不宕机服务持续服务,同时多副本机制保证数据不丢失,提高了可靠性。这里留2个问题让大家思考,为什么hbase不需要内部弄个备份sharing?一主多备架构如何保证主备之间数据的一致性以及数据同步的及时性?
上面是mongo的解决方案,下面看看elasticsearch这种天生分布式数据集群它又是如何做分表的。通常es给人的印象是对于上亿级的数据存储,其查询依旧是毫秒级响应,这和它的词索引至内存、热点数据至文件缓存、roaring bitmaps方式压缩数据和联合查询过滤、以及数据分片等有很多关系,这节重点讨论它数据分片。几乎所有的数据分片都离不开数据分片前rowkey的预先划分,es也逃不掉,但es对rowkey的sharding和前面数据库又有区别。es默认是通过对文档id 求hashcode(_id)%sharding_num来确定数据最终存储在哪个shard上的,这样理论上能保证所有的数据是负载均衡的,如下图:
为了数据的良好的并发和负载,通常需要将es的分片设置为集群节点个数,同时每个分片默认都有一个以上的副本。这里在留个问题让大家思考,如果es新增了一台机器后,原来同一个表的数据能否sharding到新的机器上?
上面说的都是非关系型数据库,对于传统的关系型数据库如mysql、postgres他们又如何应对大数据的的分表问题呢?
mysql,postgres并不直接支持分布式的分表操作,后面的集群版本后才开始支持数据库的分表分库操作,其拆分的原理和前面的sharing方式一样,都是对rowkey的拆分。早期mysql、postgres的分表分库都是通过中间件来完成的,其基本的思路都差不多,都是通过一个应用层或是一个数据库将数据的划分方式记录起来,查询聚合时应用层启动路由功能去查找数据。对于一次查询,通常分为两个阶段,查询和聚合。查询阶段,应用层本身不做数据的存储和查询,只负责数据的路由转发,将查询负载均摊具体存储机器上;在数据聚合阶段,应用层需要对数据的聚合计算求top N,join,连表查询操作非常麻烦也非常消耗性能。常用的分表分库插件的设计思路是在用户与库之间存在一个连接池(pgbouncer),然后定义一张内部表(pg_fdw)或插件保存用户划分数据的规则和路由、建立虚拟的外部表映射至具体的数据表(pg1,pg2),插入数据时,外部表不保存数据,只负责将数据通过规则路由至对应的数据库中,真正查询聚合阶段将数据取回聚合给用户,如下图所示:
上面从hbase、mongo、es、mysql/postgres等常见数据库它们的分表分库的原理,讲了这些数据库在处理大数据存储和负载均衡下的区别和作用,希望对大家理解大数据存储和分表分库原理有所帮助。下节将针对上面提到的问题详细的解答。