数据库瓶颈
IO瓶颈
第一种:磁盘读IO瓶颈,热点数据太多,数据库缓存放不下,每次查询时会产生大量的IO,降低查询速度 -> 分库和垂直分表。
第二种:网络IO瓶颈,请求的数据太多,网络带宽不够 -> 分库。
CPU瓶颈
第一种:SQL问题,如SQL中包含join,group by,order by,非索引字段条件查询等,增加CPU运算的操作 -> SQL优化,建立合适的索引,在业务Service层进行业务计算。
第二种:单表数据量太大,查询时扫描的行太多,SQL效率低,CPU率先出现瓶颈 -> 水平分表。
1、分库分表的原因
随着业务快速发展,数据量越来越大,查询所需要的时间也越来越多,访问变慢,关系型数据库本身比较容易成为系统瓶颈,单机存储容量、连接数、处理能力都有限。当单表的数据量达到1000W或100G以后,由于查询维度较多,即使添加从库、优化索引,做很多操作时性能仍下降严重。
分库的原因:QPS过高,数据库响应速度来不及,一般mysql单机也就1000左右的QPS,如果超过1000,就要考虑分库。
分表的原因:单表太大,复杂SQL的查询速度变慢,一般mysql单表也就1000万左右的量,如果超过1000万,就要考虑分表
单库发生意外的时候,需要修复的是所有的数据,而多库中的一个库发生意外的时候,只需要修复一个库(当然,也可以用物理分区的方式处理这种问题)
分库分表就是为了解决由于数据量过大而导致数据库性能降低的问题,将原来独立的数据库拆分成若干数据库组成 ,将数据大表拆分成若干数据表组成,使得单一数据库、单一数据表的数据量变小,从而达到提升数据库性能的目的。
2、分库分表的常用策略
分库分表包括分库和分表两个部分,在生产中通常包括:垂直分库、水平分库、垂直分表、水平分表四种方式。
垂直分库:以 表为依据,按照业务归属不同,将不同的 表拆分到不同的 库中 。在高并发场景下,垂直分库一定程度上能够突破 IO、连接数及单机硬件资源的瓶颈,是大型分布式系统中优化数据库架构的重要手段
垂直分表:以 字段为依据,按照字段的活跃性,将 表中字段拆到不同的 表(主表和扩展表)中
水平分库:以 字段为依据 ,按照一定策略(hash、range等),将一个 库中的数据拆分到多个 库中
水平分表:将表中不同的数据行按照一定规律分布到不同的数据库表中(这些表保存在同一个数据库中),这样来降低单表数据量,优化查询性能。最常见的方式就是通过主键或者时间等字段进行 Hash 和取模后拆分。水平分表,能够降低单表的数据量,一定程度上可以缓解查询性能瓶颈。但本质上这些表还保存在同一个库中,所以库级别还是会有 IO 瓶颈。
垂直分表带来的数据冗余,以及查询次数的增加
水平分表表多大才需要考虑分表,确定分表的阈值
垂直切分优缺点
优点
- 解决业务系统层面的耦合,业务清晰
- 与微服务的治理类似,也能对不同业务的数据进行分级管理、维护、监控、扩展等
- 高并发场景下,垂直切分一定程度的提升IO、数据库连接数、单机硬件资源的瓶颈
缺点
- 分库后无法Join,只能通过接口聚合方式解决,提升了开发的复杂度
- 分库后分布式事务处理复杂
- 依然存在单表数据量过大的问题(需要水平切分)
水平切分优缺点
优点
- 不存在单库数据量过大、高并发的性能瓶颈,提升系统稳定性和负载能力
- 应用端改造较小,不需要拆分业务模块
缺点
- 跨分片的事务一致性难以保证
- 跨库的Join关联查询性能较差
- 数据多次扩展难度和维护量极大
3、常用的分库分表中间件、工具
简单易用的组建
强悍重量级的中间件
sharding-jdbc和mycat,这两个都可以去考虑使用。通常来说,这两个方案其实都可以选用,但是我个人建议中小型公司选用sharding-jdbc,client层方案轻便,而且维护成本低,不需要额外增派人手,而且中小型公司系统复杂度会低一些,项目也没那么多;但是中大型公司最好还是选用mycat这类proxy层方案,因为可能大公司系统和项目非常多,团队很大,人员充足,那么最好是专门弄个人来研究和维护mycat,然后大量项目直接透明使用即可。
4、分库分表步骤
根据容量(当前容量和增长量)评估分库或分表个数 -> 选key(均匀)-> 分表规则(hash或range等)-> 执行(一般双写)-> 扩容问题(尽量减少数据的移动)。
5、分库分表需要解决的问题:复杂查询,分布式事务
- 扩容与迁移
升级从库法
双写迁移法
第一步:(同步双写)应用配置双写,部署;
第二步:(同步双写)将老库中的老数据复制到新库中;
第三步:(同步双写)以老库为准校对新库中的老数据;
第四步:(同步双写)应用去掉双写,部署;
注:双写是通用方案。
- 分库分表维度导致的查询问题:join操作,COUNT(*)操作,order by 操作,分页
只要是进行切分,跨节点Join的问题是不可避免的。但是良好的设计和切分却可以减少此类情况的发生。解决这一问题的普遍做法是分两次查询实现。在第一次查询的结果集中找出关联数据的id,根据这些id发起第二次请求得到关联数据。
Join是关系型数据库中最常用的特性,但是在分片集群中,join也变得非常复杂。应该尽量避免跨分片的join查询(这种场景,比上面的跨分片分页更加复杂,而且对性能的影响很大)跨库join查询的解决办法:全局表、字段冗余、数据同步到一个表上、系统层组装、在系统层面,通过调用不同模块的组件或者服务,获取到数据并进行字段拼装
在使用Max、Min、Sum、Count之类的函数进行统计和计算的时候,需要先在每个分片数据源上执行相应的函数处理,然后再将各个结果集进行二次处理,最终再将处理结果返回。但如果结果集很大,对应用程序内存的消耗是一个问题。
- 跨库事务难以实现
解决事务问题目前有两种可行的方案:分布式事务和通过应用程序与数据库共同控制实现事务下面对两套方案进行一个简单的对比。
- 方案一:使用分布式事务
优点:实现简单,工作量小。由于多数应用服务器以及一些独立的分布式事务协调器做了大量的封装工作,使得项目中引入分布式事务的难度和工作量基本上可以忽略不计。基于两阶段提交,最大限度地保证了跨数据库操作的“原子性”,是分布式系统下最严格的事务实现方式。
缺点:系统“水平”伸缩的死敌。基于两阶段提交的分布式事务在提交事务时需要在多个节点之间进行协调,最大限度地推后了提交事务的时间点,客观上延长了事务的执行时间,这会导致事务在访问共享资源时发生冲突和死锁的概率增高,随着数据库节点的增多,这种趋势会越来越严重,从而成为系统在数据库层面上水平伸缩的"枷锁", 这是很多Sharding系统不采用分布式事务的主要原因。
- 方案二:由应用程序和数据库共同控制
原理:将一个跨多个数据库的分布式事务分拆成多个仅处 于单个数据库上面的小事务,并通过应用程序来总控 各个小事务。
优点:性能上有优势
缺点:需要应用程序在事务控制上做灵活设计。如果使用 了spring的事务管理,改动起来会面临一定的困难。
基于Best Efforts 1PC模式的事务
事务补偿(幂等值)
对于那些对性能要求很高,但对一致性要求并不高的系统,往往并不苛求系统的实时一致性,只要在一个允许的时间周期内达到最终一致性即可,这使得事务补偿机制成为一种可行的方案。事务补偿机制最初被提出是在“长事务”的处理中,但是对于分布式系统确保一致性也有很好的参考意义。笼统地讲,与事务在执行中发生错误后立即回滚的方式不同,事务补偿是一种事后检查并补救的措施,它只期望在一个容许时间周期内得到最终一致的结果就可以了。事务补偿的实现与系统业务紧密相关,并没有一种标准的处理方式。一些常见的实现方式有:对数据进行对帐检查;基于日志进行比对;定期同标准数据来源进行同步,等等。
6、路由规则
- 范围路由:按照范围划分,将某张表的创建时间按照日期划分存为月表。也可以将某张表的主键按照范围划分,比如 【1~10000】在一张表,【10001~20000】在一张表。好处是自带水平扩展,不需要过多干预。缺点是可能会出现数据不均匀的情况(比如某个月请求暴增)。
就是每个库一段连续的数据,一般按比如时间范围来的,但是这种一般较少用,因为很容易产生热点问题,大量的流量都打在最新的数据上了
好处:后面扩容的时候,就很容易,因为你只要预备好,给每个月都准备一个库就可以了,到了一个新的月份的时候,自然而然,就会写新的库了
缺点:但是大部分的请求,都是访问最新的数据。实际生产用range,要看场景,你的用户不是仅仅访问最新的数据,而是均匀的访问现在的数据以及历史的数据
- hash算法:一下均匀分散,较为常用。
好处:可以平均分配没给库的数据量和请求压力
坏处:扩容起来比较麻烦,会有一个数据迁移的过程
- 路由配置
配置路由就是路由表,用一张独立的表来记录路由信息。同样以用户ID为例,我们新增一张ROUTER表,这个表包含table_Id两列,根据user_id就可以查询对应的修改路由表就可以了。
配置路由设计简单,使用起来非常灵活,尤其是在扩充表的时候,只需要迁移指定的数据,然后修改路由表就可以了。
其缺点就是必须多查询一次,会影响整体性能,而且路由表本身如果太大,性能会成为瓶颈点,如果我们再将路由表分库分表,则又面临一个死循环。
7、分布式全局唯一ID
分布式ID需要满足那些条件?
-
全局唯一:必须保证ID是全局性唯一的,基本要求
-
高性能:高可用低延时,ID生成响应要块,否则反倒会成为业务瓶颈
-
高可用:100%的可用性是骗人的,但是也要无限接近于100%的可用性
-
好接入:要秉着拿来即用的设计原则,在系统设计和实现上要尽可能的简单
-
趋势递增:最好趋势递增,这个要求就得看具体业务场景了,一般不严格要求
分布式ID都有哪些生成方式?
今天主要分析一下以下9种
-
UUID
-
数据库自增ID
-
数据库多主模式
-
号段模式
-
Redis
-
雪花算法(SnowFlake)
-
滴滴出品(TinyID)
-
百度 (Uidgenerator)
-
美团(Leaf)
参考: