精华内容
下载资源
问答
  • 分库分表原理

    2021-02-04 13:38:12
    1、为什么要分库分表 1.1 数据库性能瓶颈的出现 对于应用来说,如果数据库性能出现问题: (1)要么是无法获取连接,是因为在高并发的情况下连接数不够了。 (2)要么是操作数据变慢,数据库处理数据的效率出了问题...

    1、为什么要分库分表

    1.1 数据库性能瓶颈的出现

    对于应用来说,如果数据库性能出现问题:

    (1)要么是无法获取连接,是因为在高并发的情况下连接数不够了。
    (2)要么是操作数据变慢,数据库处理数据的效率出了问题。
    (3)要么是存储出现问题,比如单机存储的数据量太大了,存储的问题也可能会导致性能的问题。

    归根结底都是受到了硬件的限制,比如 CPU,内存,磁盘,网络等等。但是我们优化肯定不可能直接从扩展硬件入手,因为带来的收益和成本投入比例太低。

    所以我们先来分析一下,当我们处理数据出现无法连接,或者变慢的问题的时候,我们可以从哪些层面入手。

    1.2数据库优化方案对比

    数据库优化有很多层面。

    1.2.1 SQL 与索引

    因为 SQL 语句是在我们的应用端编写的,所以第一步,我们可以在程序中对 SQL 语句进行优化,最终的目标是用到索引。这个是容易的也是最常用的优化手段。

    1.2.2 表与存储引擎

    第二步,数据是存放在表里面的,表又是以不同的格式存放在存储引擎中的,所以,我们可以选用特定的存储引擎,或者对表进行分区,对表结构进行拆分或者冗余处理,或者对表结构比如字段的定义进行优化。

    1.2.3 架构

    第三步,对于数据库的服务,我们可以对它的架构进行优化。
    如果只有一台数据库的服务器,我们可以运行多个实例,做集群的方案,做负载均衡。
    或者基于主从复制实现读写分离,让写的服务都访问 master 服务器,读的请求都访问从服务器,slave 服务器自动 master 主服务器同步数据。
    或者在数据库前面加一层缓存,达到减少数据库的压力,提升访问速度的目的。
    为了分散数据库服务的存储压力和访问压力,我们也可以把不同的数据分布到不同的服务节点,这个就是分库分表(scale out)。

    注意主从(replicate)和分片(shard)的区别: 主从通过数据冗余实现高可用,和实现读写分离。分片通过拆分数据分散存储和访问压力。

    1.2.4 配置

    第四步,是数据库配置的优化,比如连接数,缓冲区大小等等,优化配置的目的都是为了更高效地利用硬件。

    1.2.5 操作系统与硬件

    最后一步操作系统和硬件的优化。

    从上往下,成本收益比慢慢地在增加。所以肯定不是查询一慢就堆硬件,堆硬件叫做向上的扩展(scale up)。

    什么时候才需要分库分表呢?我们的评判标准是什么? 如果是数据量的话,一张表存储了多少数据的时候,才需要考虑分库分表? 如果是数据增长速度的话,每天产生多少数据,才需要考虑做分库分表? 如果是应用的访问情况的话,查询超过了多少时间,有多少请求无法获取连接,才需要分库分表?这是一个值得思考的问题。

    1.3 架构演进与分库分表

    如金融系统。

    1.3.1 单应用单数据库

    早期,金融公司的消费金融核心系统一般是采购的,这个是一个典型的单体架构的应用。单体架构应用的特点就是所有的代码都在一个工程里面,打成一个 war 包部署到 tomcat,最后运行在一个进程中。

    这套消费金融的核心系统,用的是 Oracle 的数据库,初始化以后有几百张表,比如,客户信息表、账户表、商户表、产品表、放款表、还款表等等。

    在这里插入图片描述

    为了适应业务的发展,这一套系统不停地在修改,代码量越来越大,系统变得越来越臃肿。为了优化系统,通过搭集群,负载均衡,加缓存,优化数据库,优化业务代码系统,但是都应对不了系统的访问压力。

    所以这个时候系统拆分就势在必行了。将以前这一套采购的核心系统拆分出来很多的子系统,比如提单系统、商户管理系统、信审系统、合同系统、代扣系统、催收系统,所有的系统都依旧共用一套 Oracle 数据库。

    1.3.2 多应用单数据库

    对代码进行了解耦,职责进行了拆分,生产环境出现问题的时候,可以快速地排查和解决。

    在这里插入图片描述
    这种多个子系统共用一个 DB 的架构,会出现一些问题。

    第一个就是所有的业务系统都共用一个 DB,无论是从性能还是存储的角度来说,都是满足不了需求的。随着业务继续膨胀,又会增加更多的系统来访问核心数据库,但是一个物理数据库能够支撑的并发量是有限的,所有的业务系统之间还会产生竞争,最终会导致应用的性能下降,甚至拖垮业务系统。

    1.3.3 多应用独立数据库

    所以这个时候,必须要对各个子系统的数据库也做一个拆分。这个时候每个业务系统都有了自己的数据库,不同的业务系统就可以用不同的存储方案。

    在这里插入图片描述
    所以,分库其实是我们在解决系统性能问题的过程中,对系统进行拆分的时候带来的一个必然的结果。现在的微服务架构也是一样的,只拆应用不拆分数据库,不能解决根本的问题。

    1.3.4 什么时候分表?

    当我们对原来一个数据库的表做了分库以后,其中一些表的数据还在以一个非常快当我们对原来一个数据库的表做了分库以后,其中一些表的数据还在以一个非常快。

    所以,在分库之后,还需要进一步进行分表。当然,我们最开始想到的可能是在一个数据库里面拆分数据,分区或者分表,到后面才是切分到多个数据库中。

    分表主要是为了减少单张表的大小,解决单表数据量带来的性能问题。

    在这里插入图片描述

    我们需要清楚的是,分库分表会提升系统的复杂度,如果在近期或者未来一段时间内必须要解决存储和性能的问题,就不要去做超前设计和过度设计。就像我们搭建项目, 从快速实现的角度来说,肯定是从单体项目起步的,在业务丰富完善之前,也用不到微服务架构。

    如果我们创建的表结构合理,字段不是太多,并且索引创建正确的情况下,单张表存储几千万的数据是完全没有问题的,这个还是以应用的实际情况为准。当然我们也会对未来一段时间的业务发展做一个预判。

    2、分库分表的类型和特点

    从维度来说分成两种,一种是垂直,一种是水平。
    垂直切分:基于表或字段划分,表结构不同。我们有单库的分表,也有多库的分表。
    水平切分:基于数据划分,表结构相同,数据不同,也有同库的水平切分和多库的切分。

    在这里插入图片描述

    2.1 垂直切分

    垂直分表有两种,一种是单库的,一种是多库的。

    2.1.1 单库垂直分表

    单库分表,比如:商户信息表,拆分成基本信息表,联系方式表,结算信息表,附件表等等。

    2.1.2 多库垂直分表

    多库垂直分表就是把原来存储在一个库的不同的表,拆分到不同的数据库。

    比如:消费金融核心系统数据库,有很多客户相关的表,这些客户相关的表,全部单独存放到客户的数据库里面。合同,放款,风控相关的业务表也是一样的。

    在这里插入图片描述
    在这里插入图片描述

    当我们对原来的一张表做了分库的处理,如果某些业务系统的数据还是有一个非常快的增长速度,比如说还款数据库的还款历史表,数据量达到了几个亿,这个时候硬件限制导致的性能问题还是会出现,所以从这个角度来说垂直切分并没有从根本上解决单库单表数据量过大的问题。在这个时候,我们还需要对我们的数据做一个水平的切分。

    2.2 水平切分

    当我们的客户表数量已经到达数千万甚至上亿的时候,单表的存储容量和查询效率都会出现问题,我们需要进一步对单张表的数据进行水平切分。水平切分的每个数据库的表结构都是一样的,只是存储的数据不一样,比如每个库存储 1000 万的数据。

    水平切分也可以分成两种,一种是单库的,一种是多库的。

    2.2.1 单库水平分表

    银行的交易流水表,所有进出的交易都需要登记这张表,因为绝大部分时候客户都是查询当天的交易和一个月以内的交易数据,所以我们根据使用频率把这张表拆分成三张表:

    当天表:只存储当天的数据。

    当月表:在夜间运行一个定时任务,前一天的数据,全部迁移到当月表。用的是 insert into select,然后 delete。

    历史表:同样是通过定时任务,把登记时间超过 30 天的数据,迁移到 history 历史表(历史表的数据非常大,我们按照月度,每个月建立分区)。

    费用表:
    消费金融公司跟线下商户合作,给客户办理了贷款以后,消费金融公司要给商户返费用,或者叫提成,每天都会产生很多的费用的数据。为了方便管理,我们每个月建立一张费用表,例如 fee_detail_201901…fee_detail_201912。
    但是注意,跟分区一样,这种方式虽然可以一定程度解决单表查询性能的问题,但是并不能解决单机存储瓶颈的问题。

    2.2.2 多库水平分表

    另一种是多库的水平分表。比如客户表,我们拆分到多个库存储,表结构是完全一样的。

    在这里插入图片描述

    一般我们说的分库分表都是跨库的分表。

    既然分库分表能够帮助我们解决性能的问题,那我们是不是马上动手去做,甚至在项目设计的时候就先给它分几个库呢?先冷静一下,我们来看一下分库分表会带来哪些问题,也就是我们前面说的分库分表之后带来的复杂性。

    2.3 多案分库分表带来的问题

    2.3.1 跨库关联查询

    比如查询在合同信息的时候要关联客户数据,由于是合同数据和客户数据是在不同的数据库,那么我们肯定不能直接使用 join 的这种方式去做关联查询。

    我们有几种主要的解决方案:

    1. 字段冗余
      比如我们查询合同库的合同表的时候需要关联客户库的客户表,我们可以直接把一些经常关联查询的客户字段放到合同表,通过这种方式避免跨库关联查询的问题。

    2. 数据同步
      比如商户系统要查询产品系统的产品表,我们干脆在商户系统创建一张产品表,通过 ETL 或者其他方式定时同步产品数据。

    3. 全局表(广播表)
      比如行名行号信息被很多业务系统用到,如果我们放在核心系统,每个系统都要去关联查询,这个时候我们可以在所有的数据库都存储相同的基础数据。

    4. ER 表(绑定表)
      我们有些表的数据是存在逻辑的主外键关系的,比如订单表 order_info,存的是汇总的商品数,商品金额;订单明细表 order_detail,是每个商品的价格,个数等等。或者叫做从属关系,父表和子表的关系。他们之间会经常有关联查询的操作,如果父表的数据和子表的数据分别存储在不同的数据库,跨库关联查询也比较麻烦。所以我们能不能把父表和数据和从属于父表的数据落到一个节点上呢?
      比如 order_id=1001 的数据在 node1,它所有的明细数据也放到 node1; order_id=1002 的数据在 node2,它所有的明细数据都放到 node2,这样在关联查询的时候依然是在一个数据库。

    上面的思路都是通过合理的数据分布避免跨库关联查询,实际上在我们的业务中,也是尽量不要用跨库关联查询,如果出现了这种情况,就要分析一下业务或者数据拆分是不是合理。如果还是出现了需要跨库关联的情况,那我们就只能用最后一种办法。

    1. 系统层组装
      在不同的数据库节点把符合条件数据的数据查询出来,然后重新组装,返回给客户端。

    2.3.2 分布式事务

    比如在一个贷款的流程里面,合同系统登记了数据,放款系统也必须生成放款记录,如果两个动作不是同时成功或者同时失败,就会出现数据一致性的问题。如果在一个数据库里面,我们可以用本地事务来控制,但是在不同的数据库里面就不行了。所以分布式环境里面的事务,我们也需要通过一些方案来解决。

    复习一下。分布式系统的基础是 CAP 理论。

    1. C (一致性) Consistency:对某个指定的客户端来说,读操作能返回最新的写操作。对于数据分布在不同节点上的数据来说,如果在某个节点更新了数据,那么在其他节点如果都能读取到这个最新的数据,那么就称为强一致,如果有某个节点没有读取到,那就是分布式不一致。

    2. A (可用性) Availability:非故障的节点在合理的时间内返回合理的响应(不是错误和超时的响应)。可用性的两个关键一个是合理的时间,一个是合理的响应。
      合理的时间指的是请求不能无限被阻塞,应该在合理的时间给出返回。合理的响应,指的是系统应该明确返回结果并且结果是正确的。

    3. P (分区容错性) Partition tolerance:当出现网络分区后,系统能够继续工作。打个比方,这里集群有多台机器,有台机器网络出现了问题,但是这个集群仍然可以正工作。

    CAP 三者是不能共有的,只能同时满足其中两点。基于 AP,我们又有了 BASE 理论。

    基本可用(Basically Available):分布式系统在出现故障时,允许损失部分可用功能,保证核心功能可用。

    软状态(Soft state):允许系统中存在中间状态,这个状态不影响系统可用性,这里指的是 CAP 中的不一致。

    最终一致(Eventually consistent):最终一致是指经过一段时间后,所有节点数据都将会达到一致。

    分布式事务有几种常见的解决方案:

    1、全局事务(比如 XA 两阶段提交;应用、事务管理器™、资源管理器(DB)), 例如Atomikos。
    2、基于可靠消息服务的分布式事务。

    在这里插入图片描述

    3、柔性事务 TCC(Try-Confirm-Cancel)tcc-transaction

    在这里插入图片描述

    4、最大努力通知,通过消息中间件向其他系统发送消息(重复投递+定期校对)

    2.3.3 排序、翻页、函数计算问题

    跨节点多库进行查询时,会出现 limit 分页,order by 排序的问题。比如有两个节点, 节点 1 存的是奇数 id=1,3,5,7,9…;节点 2 存的是偶数 id=2,4,6,8,10…
    执行 select * from user_info order by id limit 0,10
    需要在两个节点上各取出 10 条,然后合并数据,重新排序。max、min、sum、count 之类的函数在进行计算的时候,也需要先在每个分片上执行相应的函数,然后将各个分片的结果集进行汇总和再次计算,最终将结果返回。

    2.3.4 全局主键避重问题

    MySQL 的数据库里面字段有一个自增的属性,Oracle 也有 Sequence 序列。如果是一个数据库,那么可以保证 ID 是不重复的,但是水平分表以后,每个表都按照自己的规律自增,肯定会出现 ID 重复的问题,这个时候我们就不能用本地自增的方式了。

    我们有几种常见的解决方案:

    1)UUID(Universally Unique Identifier 通用唯一识别码)

    UUID 标准形式包含 32 个 16 进制数字,分为 5 段,形式为 8-4-4-4-12 的 36 个字
    符,例如:c4e7956c-03e7-472c-8909-d733803e79a9。

    在这里插入图片描述

    xxxxxxxx-xxxx-Mxxx-Nxxx-xxxxxxxxxxxx
    M 表示 UUID 版本,目前只有五个版本,即只会出现 1,2,3,4,5,数字 N 的 一至三个最高有效位表示 UUID 变体,目前只会出现 8,9,a,b 四种情况。

    1、基于时间和 MAC 地址的 UUID
    2、基于第一版却更安全的 DCE UUID
    3、基于 MD5 散列算法的 UUID
    4、基于随机数的 UUID——用的最多,JDK 里面是 4
    5、基于 SHA1 散列算法的 UUID

    UUID 是主键是最简单的方案,本地生成,性能高,没有网络耗时。但缺点也很明显, 由于 UUID 非常长,会占用大量的存储空间;另外,作为主键建立索引和基于索引进行查询时都会存在性能问题,在 InnoDB 中,UUID 的无序性会引起数据位置频繁变动,导致分页。

    2) 数据库
    把序号维护在数据库的一张表中。这张表记录了全局主键的类型、位数、起始值,当前值。当其他应用需要获得全局 ID 时,先 for update 锁行,取到值+1 后并且更新后返回。并发性比较差。

    3)Redis
    基于 Redis 的 INT 自增的特性,使用批量的方式降低数据库的写压力,每次获取一段区间的 ID 号段,用完之后再去数据库获取,可以大大减轻数据库的压力。

    4)雪花算法 Snowflake(64bit)

    在这里插入图片描述

    核心思想:
    a)使用 41bit 作为毫秒数,可以使用 69 年
    b)10bit 作为机器的 ID(5bit 是数据中心,5bit 的机器 ID),支持 1024 个节点
    c)12bit 作为毫秒内的流水号(每个节点在每毫秒可以产生 4096 个 ID)
    d)最后还有一个符号位,永远是 0。
    代码:snowflake.SnowFlakeTest
    优点:毫秒数在高位,生成的 ID 整体上按时间趋势递增;不依赖第三方系统,稳定性和效率较高,理论上 QPS 约为 409.6w/s(1000*2^12),并且整个分布式系统内不会产生 ID 碰撞;可根据自身业务灵活分配 bit 位。
    不足就在于:强依赖机器时钟,如果时钟回拨,则可能导致生成 ID 重复。

    当我们对数据做了切分,分布在不同的节点上存储的时候,是不是意味着会产生多个数据源?既然有了多个数据源,那么在我们的项目里面就要配置多个数据源。

    现在问题就来了,我们在执行一条 SQL 语句的时候,比如插入,它应该是在哪个数据节点上面执行呢?又比如查询,如果只在其中的一个节点上面,我怎么知道在哪个节点,是不是要在所有的数据库节点里面都查询一遍,才能拿到结果?

    那么,从客户端到服务端,我们可以在哪些层面解决这些问题呢?

    2.4 多数据源/读写数据源的解决方案

    我们先要分析一下 SQL 执行经过的流程。 DAO——Mapper(ORM)——JDBC——代理——数据库服务

    2.4.1 客户端 DAO 层

    第一个就是在我们的客户端的代码,比如 DAO 层,在我们连接到某一个数据源之前, 我们先根据配置的分片规则,判断需要连接到哪些节点,再建立连接。

    Spring 中提供了一个抽象类 AbstractRoutingDataSource,可以实现数据源的动态切换。
    SSM 工程:spring-boot-dynamic-data-source-master 步骤:
    1)aplication.properties 定义多个数据源
    2)创建@TargetDataSource 注解
    3)创建 DynamicDataSource 继承 AbstractRoutingDataSource
    4)多数据源配置类 DynamicDataSourceConfig
    5)创建切面类 DataSourceAspect,对添加了@TargetDataSource 注解的类进行拦截设置数据源。
    6)在启动类上自动装配数据源配置 @Import({DynamicDataSourceConfig.class})
    7)在实现类上加上注解,如 @TargetDataSource(name = DataSourceNames.SECOND),调用

    在 DAO 层实现的优势:不需要依赖 ORM 框架,即使替换了 ORM 框架也不受影响。实现简单(不需要解析 SQL 和路由规则),可以灵活地定制。
    缺点:不能复用,不能跨语言。

    2.4.2 ORM 框架层

    第二个是在框架层,比如我们用 MyBatis 连接数据库,也可以指定数据源。我们可以基于 MyBatis 插件的拦截机制(拦截 query 和 update 方法),实现数据源的选择。
    例如:
    https://github.com/colddew/shardbatis

    2.4.3 驱动层

    不管是 MyBatis 还是 Hibernate,还是 Spring 的 JdbcTemplate,本质上都是对 JDBC 的封装,所以第三层就是驱动层。比如 Sharding-JDBC,就是对 JDBC 的对象进行了封装。JDBC 的核心对象:
    DataSource:数据源
    Connection:数据库连接
    Statement:语句对象
    ResultSet:结果集
    那我们只要对这几个对象进行封装或者拦截或者代理,就可以实现分片的操作。

    2.4.4 代理层

    前面三种都是在客户端实现的,也就是说不同的项目都要做同样的改动,不同的编程语言也有不同的实现,所以我们能不能把这种选择数据源和实现路由的逻辑提取出来,做成一个公共的服务给所有的客户端使用呢?
    这个就是第四层,代理层。比如 Mycat 和 Sharding-Proxy,都是属于这一层。

    2.4.5 数据库服务

    最后一层就是在数据库服务上实现,也就是服务层,某些特定的数据库或者数据库的特定版本可以实现这个功能。

    展开全文
  • 分库分表基础

    2021-09-06 13:49:28
    1、为什么要分库分表 1.1 数据库性能瓶颈的出现 对于应用来说,如果数据库性能出现问题, (1)要么是无法获取连接,是因为在高并发的情况下连接数不够了。 (2)要么是操作数据变慢,数据库处理数据的效率除了问题...

    1、为什么要分库分表

    1.1 数据库性能瓶颈的出现

    对于应用来说,如果数据库性能出现问题,
    (1)要么是无法获取连接,是因为在高并发的情况下连接数不够了。
    (2)要么是操作数据变慢,数据库处理数据的效率除了问题。
    (3)要么是存储出现问题,比如单机存储的数据量太大了,存储的问题也可能会导致性能的问题。

    归根结底都是受到了硬件的限制,比如 CPU,内存,磁盘,网络等等。但是我们优化肯定不可能直接从扩展硬件入手,因为带来的收益和成本投入比例太低。

    所以我们先来分析一下,当我们处理数据出现无法连接,或者变慢的问题的时候,我们可以从哪些层面入手。

    1.2数据库优化方案对比

    数据库优化有很多层面。

    1.2.1 SQL 与索引

    因为 SQL 语句是在我们的应用端编写的,所以第一步,我们可以在程序中对 SQL 语句进行优化,最终的目标是用到索引。这个是容易的也是最常用的优化手段。

    1.2.2 表与存储引擎

    第二步,数据是存放在表里面的,表又是以不同的格式存放在存储引擎中的,所以,我们可以选用特定的存储引擎,或者对表进行分区,对表结构进行拆分或者冗余处理,或者对表结构比如字段的定义进行优化。

    1.2.3 架构

    第三步,对于数据库的服务,我们可以对它的架构进行优化。
    如果只有一台数据库的服务器,我们可以运行多个实例,做集群的方案,做负载均衡。
    或者基于主从复制实现读写分离,让写的服务都访问 master 服务器,读的请求都访问从服务器,slave 服务器自动 master 主服务器同步数据。
    或者在数据库前面加一层缓存,达到减少数据库的压力,提升访问速度的目的。
    为了分散数据库服务的存储压力和访问压力,我们也可以把不同的数据分布到不同的服务节点,这个就是分库分表(scale out)。
    注意主从(replicate)和分片(shard)的区别: 主从通过数据冗余实现高可用,和实现读写分离。分片通过拆分数据分散存储和访问压力。

    1.2.4 配置

    第四步,是数据库配置的优化,比如连接数,缓冲区大小等等,优化配置的目的都是为了更高效地利用硬件。

    1.2.5 操作系统与硬件

    最后一步操作系统和硬件的优化。

    从上往下,成本收益比慢慢地在增加。所以肯定不是查询一慢就堆硬件,堆硬件叫做向上的扩展(scale up)。

    什么时候才需要分库分表呢?
    我们的评判标准是什么?
    如果是数据量的话,一张表存储了多少数据的时候,才需要考虑分库分表?
    如果是数据增长速度的话,每天产生多少数据,才需要考虑做分库分表?
    如果是应用的访问情况的话,查询超过了多少时间,有多少请求无法获取连接,才需要分库分表?

    这是一个值得思考的问题。

    1.3 架构演进与分库分表

    如金融系统。

    1.3.1 单应用单数据库

    早期,金融公司的消费金融核心系统一般是采购的,这个是一个典型的单体架构的应用。单体架构应用的特点就是所有的代码都在一个工程里面,打成一个 war 包部署到 tomcat,最后运行在一个进程中。

    这套消费金融的核心系统,用的是 Oracle 的数据库,初始化以后有几百张表,比如,客户信息表、账户表、商户表、产品表、放款表、还款表等等。

    在这里插入图片描述

    为了适应业务的发展,这一套系统不停地在修改,代码量越来越大,系统变得越来越臃肿。为了优化系统,通过搭集群,负载均衡,加缓存,优化数据库,优化业务代码系统,但是都应对不了系统的访问压力。

    所以这个时候系统拆分就势在必行了。将以前这一套采购的核心系统拆分出来很多的子系统,比如提单系统、商户管理系统、信审系统、合同系统、代扣系统、催收系统,所有的系统都依旧共用一套 Oracle 数据库。

    1.3.2 多应用单数据库

    对代码进行了解耦,职责进行了拆分,生产环境出现问题的时候,可以快速地排查和解决。
    在这里插入图片描述
    这种多个子系统共用一个 DB 的架构,会出现一些问题。
    第一个就是所有的业务系统都共用一个 DB,无论是从性能还是存储的角度来说,都是满足不了需求的。随着业务继续膨胀,又会增加更多的系统来访问核心数据库,但是一个物理数据库能够支撑的并发量是有限的,所有的业务系统之间还会产生竞争,最终会导致应用的性能下降,甚至拖垮业务系统。

    1.3.3 多应用独立数据库

    所以这个时候,必须要对各个子系统的数据库也做一个拆分。这个时候每个业务系统都有了自己的数据库,不同的业务系统就可以用不同的存储方案。

    在这里插入图片描述

    所以,分库其实是我们在解决系统性能问题的过程中,对系统进行拆分的时候带来的一个必然的结果。现在的微服务架构也是一样的,只拆应用不拆分数据库,不能解决根本的问题。

    1.3.4 什么时候分表?

    当我们对原来一个数据库的表做了分库以后,其中一些表的数据还在以一个非常快的速度增长。

    所以,在分库之后,还需要进一步进行分表。当然,我们最开始想到的可能是在一个数据库里面拆分数据,分区或者分表,到后面才是切分到多个数据库中。

    分表主要是为了减少单张表的大小,解决单表数据量带来的性能问题。

    在这里插入图片描述

    我们需要清楚的是,分库分表会提升系统的复杂度,如果在近期或者未来一段时间内必须要解决存储和性能的问题,就不要去做超前设计和过度设计。就像我们搭建项目, 从快速实现的角度来说,肯定是从单体项目起步的,在业务丰富完善之前,也用不到微服务架构。

    如果我们创建的表结构合理,字段不是太多,并且索引创建正确的情况下,单张表存储几千万的数据是完全没有问题的,这个还是以应用的实际情况为准。当然我们也会对未来一段时间的业务发展做一个预判。

    2、分库分表的类型和特点

    从维度来说分成两种,一种是垂直,一种是水平。
    垂直切分:基于表或字段划分,表结构不同。我们有单库的分表,也有多库的分表。
    水平切分:基于数据划分,表结构相同,数据不同,也有同库的水平切分和多库的
    切分。

    在这里插入图片描述

    2.1 垂直切分

    垂直分表有两种,一种是单库的,一种是多库的。

    2.1.1 单库垂直分表

    单库分表,比如:商户信息表,拆分成基本信息表,联系方式表,结算信息表,附件表等等。

    2.1.2 多库垂直分表

    多库垂直分表就是把原来存储在一个库的不同的表,拆分到不同的数据库。

    比如:消费金融核心系统数据库,有很多客户相关的表,这些客户相关的表,全部单独存放到客户的数据库里面。合同,放款,风控相关的业务表也是一样的。

    在这里插入图片描述

    在这里插入图片描述

    当我们对原来的一张表做了分库的处理,如果某些业务系统的数据还是有一个非常快的增长速度,比如说还款数据库的还款历史表,数据量达到了几个亿,这个时候硬件限制导致的性能问题还是会出现,所以从这个角度来说垂直切分并没有从根本上解决单库单表数据量过大的问题。在这个时候,我们还需要对我们的数据做一个水平的切分。

    2.2 水平切分

    当我们的客户表数量已经到达数千万甚至上亿的时候,单表的存储容量和查询效率都会出现问题,我们需要进一步对单张表的数据进行水平切分。水平切分的每个数据库的表结构都是一样的,只是存储的数据不一样,比如每个库存储 1000 万的数据。

    水平切分也可以分成两种,一种是单库的,一种是多库的。

    2.2.1 单库水平分表

    银行的交易流水表,所有进出的交易都需要登记这张表,因为绝大部分时候客户都是查询当天的交易和一个月以内的交易数据,所以我们根据使用频率把这张表拆分成三张表:

    当天表:只存储当天的数据。

    当月表:在夜间运行一个定时任务,前一天的数据,全部迁移到当月表。用的是 insert into select,然后 delete。

    历史表:同样是通过定时任务,把登记时间超过 30 天的数据,迁移到 history 历史表(历史表的数据非常大,我们按照月度,每个月建立分区)。

    费用表:
    消费金融公司跟线下商户合作,给客户办理了贷款以后,消费金融公司要给商户返费用,或者叫提成,每天都会产生很多的费用的数据。为了方便管理,我们每个月建立一张费用表,例如 fee_detail_201901…fee_detail_201912。
    但是注意,跟分区一样,这种方式虽然可以一定程度解决单表查询性能的问题,但是并不能解决单机存储瓶颈的问题。

    2.2.2 多库水平分表

    另一种是多库的水平分表。比如客户表,我们拆分到多个库存储,表结构是完全一样的。

    在这里插入图片描述

    一般我们说的分库分表都是跨库的分表。

    既然分库分表能够帮助我们解决性能的问题,那我们是不是马上动手去做,甚至在项目设计的时候就先给它分几个库呢?先冷静一下,我们来看一下分库分表会带来哪些问题,也就是我们前面说的分库分表之后带来的复杂性。

    2.3 分库分表带来的问题

    2.3.1 跨库关联查询

    比如查询在合同信息的时候要关联客户数据,由于是合同数据和客户数据是在不同的数据库,那么我们肯定不能直接使用 join 的这种方式去做关联查询。

    我们有几种主要的解决方案:

    1、字段冗余
    比如我们查询合同库的合同表的时候需要关联客户库的客户表,我们可以直接把一些经常关联查询的客户字段放到合同表,通过这种方式避免跨库关联查询的问题。

    2、数据同步:比如商户系统要查询产品系统的产品表,我们干脆在商户系统创建一张产品表,通过 ETL 或者其他方式定时同步产品数据。

    3、全局表(广播表) 比如行名行号信息被很多业务系统用到,如果我们放在核心系统,每个系统都要去关联查询,这个时候我们可以在所有的数据库都存储相同的基础数据。

    4、ER 表(绑定表)
    我们有些表的数据是存在逻辑的主外键关系的,比如订单表 order_info,存的是汇总的商品数,商品金额;
    订单明细表 order_detail,是每个商品的价格,个数等等。或者叫做从属关系,父表和子表的关系。他们之间会经常有关联查询的操作,如果父表的数据和子表的数据分别存储在不同的数据库,跨库关联查询也比较麻烦。所以我们能不能把父表和数据和从属于父表的数据落到一个节点上呢?
    比如 order_id=1001 的数据在 node1,它所有的明细数据也放到 node1; order_id=1002 的数据在 node2,它所有的明细数据都放到 node2,这样在关联查询的时候依然是在一个数据库。

    上面的思路都是通过合理的数据分布避免跨库关联查询,实际上在我们的业务中,也是尽量不要用跨库关联查询,如果出现了这种情况,就要分析一下业务或者数据拆分是不是合理。如果还是出现了需要跨库关联的情况,那我们就只能用最后一种办法。

    5、系统层组装
    在不同的数据库节点把符合条件数据的数据查询出来,然后重新组装,返回给客户端。

    2.3.2 分布式事务

    比如在一个贷款的流程里面,合同系统登记了数据,放款系统也必须生成放款记录,如果两个动作不是同时成功或者同时失败,就会出现数据一致性的问题。如果在一个数据库里面,我们可以用本地事务来控制,但是在不同的数据库里面就不行了。所以分布式环境里面的事务,我们也需要通过一些方案来解决。

    复习一下。分布式系统的基础是 CAP 理论。
    1.C (一致性) Consistency:对某个指定的客户端来说,读操作能返回最新的写操作。对于数据分布在不同节点上的数据来说,如果在某个节点更新了数据,那么在其他节点如果都能读取到这个最新的数据,那么就称为强一致,如果有某个节点没有读取到,那就是分布式不一致。

    2.A (可用性) Availability:非故障的节点在合理的时间内返回合理的响应(不是错误和超时的响应)。可用性的两个关键一个是合理的时间,一个是合理的响应。
    合理的时间指的是请求不能无限被阻塞,应该在合理的时间给出返回。合理的响应,指的是系统应该明确返回结果并且结果是正确的。

    3.P (分区容错性) Partition tolerance:当出现网络分区后,系统能够继续工作。打个比方,这里集群有多台机器,有台机器网络出现了问题,但是这个集群仍然可以正工作。

    CAP 三者是不能共有的,只能同时满足其中两点。基于 AP,我们又有了 BASE 理论。

    基本可用(Basically Available):分布式系统在出现故障时,允许损失部分可用功能,保证核心功能可用。

    软状态(Soft state):允许系统中存在中间状态,这个状态不影响系统可用性,这里指的是 CAP 中的不一致。

    最终一致(Eventually consistent):最终一致是指经过一段时间后,所有节点数据都将会达到一致。

    分布式事务有几种常见的解决方案:

    1、全局事务(比如 XA 两阶段提交;应用、事务管理器™、资源管理器(DB)), 例如Atomikos。
    2、基于可靠消息服务的分布式事务。

    在这里插入图片描述

    3、柔性事务 TCC(Try-Confirm-Cancel)tcc-transaction

    在这里插入图片描述
    4、最大努力通知,通过消息中间件向其他系统发送消息(重复投递+定期校对)

    2.3.3 排序、翻页、函数计算问题

    跨节点多库进行查询时,会出现 limit 分页,order by 排序的问题。比如有两个节点, 节点 1 存的是奇数 id=1,3,5,7,9…;节点 2 存的是偶数 id=2,4,6,8,10…
    执行 select * from user_info order by id limit 0,10
    需要在两个节点上各取出 10 条,然后合并数据,重新排序。max、min、sum、count 之类的函数在进行计算的时候,也需要先在每个分片上执行相应的函数,然后将各个分片的结果集进行汇总和再次计算,最终将结果返回。

    2.3.4 全局主键避重问题

    MySQL 的数据库里面字段有一个自增的属性,Oracle 也有 Sequence 序列。如果是一个数据库,那么可以保证 ID 是不重复的,但是水平分表以后,每个表都按照自己的规律自增,肯定会出现 ID 重复的问题,这个时候我们就不能用本地自增的方式了。

    我们有几种常见的解决方案:

    1)UUID(Universally Unique Identifier 通用唯一识别码)

    UUID 标准形式包含 32 个 16 进制数字,分为 5 段,形式为 8-4-4-4-12 的 36 个字
    符,例如:c4e7956c-03e7-472c-8909-d733803e79a9。

    在这里插入图片描述

    xxxxxxxx-xxxx-Mxxx-Nxxx-xxxxxxxxxxxx
    M 表示 UUID 版本,目前只有五个版本,即只会出现 1,2,3,4,5,数字 N 的 一至三个最高有效位表示 UUID 变体,目前只会出现 8,9,a,b 四种情况。

    1、基于时间和 MAC 地址的 UUID
    2、基于第一版却更安全的 DCE UUID
    3、基于 MD5 散列算法的 UUID
    4、基于随机数的 UUID——用的最多,JDK 里面是 4
    5、基于 SHA1 散列算法的 UUID

    UUID 是主键是最简单的方案,本地生成,性能高,没有网络耗时。但缺点也很明显, 由于 UUID 非常长,会占用大量的存储空间;另外,作为主键建立索引和基于索引进行查询时都会存在性能问题,在 InnoDB 中,UUID 的无序性会引起数据位置频繁变动,导致分页。

    2) 数据库
    把序号维护在数据库的一张表中。这张表记录了全局主键的类型、位数、起始值,当前值。当其他应用需要获得全局 ID 时,先 for update 锁行,取到值+1 后并且更新后返回。并发性比较差。

    3)Redis
    基于 Redis 的 INT 自增的特性,使用批量的方式降低数据库的写压力,每次获取一段区间的 ID 号段,用完之后再去数据库获取,可以大大减轻数据库的压力。

    4)雪花算法 Snowflake(64bit)

    在这里插入图片描述
    核心思想:
    a)使用 41bit 作为毫秒数,可以使用 69 年
    b)10bit 作为机器的 ID(5bit 是数据中心,5bit 的机器 ID),支持 1024 个
    节点
    c)12bit 作为毫秒内的流水号(每个节点在每毫秒可以产生 4096 个 ID)
    d)最后还有一个符号位,永远是 0。
    代码:snowflake.SnowFlakeTest
    优点:毫秒数在高位,生成的 ID 整体上按时间趋势递增;不依赖第三方系统,稳定性和效率较高,理论上 QPS 约为 409.6w/s(1000*2^12),并且整个分布式系统内不会产生 ID 碰撞;可根据自身业务灵活分配 bit 位。
    不足就在于:强依赖机器时钟,如果时钟回拨,则可能导致生成 ID 重复。

    当我们对数据做了切分,分布在不同的节点上存储的时候,是不是意味着会产生多个数据源?既然有了多个数据源,那么在我们的项目里面就要配置多个数据源。

    现在问题就来了,我们在执行一条 SQL 语句的时候,比如插入,它应该是在哪个数据节点上面执行呢?又比如查询,如果只在其中的一个节点上面,我怎么知道在哪个节点,是不是要在所有的数据库节点里面都查询一遍,才能拿到结果?

    那么,从客户端到服务端,我们可以在哪些层面解决这些问题呢?

    2.4 多数据源/读写数据源的解决方案

    我们先要分析一下 SQL 执行经过的流程。 DAO——Mapper(ORM)——JDBC——代理——数据库服务

    2.4.1 客户端 DAO 层

    第一个就是在我们的客户端的代码,比如 DAO 层,在我们连接到某一个数据源之前, 我们先根据配置的分片规则,判断需要连接到哪些节点,再建立连接。

    Spring 中提供了一个抽象类 AbstractRoutingDataSource,可以实现数据源的动态切换。
    SSM 工程:spring-boot-dynamic-data-source-master 步骤:
    1)aplication.properties 定义多个数据源
    2)创建@TargetDataSource 注解
    3)创建 DynamicDataSource 继承 AbstractRoutingDataSource
    4)多数据源配置类 DynamicDataSourceConfig
    5)创建切面类 DataSourceAspect,对添加了@TargetDataSource 注解的类进行拦截设置数据源。
    6)在启动类上自动装配数据源配置 @Import({DynamicDataSourceConfig.class})
    7)在实现类上加上注解,如 @TargetDataSource(name = DataSourceNames.SECOND),调用

    在 DAO 层实现的优势:不需要依赖 ORM 框架,即使替换了 ORM 框架也不受影响。实现简单(不需要解析 SQL 和路由规则),可以灵活地定制。

    缺点:不能复用,不能跨语言。

    2.4.2 ORM 框架层

    第二个是在框架层,比如我们用 MyBatis 连接数据库,也可以指定数据源。我们可以基于 MyBatis 插件的拦截机制(拦截 query 和 update 方法),实现数据源的选择。
    例如:
    https://github.com/colddew/shardbatis

    2.4.3 驱动层

    不管是 MyBatis 还是 Hibernate,还是 Spring 的 JdbcTemplate,本质上都是对 JDBC 的封装,所以第三层就是驱动层。比如 Sharding-JDBC,就是对 JDBC 的对象进行了封装。JDBC 的核心对象:
    DataSource:数据源
    Connection:数据库连接
    Statement:语句对象
    ResultSet:结果集
    那我们只要对这几个对象进行封装或者拦截或者代理,就可以实现分片的操作。

    2.4.4 代理层

    前面三种都是在客户端实现的,也就是说不同的项目都要做同样的改动,不同的编程语言也有不同的实现,所以我们能不能把这种选择数据源和实现路由的逻辑提取出来,做成一个公共的服务给所有的客户端使用呢?
    这个就是第四层,代理层。比如 Mycat 和 Sharding-Proxy,都是属于这一层。

    2.4.5 数据库服务

    最后一层就是在数据库服务上实现,也就是服务层,某些特定的数据库或者数据库的特定版本可以实现这个功能。

    展开全文
  • 能否ping通 netstat -apt 端口监听状态 telnet ip port 能否远程连接 ECS 安全规则配置 防火墙(ufw) iptables (iptables -I INPUT -p tcp --dport 3308 -j ACCEPT) 二、分布式数据库中间件Mycat分库分表简单...

    系统环境:Ubuntu 18.04.4 Docker 19.03.6

    一、MySQL读写分离主从模式

    1. 下载镜像

    docker pull mysql

    当前最新版本:mysql Ver 8.0.19 for Linux on x86_64 (MySQL Community Server - GPL)

    2. 启动主节点并修改配置文件

    docker run -it --name mysql -p 3306:3306 -e MYSQL_ROOT_PASSWORD=123456 mysql bash

    修改/etc/mysql/my.cnf配置文件(还得先apt update更新软件包列表才能安装vim等编辑工具,比较繁琐,所以可以选择先配置好my.cnf文件再docker run -v 挂载进容器)

    配置文件 my.cnf 修改如下:

    [mysqld]

    pid-file = /var/run/mysqld/mysqld.pid

    socket = /var/run/mysqld/mysqld.sock

    datadir = /var/lib/mysql

    # 以上是原有默认配置,add variables如下:

    # 在同一个Master-Slave集群里不能有重复id

    server-id = 3306

    # 不配置默认3306

    # port= 3306

    # 二进制日志文件存储路径及名称(在docker容器内涉及到权限问题,所以直接写在datadir下)

    log-bin = /var/lib/mysql/binlog

    # 需要开启binlog的数据库,多个则配置多行binlog-do-db

    binlog-do-db = training

    # binlog-do-db= db2

    # binlog-do-db= db3

    # 密码认证方式(加密方式) MySQL 8.0+ 默认是caching_sha2_password

    # 如果需要直接连接可以如下改回老版本的mysql_native_password或者create user时identified with mysql_native_password by 针对单个用户

    # default_authentication_plugin= mysql_native_password

    注:修改完配置文件后需要重启mysql才能生效,一般命令是systemctl restart mysqld,但是在docker容器中没有权限执行systemctl命令,所以选择退出容器后 docker restart

    docker exec -it mysql bash

    mysql -uroot -p [-hx.x.x.x -P3306]

    # 创建用户, @'ip'可以用@'%',不限IP

    create user 'replica'@'x.x.x.x' identified by '123456';

    # 赋予复制权限

    GRANT REPLICATION SLAVE ON *.* to 'replica'@'x.x.x.x';

    FLUSH PRIVILEGES;

    # 查看主机状态

    show master status;

    67705425fd98a78bf443c54bc8e4f288.png

    记下File和Position两个参数值在从库连接主库时将用到

    从节点配置

    # 容器内依然使用3306端口,免去修改端口的麻烦,宿主机端口使用3307

    docker run -it --name mysql_3307 -p 3307:3306 -e MYSQL_ROOT_PASSWORD mysql bash

    从节点my.cnf配置主要修改server-id不同,不需要log-bin和binlog-do-db参数

    [mysqld]

    server-id = 3307

    # 容器内端口

    # port= 3306

    # 读写分离,从库只读(0: 读写,1: 只读)

    read_only= 1

    # 限制super用户,read_only只限制普通用户写操作

    super_read_only= 1

    重启容器后生效

    Ctrl+P+Q 快捷键

    docker restart mysql_3307

    docker exec -it mysql_3307

    mysql -uroot -p123456# 如果容器内也使用3307端口,需跟上参数 -P3307

    stop slave;

    # 连接主机CHANGE MASTER TO option [, option] ...

    change master to master_host='xxx.xxx.xxx.xx',

    master_port=3306,

    master_user='replica',

    master_password='123456',

    # 这两个参数要跟上述截图master status的参数值一致

    master_log_file='binlog.000010',

    master_log_pos=155,

    # 默认使用caching_sha2_password加密方式时添加如下参数

    get_master_public_key=1,

    # 或者使用master_public_key_path指定主机公钥文件

    master_public_key_path='/etc/mysql/master_rsa_public.key';

    # 启动slave进程

    # 查看从机状态

    show slave status\G

    至此主从复制配置完成,Slave_IO_Running和Slave_SQL_Running都为Yes说明开启主从同步成功,

    否则可以查看Slave_SQL_Running_State状态查看失败原因,或者没有没有执行start slave;

    a85b63c663895bd50a85c10217ef215c.png

    6f1b305694f9f5bfc53e55027806e241.png

    change master的更多参数可见官网:https://dev.mysql.com/doc/refman/8.0/en/change-master-to.html

    关于主机公钥文件获取:

    进入master后show variables like "%caching_sha2_password%";

    可以看到Caching_sha2_password_rsa_public_key或者key_path: public_key.pem,这个文件路径:/var/lib/mysql/public_key.pem

    拷贝--BEGIN……END--这部分内容到从服务器,可以新建文件/etc/mysql/master_rsa_public.key写入。

    change master时master_public_key_path='/etc/mysql/master_rsa_public.key'参数指定 ;

    客户端登录时mysql -uroot -p --server-public-key-path=/etc/mysql/master_rsa_public.key指定

    66dd9f31ab7260ae89efda2d73756ab8.png

    34f012db50f051a3b744febaf41c65d4.png

    备份主库已有数据 完成上述步骤后,后续对主机的写操作才会同步到从机,开启同步前主机已有数据并不会同步,需要备份后导入从库,再开启主从同步。

    (1)备份前锁定主库,只允许读不允许写,防止备份过程中、开启主从同步前有新数据插入

    mysql> show global variables like "%read_only%";

    mysql> set global read_only=1;

    mysql> set global super_read_only=1# 限制super用户写操作

    # 如果当前不是super用户,需要限制super用户写操作就执行给所有表加读锁命令,但是当前连接不能退出,否则就自动释放锁了

    mysql> flush tables with read lock;

    (2)备份主库

    mysqldump语法:

    97c04640acaf118f5f04a8dcacef7c6d.png

    备份命令: docker exec mysql /usr/bin/mysqldump -uroot -p123456 training > mysql_master_backup.sql

    注:对于警告不要在命令行输入密码的问题,可以先在/etc/mysql/my.cn添加配置:

    [mysqldump]

    user= root

    password= 123456

    然后执行docker exec mysql mysqldump training > mysql_master_backup.sql

    (3)将备份数据导入从库

    注:需要在从库中建立好同名数据库才能将数据导入从库

    mysql> create database training;

    同理在从库的my.cnf中添加配置:

    [mysql]

    user= root

    password= 123456

    导入数据: cat mysql_master_backup.sql | docker exec -i mysql_3307 /usr/bin/mysql training

    (4)开启主从同步

    也就是上面2、 3点的步骤了,最好是在锁表前先配置好,只留最后 change master 和 start slave 到这里执行,以减少锁表的时间。

    change master 注意修改 master_log_file 和 master_log_pos 的值与 master status 的值一致。

    (5)释放主库的锁

    mysql> unlock tables;

    mysql> set global read_only=0;

    mysql> set global super_read_only=0;

    mysql> show global variables like "%read_only%";

    注:线上环境要注意尽量减少锁表的时间和锁表的范围!

    mysql> show full processlist; 可以看到:

    fb0155b74cf16fb9f137d08f8fb585e0.png

    至此读写分离的主从架构就搭建完成了,但不会自动进行主备切换,自动切换需要第三方工具配合,如:

    Keepalived 与MySQL互为主从自动切换配置

    MHA 实现MySQL主从自动切换 高可用 (Master High Availability)

    对于配置文件错误无法启动容器的情况可以先把配置文件复制出来修改好再复制回去:

    docker cp [OPTIONS] CONTAINER:SRC_PATH DEST_PATH

    docker cp [OPTIONS] SRC_PATH CONTAINER:DEST_PATH

    docker cp mysql_3307:/etc/mysql/my.cnf ./my.cnf

    docker cp ./my.cnf mysql_3307:/etc/mysql/my.cnf

    端口开放情况排查:

    能否ping通

    netstat -apt 端口监听状态

    telnet ip port 能否远程连接

    ECS 安全规则配置

    防火墙(ufw)

    iptables (iptables -I INPUT -p tcp --dport 3308 -j ACCEPT)

    二、分布式数据库中间件Mycat分库分表简单应用

    Mycat 安装

    Mycat 官网 查找所需版本下载链接

    # 下载

    wget http://dl.mycat.io/1.6.7.4/Mycat-server-1.6.7.4-release/Mycat-server-1.6.7.4-release-20200105164103-linux.tar.gz Mycat-server-1.6.7.4-release-linux.tar.gz

    # 解压至 /etc/,会自动创建 mycat 目录

    tar -zxvf Mycat-server-1.6.7.4-release-20200105164103-linux.tar.gz -C /etc

    4dc1e8e95ca07ac9b0edc8b8b9ebbd7e.png

    conf目录下有三个主要的配置文件:

    server.xml Mycat系统配置信息,包括账号密码、权限、端口等参数

    scheme.xml Mycat的逻辑库、表、分片规则、DataNode以及DataSource

    rule.xml 表拆分规则定义

    待续……

    展开全文
  • 分库分表常见问题和解决方案

    千次阅读 2022-01-01 20:06:46
    分库分表常见问题和解决方案

    MySQL出现的性能问题

    • 表数据量过大
    • sql查询太复杂
    • sql查询没走索引
    • 数据库服务器的性能过低等

    Mysql常见的优化手段

    • 增加索引,索引是直观也是最快速优化检索效率的方式。
    • 基于Sql语句的优化,比如最左匹配原则,用索引字段查询、降低sql语句的复杂度等。
    • 表的合理设计,比如符合三范式、或者为了一定的效率破坏三范式设计等。
    • 数据库参数优化,比如并发连接数、数据刷盘策略、调整缓存大小。
    • 数据库服务器硬件升级。
    • mysql大家主从复制方案,实现读写分离。

    大数据表优化方案

    对于大数据表的优化最直观的方式就是减少单表数据量,所以常见的解决方案是:

    • 分库分表,大表拆小表。
    • 冷热数据分离,所谓的冷热数据,其实就是根据访问频次来划分的,访问频次较多的数据是热数据,访问频次少的数据是冷数据。冷热数据分离就是把这两类数据分离到不同的表中,从而减少热数据表的大小。
    • 历史数据归档,简单来说就是把时间比较久远的数据分离出来存档,保证实时库的数据的有效生命周期。(磁带等,低价的存储介质)

    详解分库分表

    分库分表是非常常见针对单个数据表数据量过大的优化方式,它的核心思想是把一个大的数据表拆分成多个小的数据表,这个过程也叫(数据分片),它的本质其实有点类似于传统数据库中的分区表,比如mysql和oracle都支持分区表机制。

    分库分表是一种水平扩展手段,每个分片上包含原来总的数据集的一个子集。这种分而治之的思想在技术中很常见,比如多CPU、分布式架构、分布式缓存等等,像前面我们讲redis cluster集群时,slot槽的分配就是一种数据分片的思想。

    如图6-1所示,数据库分库分表一般有两种实现方式:
    在这里插入图片描述

    垂直拆分

    垂直拆分有两种,一种是单库的垂直拆分,另一种是多个数据库的垂直拆分。

    单库垂直分表

    单个表的字段数量建议控制在20~50个之间,之所以建议做这个限制,是因为如果字段加上数据累计的长度超过一个阈值后,数据就不是存储在一个页上,就会产生分页的问题,而这个问题会导致查询性能下降。

    所以如果当某些业务表的字段过多时,我们一般会拆去垂直拆分的方式,把一个表的字段拆分成多个表,如图6-2所示,把一个订单表垂直拆分成一个订单主表和一个订单明细表。
    在这里插入图片描述
    在Innodb引擎中,单表字段最大限制为1017,参考mysql官网。

    多库垂直分表

    多库垂直拆分实际上就是把存在于一个库中的多个表,按照一定的纬度拆分到多个库中,如图6-3所示。这种拆分方式在微服务架构中也是很常见,基本上会按照业务纬度拆分数据库,同样该纬度也会影响到微服务的拆分,基本上服务和数据库是独立的。
    在这里插入图片描述
    多库垂直拆分最大的好处就是实现了业务数据的隔离。其次就是缓解了请求的压力,原本所有的表在一个库的时候,所有请求都会打到一个数据库服务器上,通过数据库的拆分,可以分摊掉请求,在这个层面上提升了数据库的吞吐能力。

    水平拆分

    垂直拆分的方式并没有解决单表数据量过大的问题,所以我们还需要通过水平拆分的方式把大表数据做数据分片。

    水平切分也可以分成两种,一种是单库的,一种是多库的。

    单库水平分表

    如图6-4所示,表示把一张有10000条数据的用户表,按照某种规则拆分成了4张表,每张表的数据量是2500条。
    在这里插入图片描述
    两个案例:
    银行的交易流水表,所有进出的交易都需要登记这张表,因为绝大部分时候客户都是查询当天的交易和一个月以内的交易数据,所以我们根据使用频率把这张表拆分成三张表:

    当天表:只存储当天的数据。

    当月表:我们在夜间运行一个定时任务,前一天的数据,全部迁移到当月表。用的是insert intoselect,然后delete。

    历史表:同样是通过定时任务,把登记时间超过30天的数据,迁移到history历史表(历史表的数据非常大,我们按照月度,每个月建立分区)。

    费用表: 消费金融公司跟线下商户合作,给客户办理了贷款以后,消费金融公司要给商户返费用,或者叫提成,每天都会产生很多的费用的数据。为了方便管理,我们每个月建立一张费用表,例如fee_detail_201901…fee_detail_201912。

    但是注意,跟分区一样,这种方式虽然可以一定程度解决单表查询性能的问题,但是并不能解决单机存储瓶颈的问题。

    多库水平分表

    多库水平分表,其实有点类似于分库分表的综合实现方案,从分表来说是减少了单表的数据量,从分库层面来说,降低了单个数据库访问的性能瓶颈,如图6-5所示。
    在这里插入图片描述

    常见的水平分表策略

    哈希取模分片

    哈希分片,其实就是通过表中的某一个字段进行hash算法得到一个哈希值,然后通过取模运算确定数据应该放在哪个分片中,如图6-6所示。这种方式非常适合随机读写的场景中,它能够很好的将一个大表的数据随机分散到多个小表。
    在这里插入图片描述

    hash取模的问题

    hash取模运算有个比较严重的问题,假设根据当前数据表的量以及增长情况,我们把一个大表拆分成了4个小表,看起来满足目前的需求,但是经过一段时间的运行后,发现四个表不够,需要再增加4个表来存储,这种情况下,就需要对原来的数据进行整体迁移,这个过程非常麻烦。

    一般为了减少这种方式带来的数据迁移的影响,我们会采用一致性hash算法。

    一致性hash算法

    按照范围分片

    按范围分片,其实就是基于数据表的业务特性,按照某种范围拆分,这个范围的有很多含义,比如:

    • 时间范围,比如我们按照数据创建时间,按照每一个月保存一个表。基于时间划分还可以用来做冷热数据分离,越早的数据访问频次越少。
    • 区域范围,区域一般指的是地理位置,比如一个表里面存储了来自全国各地的数据,如果数据量较大的情况下,可以按照地域来划分多个表。
    • 数据范围,比如根据某个字段的数据区间来进行划分。

    如图6-7所示,表示按照数据范围进行拆分。
    在这里插入图片描述

    分库分表实战

    假设存在一个用户表,用户表的字段如下。
    该表主要提供注册、登录、查询、修改等功能。
    在这里插入图片描述
    该表的具体的业务情况如下(需要注意,在进行分表之前,需要了解业务层面对这个表的使用情况,然后再决定使用什么样的方案,否则脱离业务去设计技术方案是耍流氓)。

    用户端: 前台访问量较大,主要涉及两类请求:

    • 用户登录,面向C端,对可用性和一致性要求较高,主要通过login_name、email、phone来查询用户信息,1%的请求属于这种类型。
    • 用户信息查询,登录成功后,通过uid来查询用户信息,99%属于这种类型。

    运营端: 主要是运营后台的信息访问,需要支持根据性别、手机号、注册时间、用户昵称等进行分页查询,由于是内部系统,访问量较低,对可用性一致性要求不高。

    根据uid进行水平分表

    由于99%的请求是基于uid进行用户信息查询,所以毫无疑问我们选择使用uid进行水平分表。那么这里我们采用uid的hash取模方法来进行分表,具体的实施如图6-9所示,根据uid进行一致性hash取模运算得到目标表进行存储。

    在这里插入图片描述
    按照图6-9的结构,分别复制user_info表,重新命名为01~04,如图6-10所示。
    在这里插入图片描述

    如何实现全局唯一ID

    • 数据库自增ID(定义全局表)
    • UUID
    • Redis的原子递增
    • Twitter-Snowflake算法
    • 美团的leaf
    • MongoDB的ObjectId
    • 百度的UidGenerator

    分布式ID的特性

    • 唯一性:确保生成的ID是全局唯一的。
    • 有序递增性:确保生成的ID是对于某个用户或者业务是按一定的数字有序递增的。
    • 高可用性:确保任何时候都能正确的生成ID。
    • 带时间:ID里面包含时间,一眼扫过去就知道哪天的数据。

    数据库自增方案

    在数据库中专门创建一张序列表,利用数据库表中的自增ID来为其他业务的数据生成一个全局ID,那么每次要用ID的时候,直接从这个表中获取即可。

    CREATE TABLE `uid_table` (
    `id` bigint(20) NOT NULL AUTO_INCREMENT,
    `business_id` int(11)  NOT NULL,
    PRIMARY KEY (`id`) USING BTREE,
    UNIQUE (business_type)
    )
    

    在应用程序中,每次调用下面这段代码,就可以持续获得一个递增的ID。

    begin;
    REPLACE INTO uid_table (business_id) VALUES (2);
    SELECT LAST_INSERT_ID();
    commit;
    

    其中,replace into是每次删除原来相同的数据,同时加1条,就能保证我们每次得到的就是一个自增的ID。

    这个方案的优点是非常简单,它也有缺点,就是对于数据库的压力比较大,而且最好是独立部署一个DB,而独立部署又会增加整体的成本。

    优点:

    • 非常简单,利用现有数据库系统的功能实现,成本小,有DBA专业维护。
    • ID号单调自增,可以实现一些对ID有特殊要求的业务。

    缺点:

    • 强依赖DB,当DB异常时整个系统不可用,属于致命问题。配置主从复制可以尽可能的增加可用性,但是数据一致性在特殊情况下难以保证。主从切换时的不一致可能会导致重复发号。
    • ID发号性能瓶颈限制在单台MySQL的读写性能。

    UUID

    UUID的格式是: xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx 8-4-4-4-12共36个字符,它是一个128bit的二进制转化为16进制的32个字符,然后用4个 - 连接起来的字符串。

    UUID的五种生成方式:

    • 基于时间的UUID(date-time & MAC address): 主要依赖当前的时间戳及机器mac地址,因此可以保证全球唯一性。(使用了Mac地址,因此会暴露Mac地址和生成时间。)
    • 分布式安全的UUID(date-time & group/user id)将版本1的时间戳前四位换为POSIX的UID或GID。
    • 基于名字空间的UUID-MD5版(MD5 hash & namespace),基于指定的名字空间/名字生成MD5散列值得到,标准不推荐。
    • 基于随机数的UUID(pseudo-random number):基于随机数或伪随机数生成。
    • 基于名字空间的UUID-SHA1版(SHA-1 hash & namespace):将版本3的散列算法改为SHA1。

    在Java中,提供了基于MD5算法的UUID、以及基于随机数的UUID。
    优点:

    • 本地生成,没有网络消耗,生成简单,没有高可用风险。

    缺点:

    • 不易于存储:UUID太长,16字节128位,通常以36长度的字符串表示,很多场景不适用。
    • 信息不安全:基于MAC地址生成UUID的算法可能会造成MAC地址泄露,这个漏洞曾被用于寻找梅丽莎病毒的制作者位置。
    • 无序查询效率低:由于生成的UUID是无序不可读的字符串,所以其查询效率低。
    • UUID不适合用来做数据库的唯一ID,如果用UUID做主键,无序的不递增,大家都知道,主键是有
      索引的,然后mysql的索引是通过b+树来实现的,每一次新的UUID数据的插入,为了查询的优
      化,都会对索引底层的b+树进行修改,因为UUID数据是无序的,所以每一次UUID数据的插入都会对主键的b+树进行很大的修改,严重影响性能。

    雪花算法

    SnowFlake 算法,是 Twitter 开源的分布式 id 生成算法。其核心思想就是:使用一个 64 bit 的 long 型的数字作为全局唯一 id。雪花算法比较常见,在百度的UidGenerator、美团的Leaf中,都有用到雪花算法的实现。

    如图6-11所示,表示雪花算法的组成,一共64bit,这64个bit位由四个部分组成。

    • 第一部分, 1bit位,用来表示符号位,而ID一般是正数,所以这个符号位一般情况下是0。
    • 第二部分, 占41 个 bit:表示的是时间戳,是系统时间的毫秒数,但是这个时间戳不是当前系统的时间,而是当前 系统时间-开始时间 ,更大的保证这个ID生成方案的使用的时间!
    • 那么我们为什么需要这个时间戳,目的是为了保证有序性,可读性,我一看我就能猜到ID是什么时候生成的。

    41位可以2 41 - 1表示个数字,
    如果只用来表示正整数(计算机中正数包含0),可以表示的数值范围是:0 至 2 41 -1,减1
    是因为可表示的数值范围是从0开始算的,而不是1。
    也就是说41位可以表示2 41 -1个毫秒的值,转化成单位年则是(2 41 -1)/1000 * 60 * 60 * 24
    *365=69年,也就是能容纳69年的时间

    • 第三部分, 用来记录工作机器id,id包含10bit,意味着这个服务最多可以部署在 2^10 台机器上,也就是 1024 台机器。
      其中这10bit又可以分成2个5bit,前5bit表示机房id、5bit表示机器id,意味着最多支持2^5个机房(32),每个机房可以支持32台机器。
    • 第四部分, 第四部分由12bit组成,它表示一个递增序列,用来记录同毫秒内产生的不同id。

    那么我们为什么需要这个序列号,设想下,如果是同一毫秒同一台机器来请求,那么我们怎么保证他的唯一性,这个时候,我们就能用到我们的序列号,

    目的是为了保证同一毫秒内同一机器生成的ID是唯一的,这个其实就是为了满足我们ID的这个高
    并发,就是保证我同一毫秒进来的并发场景的唯一性。

    12位(bit)可以表示的最大正整数是2^12-1=4095,即可以用0、1、2、3、…4094这4095个数字,来表示同一机器同一时间截(毫秒)内产生的4095个ID序号。

    12位2进制,如果全部都是1的情况下,那么最终的值就是4095,也就是12bit能够存储的最大的数字是4095.

    在这里插入图片描述

    非分片键查询

    我们对user_info表的分片,是基于biz_id来实现的,也就是意味着如果我们想查询某张表的数据,必须先要使用biz_id路由找到对应的表才能查询到。

    那么问题来了,如果查询的字段不是分片键(也就是不是biz_id),比如本次分库分表实战案例中,运营端查询就有根据名字、手机号、性别等字段来查,这时候我们并不知道去哪张表查询这些信息。

    非分片键和分片键建立映射关系

    第一种解决办法就是,把非分片键和分片键建立映射关系,比如login_name -> biz_id 建立映射,相当于建立一个简单的索引,当基于login_name查询数据时,先通过映射表查询出login_name对应的biz_id,再通过biz_id定位到目标表。

    映射表的只有两列,可以承载很多的数据,当数据量过大时,也可以对映射表做水平拆分。 同时这种映射关系其实就是k-v键值对的关系,所以我们可以使用k-v缓存来存储提升性能。

    同时因为这种映射关系的变更频率很低,所以缓存命中率很高,性能也很好。

    用户端数据库和运营端数据库进行分离

    运营端的查询可能不止于单个字段的映射来查询,可能更多的会涉及到一些复杂查询,以及分页查询等,这种查询本身对数据库性能影响较大,很可能影响到用户端对于用户表的操作,所以一般主流的解决方案就是把两个库进行分离。

    由于运营端对于数据的一致性和可用性要求不是很高,也不需要实时访问数据库,所以我们可以把C端用户表的数据同步到运营端的用户表,而且用户表可以不需要做分表操作,直接全量查表即可。

    当然,如果运营端的操作性能实在是太慢了,我们还可以采用ElasticSearch搜索引擎来满足后台复杂查询的需求。

    实际应用中会遇到的问题

    数据迁移解决方案:
    https://blog.csdn.net/xiaowanzi_zj/article/details/118715503

    展开全文
  •  sharding jdbc 这个分库分表技术要解决的问题就是,随着数据量级的提升,物理硬件达到瓶颈,单表的性能优化也带来了瓶颈。 而数据量仍然要突破的难题。  这个问题的解决方案,其实就是一种分治的思想。用一句...
  • 随着互联网的高速发展,带来了海量数据...特别是NoSql的生态,我在前面讲过的k-v数据库、文档数据库、图形数据库等,都是比较主流的分布式数据库解决方案。 即便如此,关系型数据库仍然有它不可替代的特性,所以...
  • 分库分表架构实践(文末送书)

    千次阅读 2017-12-18 00:00:00
    作者介绍:丁浪,现就职于某垂直电商平台,担任技术架构师。关注高并发、高可用的架构设计,对系统服务化、分库分表、性能调优等...在谈论数据库架构和数据库优化的时候,我们经常会听到“分库分表”、“分片”、“Sha
  • 在基于springboot的ShardingSphere5.X的分库分表的解决方案之基于分片容量的范围分片算法解决方案(二十二)中我们讲解了基于5.0.0-alpha版的分片容量的范围分片算法的解决方案,那么在本章我们将为大家讲解5.0.0版本...
  • 数据库系列篇-中间件mycat 目录1. 安装MYCAT2.3. 1. 安装MYCAT 打开地址 http://www.mycat.org.cn/ 点击下载,选择版本1.6.7.6-win http://dl.mycat.org.cn/1.6.7.6/ 切换到lib路径, 替换mysql-connector-java-5.XX....
  • 前几天时间写了如何使用Sharding-JDBC进行分库分表和读写分离的例子,相信能够感受到Sharding-JDBC的强大了,而且使用配置都非常干净。官方支持的功能还很多功能分布式主键、强制路由等。这里是最终版介绍下如何在...
  • 学习 MySQL 必备的几个示例数据库

    千次阅读 2020-06-17 16:47:54
    MySQL 官方网站提供了以下几个示例数据库:Sakila、Employees、world、world_x 以及 menagerie。这些数据库既可以用于日常学习和测试,也可以作为我们设计时数据库的一个参考。本文就来介绍一下这些数据库的模式结构...
  • Sharding Sphere(一) 一、什么是Sharding Sphere 一套开源的分布式数据库中间件解决方案 有三个产品:Sharding-Jdbc和Shaeding-Proxy 定位为关系型数据库中间件,合理在...2、数据库进行分库分表(将单一库和单一
  • 在上面的章节我们讲解了如何基于AES实现数据的加密,但是我们生产中可能还有其他的加密方式,比如我希望使用RSA或者SM2进行加密,那这时候我们该...由于我们之前设计的数据库的加密字段最大长度为64,因此我们需要将.
  • 哈希算法历史悠久,业界著名的哈希算法也很多,比如MD5、SHA等。在我们平时的开发中,基本上都是拿现成的直接用。今天不会重点剖析哈希算法的原理,也不会教你如何设计一个哈希算法,而是从实战角度告诉你,在实际...
  • 数据库是什么 数据库管理系统,简称为DBMS(Database Management System),是用来存储数据的管理系统。 DBMS 的重要性 无法多人共享数据 无法提供操作大量数据所需的格式 实现读取自动化需要编程技术能力 无法应对...
  • 很多系统在设计之初就没有考虑过后期的分库与分表,甚至开发团队没有架构和DBA人员,开发团队也比较年轻,对于数据库的架构定义非常随意,满足当前需求即可。实际上数据库结构等同于建筑里面的地基,地基没有打好,...
  • 然后使用SqlRouter组件读取配置的分库分表或者其他策略,重写SQL语句,并且选择路由的节点 然后把这些SQL放到SqlExecutor组件中,选择合适的执行策略,例如线程池,栅栏,信号量等 最后再通过通信层将这些需要...
  • 最近公司一个新项目,需要进行分库分表,本人调研了mycat和sharding-jdbc后决定使用sharding-jdbc来进行分库分表,至于这两个的区别以及特点可以自行到官网查看,mycat更偏向于运维部署层面的分库分表,基于插件来...
  • 找到pycharm file settings 2,打开搜索cryptography包下载 3,下载成功重新启动就ok了 如果下载失败,见我的pycharm报错分类专栏中:pycharm安装其它包,报错提示error:can‘t find Rust compiler,错误:找不到...
  • H2数据库教程

    万次阅读 2018-07-16 18:53:54
    这可以是H2数据库,也可以是支持JDBC API的其他数据库。 这是一个客户端/服务器应用程序,因此需要服务器和客户端(浏览器)来运行它。 根据您的平台和环境,有多种方法可以启动H2控制台: OS 开始 视窗 ...
  • byte 保存到数据库

    2021-01-21 17:10:04
    使用Stream进行byte[]进行转换时要注意的事最近在做项目的时候为了对付NLB,把原来附件保存到Web服务器的方式改成了保存到数据库的方式。这样改动后,一般的附件上传没有问题,但是有一个做了Hash校验的附件上传页面...
  • 这里写自定义目录标题前言环境准备服务器准备获取集群安装包获取license安装集群版数据库高可用集群安装运行数据库部署工具创建项目创建集群 前言 可用性作为数据库的重要指标之一,集群化是一种通用的解决方案,...
  • Python数据库

    2020-03-17 17:22:45
    1.数据库基本概念 程序运行,数据是存在内存中,当程序终止,通常需要将数据保存到磁盘中。 数据库是存放数据仓库,它储存空间很大,可以存放百万条、千万条、上亿条数据,数据库存放数据有一定规则 2.数据库编程...
  • PostgreSQL是一个功能强大的开源对象关系型数据库系统,他使用和扩展了SQL语言,并结合了许多安全存储和扩展最复杂数据工作负载的功能。PostgreSQL的起源可以追溯到1986年,作为加州大学伯克利分校POSTGRES项目的一...
  • 去年开发一个项目的时候,因为系统的核心数据是定时从外界发送过来的,数据量比较大,后来很快单表就达到了千万级别,这就需要分库分表,最后选择了ShardingSphere,原因就是比较容易上手。 2. Sharding JDBC简介 ...
  • 【GaussDB数据库----连接】

    千次阅读 2020-06-09 16:59:15
    客户端工具通过CN连接数据库。因此连接前,需获取CN所在服务器的IP地址及CN的端口号信息。客户端工具可以通过任何一个CN连接数据库。 以操作系统用户omm登录安装有MPPDB服务的任一主机。执行source ${BIGDATA_HOME...
  • 常用内存数据库介绍

    万次阅读 2018-07-09 23:18:55
    1. 内存数据库简介 1.1 概念 一、什么是内存数据库 传统的数据库管理系统把所有数据都放在磁盘上进行管理,所以称做磁盘数据库(DRDB:Disk-Resident Database)。磁盘数据库需要频繁地访问磁盘来进行数据的操作,...
  • 为什么需要分库分表

    2020-01-31 21:37:52
    1 为什么要分库分表  1.1 数据库性能瓶颈的出现  对于应用来说,如果数据库性能出现问题,要么是无法获取连接,是因为在高并发的情况下连接数不够了。要么是操作数据变慢,数据库处理数据的效率除了问题。要么 ...
  • 数据库历史回顾和趋势:云化,融合是方向 数据库技术是信息技术领域的核心技术之一,几乎所有的信息系统都需要使用数据库系统来组织、存储、操纵和管理业务数据。数据库领域也是现代计算机学科的重要分支和研究方向...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 12,656
精华内容 5,062
关键字:

数据库分库sha