在之前的文章中。我介绍了分库分表的几种表现形式和玩法,也重点介绍了垂直分库所带来的问题和解决方法。
本篇中,我们将继续聊聊水平分库分表的一些技巧。
分片技术的由来
关系型数据库本身比較easy成为系统性能瓶颈,单机存储容量、连接数、处理能力等都非常有限,数据库本身的“有状态性”导致了它并不像Web和应用server那么easy扩展。在互联网行业海量数据和高并发訪问的考验下,聪明的技术人员提出了分库分表技术(有些地方也称为Sharding、分片)。
同一时候。流行的分布式系统中间件(比如MongoDB、ElasticSearch等)均自身友好支持Sharding。其原理和思想都是大同小异的。
分布式全局唯一ID
在非常多中小项目中,我们往往直接使用数据库自增特性来生成主键ID,这样确实比較简单。
而在分库分表的环境中,数据分布在不同的分片上。不能再借助数据库自增长特性直接生成。否则会造成不同分片上的数据表主键会反复。简介下使用和了解过的几种ID生成算法。
- Twitter的Snowflake(又名“雪花算法”)
- UUID/GUID(一般应用程序和数据库均支持)
- MongoDB ObjectID(相似UUID的方式)
- Ticket Server(数据库生存方式,Flickr採用的就是这样的方式)
当中,Twitter 的Snowflake算法是笔者近几年在分布式系统项目中使用最多的,未发现反复或并发的问题。该算法生成的是64位唯一Id(由41位的timestamp+ 10位自己定义的机器码+ 13位累加计数器组成)。这里不做过多介绍,感兴趣的读者可自行查阅相关文档。
常见分片规则和策略
分片字段该怎样选择
在開始分片之前,我们首先要确定分片字段(也可称为“片键”)。
非常多常见的样例和场景中是採用ID或者时间字段进行拆分。
这也并不绝对的,我的建议是结合实际业务,通过对系统中运行的sql语句进行统计分析,选择出须要分片的那个表中最频繁被使用,或者最重要的字段来作为分片字段。
常见分片规则
常见的分片策略有随机分片和连续分片这两种,例如以下图所看到的:
当须要使用分片字段进行范围查找时,连续分片能够快速定位分片进行高效查询。大多数情况下能够有效避免跨分片查询的问题。
后期假设想对整个分片集群扩容时,仅仅须要加入节点就可以。无需对其它分片的数据进行迁移。可是,连续分片也有可能存在数据热点的问题,就像图中按时间字段分片的样例。有些节点可能会被频繁查询压力较大,热数据节点就成为了整个集群的瓶颈。而有些节点可能存的是历史数据。非常少须要被查询到。
随机分片事实上并非随机的,也遵循一定规则。通常,我们会採用Hash取模的方式进行分片拆分。所以有些时候也被称为离散分片。随机分片的数据相对照较均匀。不easy出现热点和并发訪问的瓶颈。
可是,后期分片集群扩容起来须要迁移旧的数据。使用一致性Hash算法能够非常大程度的避免这个问题,所以非常多中间件的分片集群都会採用一致性Hash算法。
离散分片也非常easy面临跨分片查询的复杂问题。
数据迁移,容量规划,扩容等问题
非常少有项目会在初期就開始考虑分片设计的。一般都是在业务快速发展面临性能和存储的瓶颈时才会提前准备。因此。不可避免的就须要考虑历史数据迁移的问题。一般做法就是通过程序先读出历史数据。然后依照指定的分片规则再将数据写入到各个分片节点中。
此外,我们须要依据当前的数据量和QPS等进行容量规划。综合成本因素,推算出大概须要多少分片(一般建议单个分片上的单表数据量不要超过1000W)。
假设是採用随机分片,则须要考虑后期的扩容问题。相对会比較麻烦。
假设是採用的范围分片,仅仅须要加入节点就能够自己主动扩容。
跨分片技术问题
跨分片的排序分页
一般来讲,分页时须要依照指定字段进行排序。当排序字段就是分片字段的时候,我们通过分片规则能够比較easy定位到指定的分片,而当排序字段非分片字段的时候。情况就会变得比較复杂了。为了终于结果的准确性,我们须要在不同的分片节点中将数据进行排序并返回,并将不同分片返回的结果集进行汇总和再次排序,最后再返回给用户。例如以下图所看到的:
上面图中所描写叙述的仅仅是最简单的一种情况(取第一页数据),看起来对性能的影响并不大。可是,假设想取出第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左右的数据是没有问题的(前提是应用系统和数据库等层面设计和优化的比較好)。当然,除了考虑当前的数据量和性能情况时,作为架构师,我们须要提前考虑系统半年到一年左右的业务增长情况,对数据库server的QPS、连接数、容量等做合理评估和规划,并提前做好相应的准备工作。
假设单机无法满足,且非常难再从其它方面优化。那么说明是须要考虑分片的。这样的情况能够先去掉数据库中自增ID,为分片和后面的数据迁移工作提前做准备。
非常多人觉得“分库分表”是宜早不宜迟。应该尽早进行。由于操心越往后公司业务发展越快、系统越来越复杂、系统重构和扩展越困难…这样的话听起来是有那么一点道理。但我的观点恰好相反,对于关系型数据库来讲,我觉得“能不分片就别分片”,除非是系统真正须要,由于数据库分片并非低成本或者免费的。
这里笔者推荐一个比較靠谱的过渡技术–“表分区”。主流的关系型数据库中基本都支持。不同的分区在逻辑上仍是一张表。可是物理上却是分开的。能在一定程度上提高查询性能。并且相应用程序透明,无需改动不论什么代码。笔者以前负责优化过一个系统,主业务表有大约8000W左右的数据,考虑到成本问题。当时就是採用“表分区”来做的。效果比較明显,且系统运行的非常稳定。
小结
最后,有非常多读者都想了解当前社区中有没有开源免费的分库分表解决方式,毕竟站在巨人的肩膀上能省力非常多。当前主要有两类解决方式:
基于应用程序层面的DDAL(分布式数据库訪问层)
比較典型的就是淘宝半开源的TDDL,当当网开源的Sharding-JDBC等。
分布式数据訪问层无需硬件投入。技术能力较强的大公司一般会选择自研或參照开源框架进行二次开发和定制。相应用程序的侵入性一般较大。会添加技术成本和复杂度。
通常仅支持特定编程语言平台(Java平台的居多),或者仅支持特定的数据库和特定数据訪问框架技术(一般支持MySQL数据库,JDBC、MyBatis、Hibernate等框架技术)。
数据库中间件。比較典型的像mycat(在阿里开源的cobar基础上做了非常多优化和改进。属于后起之秀,也支持非常多新特性),基于Go语言实现kingSharding,比較老牌的Atlas(由360开源)等。
这些中间件在互联网企业中大量被使用。另外。MySQL 5.x企业版中官方提供的Fabric组件也号称支持分片技术,只是国内使用的企业较少。
中间件也能够称为“透明网关”。大名鼎鼎的mysql_proxy大概是该领域的鼻祖(由MySQL官方提供。仅限于实现“读写分离”)。中间件一般实现了特定数据库的网络通信协议,模拟一个真实的数据库服务,屏蔽了后端真实的Server。应用程序通常直接连接中间件就可以。而在运行SQL操作时,中间件会依照预先定义分片规则。对SQL语句进行解析、路由,并对结果集做二次计算再终于返回。引入数据库中间件的技术成本更低,相应用程序来讲侵入性差点儿没有,能够满足大部分的业务。添加了额外的硬件投入和运维成本,同一时候,中间件自身也存在性能瓶颈和单点故障问题。须要能够保证中间件自身的高可用、可扩展。
总之。无论是使用分布式数据訪问层还是数据库中间件,都会带来一定的成本和复杂度,也会有一定的性能影响。所以,还需读者依据实际情况和业务发展须要谨慎考虑和选择。
作者介绍
丁浪,技术架构师。关注高并发、高可用的架构设计。对系统服务化、分库分表、性能调优等方面有深入研究和丰富实践经验。热衷于技术研究和分享。