`
一江春水邀明月
  • 浏览: 77609 次
  • 性别: Icon_minigender_1
  • 来自: 杭州
社区版块
存档分类
最新评论

DB 分库分表项目改造心得

 
阅读更多

 

http://wangbt5191-hotmail-com.iteye.com/blogs/1736589

 

目前笔者在主导公司一个已有产品的改造项目, 目前在方案讨论阶段。

先介绍下目前系统的现状, 目前系统中订单相关的数据日新增量已经到了百万级别,围绕订单相关的订单分拣, 打包,发货相关操作的transaction 达到近千万级别每日。 在做了历史归档以后, db 性能还是比较吃紧, 所以我们想到了分库分表的方案

 

这里是中间我所提出的问题和备选方案

 

问题1. 分库以后登录如何做?

或者说User 登录的User 表数据存储在什么地方。
   这里有两个方案:

    方案1:

  1.     所有User 表相关的CRUD 操作都在一个含有User 表的引导库上进行, 其他业务库不需要User 表;
  2.     高级权限用户的新建用户, 更新和删除用户操作都会根据操作类型被路由到引导库上进行
  3.     用户的登录操作(读 User 表操作) 也会被路由到引导库上进行

    Pros  and  crons
    pros: 实现简单
    crons: 存在单点, 一旦引导库挂了, 所有用户都不可登录

    方案2: 是方案1 的折中

  1.   所有写操作在引导库上发生, 写操作限定在引导库上是为了使用我们User 表中的ID sequence 保证ID唯一和便于同步
  2.   各业务库中也存在User 表, 业务库上User 表只做读操作, 引导库上和业务库之间使用DB 复制工具对User 表数据做同步复制。
  3.   登录操作可以任选引导库和业务库中的任何一个db 实例做登录操作

    Pros  and  crons
    pros:不存在登录的单点问题;
    corns:

  1. 需要对DB做额外的同步, 不过相对来说这个代价还不是太高;
  2. 新建用户后的时效性依赖于DB 复制的时效性;

 

   经过讨论定下了第二套方案

问题2. 分库分表对象ID 如何做到全局唯一?

     我们以前DB中各表中的primary Key都是以Numberic 作为表字段类型, 辅以对ID 字段建sequence 来保证自增长来做到唯一的; 那么分库以后问题来了, 不同库中根据seq 生成的ID 会出现重复的, 这个对业务上是不允许的, 会导致数据正确性的问题;
     这里有两个方案:

    方案1.

  1.     保留老的numberic 的ID 字段,
  2.     再新增char 类型的external_ID 字段,external_ID 字段对应用来说可读不可写, 
  3.     各库对各表建create trigger, 在新增完成以后 external_ID = ${db instance number}+ "_"+ ${table_postfix} + "_"+ ${ID},
  4.     应用根据external_ID 中的${db instance number} 做read,update 和delete 的db路由;
  5.     ${db instance number} 和 ${table_postfix}根据商户ID 和base 表名做规则匹配产生, 这个规则也就是固化在DB的trigger 中

    在cache 中使用external_ID 为key
    Pros  and  crons
    pros: 增加的冗余字段external_ID 不需要做复杂的业务逻辑
        crons:

  1. 今后如果要把 db1 中的部分数据迁移到新增的db 中去的时候需要对数据进行清洗(update external_ID)
  2.  需要新增大量的trigger, 会带来DBA 大量的工作
  3.  trigger 会带来插入操作额外的db 开销

  


    方案2.

  1.     保留老的numberic 的ID 字段, 为每个表新增Customer_ID的冗余字段,
  2.     业务上的新增操作由业务层指定Customer_ID, 作为App 中的唯一性ID external_ID 是由 ${Customer_ID} + "_" + ${table_postfix} +"_"+ ${ID} 产生的, 由App自己拼装, App 也可以根据Base 表名决定, 某些对象的ID只是 ${table_postfix} +"_"+ ${ID} 或者 ${Customer_ID} + "_" + ${ID}
  3.       在App中, app 以external_ID 作为cache 层的Key, DB 的read, update, delete 操作是做一次split, 拿到Customer_ID根据db 路由规则定位db和表, 路由到db中的表后后还是以原始id 来做底层的读写。

    Pros  and  crons
        pros:

  1. 不需要大量的trigger带来的额外开销
  2. 可以根据Customer_ID和db 的对应关系自由的建立db 路由规则, 今后如果把某个db 实例中的部分Customer_ID相关的数据迁移到新的实例中, 只要更改路由规则就可以了, 可以对DB 和应用的业务层透明

        crons:分库数据迁移过程中, 有若干业务表还没有Customer_ID字段冗余, 这个需要做前期的准备

 

经过讨论定下了第二套方案

 

 

问题3. 分库后跨DB实例的事务如何处理?

根据我们现在的业务模型, 按照Customer_ID 做水平切分以后, 让客户相关的操作在一个request 访问的生命周期的DB hit 都落在一个db 实例上, 从而规避掉这个问题。

 

 

 

代码演示

Talk is cheap, show me the code. 

好吧, 我来上代码, 这个Demo 是基于mybatis 官网上的jpetstore 的例子, 这个例子做了以下几个功能点的演示:

1. 根据规则hit 到不同的db 实例(实例0 和实例1)上

2. 在db 实例0 上, 我们对product 表做了分表处理, 根据规则hit 不同的表

 

 

它的实现思路是:

1. 分库实现: 是改写SqlSessionDaoSupport 和MapperFactory Bean , 在SqlSessionDaoSupport 中的 SqlSession sqlSession 属性换成   List<SqlSession> sqlSessions, 在拿getSqlSession() 方法中写入规则来动态获取SqlSession;

2. 分表实现: 使用开源框架shardbatis, 详细信息可以参考http://seanhe.iteye.com/ 的博客

 

 

 

 

 

 

 

分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics