写点什么

水平分库分表的关键步骤以及可能遇到的问题

  • 2016-11-14
  • 本文字数:4131 字

    阅读完需:约 14 分钟

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

分片技术的由来

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

分布式全局唯一 ID

在很多中小项目中,我们往往直接使用数据库自增特性来生成主键 ID,这样确实比较简单。而在分库分表的环境中,数据分布在不同的分片上,不能再借助数据库自增长特性直接生成,否则会造成不同分片上的数据表主键会重复。简单介绍下使用和了解过的几种 ID 生成算法。

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

其中,Twitter 的 Snowflake 算法是笔者近几年在分布式系统项目中使用最多的,未发现重复或并发的问题。该算法生成的是 64 位唯一 Id(由 41 位的 timestamp+ 10 位自定义的机器码 + 13 位累加计数器组成)。这里不做过多介绍,感兴趣的读者可自行查阅相关资料。

常见分片规则和策略

分片字段该如何选择

在开始分片之前,我们首先要确定分片字段(也可称为“片键”)。很多常见的例子和场景中是采用 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 语句进行解析、路由,并对结果集做二次计算再最终返回。引入数据库中间件的技术成本更低,对应用程序来讲侵入性几乎没有,可以满足大部分的业务。增加了额外的硬件投入和运维成本,同时,中间件自身也存在性能瓶颈和单点故障问题,需要能够保证中间件自身的高可用、可扩展。

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

作者介绍

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


感谢郭蕾对本文的审校。

给InfoQ 中文站投稿或者参与内容翻译工作,请邮件至 editors@cn.infoq.com 。也欢迎大家通过新浪微博( @InfoQ @丁晓昀),微信(微信号: InfoQChina )关注我们。

2016-11-14 17:1114915

评论 1 条评论

发布
用户头像
楼主写得很棒
2020-02-25 19:08
回复
没有更多了
发现更多内容

面试突击17:HashMap除了死循环还有什么问题?

王磊

Serverless,引领云计算下一个阶段

华为云开发者联盟

MySQL 云计算 Serverless 华为云 FunctionGraph

一个BPMN流程示例带你认识项目中流程的生命周期

华为云开发者联盟

工作流 项目 BPM BPMN Activiti框架

大数据开发之Flink sql 的基础用法

@零度

flink sql 大数据开发

面对 Log4j2 漏洞,安全人都做了什么?

华为云开发者联盟

Java 漏洞 Apache Log4j2 Log4j2 漏洞 漏洞防护

基于Javaweb,Mysql图书管理系统

叫练

算法大佬Carl的面试简历长啥样?同款模板让你脱胎换骨!

博文视点Broadview

潘娟:从女工程师转变成开源商业化Infra公司创始人,痛并快乐着

腾源会

数据库 开源 Apache ShardingSphere 开源商业化

Promise 异步流程控制

编程江湖

前端开发之React调度算法的迭代过程

@零度

前端开发 React

30人的产研团队如何高效协同?

阿里云云效

阿里云 DevOps 云原生 研发管理 研发团队

【等保小知识】等保二级是否需要做密评?什么是密评?

行云管家

网络安全 等级保护 等保2.0 等保二级

AI 收藏夹 Vol.004:Waifu Lab 火了,AI 是如何创作的?

Zilliz

第三节:SpringBoot中web项目推荐目录结构

入门小站

springboot java 编程

开源实践 | OceanBase 在红象云腾大数据场景下的实践与思考

OceanBase 数据库

OceanBase 开源 客户案例 开源实践

中间件头部厂商加入,龙蜥社区携手东方通共创开源新生态

OpenAnolis小助手

Linux 开源

译文|借助 Pulsar Functions 迁移到无服务应用程序

Apache Pulsar

Java 开源 架构 云原生 Apache Pulsar

Flume简介和架构安装配置详解

编程江湖

BigDecimal 被拼多多的"砍一刀"应用到了极致

恒生LIGHT云社区

Java 拼多多 Java中精确小数计算

不会使用Spring的配置文件,赶紧把这个甩给他

华为云开发者联盟

Java spring API bean 配置文件

火山引擎边缘计算节点通过 EC Ready 边缘云首批评测

火山引擎边缘云

云原生 边缘计算 测评

使用 electron-builder 打包 Electron 程序

编程三昧

Electron electron实战 1月月更

Redis持久化RDB和AOF区别

编程江湖

redis'

尚硅谷JavaWeb新版视频教程发布

@零度

javaWeb

引领中国分布式数据库企业技术创新力,平凯星辰获得赛迪顾问报告推荐

PingCAP

从零开发区块链应用(五)--golang网络请求

杰哥的技术杂货铺

golang 区块链 HTTP post GET

Java Spring Beans.xml里的Bean定义是如何被解析出来的

汪子熙

Java Spring Boot Spring Java 1月月更

等保2.0基本要求是什么?跟等保1.0一样吗?

行云管家

网络安全 等保 等级保护 等保2.0

飞瓜数据发布2021年抖音短视频直播营销报告(年度版)

Geek_2d6073

干掉大小流切换 I 帧!阿里云 RTC QoS 及视频编码联合优化之切流编码

阿里云视频云

阿里云 WebRTC 直播 RTC 视频编码

SSH 端口转发与 SOCKS 代理

CRMEB

水平分库分表的关键步骤以及可能遇到的问题_语言 & 开发_丁浪_InfoQ精选文章