如果第二次看到我的文章,欢迎文末扫码订阅我的个人公众号(跨界架构师)哟~
本文长度为5389字,建议阅读14分钟。
坚持原创,每一篇都是用心之作~
没想到这篇文章写了这么长,一时半会没消化完的话,可以收藏一下先。
这是「伸缩性」章节的第四篇,先给新来的小伙伴们简单回顾下前三篇的内容。
做「伸缩性」最重要的就是先做好「无状态」,如此才可以随心所欲的进行横向“扩展”,而不用担心在多个副本之间切换会产生错乱。《分布式系统关注点——「无状态」详解》聊的就是这个。
不过,就算做好了横向扩展,本质上还是一个“大程序”,只是变得「可复制」了而已。
如果要消灭“大程序”,那就得“切分”,做好切分必然离不开「高内聚低耦合」的核心思想。《分布式系统关注点——「高内聚低耦合」详解》这篇聊的就是这个。
题外话:当你遇到单点单应用支撑不住使用的时候,Z哥给你的普适性建议是:先考虑“扩”,再考虑“切”。这个和写代码一样,“增加”新功能往往比在老功能上改容易。
“扩”的话先考虑「垂直扩」(加硬件,钱能解决的都不是问题),再考虑「水平扩」(无状态改造+多节点部署,这是小手术)。
“切”的话一般就是「垂直切」(根据业务切分,这是大手术),偶尔会用到「水平切」(其实就是单个应用里的分层,比如前后端分离)。
第三篇《分布式系统关注点——弹性架构》我们聊了常见的两种「松耦合」架构模式,为的是让应用程序的「伸缩性」更上一层楼。
以上这些呢都是应用程序层面的工作。一般情况下,在应用程序层面做做手术,再配合以缓存的充分运用,就可以支撑系统发展很长时间了。特别是数据量不大,只是请求量大的「CPU密集型」场景。
但是,如果所处的工作场景是一个非常成熟且具有一定规模的项目,越发展到后面瓶颈总是出现在数据库这里。甚至会出现cpu长期高负荷、宕机等现象。
在如此场景下,就不得不对数据库开刀了。这次Z哥就来和你聊聊做数据库的「伸缩性」有哪些好方法。
核心诉求
面临数据库需要开刀的时候,整个系统往往已经长成这个样子了。
正如前面所说,这时候的瓶颈往往会体现在「CPU」上。
因为对数据库来说,硬盘和内存的扩容相对容易,因为它们都可以直接用“增加”的方式进行。
CPU就不同了,一旦CPU飙高,最多检查下索引有没有做好,完了之后基本就只能干看着。
所以解决这个问题的思路自然就变成了:如何将一个数据库的CPU压力分摊到多个CPU上去。甚至可以做到按需随时增加。
那这不就是和应用程序一样做「切分」嘛。也是分布式系统的「分治」思想体现。
既然是切分,本质上就和应用程序一样,也分为「垂直切分」和「水平切分」。
垂直切分
垂直切分有时候也会被称作「纵向切分」。
同应用程序一样,它是以「业务」为维度的切分方式,在不同的数据库服务器上跑不同业务的数据库,各司其职。
一般情况下,Z哥建议你优先考虑「垂直切分」而不是「水平切分」,为什么呢?你可以随意打开手头项目中的SQL语句看看,我想必然存在着大量的「join」和「transaction」关键字,这种关联查询和事务操作,本质上是一种「关系捆绑」,一旦面临数据库拆分之后,就没法玩了。
此时你只有2个选择。
-
要么将不必要的「关系捆绑」逻辑舍弃掉,这需要在业务上作出调整,去除不必要的“批量操作”业务,或者去除不必要的强一致性事务。不过你也知道,肯定有一些场景是去不完的。
-
要么将「合并」,「关联」等逻辑上浮,体现到业务逻辑层甚至是应用层的代码中。
最终,不管怎么选择,改动起来都是一个大工程。
为了让这个工程尽可能的动作小一些,追求更好的性价比,需要坚持一个原则——“避免拆分紧密关联的表”。
因为两个表之间关联越紧密,意味着对「join」和「transaction」的需求越多,所以坚持这个原则可以使得相同的模块,紧密相关的业务都落在同一个库中,这样它们可以继续使用「join」和「transaction」来工作。
因此,我们应当优先采用「垂直切分」的方式。
做「垂直切分」思路很简单,一般情况下,建议是与切分后的应用程序一一对应就好,不用多也不用少。
实际工作中,要做好「垂直切分」主要体现在「业务」的熟悉度上,所以这里就不继续展开了。
「垂直切分」的优点是:
-
高内聚,拆分规则清晰。相比「水平切分」数据冗余度更低。
-
与应用程序是1:1的关系,方便维护和定位问题。一旦某个数据库中发现异常数据,排查这个数据库的关联程序就行了。
但是这并不是一个「一劳永逸」的方案,因为没人能预料到未来业务会发展的怎么样,所以最明显的缺点就是:对于访问极其频繁或者数据量超大的表仍然存在性能瓶颈。
确实需要解决这个问题的话,就需要搬出「水平切分」了。
题外话:不到迫不得己,尽量避免进行「水平切分」。看完接下去的内容你就知道原因了。
下面Z哥就给你好好聊聊「水平切分」,这才是本文的重点。
水平切分
想象一下,在你做了「垂直切分」之后,还是在某个数据库中发现了一张数据量超过10亿条的表。
这个时候要对这个表做「水平切分」,你会怎么思考这个事情?
Z哥教给你的思路是:
-
先找到“最高频“的「读」字段。
-
再看这个字段的实际使用中有什么特点(批量查询多还是单个查询多,是否同时是其它表的关联字段等等)。
-
再根据这个特点选择合适的切分方案。
为什么要先找到高频的「读」字段呢?
因为在实际的使用中,「读」操作往往是远大于「写」操作的。一般进行「写」之前都得通过「读」来做先行校验,然而「读」还有自己单独的使用场景。所以针对更高频的「读」场景去考虑,产生的价值必然也更大。
比如,现在那张10亿数据量的表是一张订单表,结构是这样:
order (orderId long, createTime datetime, userId long)
下面我们先来看看有哪几种「水平切分」的方式,完了才能明白什么样的场景适合哪种方式。
范围切分
这是一种「连续式」的切分方式。
比如根据时间(createTime)切分的话,我们可以按年月来分,order_201901一个库,order_201902一个库,以此类推。
根据顺序数(orderId)切分的话,可以100000~199999一个库,200000~299999一个库,以此类推。
这种切分法的优点是:单个表的大小可控,扩展的时候无需数据迁移。
缺点也很明显,一般来说时间越近或者序号越大的数据越“新”,因此被访问的频率和概率相比“老”数据更多。会导致压力主要集中在新的库中,而历史越久的库,越空闲。
Hash切分
与「范围切分」正好相反,这是一种「离散式」的切分方式。
它的优点就是解决了「范围切分」的缺点,新数据被分散到了各个节点中,避免了压力集中在少数节点上。
同样,缺点与「范围切分」的优点相反,一旦进行二次扩展,必然会涉及到数据迁移。因为Hash算法是固定的,算法一变,数据分布就变了。
大多数情况下,我们的hash算法可以通过简单的「取模」运算来进行即可。就像下面这样:
假如分成11个库的话,公式就是 orderId % 10。 100000 % 10 = 0,分配到db0。 100001 % 10 = 1,分配到db1。 .... 100010 % 10 = 0,分配到db0。 100011 % 10 = 1,分配到db1。
其实,在某些场景下,我们可以通过自定义id的生成(可以参考之前的文章,《分布式系统中的必备良药 —— 全局唯一单据号生成》)来做到既可以通过hash切分来打散热点数据,又可以减少依赖全局表来定位具体的数据。
比如,在orderId中加入userId的尾数,以此达到orderId和userId取模结果相等的效果。还是来举个例子:
一个用户的userId是200004,如果取一个4bit尾数的话,这里就是4,用0100表示。
然后,我们通过自定义id算法生成orderId的前60位,在后面补上0100。
于是,orderId % 10和 userId % 10的结果就是一样的了。
当然,除了userId之外还想加入其他的因子就不好使了。也就是,可以在不增加全局表的情况下,额外多支持1个维度。
提到了两次全局表,那么啥是全局表呢?
全局表
这种方式就是将用作切分依据的分区Key与对应的每一条具体数据的id保存到一个单独的库或者表中。例如要增加一张这样的表:
nodeId orderId 01 100001 02 100002 01 100003 01 100004 ...
如此一来,的确将大部分具体的数据分布在了不同服务器上,但是这张全局表会给人一种「形散神不散」的感觉。
因为请求数据的时候无法直接定位需要的数据在哪台服务器上,所以每一次操作都要先查询一下这张全局表好知道具体的数据被存放在哪里。
这种「中心化」的模式带来的副作用就是瓶颈和风险转移到了这张全局表上。但是,胜在逻辑简单。
好了,那么这几种切分方案怎么选择呢?
Z哥给你的建议是,如果热点数据不是特别集中的场景,建议先用「范围切分」,否则选择另外2种。
选择另外两种的时候,数据量越大越倾向选择Hash切分。因为后者在整体的可用性和性能上都比前者好,就是实现成本高一些。
「水平切分」真正做到了可以“无限扩展”,但是也存在相应的弊端。
1)批量查询、分页等需要做更多的额外工作。特别是当一个表存在多个高频字段用于where、order by或者group by的时候。
2)拆分规则不如「垂直切分」那么明确。
所以还是多说一句“废话”:没有完美的方案只有合适的方案,要结合具体的场景来选择。(欢迎你在留言区提出你有疑惑的场景,和Z哥来讨论讨论)
如何实施
当你在具体实施「水平切分」的时候可以在2个层面动刀,可以是「表」层面,也可以是「库」层面。
表
在同一个数据库下面分表,表名order_0 ,order_1, order_2.....。
它可以解决单表数据过大,但并不能解决CPU负荷的问题。所以,当CPU并没多少压力,只是由于表太大,导致执行SQL操作比较慢的话,可以选择这种方式。
库
这个时候表名可以不变,都叫order,只是分成10个库。那么就是db0-user db1-user db2-user......。
我们前面大篇幅都是基于这个模式在聊,就不多说了。
表+库
也可以既分库又分表,比如先分10个库,然后每个库再分10张表。
这其实是个二级索引的思路,通过库来进行第一次定位,减少一定的资源消耗。
比如,先按年分库,再按月分表。如此一来,如果需要获取的数据只跨月但不跨年,我们就可以在单个库内做聚合运算来完成,不涉及到跨库操作。
不过,不管选择哪种方式来进行,你还是会或多或少面临以下两个问题,逃不掉的。
-
跨库join。
-
全局聚合或者排序操作。
解决第一个问题最佳方式还是需要改变你的编程思维。尽量将一些逻辑、关系、约束等体现在应用程序的代码中,避免因为方便而在SQL中做这些事情。
毕竟代码是可以写成“无状态”的,可以随时做扩展,但是SQL是跟着数据走的,而数据就是“状态”,天然不利于扩展。
当然了,退而求其次,你也可以冗余大量的全局表来应对。只是如此一来,对「数据一致性」工作是个很大的考验,另外,对存储资源也是很大的开销。
第二个问题的解决方案就是需要将原本的一次聚合或者一次排序变成两次操作。其中的遍历多个节点可以以「并行」的方式进行。
那么数据切分完之后程序如何来使用呢?这又可以分为两种模式,「进程内」和「进程外」。
「进程内」的话,可以在封装好的DAL访问框架中做,也可以在ORM框架中做,还可以在数据库驱动中做。这个模式比较知名的解决方案如阿里的tddl。
「进程外」的话,就是代理模式,这个模式比较知名的解决方案是mycat、cobar、atlas等等,相对多一些,因为这种模式对应用程序是「低侵入」的,使用起来像“一个数据库”。但是由于多了一道网络通信,性能上会多一些损耗。
老规矩,下面再分享一些最佳实践。
最佳实践
首先分享两个可以不停机做数据切分的小窍门。我们以实施hash法做水平切分的例子来看一下。
第一次做切分的时候,你可以以「主-从」的形式将新增的节点作为原始节点的副本,进行全量实时同步。
然后在这个基础上删除不属于它的数据。(当然了,不删也没啥问题,就是多占用一些空间)
这样就可以不用停机了。
第二,随着时间的推移,如果后续支撑不住了,需要二次切分的话,我们可以选择用2的倍数来扩展。
如此一来,数据的迁移变得很简单,只需要做局部的迁移,和第一次做切分的思路是一样的。
当然了,如果选择的切分方式是「范围切分」的话,就没有二次切分时的困扰,数据自然跑到最新的节点上去了。比如我们按年月分表的话。2019年3月的数据自然就落到了xxxx_201903的表中。
到这里,Z哥还是想特别强调的是,能不切分尽量不要切分,可以先使用「读写分离」之类的方案先来应对面临的问题。
如果实在要进行切分的话,务必先「垂直切分」,再考虑「水平切分」。
一般来说,以这样的顺序来考虑,性价比更好。
总结
好了,我们总结一下。
这次呢,Z哥先向你介绍了做数据库切分的两种思路。两种思路通俗理解就是:「垂直拆分」等于“列”变“行”不变,「水平拆分」等于“行”变“列”不变。
然后着重聊了下「水平切分」的3种实现方式和具体实施的思路。
最后分享了一些实践中的经验给你。
希望对你有所启发。
相关文章:
作者:Zachary
出处:https://www.cnblogs.com/Zachary-Fan/p/databasesegmentation.html
如果你喜欢这篇文章,可以关注下我的个人公众号哦。
▶关于作者:张帆(Zachary,个人微信号:Zachary-ZF)。坚持用心打磨每一篇高质量原创。欢迎扫描下方的二维码~。
定期发表原创内容:架构设计丨分布式系统丨产品丨运营丨一些思考。
如果你是初级程序员,想提升但不知道如何下手。又或者做程序员多年,陷入了一些瓶颈想拓宽一下视野。欢迎关注我的公众号「跨界架构师」,回复「技术」,送你一份我长期收集和整理的思维导图。
如果你是运营,面对不断变化的市场束手无策。又或者想了解主流的运营策略,以丰富自己的“仓库”。欢迎关注我的公众号「跨界架构师」,回复「运营」,送你一份我长期收集和整理的思维导图。
相关推荐
分布式系统可伸缩性是衡量一个系统在面对不断增长的工作负载或资源变化时,能否保持其性能和功能稳定性的重要指标。陈斌等人的研究综述深入探讨了这一关键概念,指出可伸缩性不仅涉及系统的基本性能,还与资源管理和...
可伸缩性可以通过垂直伸缩和水平伸缩两种基本方式实现。垂直伸缩通常指提升单个节点的硬件能力,如增加CPU、内存等资源,但这种方式在云计算环境下,相较于传统的物理硬件提升更为方便和灵活。水平伸缩则是增加更多...
综上所述,可伸缩性是分布式系统设计的核心问题之一,它直接关系到系统的性能、可靠性和经济性。设计一个高度可伸缩的分布式系统需要深入理解可伸缩性的定义和度量方法,并采用有效的技术手段来实现动态资源管理。...
Java分布式系统架构是一种将应用程序分布在多个计算节点上运行的技术,以提高系统的可伸缩性、容错性和性能。源码分析对于理解这种架构至关重要,尤其是对于开发者来说,它提供了深入学习和自定义系统的机会。本资源...
分布式系统的设计和实现是计算机科学和工程领域的一个重要研究课题,主要的目标是构建能够支持大量并发操作,且具备高可用性和可伸缩性的系统。Andrew S. Tanenbaum教授是早期操作系统领域的知名学者,其著作深入浅...
在当今互联网时代,大型电商企业为了应对海量的用户请求和保证业务的高可用性,往往采用分布式系统架构来构建他们的技术平台。分布式系统通过网络将物理上分散的多个服务器连接在一起,以协同完成共同的任务。在大型...
分布式系统以其强大的扩展性和灵活性成为解决大规模数据处理的关键技术之一。本文将深入探讨分布式系统的基本概念及其核心设计模式,帮助读者理解如何构建稳定、高效且可扩展的分布式系统。 #### 二、分布式系统的...
分布式系统设计的关键技术之一是分布式数据存储。这包括分布式数据库和分布式文件系统,如Google的Bigtable、Hadoop的HDFS和Amazon的DynamoDB。这些系统利用复制和分片策略来提高读写性能和可用性,同时通过复杂的...
分布式系统是一种计算机科学中的概念,它涉及多个独立的计算节点通过网络进行通信和协作,共同完成一项任务。这种系统能够实现资源的共享、任务的并行处理和高可用性,从而提升整体性能和可靠性。 一、分布式系统的...
分片和切分技术能有效提高系统的可用性和并发处理能力,同时保持架构的可伸缩性,以适应互联网应用的需求。 总之,MySQL的水平和垂直切分以及分片是数据库优化的重要手段,通过合理的设计和中间件支持,可以在不...
分布式系统的设计和实施面对的首要问题之一就是如何保证数据一致性。数据一致性是分布式系统中一个核心问题,它涉及到系统中各个组件在数据更新、事务处理等方面能够保持一致的状态。那么,分布式系统在面临各种业务...
分布式系统是一种通过网络将多个独立计算节点连接起来协同工作的系统,它旨在提供高可用性、可伸缩性和容错能力。本文将探讨分布式系统的设计原理、核心概念、挑战以及实际应用,尤其关注数据中心环境中商业系统的...
分布式计算系统的主要目标是提高系统的性能、可伸缩性和可靠性。通过将任务分解到多个计算节点,它能够处理比单个计算机更大的负载,并在某个节点故障时提供容错能力。以下是一些关键知识点: 1. **分布式系统架构*...
这种系统的核心在于,各个节点之间可以相互通信,并且能够共享资源,共同处理任务,从而达到提高系统性能、可伸缩性和容错性的目的。在这个关于分布式系统的PPT中,我们可以期待涵盖以下几个重要的知识点: 1. **...
分布式系统架构设计与实现是当今互联网领域中一个非常重要的研究方向,特别是随着移动互联网的快速发展以及大数据应用水平的不断提高,分布式系统的关键业务组件需求越来越高,包括更好的可扩展性、可靠性和实时性。...
它们可能会讨论分布式系统如何提高可伸缩性、容错性和性能,以及在大数据处理、云计算和互联网服务中的应用。 2. **WINZHENG.txt**: 可能是作者或教程的介绍,或者是关于Windows环境下搭建和管理分布式系统的指南...