MySQL分库分表 【转】http://blog.itpub.net/29254281/viewspace-1819422/
MySQL处理大规模业务数据的方案一般都是分库分表.
最开始一般都选择垂直拆分.
比如电商网站,可能按照家电,图书,母婴等商品分类进行拆分.
这样做的好处是拆分简单,并且没有破坏数据库事务.
但是随着业务的增长,比如图书分类的订单数据表已经到达了10个T的规模.
就需要考虑做水平拆分了.把逻辑上一个表的数据,分别存放到不同的数据库服务器.
水平拆分的好处是
多个数据库服务器分担CPU,内存,网络带宽的压力.
多个数据库服务器分担备份、恢复的压力.
缺点是
破坏了原生的数据库事务.如果使用分布式事务,则会拖累数据库性能.
增加了运维管理的负担.原来管一台服务器就行了,现在得管一堆服务器.
水平拆分的三种主要方式
1.Hash拆分.比如按照 userId mod 64.将数据分布在64个服务器上
2.范围拆分.比如每台服务器计划存放一个亿的数据,先将数据写入服务器A.一旦服务器A写满,则将数据写入服务器B,以此类推.
这种方式的好处是扩展方便.数据在各个服务器上分布均匀.
3.路由表.自定义分布方式.
吕海波老师SACC2015的内容.
用事务补偿的方式,实现最终一致性.
下图是水平拆分之后的结构. 假设用户A向B转账100元.
由于水平拆分破坏了原有的事务.一个转账的业务,可能遇到如下的几个情况.
第一种情况,应用写队列超时导致重发了消息.那么结果是A本来向B转账100元.结果却转账了200元..
第二种情况,应用将消息成功写入队列,但是队列服务器挂了.结果是A向B转账失败.
第三种情况,中间层(队列的消费者)将消息取出,修改A的账户余额,但是用户A的库挂了,导致事务失败.结果是A向B转账失败.
第四种情况,中间层已经成功修改了用户A的账户余额,但是在修改B用户余额的时候,用户B的数据库挂了。结果是用户A的钱扣了,但是用户B的钱没有增加.
第五种情况.中间层从队列拿到了消息,但是还未及处理,中间层本身挂了..
最终一致性.
1.应用先将本次事务的业务日志写入业务日志的数据库,暂不提交
然后,向队列发送两个消息.一个消息是用户A -100元,另一个消息是用户B +100元.
确保两个消息都成功入队,则提交业务日志的事务,获取全局事务ID(tran_id).一旦有任何异常,回滚事务.
提交了事务,应用则可以直接返回.提示用户交易完成.
2.中间层获取消息.先连接用户A的数据库.
查询业务日志表(tran_log),如果没有该全局事务ID,则不予处理.(确认有这个全局事务,才处理)
查询消息日志表(msg_log),如果存在记录,则不予处理.(防止消息超时重发)
然后,开始事务.
先update用户A,减100元.
再写消息日志表,记录本次处理
最后提交事务.
3.中间层连接用户B的数据库,做相同的操作.
每隔5分钟,检查tran_log和msg_log.如果有不一致的情况,则进行事务补偿.
吕老师说到这里的时候,我觉得tran_log应该在如下的位置,然后作为各个底层库的Master.
用MySQL异步复制,将tran_log复制到用户A、B所在的库.
但是当面问吕老师,他说他们不是这么弄的.因为这个tran_log的数据量也是非常巨大.
他们把tran_log放在了底层库,但是我实在想不出来这样怎么弄.
可惜当时的环境也不容许我再刨根问底了.
不过留着自己琢磨,也挺有意思.
如果按照我的这个方式设计,后期问题会比较多,一个是tran_log数据量巨大.另外,tran_log所在的数据库容易产生瓶颈.
最开始一般都选择垂直拆分.
比如电商网站,可能按照家电,图书,母婴等商品分类进行拆分.
这样做的好处是拆分简单,并且没有破坏数据库事务.
但是随着业务的增长,比如图书分类的订单数据表已经到达了10个T的规模.
就需要考虑做水平拆分了.把逻辑上一个表的数据,分别存放到不同的数据库服务器.
水平拆分的好处是
多个数据库服务器分担CPU,内存,网络带宽的压力.
多个数据库服务器分担备份、恢复的压力.
缺点是
破坏了原生的数据库事务.如果使用分布式事务,则会拖累数据库性能.
增加了运维管理的负担.原来管一台服务器就行了,现在得管一堆服务器.
水平拆分的三种主要方式
1.Hash拆分.比如按照 userId mod 64.将数据分布在64个服务器上
2.范围拆分.比如每台服务器计划存放一个亿的数据,先将数据写入服务器A.一旦服务器A写满,则将数据写入服务器B,以此类推.
这种方式的好处是扩展方便.数据在各个服务器上分布均匀.
3.路由表.自定义分布方式.
吕海波老师SACC2015的内容.
用事务补偿的方式,实现最终一致性.
下图是水平拆分之后的结构. 假设用户A向B转账100元.
由于水平拆分破坏了原有的事务.一个转账的业务,可能遇到如下的几个情况.
第一种情况,应用写队列超时导致重发了消息.那么结果是A本来向B转账100元.结果却转账了200元..
第二种情况,应用将消息成功写入队列,但是队列服务器挂了.结果是A向B转账失败.
第三种情况,中间层(队列的消费者)将消息取出,修改A的账户余额,但是用户A的库挂了,导致事务失败.结果是A向B转账失败.
第四种情况,中间层已经成功修改了用户A的账户余额,但是在修改B用户余额的时候,用户B的数据库挂了。结果是用户A的钱扣了,但是用户B的钱没有增加.
第五种情况.中间层从队列拿到了消息,但是还未及处理,中间层本身挂了..
最终一致性.
1.应用先将本次事务的业务日志写入业务日志的数据库,暂不提交
然后,向队列发送两个消息.一个消息是用户A -100元,另一个消息是用户B +100元.
确保两个消息都成功入队,则提交业务日志的事务,获取全局事务ID(tran_id).一旦有任何异常,回滚事务.
提交了事务,应用则可以直接返回.提示用户交易完成.
2.中间层获取消息.先连接用户A的数据库.
查询业务日志表(tran_log),如果没有该全局事务ID,则不予处理.(确认有这个全局事务,才处理)
查询消息日志表(msg_log),如果存在记录,则不予处理.(防止消息超时重发)
然后,开始事务.
先update用户A,减100元.
再写消息日志表,记录本次处理
最后提交事务.
3.中间层连接用户B的数据库,做相同的操作.
每隔5分钟,检查tran_log和msg_log.如果有不一致的情况,则进行事务补偿.
吕老师说到这里的时候,我觉得tran_log应该在如下的位置,然后作为各个底层库的Master.
用MySQL异步复制,将tran_log复制到用户A、B所在的库.
但是当面问吕老师,他说他们不是这么弄的.因为这个tran_log的数据量也是非常巨大.
他们把tran_log放在了底层库,但是我实在想不出来这样怎么弄.
可惜当时的环境也不容许我再刨根问底了.
不过留着自己琢磨,也挺有意思.
如果按照我的这个方式设计,后期问题会比较多,一个是tran_log数据量巨大.另外,tran_log所在的数据库容易产生瓶颈.
参考文档 :