设为首页 收藏本站
查看: 2062|回复: 0

[经验分享] 转:Mybatis分库分表扩展插件

[复制链接]

尚未签到

发表于 2016-11-26 08:09:24 | 显示全部楼层 |阅读模式
  原文地址:http://fangjialong.iyunv.com/blog/2240880
对于一个刚上线的互联网项目来说,由于前期活跃用户数量并不多,并发量也相对较小,所以此时企业一般都会选择将所有数据存放在一个数据库中进行访问操作。但随着后续的市场推广力度不断加强,用户数量和并发量不断上升,这时如果仅靠一个数据库来支撑所有访问压力,几乎是在自寻死路。所以一旦到了这个阶段,大部分Mysql DBA就会将数据库设置成读写分离状态,也就是一个Master节点对应多个Salve节点。经过Master/Salve模式的设计后,完全可以应付单一数据库无法承受的负载压力,并将访问操作分摊至多个Salve节点上,实现真正意义上的读写分离。但大家有没有想过,单一的Master/Salve模式又能抗得了多久呢?如果用户数量和并发量出现量级上升,单一的Master/Salve模式照样抗不了多久,毕竟一个Master节点的负载还是相对比较高的。为了解决这个难题,Mysql DBA会在单一的Master/Salve模式的基础之上进行数据库的垂直分区(分库)。所谓垂直分区指的是可以根据业务自身的不同,将原本冗余在一个数据库内的业务表拆散,将数据分别存储在不同的数据库中,同时仍然保持Master/Salve模式。经过垂直分区后的Master/Salve模式完全可以承受住难以想象的高并发访问操作,但是否可以永远高枕无忧了?答案是否定的,一旦业务表中的数据量大了,从维护和性能角度来看,无论是任何的CRUD操作,对于数据库而言都是一件极其耗费资源的事情。即便设置了索引,仍然无法掩盖因为数据量过大从而导致的数据库性能下降的事实,因此这个时候Mysql DBA或许就该对数据库进行水平分区(分表,sharding),所谓水平分区指的是将一个业务表拆分成多个子表,比如user_table0、user_table1、user_table2。子表之间通过某种契约关联在一起,每一张子表均按段位进行数据存储,比如user_table0存储1-10000的数据,而user_table1存储10001-20000的数据,最后user_table3存储20001-30000的数据。经过水平分区设置后的业务表,必然能够将原本一张表维护的海量数据分配给N个子表进行存储和维护,这样的设计在国内一流的互联网企业比较常见,如图所示:

DSC0000.jpg
 
水平分区

 
       以上是数据库分库分表的原理,但是如果每次在数据访问层的设计中实现分库分表,将会显得非常麻烦。笔者参考了了下code google shardbatis的设计方式,发现googlecode上的shardbatis无法做到分库,只能做到分表,而且配置相当不灵活,源码很难找到。所以笔者重新设计了一套mybatis插件,相比google code的那套,该框架可以做到完整的分库分表和友好的配置。组件结构如图所示:
DSC0001.png
组件结构图

 
使用方法:
1.准备数据库集群配置文件
 
 

<span style="font-family: 'Microsoft YaHei', 微软雅黑, SimHei, tahoma, arial, helvetica, sans-serif;"><?xml version="1.0" encoding="UTF-8"?>
<databases xmlns="http://fangjialong.iteye/schema/snsprod"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.vivo.com.cn/schema/snsprod
http://fangjialong.iteye/schema/snsprod
http://fangjialong.iteye/schema/snsprod/shardbatis-db.xsd">
<!-- 全局配置 -->
<logicName value="test"/>
<configs>
<property name="minPoolSize" value="4" />
<property name="minPoolSize" value="8" />
<property name="driverClass" value="com.mysql.jdbc.Driver" />
<property name="maxIdleTime" value="900" />
<property name="idleConnectionTestPeriod" value="1800" />
</configs>
<database suffix="_00" username="root" password=""
jdbcUrl="jdbc:mysql://localhost:3306/test_00?useUnicode=true&amp;characterEncoding=UTF-8&amp;zeroDateTimeBehavior=convertToNull">
<property name="minPoolSize" value="4" />
<property name="minPoolSize" value="8" />
<property name="driverClass" value="com.mysql.jdbc.Driver" />
<property name="maxIdleTime" value="900" />
<property name="idleConnectionTestPeriod" value="1800" />
</database>
<database suffix="_01" username="root" password=""
jdbcUrl="jdbc:mysql://localhost:3306/test_01?useUnicode=true&amp;characterEncoding=UTF-8&amp;zeroDateTimeBehavior=convertToNull" />
</databases>
</span>
  2.准备分库分表规则配置
 

<span style="font-family: 'Microsoft YaHei', 微软雅黑, SimHei, tahoma, arial, helvetica, sans-serif;"><configs xmlns="http://fangjialong.iyunv.com/schema/snsprod"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://fangjialong.iyunv.com/schema/snsprod
http://fangjialong.iyunv.com/schema/snsprod
http://fangjialong.iyunv.com/schema/snsprod/shardbatis-config.xsd">
<strategy logicTable="t_student" class="com.cannon.prod.dal.strategy.StudentShardStrategy"/>
</configs></span>
3.为mybatis配置扩展插件
 

<span style="font-family: 'Microsoft YaHei', 微软雅黑, SimHei, tahoma, arial, helvetica, sans-serif;"><?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN" "http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<typeAliases>
<typeAlias alias="Student" type="com.cannon.prod.dal.model.Student" />
</typeAliases>
<plugins>
<plugin interceptor="com.cannon.prod.dal.shardbatis.ShardPlugin">
<property name="configsLocation" value="META-INF/mybatis/shardbatis-config.xml" />
</plugin>
</plugins>
<mappers>
<mapper resource="META-INF/mybatis/mapper/mybatis-mapper-student.xml" />
</mappers>
</configuration></span>
  4.为mybatis配置分库分表数据源
 

<span style="font-family: 'Microsoft YaHei', 微软雅黑, SimHei, tahoma, arial, helvetica, sans-serif;"><?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.2.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-3.2.xsd">
<!-- 配置数据源 -->
<bean id="distributeDefaultDataSource" class="com.cannon.prod.dal.shardbatis.ShardDataSource"
init-method="init" destroy-method="destroy">
<property name="configsLocation" value="META-INF/database/distributed-default-db.xml"></property>
</bean>
<bean id="distributeDefaultTransactionManager"
class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="distributeDefaultDataSource" />
</bean>
<bean id="distributeDefaultTransactionTemplate"
class="org.springframework.transaction.support.TransactionTemplate">
<property name="transactionManager" ref="distributeDefaultTransactionManager" />
</bean>
<bean id="distributeDefaultSqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="configLocation">
<bean class="org.springframework.core.io.ClassPathResource">
<constructor-arg index="0"
value="META-INF/mybatis/mybatis-config.xml" />
</bean>
</property>
<property name="dataSource" ref="distributeDefaultDataSource" />
</bean>
</beans></span>
  5.使用逻辑表配置mybatis mapper
 

<span style="font-family: 'Microsoft YaHei', 微软雅黑, SimHei, tahoma, arial, helvetica, sans-serif;"><?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.cannon.prod.dal.mapper.Student">
<resultMap type="Student" id="StudentResultMap">
<id property="no" column="no" />
<result property="name" column="name" />
<result property="sex" column="sex" />
</resultMap>
<!-- 查询学生,根据id -->
<select id="getByNo" parameterType="String" resultType="Student"
resultMap="StudentResultMap">  
<![CDATA[
SELECT * FROM `t_student` WHERE `no` = #{no}  
]]>
</select>
<delete id="deleteByNo" parameterType="String">
<![CDATA[
DELETE FROM `t_student` WHERE `no` = #{no}  
]]>
</delete>
<insert id="create" parameterType="Student">
INSERT INTO t_student(no, name, sex) VALUES(#{no}, #{name}, #{sex})
</insert>
<update id="update" parameterType="Student">
UPDATE t_student
<trim prefix="SET" suffixOverrides=",">
<if test="name != null">name=#{name},</if>
</trim>
WHERE no=#{no}
</update>
</mapper></span>
6.编写分库分表规则
 

<span style="font-family: 'Microsoft YaHei', 微软雅黑, SimHei, tahoma, arial, helvetica, sans-serif;">public class StudentShardStrategy implements ShardStrategy {
@Override
public ShardCondition parse(Map<String, Object> params) {
String no = (String) params.get("no");
char c = no.charAt(0);
ShardCondition condition = new ShardCondition();
if (c == '1') {
condition.setDatabaseSuffix("_01");
condition.setTableSuffix("_01");
} else {
condition.setDatabaseSuffix("_00");
condition.setTableSuffix("_00");
}
return condition;
}
}</span>
  全部配置好了之后就可以按照以前使用mybatis的方式将数据库集群当做一个逻辑表来操作了。剩余的转换操作就交给中间扩展层插件来转换吧。
以下是转换结果:
Shard Original SQL:SELECT * FROM `t_student` WHERE `no` = ?
 
Shard Convert SQL:SELECT * FROM `test_00`.`t_student_00` WHERE `no` = ?
 
Shard Original SQL:DELETE FROM `t_student` WHERE `no` = ?
 
Shard Convert SQL:DELETE FROM `test_00`.`t_student_00` WHERE `no` = ?
 
Shard Original SQL:INSERT INTO t_student(no, name, sex) VALUES(?, ?, ?)
 
Shard Convert SQL:INSERT INTO test_00.t_student_00 (no, name, sex) VALUES (?, ?, ?)
 
Shard Original SQL:UPDATE t_student SET name=?  WHERE no=?
 
Shard Convert SQL:UPDATE test_00.t_student_00 SET name = ? WHERE no = ?
 
注意:分库分表从原则上是不支持跨库事物的,如果需要使用事务必须保证在多个表在同一个库中。
以下是事物支持的测试用例:
 
 

<span style="font-family: 'Microsoft YaHei', 微软雅黑, SimHei, tahoma, arial, helvetica, sans-serif;">/**
* @author fangjialong
* @date 2015年9月5日 下午4:27:50
*/
public class StudentDAOTransactionTest {
private DalSpringContext context;
private StudentDAO studentDAO;
private TransactionTemplate tt;
@Test
public void test() {
LogFactory.useNoLogging();
this.context = new DalSpringContext();
this.context.refresh();
this.studentDAO = context.getBean(StudentDAO.class);
this.tt = context.getBean("distributeDefaultTransactionTemplate", TransactionTemplate.class);
tt.execute(new TransactionCallbackWithoutResult() {
@Override
protected void doInTransactionWithoutResult(TransactionStatus status) {
Student s = new Student();
s.setNo("0001");
s.setName("房佳龙");
studentDAO.update(s);
status.setRollbackOnly();
}
});
context.close();
}
}</span>
  change log:
2015-09-08:1、增加指定库表查询方案,2、增加分库分表Sequence创建方案

运维网声明 1、欢迎大家加入本站运维交流群:群②:261659950 群⑤:202807635 群⑦870801961 群⑧679858003
2、本站所有主题由该帖子作者发表,该帖子作者与运维网享有帖子相关版权
3、所有作品的著作权均归原作者享有,请您和我们一样尊重他人的著作权等合法权益。如果您对作品感到满意,请购买正版
4、禁止制作、复制、发布和传播具有反动、淫秽、色情、暴力、凶杀等内容的信息,一经发现立即删除。若您因此触犯法律,一切后果自负,我们对此不承担任何责任
5、所有资源均系网友上传或者通过网络收集,我们仅提供一个展示、介绍、观摩学习的平台,我们不对其内容的准确性、可靠性、正当性、安全性、合法性等负责,亦不承担任何法律责任
6、所有作品仅供您个人学习、研究或欣赏,不得用于商业或者其他用途,否则,一切后果均由您自己承担,我们对此不承担任何法律责任
7、如涉及侵犯版权等问题,请您及时通知我们,我们将立即采取措施予以解决
8、联系人Email:admin@iyunv.com 网址:www.yunweiku.com

所有资源均系网友上传或者通过网络收集,我们仅提供一个展示、介绍、观摩学习的平台,我们不对其承担任何法律责任,如涉及侵犯版权等问题,请您及时通知我们,我们将立即处理,联系人Email:kefu@iyunv.com,QQ:1061981298 本贴地址:https://www.yunweiku.com/thread-305642-1-1.html 上篇帖子: 【Mybatis】表关联和resultMap的使用 下篇帖子: mybatis配置第三方连接池
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

扫码加入运维网微信交流群X

扫码加入运维网微信交流群

扫描二维码加入运维网微信交流群,最新一手资源尽在官方微信交流群!快快加入我们吧...

扫描微信二维码查看详情

客服E-mail:kefu@iyunv.com 客服QQ:1061981298


QQ群⑦:运维网交流群⑦ QQ群⑧:运维网交流群⑧ k8s群:运维网kubernetes交流群


提醒:禁止发布任何违反国家法律、法规的言论与图片等内容;本站内容均来自个人观点与网络等信息,非本站认同之观点.


本站大部分资源是网友从网上搜集分享而来,其版权均归原作者及其网站所有,我们尊重他人的合法权益,如有内容侵犯您的合法权益,请及时与我们联系进行核实删除!



合作伙伴: 青云cloud

快速回复 返回顶部 返回列表