http://wangbt5191-hotmail-com.iteye.com/blogs/1736589
目前笔者在主导公司一个已有产品的改造项目, 目前在方案讨论阶段。
先介绍下目前系统的现状, 目前系统中订单相关的数据日新增量已经到了百万级别,围绕订单相关的订单分拣, 打包,发货相关操作的transaction 达到近千万级别每日。 在做了历史归档以后, db 性能还是比较吃紧, 所以我们想到了分库分表的方案
这里是中间我所提出的问题和备选方案
问题1. 分库以后登录如何做?
或者说User 登录的User 表数据存储在什么地方。
这里有两个方案:
方案1:
- 所有User 表相关的CRUD 操作都在一个含有User 表的引导库上进行, 其他业务库不需要User 表;
- 高级权限用户的新建用户, 更新和删除用户操作都会根据操作类型被路由到引导库上进行
- 用户的登录操作(读 User 表操作) 也会被路由到引导库上进行
Pros and crons
pros: 实现简单
crons: 存在单点, 一旦引导库挂了, 所有用户都不可登录
方案2: 是方案1 的折中
- 所有写操作在引导库上发生, 写操作限定在引导库上是为了使用我们User 表中的ID sequence 保证ID唯一和便于同步
- 各业务库中也存在User 表, 业务库上User 表只做读操作, 引导库上和业务库之间使用DB 复制工具对User 表数据做同步复制。
- 登录操作可以任选引导库和业务库中的任何一个db 实例做登录操作
Pros and crons
pros:不存在登录的单点问题;
corns:
- 需要对DB做额外的同步, 不过相对来说这个代价还不是太高;
- 新建用户后的时效性依赖于DB 复制的时效性;
经过讨论定下了第二套方案
问题2. 分库分表对象ID 如何做到全局唯一?
我们以前DB中各表中的primary Key都是以Numberic 作为表字段类型, 辅以对ID 字段建sequence 来保证自增长来做到唯一的; 那么分库以后问题来了, 不同库中根据seq 生成的ID 会出现重复的, 这个对业务上是不允许的, 会导致数据正确性的问题;
这里有两个方案:
方案1.
- 保留老的numberic 的ID 字段,
- 再新增char 类型的external_ID 字段,external_ID 字段对应用来说可读不可写,
- 各库对各表建create trigger, 在新增完成以后 external_ID = ${db instance number}+ "_"+ ${table_postfix} + "_"+ ${ID},
- 应用根据external_ID 中的${db instance number} 做read,update 和delete 的db路由;
- ${db instance number} 和 ${table_postfix}根据商户ID 和base 表名做规则匹配产生, 这个规则也就是固化在DB的trigger 中
在cache 中使用external_ID 为key
Pros and crons
pros: 增加的冗余字段external_ID 不需要做复杂的业务逻辑
crons:
- 今后如果要把 db1 中的部分数据迁移到新增的db 中去的时候需要对数据进行清洗(update external_ID)
- 需要新增大量的trigger, 会带来DBA 大量的工作
- trigger 会带来插入操作额外的db 开销
方案2.
- 保留老的numberic 的ID 字段, 为每个表新增Customer_ID的冗余字段,
- 业务上的新增操作由业务层指定Customer_ID, 作为App 中的唯一性ID external_ID 是由 ${Customer_ID} + "_" + ${table_postfix} +"_"+ ${ID} 产生的, 由App自己拼装, App 也可以根据Base 表名决定, 某些对象的ID只是 ${table_postfix} +"_"+ ${ID} 或者 ${Customer_ID} + "_" + ${ID}
- 在App中, app 以external_ID 作为cache 层的Key, DB 的read, update, delete 操作是做一次split, 拿到Customer_ID根据db 路由规则定位db和表, 路由到db中的表后后还是以原始id 来做底层的读写。
Pros and crons
pros:
- 不需要大量的trigger带来的额外开销
- 可以根据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/ 的博客