浅谈分库分表



前言

  • 理解分库分表的意义
  • 理解数据切分的不同方式,以及带来的问题与解决方案

一、为什么要分库分表

  • 随着业务变得越来越复杂,用户越来越多,集中式的架构性能回出现巨大问题,比如系统会越来越慢,而且时不时会宕机,所以必须要解决高性能和可用性的问题。

1.数数据性能瓶颈的出现

  • 高并发下,链接数不够
  • 数据量太大,查询效率下降
  • 存储问题,数据库所在的机器性能下降

2.数据库优化方案对比

2.1 重启

重启:是释放资源的最好方法

2.2 SQL与索引

慢SQL:SQL语句写的非常复杂,比如关联的表特别多,条件也特别多,导致查询所消耗的时间越来越长
方案:SQL优化,尽可能用到索引

参考SQL优化方案

2.3 表与存储引擎

如果SQL本身没有什么大问题,接着检查表结构的设计。比如字段类型和长度的选择、表结构是不是需要拆分或合并,不同表应该选择什么存储引擎,是不是要分区等等。

2.4 架构

表结构没有问题的话,就要从数据库服务、结构层面进行优化

  • 添加缓存:部署缓存服务器,把数据在内存里面缓存起来。先查缓存,没有再查数据库,提升查询效率,减少数据库压力。
  • 集群部署:一台数据库承受不了访问压力,可以部署集群做负载均衡。实现读写分离,写服务访问主节点,读服务访问从节点。
  • 分库分表:如果读写分离没有解决问题,因为只有一个主节点,写的压力并没有得到分摊,可以采用分库分表方案。

2.5 数据库配置

如果通过结构层面没有解决问题,活着机器随让配置的蘅皋但是性能没有发挥到极致,还可以优化数据库的配置,比如连接数、缓冲区大小等等。

2.6 操作系统

数据库是安装在操作系统上的,所以操作系统的配置也有优化的空间。

3. 什么时候分表

推荐: 单表行数超过500万行活着单表容量超过2GB,才推荐进行分库分表。(如果预计三年后数据量根本达不到这个级别,请不要在创建表时就分库分表)

  • 可以不用分库分表,尽量不要分库分表
  • 分库分表是一个长期的规划,要解决的不只是现在的问题。

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

  • 垂直切分:基于表或字段划分,表结构不同。有单库的分表,也有多库的分表。
  • 水平切分:基于数据划分,表结构相同,数据不同,也有同库的水平切分和多库的切分。

1. 垂直切分

垂直切分有两种,一种是单库的,一种是多库的。字段太多了,就要拆表,表太多了,就要拆库。

2. 水平切分

水平切分就是按照数据的维度分布不同的表中,可以是单库的,也可以是多库。

3. 分库分表带来的问题

3.1 跨库关联查询

因为在不同的(指在不同服务器上的)数据库,不能直接使用join做关联查询

3.1.1 解决方案
1) 字段冗余

可以将需要查询的字段,做成冗余字段,避免跨库关联查询的问题(反范式的设计)

第一范式:属性不可以再拆分。

第二范式:行必须有一个唯一标识。

第三范式:非主属性之间不能存在依赖关系(比如在表中关联另一张表的ID,就不能存在另一张表中的其他属性,实际项目中,为了提高查询效率都会进行反范式设计)

2) 数据同步

比如商户系统要查询产品系统的产品表,可以直接在商户系统中创建一张产品表,通过ETL、MQ或者Canal定时同步产品数据。

3) 全局表(广播表)

有一些基础信息表,比如行名行号表、行政区划表,被很多业务系统用到,如果放在核心系统,每个系统都要调用接口去查询,这个时候可以在所有的数据库都存储相同的基础数据,各个系统自己维护,保持同步。

4) 系统组装层

在不同的数据库节点,各自利用查询条件,把符合条件数据的数据查询出来,然后再内存中重新组装,返回给客户端。

3.2 分布式事务

