水平分库分表的关键问题及解决思路

在以前的文章中,我介绍了分库分表的几种表现形式和玩法,也重点介绍了垂直分库所带来的问题和解决方法。本篇中,咱们将继续聊聊水平分库分表的一些技巧。mysql

分片技术的由来

关系型数据库自己比较容易成为系统性能瓶颈,单机存储容量、链接数、处理能力等都颇有限,数据库自己的“有状态性”致使了它并不像Web和应用服务器那么容易扩展。在互联网行业海量数据和高并发访问的考验下,聪明的技术人员提出了分库分表技术(有些地方也称为Sharding、分片)。同时,流行的分布式系统中间件(例如MongoDB、ElasticSearch等)均自身友好支持Sharding,其原理和思想都是大同小异的。算法

分布式全局惟一ID

在不少中小项目中,咱们每每直接使用数据库自增特性来生成主键ID,这样确实比较简单。而在分库分表的环境中,数据分布在不一样的分片上,不能再借助数据库自增加特性直接生成,不然会形成不一样分片上的数据表主键会重复。简单介绍下使用和了解过的几种ID生成算法。sql

 

  1. Twitter的Snowflake(又名“雪花算法”)
  2. UUID/GUID(通常应用程序和数据库均支持)
  3. MongoDB ObjectID(相似UUID的方式)
  4. Ticket Server(数据库生存方式,Flickr采用的就是这种方式)
 

常见分片规则和策略

分片字段该如何选择

在开始分片以前,咱们首先要肯定分片字段(也可称为“片键”)。不少常见的例子和场景中是采用ID或者时间字段进行拆分。这也并不绝对的,个人建议是结合实际业务,经过对系统中执行的sql语句进行统计分析,选择出须要分片的那个表中最频繁被使用,或者最重要的字段来做为分片字段。数据库

常见分片规则

常见的分片策略有随机分片和连续分片这两种,以下图所示:编程

 

当须要使用分片字段进行范围查找时,连续分片能够快速定位分片进行高效查询,大多数状况下能够有效避免跨分片查询的问题。后期若是想对整个分片集群扩容时,只须要添加节点便可,无需对其余分片的数据进行迁移。可是,连续分片也有可能存在数据热点的问题,就像图中按时间字段分片的例子,有些节点可能会被频繁查询压力较大,热数据节点就成为了整个集群的瓶颈。而有些节点可能存的是历史数据,不多须要被查询到。后端

随机分片其实并非随机的,也遵循必定规则。一般,咱们会采用Hash取模的方式进行分片拆分,因此有些时候也被称为离散分片。随机分片的数据相对比较均匀,不容易出现热点和并发访问的瓶颈。可是,后期分片集群扩容起来须要迁移旧的数据。使用一致性Hash算法可以很大程度的避免这个问题,因此不少中间件的分片集群都会采用一致性Hash算法。离散分片也很容易面临跨分片查询的复杂问题。服务器

数据迁移,容量规划,扩容等问题

不多有项目会在初期就开始考虑分片设计的,通常都是在业务高速发展面临性能和存储的瓶颈时才会提早准备。所以,不可避免的就须要考虑历史数据迁移的问题。通常作法就是经过程序先读出历史数据,而后按照指定的分片规则再将数据写入到各个分片节点中。网络

此外,咱们须要根据当前的数据量和QPS等进行容量规划,综合成本因素,推算出大概须要多少分片(通常建议单个分片上的单表数据量不要超过1000W)。架构

若是是采用随机分片,则须要考虑后期的扩容问题,相对会比较麻烦。若是是采用的范围分片,只须要添加节点就能够自动扩容。并发

跨分片技术问题

跨分片的排序分页

通常来说,分页时须要按照指定字段进行排序。当排序字段就是分片字段的时候,咱们经过分片规则能够比较容易定位到指定的分片,而当排序字段非分片字段的时候,状况就会变得比较复杂了。为了最终结果的准确性,咱们须要在不一样的分片节点中将数据进行排序并返回,并将不一样分片返回的结果集进行汇总和再次排序,最后再返回给用户。以下图所示:

上面图中所描述的只是最简单的一种状况(取第一页数据),看起来对性能的影响并不大。可是,若是想取出第10页数据,状况又将变得复杂不少,以下图所示:

有些读者可能并不太理解,为何不能像获取第一页数据那样简单处理(排序取出前10条再合并、排序)。其实并不难理解,由于各分片节点中的数据多是随机的,为了排序的准确性,必须把全部分片节点的前N页数据都排序好后作合并,最后再进行总体的排序。很显然,这样的操做是比较消耗资源的,用户越日后翻页,系统性能将会越差。

跨分片的函数处理

在使用Max、Min、Sum、Count之类的函数进行统计和计算的时候,须要先在每一个分片数据源上执行相应的函数处理,而后再将各个结果集进行二次处理,最终再将处理结果返回。以下图所示:

跨分片join

Join是关系型数据库中最经常使用的特性,可是在分片集群中,join也变得很是复杂。应该尽可能避免跨分片的join查询(这种场景,比上面的跨分片分页更加复杂,并且对性能的影响很大)。一般有如下几种方式来避免:

全局表

全局表的概念以前在“垂直分库”时提过。基本思想一致,就是把一些相似数据字典又可能会产生join查询的表信息放到各分片中,从而避免跨分片的join。

