关于分库分表大体思路,做法

起因:公司项目的数据量过大,已经超过20T,单张表数据+索引近5T,单表及单库性能都面临巨大的挑战。为了保证用户体验,提升效率,数据库方面需要优化。

项目:分布式项目,单系统已做集群,日均查询量2000W左右,交易量800W左右

特点:数据量大,并发量大

***(由于本身所在的项目属于核心系统部分与数据库交互,其他系统调用核心系统接口,所以不做阐述,仅记录本系统做法)

数据库:Oracle

语言:Java

技术:zookeeper+dubbo+spring+mybatis+内部框架

分库分表:mycat+内部中间件

===========================================分割线================================================

在阐述之前,要先说明一下:

1,项目在线上稳定运行,要保证用户的服务,不能停止项目的运行

2,版本的切换要迅速,以最少的时间完成

3,代码的改造要兼容旧系统以及其他系统

4,不能影响业务

5,平滑过渡

等等......

===========================================分割线================================================

数据库部分

旧版本:原有的系统是采用单个oracle数据库,所有的业务都指向同一个数据库

新版本:将单个数据库,依据业务的组成,拆分成为几部分,如下图

比如原先的一个系统,所有的业务都指向同一个数据库

旧系统业务=(订单+产品+用户+....)DB              (一个DB)

新系统业务=订单DB+产品DB+用户DB+....              (多个DB)

 

实际做法:我们的实际做法是依据业务,将所有的表划分到不同的归属域中,并不是一个业务一个库,另外增加分库分表的路由表,分区属性,分库属性。

原因:根据业务,将不同的表划分到不同的归属域,要考虑到原系统的实际情况和新方案之间的一些过渡,兼容,可行性问题。

比如原代码的改造,重构的工作量的大小;是否会影响到上游系统;旧表和新表之间的数据问题等等。 

另外不同域代表的不仅是业务归属,还代表数据库归属和表归属,为什么这么说呢?

因为原先一个库根据业务的归属域,拆分成多个库,用分区键路由,看你去哪一个归属域;在一个归属域中,一个库又拆成多个库,存储不同数据,用分库键路由,看你去哪一个数据库,其实库中的表结构都一样,只是存放的数据不同。

 

 

以上是数据库的拆分思路,要弄清楚一件事情,就是原本是所有的表都在一个库里面,现在是把不同的表放到不同的数据库里面

===========================================分割线================================================

相关问题部分:

有人会说,原先的代码,SQL指向的是同一个数据源,那么做了拆分之后,指向多个不同的数据源,那么会出问题

比如:

1,已经稳定运行的庞大系统,老代码,新代码,怎么知道哪些sql会在分库分表的时候出现问题?

2,多表查询的时候,不是同一个数据源怎么多表查询?

3,多数据源之间怎么切换,保持事务?

4,原有业务代码逻辑怎么改?

5,并发和防重怎么解决?

6,分库分表的实施,肯定会改变原表的结构导致数据问题,怎么办?

首先,要做前期的准备工作

===========================================分割线================================================

前期准备工作

1,已经稳定运行的庞大系统,老代码,新代码,怎么知道哪些sql会在分库分表的时候出现问题,怎么知道需要修改哪些SQL?

方案:监控系统

思路:简单的监控系统是由过滤器或者拦截器构成,而且本身mybatis可以配置打印sql

实际做法:其实我们的做法是新增监控系统(当然会复杂一些),采取为期一段时间(需要评估)的监控,每天搜集日志,整理,归纳,可以用python脚本。

把项目当中使用的sql整理出来,并且同时项目依据业务逻辑拆分成几大块,分配给不同的开发人员整理,归纳,与监控系统的结果做对比,把相关SQL整理出来。

 

2,多表查询的时候,不是同一个数据源怎么多表查询?

方案:SQL拆分+接口改造

思路:跨数据源的SQL拆分成多个SQL,业务代码重新整理

实际做法:

a,根据业务的拆分, 分配给不同的开发人员整理,归纳业务接口

b,依据不同的接口,根据接口中的业务和SQL,对比整理出的正在使用的SQL

