分库分表
树图思维导图提供 分库分表 在线思维导图免费制作,点击“编辑”按钮,可对 分库分表 进行在线思维导图编辑,本思维导图属于思维导图模板主题,文件编号是:9b5c0be15ae55f2b39ddaae1b8a34d93
分库分表思维导图模板大纲
分片
Sharding
分库、分表
Partition(分区)
数据应用系统中,80%的时间是在处理20%的数据, 20%的时间在处理另外80%的数据。
在设计时将主要精力关注在20%的数据上,通过对这20%的数据建立独立的分区的数据建立独立的分区,建立索引等方式,提高其性能。
80-20法则意味着切割,对数据要进行切割,周期工作要切割,用户功能要切割,甚至硬件使用方式、 资源的分配都可能进行调整。
把一个很大的库(表)的数据拆分到几个库(表)中,每个库(表)的结构都相同,但他们可能分布在不同的mysql实例,甚至不同的物理机器上,以达到降低单库(表)数据量,提高访问性能的目的。
什么时候开始分库分表
根据系统架构和业务的实际情况来,如果系统还是简单的单体应用,并且没有什么访问量和数据量,那就别着急折腾“分表分库”了,而应该是随着业务的发展与增长,在无法继续优化的情况下,再考虑“分库分表”来提高系统的性能。否则没有任何收益,也很难有好结果。
切记,“过度设计”和“过早优化”是很多架构师和技术人员常犯的毛病。
一些建议
去掉sql之间的表关联,传统关系型数据库理论中的三范式在互联网的数据库模型中是不适用的,主要造成的问题是无法进行分表分库。这就要求所有dao方法必须保持单表操作。保持单表操作为分表改造奠定了改造基础。
单库的问题
超大容量问题
单库包含多个超大表,占用的硬盘空间已经接近了服务器的硬盘极限,很快将无空间可用
即数据量太大,单库已经放不下了
单库物理文件太大、单表过大, DDL无法接受
性能问题
单一服务器处理能力是有限的,单一订单库的TPS也有上限,不管如何优化,总会有达到上限,这限制了单位时间的订单处理能力,这个问题在大促时更加明显,如果不重构,订单达到一定量以后,就无法再继续增长,严重影响到用户体验。
当一张表的数据达到几千万时,你查询一次所花的时间会变多,如果有联合查询的话,我想有可能会死在那儿了。分表的目的就在于此,减小数据库的负担,缩短查询时间。
单机单实例已无法承压访问压力
系统层面的“服务化”拆分操作,能够解决业务系统层面的耦合和性能瓶颈,有利于系统的扩展维护。而数据库层面的拆分,道理也是相通的。与服务的“治理”和“降级”机制类似,我们也能对不同业务类型的数据进行“分级”管理、维护、监控、扩展等。
众所周知,数据库往往最容易成为应用系统的瓶颈,而数据库本身属于“有状态”的,相对于Web和应用服务器来讲,是比较难实现“横向扩展”的。
数据库的连接资源比较宝贵且单机处理能力也有限,在高并发场景下,垂直分库一定程度上能够突破IO、连接数及单机硬件资源的瓶颈,是大型分布式系统中优化数据库架构的重要手段。
在高并发和海量数据的场景下,分库分表能够有效缓解单机和单库的性能瓶颈和压力,突破IO、连接数、硬件资源的瓶颈。当然,投入的硬件成本也会更高。同时,这也会带来一些复杂的技术问题和挑战(例如:跨分片的复杂查询,跨分片事务等)
升级、扩展问题
单一主库无法灵活的进行升级和扩展,无法满足公司快速发展要求; 所有的订单数据都放在同一库里面,存在单点故障的风险;
垂直拆分
垂直拆库
垂直拆库是根据数据库里面的数据表的相关性进行拆分,比如:一个数据库里面既存在用户数据,又存在订单数据,那么垂直拆分可以把用户数据放到用户库、把订单数据放到订单库。
垂直分库在“微服务”盛行的今天已经非常普及了。基本的思路就是按照业务模块来划分出不同的数据库,而不是像早期一样将所有的数据表都放到同一个数据库中
原订单库把所有订单相关的数据(订单销售、订单售后、订单任务处理等数据)都放在同一数据库中,不符合电商系统分层设计,对于订单销售数据,性能第一,需要能够在大促高峰承受每分钟几万到几十万订单的压力;而售后数据,是在订单生成以后,用于订单物流、订单客服等,性能压力不明显,只要保证数据的及时性即可;所以根据这种情况,把原订单库进行垂直拆分,拆分成订单售后数据、订单销售数据、其他数据等
图示
垂直拆表
垂直分表在日常开发和设计中比较常见,通俗的说法叫做“大表拆小表”(按逻辑把大表拆成小表),拆分是基于关系型数据库中的“列”(字段)进行的。通常情况,某个表中的字段比较多,可以新建立一张“扩展表”,将不经常使用或者长度较大的字段拆分出去放到“扩展表”中
拆分字段的操作建议在数据库设计阶段就做好。如果是在发展过程中拆分,则需要改写以前的查询语句,会额外带来一定的成本和风险,建议谨慎。
垂直拆表是对数据表进行垂直拆分的一种方式,常见的是把一个多字段的大表按常用字段和非常用字段进行拆分,每个表里面的数据记录数一般情况下是相同的,只是字段不一样,使用主键关联
我们来考虑几个场景: 在用户表表中有一个字段是家庭地址,这个字段是可选字段,而且你在数据库操作的时候除了个人信息外,你并不需要经常读取或是改写这个字段。是么?那为什么不把他放到另外一张表中呢? 这样会让你的表有更好的性能。 如果你在用户表的设计中,还存在一个“最后登录时间”字段,在每次用户登录时被更新。首先,这张用户表会存在频繁的更新操作,此外,每次更新时会导致该表的查询缓存被清空。所以,你可以把这个字段放到另一个表中,这样就不会影响你对用户ID,用户名的不停地读取,因为查询缓存会帮你增加很多性能。
垂直拆表原则
把不常用的字段单独放在一张表。 把text,blob等大字段拆分出来放在附表中。 经常组合查询的列放在一张表中。
图示
水平拆分
水平拆分可以降低单表数据量,让每个单表的数据量保持在一定范围内,从而提升单表读写性能。但水平拆分后,同一业务数据分布在不同的表或库中,可能需要把单表事务改成跨表事务,需要转变数据统计方式等。
水平分表
水平分表也称为横向分表,比较容易理解,就是将表中不同的数据行按照一定规律分布到不同的数据库表中(这些表保存在同一个数据库中),这样来降低单表数据量,优化查询性能。最常见的方式就是通过主键或者时间等字段进行Hash和取模后拆分
水平分表,能够降低单表的数据量,一定程度上可以缓解查询性能瓶颈。但本质上这些表还保存在同一个库中,所以库级别还是会有IO瓶颈。所以,一般不建议采用这种做法。
图示
水平分库分表(也叫垂直水平拆分)
水平分库分表与水平分表的思想相同,唯一不同的就是将这些拆分出来的表保存在不同的数据中。这也是很多大型互联网公司所选择的做法
某种意义上来讲,有些系统中使用的“冷热数据分离”(将一些使用较少的历史数据迁移到其他的数据库中。而在业务功能上,通常默认只提供热点数据的查询),也是类似的实践。
水平拆分是把单表按某个规则把数据分散到多个表的拆分方式,比如:把单表1亿数据按某个规则拆分,分别存储到10个相同结果的表,每个表的数据是1千万,拆分出来的表,可以分别放至到不同数据库中,即同时进行水平拆库操作
垂直拆分从业务上把订单下单数据与下单后处理数据分开,但对于订单销售数据,由于数据量仍然巨大,最大的订单销售相关表达到几十亿的数据量,如果遇到大型促销(如:店庆128、419、618、双十一等等),数据库TPS达到上限,单销售库单订单表仍然无法满足需求,还需要进一步进行拆分,在这里使用水平拆分策略。 订单分表是首先考虑的,分表的目标是保证每个数据表的数量保持在1000~5000万左右,在这个量级下,数据表的大小与性能是最理想的。 如果几十个分表都放到一个订单库里面,运行于单组服务器上,则受限于单组服务器的处理能力,数据库的TPS有限,所以需要考虑分库,把分表放到分库里面,减轻单库的压力,增加总的订单TPS。
水平拆分是指数据表行的拆分,表的行数超过几百万行时,就会变慢,这时可以把一张的表的数据拆成多张表来存放。 水平拆分,有许多策略,例如,取模分表,时间维度分表,以及自定义的组织ID分表,用户ID分表等。在不同策略分表情况下,我们在根据各自的策略写入与读取。 我们再来考虑几个场景: 日志类型场景,你可以考虑年份月份分表,在日志记录表的名字中包含年和月的信息,例如log_2016_01,这样可以在已经没有插入操作的历史表上做频繁的查询操作,而不会干扰最新的当前表上插入操作。此外,你还可以考虑采用MyISAM或者Archive存储引擎,因为它们开销低,而且插入速度非常快。 组织类型场景,你可以考虑组织分表,不同的组织数据独立,而不应该在每张表中添加组织ID。这是一个不错的选择。
图示
跨库join的问题
在拆分之前,系统中很多列表和详情页所需的数据是可以通过sql join来完成的。而拆分后,数据库可能是分布式在不同实例和不同的主机上,join将变得非常麻烦。而且基于架构规范,性能,安全性等方面考虑,一般是禁止跨库join的。那该怎么办呢
跨库Join的几种解决思路
全局表
,就是有可能系统中所有模块都可能会依赖到的一些表。比较类似我们理解的“数据字典”。为了避免跨库join查询,我们可以将这类表在其他每个数据库中均保存一份。同时,这类数据通常也很少发生修改(甚至几乎不会),所以也不用太担心“一致性”问题。
字段冗余
这是一种典型的反范式设计,在互联网行业中比较常见,通常是为了性能来避免join查询。 举个电商业务中很简单的场景: “订单表”中保存“卖家Id”的同时,将卖家的“Name”字段也冗余,这样查询订单详情的时候就不需要再去查询“卖家用户表”。 字段冗余能带来便利,是一种“空间换时间”的体现。但其适用场景也比较有限,比较适合依赖字段较少的情况。最复杂的还是数据一致性问题,这点很难保证,可以借助数据库中的触发器或者在业务代码层面去保证。 当然,也需要结合实际业务场景来看一致性的要求。就像上面例子,如果卖家修改了Name之后,是否需要在订单信息中同步更新呢?
数据同步
定时A库中的tab_a表和B库中tbl_b有关联,可以定时将指定的表做同步。当然,同步本来会对数据库带来一定的影响,需要性能影响和数据时效性中取得一个平衡。这样来避免复杂的跨库查询。笔者曾经在项目中是通过ETL工具来实施的。
系统(服务)层组装
在系统层面,通过调用不同模块的组件或者服务,获取到数据并进行字段拼装。说起来很容易,但实践起来可真没有这么简单,尤其是数据库设计上存在问题但又无法轻易调整的时候。 具体情况通常会比较复杂。
简单字段组装的情况下,我们只需要先获取“主表”数据,然后再根据关联关系,调用其他模块的组件或服务来获取依赖的其他字段(如例中依赖的用户信息),最后将数据进行组装。 通常,我们都会通过缓存来避免频繁RPC通信和数据库查询的开销。
跨库事务(分布式事务)的问题
按业务拆分数据库之后,不可避免的就是“分布式事务”的问题。以往在代码中通过spring注解简单配置就能实现事务的,现在则需要花很大的成本去保证一致性。
我们的后台报表系统中join的表都有n个了, 分库后该怎么查?
互联网的业务系统中,本来就应该尽量避免join的,如果有多个join的,要么是设计不合理,要么是技术选型有误。
报表类的系统在传统BI时代都是通过OLAP数据仓库去实现的(现在则更多是借助离线分析、流式计算等手段实现),而不该向上面描述的那样直接在业务库中执行大量join和统计。
跨分片join
Join是关系型数据库中最常用的特性,但是在分片集群中,join也变得非常复杂。应该尽量避免跨分片的join查询。我们可以通过反规范化设计规避join查询,或者通过id二次查询join的数据。
跨分片的排序分页
一般来讲,分页时需要按照指定字段进行排序。当排序字段就是分片字段的时候,我们通过分片规则可以比较容易定位到指定的分片,而当排序字段非分片字段的时候,情况就会变得比较复杂了。为了最终结果的准确性,我们需要在不同的分片节点中将数据进行排序并返回,并将不同分片返回的结果集进行汇总和再次排序,最后再返回给用户。
跨分片的函数处理
在使用Max、Min、Sum、Count之类的函数进行统计和计算的时候,需要先在每个分片数据源上执行相应的函数处理,然后再将各个结果集进行二次处理。
分类
使用分库分表中间件,客户端连分库分表中间件,由中间件完成分库分表操作
客户端分库分表,在客户端完成分库分表操作,直连数据库
客户端分库分表由于直连数据库,所以性能比使用分库分表中间件高15%到20%。而使用分库分表中间件由于进行了统一的中间件管理,将分库分表操作和客户端隔离,模块划分更加清晰,便于DBA进行统一管理。
MySQL中间件和Smart Client
Altas – Qihoo360
英[ˈætləs]
Cobar - Ailibaba
mysql官方的MySQL Proxy
MyCAT – MyCAT社区
http://www.mycat.org.cn/
Vitess - Google
OneProxy – 平民软件
搜狐的dbproxy
https://github.com/SOHUDBA/SOHU-DBProxy
Amoeba
TDDL
tsharding
https://github.com/baihui212/tsharding
携程数据库访问框架Ctrip DAL (支持分库分表)
https://github.com/ctripcorp/dal
dangdang sharding-jdbc
https://github.com/dangdangdotcom/sharding-jdbc
mango
http://mango.jfaster.org/
http://mango.jfaster.org/index.html
github.com/liangyanghe/
全局主键生成器
Id首先要保证的是全局唯一,可以不必保证精确的顺序性,保证顺序意义不大,试想,就算这里保证了,由于现在的系统都是分布式的,先拿到的也不一定会先执行完。只要有大概的顺序对于调试、简单识别而言已经非常不错。
一些方案
UUID
32位,太长了,不可取。
UUID是指在一台机器上生成的数字,它保证对在同一时空中的所有机器都是唯一的。通常平台会提供生成的API。按照开放软件基金会(OSF)制定的标准计算,用到了以太网卡地址、纳秒级时间、芯片ID码和许多可能的数字(引自百度百科) UUID由以下几部分的组合: (1)当前日期和时间,UUID的第一个部分与时间有关,如果你在生成一个UUID之后,过几秒又生成一个UUID,则第一个部分不同,其余相同。 (2)时钟序列。 (3)全局唯一的IEEE机器识别号,如果有网卡,从网卡MAC地址获得,没有网卡以其他方式获得。 有 4 种不同的基本 UUID 类型:基于时间的 UUID、DCE 安全 UUID、基于名称的 UUID 和随机生成的 UUID。 Java使用UUID非常方便 UUID uuid = UUID.randomUUID(); 格式是35b3dda6-f481-410a-b393-9adc91703c68 优点:使用方便,无状态 缺点:太长,32位,
Twitter SnowFlake
源自twitter。性能出色,每秒几十w。比uuid短。
Twitter的SnowFlake是一个非常优秀的id生成方案,实现也非常简单,8Byte 是一个Long,8Byte等于64bit,核心代码就是毫秒级时间41位+机器ID 10位+毫秒内序列12位。当然可以调整机器位数和毫秒内序列位数比例,实际上从这个项目衍生出来很多实现方式。该项目地址为:https://github.com/twitter/snowflake是用Scala实现的。 优点: l 比UUID短,一般9-17位左右; l 性能非常出色,每秒几十万; 如果能用snowFlaker的方式,绝对不用UUID。
有各种衍生的Java版本
图示
Flickr
需要兼容老业务的应用,特别是拿到id要进行拼装以表示某种意义,并发又不那么高,可以使用flickr的方案,两台物理机也能轻松到达1.8万tps,响应时间在毫秒级。
采用mysql自增长id实现,实现也非常简单,至少使用两个节点容灾, 设置节点一 auto_increment_increment=2; auto_increment_offset=1; 设置节点二 auto_increment_increment=2; auto_increment_offset=2; 需要注意的是这两个配置最好放在配置文件中,否则mysql服务重启将丢失设置;如果直接通过set来设置,已经建立的连接不会改变变量的值,特别是使用连接池的时候。 分表在两个库里面建表 CREATE TABLE `tickets1` ( `id` bigint(20) unsigned NOT NULL auto_increment, `stub` char(1) NOT NULL default '', PRIMARY KEY (`id`), UNIQUE KEY `stub` (`stub`) ) ENGINE=MyISAM 注意建的是MyISAM表,MyISAM是表级锁,能保证所有replace的原子性。 不断的通过replace促使id自增,这样表中只有一条记录。 REPLACE INTO tickets1 (stub) VALUES ('a')REPLACE INTO tickets1 (stub) VALUES ('a') 在同一个连接内,通过last insert id获取自增的id值。 对MyISAM性能影响比较大的参数是key_buffer_size,可以适当调整。 优点: n 对于数据量不是特别大的应用,长度最小,从零开始。 n 如果已经有应用是基于sequence或者mysql的自增id,采用此方案非常容易迁移,兼容性好。 这种方案是没有绝对顺序的,只能有一个近似顺序。有可能一个实例跑的更快。
可以自己用多台Redis来写一个分布式id生成器。
基于分布式集群协调器生成
在不使用数据库的情况下,通过一个后台服务对外提供高可用的、固定步长标识生成,则需要分布式的集群协调器进行。 一般的,主流协调器有两类: 以强一致性为目标的:ZooKeeper为代表 以最终一致性为目标的:Consul为代表 ZooKeeper的强一致性,是由Paxos协议保证的;Consul的最终一致性,是由Gossip协议保证的。 在步长累计型生成算法中,最核心的就是保持一个累计值在整个集群中的「强一致性」。同时,这也会为唯一性标识的生成带来新的形成瓶颈。
用zk来生成分布式id,性能上可能是一个问题
总结
如果在高并发,大数据量的情况下,建议采用SnowFake方案,性能非常突出,而对于需要兼容老业务的应用,特别是拿到id要进行拼装以表示某种意义,并发又不那么高,可以使用flickr的方案,两台物理机也能轻松到达1.8万tps,响应时间在毫秒级。 Id的长度也是一个非常重要的指标,如果在并发量比较大的情况下,可以计算一下,传输的数据量非常可观。
全局表
分区表和非分区表分开放
按时间,如年、月
按日期时间分表需符合YYYY[MM][DD][HH]格式
如果主键是自增的数字,可以按区间,如1至500w放一个db,501至1000w放一个db
按多个字段切分,一般是将2个字段进行连接,然后Hash取模
Hash取模
连续分片
按时间、按主键自增区间来分片,都属于连续分片
连续分片可以快速定位分片进行高效查询,大多数情况下可以有效避免跨分片查询的问题。后期如果想对整个分片集群扩容时,只需要添加节点即可,无需对其他分片的数据进行迁移。但是,连续分片也有可能存在数据热点的问题,有些节点可能会被频繁查询压力较大,热数据节点就成为了整个集群的瓶颈。而有些节点可能存的是历史数据,很少需要被查询到。
随机分片
hash取模就属于随机分片
随机(离散)分片其实并不是随机的,也遵循一定规则。通常,我们会采用Hash取模的方式进行分片拆分,所以有些时候也被称为离散分片。随机分片的数据相对比较均匀,不容易出现热点和并发访问的瓶颈。但是,后期分片集群扩容起来需要迁移旧的数据。使用一致性Hash算法能够很大程度的避免这个问题,所以很多中间件的分片集群都会采用一致性Hash算法。随机分片也很容易面临跨分片查询的复杂问题。
二叉树分库
所谓“二叉树分库”指的是:我们在进行数据库扩容时,都是以2的倍数进行扩容。比如:1台扩容到2台,2台扩容到4台,4台扩容到8台,以此类推。这种分库方式的好处是,我们在进行扩容时,只需DBA进行表级的数据同步,而不需要自己写脚本进行行级数据同步。
树图思维导图提供 904名中国成年人第三磨牙相关知识、态度、行为和病史的横断面调查 在线思维导图免费制作,点击“编辑”按钮,可对 904名中国成年人第三磨牙相关知识、态度、行为和病史的横断面调查 进行在线思维导图编辑,本思维导图属于思维导图模板主题,文件编号是:10b9a8a2dd2fb4593f8130ef16c320fc
树图思维导图提供 9.战斗的基督教 在线思维导图免费制作,点击“编辑”按钮,可对 9.战斗的基督教 进行在线思维导图编辑,本思维导图属于思维导图模板主题,文件编号是:33d168acd0cd9f767f809c7a5df86e3a