ER分片

在关系型数据库中,表之间每每存在一些关联的关系。若是咱们能够先肯定好关联关系,并将那些存在关联关系的表记录存放在同一个分片上,那么就能很好的避免跨分片join问题。在一对多关系的状况下,咱们一般会选择按照数据较多的那一方进行拆分。以下图所示:

这样一来,Data Node1上面的订单表与订单详细表就能够直接关联,进行局部的join查询了,Data Node2上也同样。基于ER分片的这种方式,可以有效避免大多数业务场景中的跨分片join问题。

内存计算

随着spark内存计算的兴起,理论上来说,不少跨数据源的操做问题看起来彷佛都可以获得解决。能够将数据丢给spark集群进行内存计算,最后将计算结果返回。

跨分片事务问题

跨分片事务也分布式事务,想要了解分布式事务,就须要了解“XA接口”和“两阶段提交”。值得提到的是,MySQL5.5x和5.6x中的xa支持是存在问题的,会致使主从数据不一致。直到5.7x版本中才获得修复。Java应用程序能够采用Atomikos框架来实现XA事务(J2EE中JTA)。感兴趣的读者能够自行参考《分布式事务一致性解决方案》,连接地址:

http://www.infoq.com/cn/articles/solution-of-distributed-system-transaction-consistency

咱们的系统真的须要分库分表吗

读完上面内容,不由引发有些读者的思考,咱们的系统是否须要分库分表吗?

其实这点没有明确的判断标准,比较依赖实际业务状况和经验判断。依照笔者我的的经验,通常MySQL单表1000W左右的数据是没有问题的(前提是应用系统和数据库等层面设计和优化的比较好)。固然,除了考虑当前的数据量和性能状况时,做为架构师,咱们须要提早考虑系统半年到一年左右的业务增加状况,对数据库服务器的QPS、链接数、容量等作合理评估和规划,并提早作好相应的准备工做。若是单机没法知足,且很难再从其余方面优化,那么说明是须要考虑分片的。这种状况能够先去掉数据库中自增ID,为分片和后面的数据迁移工做提早作准备。

不少人以为“分库分表”是宜早不宜迟,应该尽早进行,由于担忧越日后公司业务发展越快、系统愈来愈复杂、系统重构和扩展越困难…这种话听起来是有那么一点道理,但个人观点刚好相反,对于关系型数据库来说,我认为“能不分片就别分片”,除非是系统真正须要,由于数据库分片并不是低成本或者免费的。

这里笔者推荐一个比较靠谱的过渡技术–“表分区”。主流的关系型数据库中基本都支持。不一样的分区在逻辑上还是一张表,可是物理上倒是分开的,能在必定程度上提升查询性能,并且对应用程序透明,无需修改任何代码。笔者曾经负责优化过一个系统,主业务表有大约8000W左右的数据,考虑到成本问题,当时就是采用“表分区”来作的,效果比较明显,且系统运行的很稳定。

小结

最后,有不少读者都想了解当前社区中有没有开源免费的分库分表解决方案,毕竟站在巨人的肩膀上能省力不少。当前主要有两类解决方案:

  1. 基于应用程序层面的DDAL(分布式数据库访问层) 

    比较典型的就是淘宝半开源的TDDL,当当网开源的Sharding-JDBC等。分布式数据访问层无需硬件投入,技术能力较强的大公司一般会选择自研或参照开源框架进行二次开发和定制。对应用程序的侵入性通常较大,会增长技术成本和复杂度。一般仅支持特定编程语言平台(Java平台的居多),或者仅支持特定的数据库和特定数据访问框架技术(通常支持MySQL数据库,JDBC、MyBatis、Hibernate等框架技术)。

  2. 数据库中间件,比较典型的像mycat(在阿里开源的cobar基础上作了不少优化和改进,属于后起之秀,也支持不少新特性),基于Go语言实现kingSharding,比较老牌的Atlas(由360开源)等。这些中间件在互联网企业中大量被使用。另外,MySQL 5.x企业版中官方提供的Fabric组件也号称支持分片技术,不过国内使用的企业较少。 

    中间件也能够称为“透明网关”,大名鼎鼎的mysql_proxy大概是该领域的鼻祖(由MySQL官方提供,仅限于实现“读写分离”)。中间件通常实现了特定数据库的网络通讯协议,模拟一个真实的数据库服务,屏蔽了后端真实的Server,应用程序一般直接链接中间件便可。而在执行SQL操做时,中间件会按照预先定义分片规则,对SQL语句进行解析、路由,并对结果集作二次计算再最终返回。引入数据库中间件的技术成本更低,对应用程序来说侵入性几乎没有,能够知足大部分的业务。增长了额外的硬件投入和运维成本,同时,中间件自身也存在性能瓶颈和单点故障问题,须要可以保证中间件自身的高可用、可扩展。

总之,无论是使用分布式数据访问层仍是数据库中间件,都会带来必定的成本和复杂度,也会有必定的性能影响。因此,还需读者根据实际状况和业务发展须要慎重考虑和选择。

做者介绍

丁浪,技术架构师。关注高并发、高可用的架构设计,对系统服务化、分库分表、性能调优等方面有深刻研究和丰富实践经验。热衷于技术研究和分享。

相关文章
相关标签/搜索