c,依据分库逻辑,把跨数据源的多表关联的SQL拆分成单个SQL,如果不跨数据源,依旧可以多表查询

这里其实还涉及到接口的改造,因为在拆分SQL的时候,接口代码也会有相应的改动,这就要分配任务了......总之很难受

 

3,多数据源之间怎么切换,保持事务?

方案:单数据源单事务

思路:跨数据源的SQL已经拆分为前提,多个单数据源的SQL分别为各自的事务

实际做法:由于有内部框架,我们自己封装了jar包,手动开启,提交,回滚事务

举个例子:

旧系统:不管多少业务,不管什么业务,都是1个库,库里有A,B,C,D,E五张表,那么你查询一下就直接关联5张表就好了,需要update就直接update就好了,随便怎么搞

新系统:分别是5个库,5个业务,公共区域(品牌)+订单+用户+交易+积分一共5个业务,不同库里的表不同(理一下之间的关系)

a,公共区域库:品牌表A

b,订单库:订单表B

c,用户库:用户表C

d,交易库:交易表D

e,积分库:积分表E

比如一个业务:需要查询某个品牌的某个时间段的订单记录和这些订单的用户信息

<1>,先把需要查询的品牌先从表A中查询出来

<2>,依据<1>中的结果,当做参数传递到订单表中去查询,并且带上时间参数,获取订单信息

<3>,把<2>中的结果当做参数传递到用户表中去查询,获取用户信息

<4>,组装需要的结果

再比如:用户下单购买多件商品,根据用户订单的金额给用户加积分

<1>,用户下单,订单表增加数据

<2>,交易表记录流水

<3>,查询这个用户这比订单总金额

<4>,把<3>的结果当做参数传递到积分表算积分

不同的库,都分别开启和提交各自的事务,通过程序来控制各个小事务

 

关于产生脏数据的问题如下:

比如有A,B,C,D4个库,A中有1表,B中有2表,C中有3表,D中有4表,有一个业务需要做如下操作:

1,在A的1表中做select

2,把1的结果当做参数给B中2表,做insert

3,把1的结果当做参数给C中3表,做update

4,最后再把3的返回值,当做条件和参数,去D中4表做update

 

那么当3步骤出错的话,我们肯定是会catch的,但是2步骤的事务是提交了的,那么怎么办?

答案是:

1,3步骤出现错误,3步骤是可以回滚的,没有问题

2,2步骤的话,数据需要删除

那么又会问如果又出错了怎么办?

当然也确实有这个问题,我们的做法是有一个监控系统和补偿机制。

比如制定规则之后,每隔半个小时去扫描一些表,看哪些数据是有问题的,依据补偿的机制去补偿。

因为其实在实际操作过程中会增加很多表来完成这些操作。

比如最常见的补偿就是冲正了。

举个例子(自行百度)

即一笔交易在终端已经置为成功标志,但是发送到主机的帐务交易包没有得到响应,即终端交易超时,所以不确定该笔交易是否在主机端也成功完成,为了确保用户的利益,终端重新向主机发送请求,请求取消该笔交易的流水,如果主机端已经交易成功,则回滚交易,否则不处理,然后将处理结果返回给终端。

4,分库分表的实施,肯定会改变原表的结构导致数据问题,怎么办?

方案:数据清洗。

思路:依据分库分表的方案,确定新库的表结构,确定增加或者删除哪些字段,重新建立新表。在原表的基础上,新增字段,去除脏数据。

实际作法:

1,会先清除一批脏数据,这些脏数据是由于业务原因所导致的一些历史遗留问题,不会影响到现有的使用,比如说临时账户之类,临时数据之类。

2,保留可用数据,在现有表的基础上增加分库分表必要的字段

3,拆分原有表,比如说字段太多,可拆(看情况)

4,新增分库分表必须表,比如说路由表

5,去除冗余,无效字段

===========================================分割线================================================

4,原有业务代码逻辑怎么改

一般分库分表,如果会拆表或者废除一些表,那么就需要看代码,把废除的表的代码和拆表的代码重构,