如果是在一个数据库里面,我们可以用本地事务来控制,但是在不同的数据库里面就不行了。这里必须要出现一个协调者的角色,让大家统一行动,而且要分成多个阶段,一般是先确定都能成功才成功,只要有一个人不能成功,就要全部失败。

核心思想其实就是在预先提交能够成功的情况下,尽量缩短同时提交的时间差,来提升成功的概率。

分布式事务实现方案

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

跨节点多库进行查询时,会出现limit分页,order by排序的问题。

3.4 分布式全局ID

MySQL的数据库里面字段有一个自增的属性,Oracle也有Sequence序列。如果是一个数据库,那么可以保证ID是不重复的,但是水平分表以后,每个表都按照自己的规律自增,不同的表之间肯定会出现ID重复的问题。

分布式全局ID实现方案

3.4.1 UUID

UUID标准形式包含32个16进制数字,分为5段,形式为8-4-4-4-12的36个字符

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

3.4.2 数据库

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

3.4.3 Redis

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

3.4.4 雪花算法

在这里插入图片描述

  • 使用41bit作为毫秒数,可以使用69年
  • 10bit作为机器的ID(5bit是数据中心,5bit的机器ID),支持1024个节点
  • 12bit作为毫秒内的流水号(每个节点在每毫秒可以产生4096个ID)
  • 最后还有一个符号位,永远是0

3.5 多数据源(动态数据源)

在SSM的项目里面,查询一般要经过这些流程:在任何阶段都可以实现动态切换数据源
DAO——Mapper(ORM)——JDBC——代理——数据库服务

3.5.1 编码层(DAO)

在我们连接到某一个数据源之前,先根据配置的分片规则,判断需要连接到哪些节点,再建立连接。

Spring中提供了一个抽象类AbstractRoutingDataSource,可以实现数据源的动态切换。

步骤:

1)application.yml定义多个数据源
2)创建@TargetDataSource注解和DataSourceNames
3)创建DynamicDataSource继承AbstractRoutingDataSource
4)多数据源配置类DynamicDataSourceConfig
5)创建切面类DataSourceAspect,对添加了@TargetDataSource注解的类进行拦截设置数据源,使用DynamicDATa Source.setDataSource动态设置数据源
6)在启动类上自动装配数据源配置@Import({DynamicDataSourceConfig.class})
7)在实现类SysUserServiceImpl上加上注解,如@TargetDataSource(name=DataSourceNames.SECOND)

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

3.5.2 框架层

比如我们用的是MyBatis连接数据库,也可以指定数据源。可以基于MyBatis插件的拦截机制(拦截query和update方法),实现数据源的选择。

3.5.3 驱动层

不管是MyBatis还是Hibernate,还是JdbcTemplate,本质上都是对JDBC的封装,所以第三层就是驱动层。比如Sharding-JDBC,就是对JDBC的对象进行了封装。JDBC里面的两个核心对象,一个是Connection,是对连接的封装;一个是DataSource,是对一个数据库的封装。

实现思路:自己封装一个DataSource,在项目中配置多个数据源,这样就可以随心所欲的切换数据源了

3.5.4 代理层

前面都是在客户端实现的。也就是说,如果你有10个项目,那就要对10个项目进行改造,当然这种情况下我们会把逻辑抽取出来达成jar包,直接依赖使用。单如果不是Java项目,同样的逻辑还是要实现一遍。

所以可以直接做一个服务,不同语言的项目都可以直接连接,使用同一个逻辑。只需要提供跟数据库一样的协议,减少客户端的变动,比如原来用的Spring、MyBatis、Druid,都不用去修改,只要修改数据库Ip和连接规则就行了。

比如Mycat、Sharding-Proxy

3.5.5 服务层

在数据库服务前面加一个路由层,后面支持多个数据源。

腾讯云现在主推TDSQL,就是这样的一种实现。不过,这样的数据库在部署的时候,多了很多的节点需要部署。
在这里插入图片描述


总结

  • 2
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值