原则上是需要多人协作仔细看代码来做这件事情的,分工合作

5,关于并发和防重问题

其实网上有很多解决方法

我们采取的做法是

1,乐观锁

2,悲观锁

3,幂等性校验

4,少量java代码的锁比如synchronized和lock锁(一般采用前三个,这个耗性能)

===========================================分割线================================================

在完成切换的过程之前,其实是有一个过渡期的,什么叫过渡期?

1,代码是一点一点写的,不是一下能完成的

2,多个团队协作,不是你的版本上线了,人家就要上线的

3,不同的团队负责的东西不同

 

现实:

1,在数据库的实际分库分表部分是由DBA负责的

2,在和数据库交互的过程中,是由中间件团队负责的

3,核心系统部分其实只是修改自己的业务代码和SQL语句来配合

举个例子比如说,我们要把一个库拆分成5个区域,每个区域都有10个库,那么一共50个库,怎么过度呢?

 

<1>,首先核心系统需要切换配置,首先要先配置好50个库的文件,但是路径指向的是同一个库,这是在过渡

<2>,到了和中间件团队约定的时间,由中间件团队的中间件去路由各个库

<3>,实际的切换不是同时切换到50个库,而是一个一个的切,每个大约在5分钟左右,在切库的过程中,只允许查询功能出现,停止一切写入库的操作

===========================================分割线================================================

关于路由问题:

虽然和数据库的交互是由中间件的团队来负责,但是我们也需要告诉中间件,到底哪一个连接是连哪一个数据库的,不然中间件也不能识别到底哪一个请求是连A库,哪一个请求去连B库。

方案:增加分区键,分库键

思路:请求参数中新增分区属性,分库属性参数,通过此参数告诉中间件

实际做法:我们的实际做法是依据业务,将所有的表划分到不同的归属域中,并不是一个业务一个库,另外增加分库分表的路由表,分区属性,分库属性。

原因:根据业务,将不同的表划分到不同的归属域,要考虑到原系统的实际情况和新方案之间的一些过渡,兼容,可行性问题。

比如原代码的改造,重构的工作量的大小;是否会影响到上游系统;旧表和新表之间的数据问题等等。 

另外不同域代表的不仅是业务归属,还代表数据库归属和表归属,为什么这么说呢?

因为原先一个库根据业务的归属域,拆分成多个库,用分区键路由,看你去哪一个归属域;在一个归属域中,一个库又拆成多个库,存储不同数据,用分库键路由,看你去哪一个数据库,其实库中的表结构都一样,只是存放的数据不同。

===========================================分割线================================================

关于SQL拆分问题

1,在大部分的时候,查询的需求会多一些,所以需要查询的速度很快,那么在优化的时候肯定有加索引这一条,但是我们在使用mybatis的时候经常会写一些判断语句,如下:

<if test="fwbdh != null and fwbdh == 'BAK'">

  fwbdh=#{fwbdh}

<if>

这种语句呢其实会很尴尬,为什么呢?因为DBA在加索引的时候,他不知道你的SQL中的where条件里到底这个字段要不要加索引,因为这个字段可能查询的时候不会用到。

所以,优化中的一条就是尽量不要写这种SQL,能判断的条件就在java代码里判断掉,或者把复杂SQL拆成简单SQL,用很多简单SQL来代替这些复杂SQL,说白了就是JAVA代码里多写if判断。但是呢,if判断多了也很耗性能,这也是很尴尬的,所以就权衡利弊了,不过一般来说,数据量超级大的时候,数据库查询的速度会比JAVA代码的运行速度慢很多。

2,一条复杂SQL和多条简单SQL执行一个业务

其实这个要分场景的,需要评估

3,千万不要写子查询,尤其是嵌套子查询

4,千万不要写select * from 

5,常见优化方案百度

===========================================分割线================================================

 

以上的方案实际上还在优化的过程中,但是已经是按照这个方向在走了,这个工程浩大,是以年来计算的,并不是一两个月能完成的,走到某一步,下一步可能都需要优化,改进。

 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值