在详细描述之前,我们需要定义以下两件事:
(1)定义sharding的方式,设定我们需要sharding模式是通过SQL方式访问数据库。
(2)定义SQL访问时sharding的精细度,即需要SQL解析的模式还是简单的数据源绑定模式,不同的实现方式其实现复杂度也完全不一样。
接下来我们开始详细讲解数据源绑定模式和SQL解析模式的具体实现方法。
假设我们有用户信息2000万或者更多,随着业务的增长我们需要根据userid将此sharding, 数据源绑定的模式是如何实现的?
第一步,定义数据库的sharding方式,将数据库通过schema方式切分开来,简单SQL代码如下。
SQL代码如下:
create database user00;
create database user01;
...
create database user09;
第二步,将10个schema分布到不通的物理机器上,并创建相同的表,表的个数一定为10份。
machine1:
user00~user02:
create table user
(
userid bigint (12),
email varchar(256),
........
)
machine2:
user03~user05:
create table user
(
userid bigint (12),
email varchar(256),
........
)
machine3:
user06~user09:
create table user
(
userid bigint (12),
email varchar(256),
........
)
第三步,定义数据源与具体逻辑数据库的关系,简单的XML配置代码如下:
<datasource-config>
<datasource name="user00" value="0">
<property name="driver" value="com.mysql.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://10.224.57.116:3306/user00"/>
<property name="user" value="test"/>
<property name="password" value="pass"/>
</datasource>
...
<datasource name="user03" value="3">
<property name="driver" value="com.mysql.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://10.224.57.117:3306/user03"/>
<property name="user" value="test"/>
<property name="password" value="pass"/>
</datasource>
...
<datasource name="user06" value="6">
<property name="driver" value="com.mysql.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://10.224.57.118:3306/user06"/>
<property name="user" value="test"/>
<property name="password" value="pass"/>
</datasource>
...
</datasource-config>
第四步,根据传入的USERID,进行数据源的选择,代码逻辑如下:
(1)插入数据:
SQL语句为: insert into user(userid,email) values(1,'aa@sina.com');
插入逻辑:根据userid的value=1进行取模10的值=1,知道此时插入的数据源为user01,根据数据源打开数据库,插入数据。
(2)查询数据:
SQL语句为: select * from user where userid=12;
查询逻辑:根据userid的value=1进行取模10的值=2,知道此时查询的数据源为user02,根据数据源打开数据库,获取查询数据。
总结:数据源绑定模式相对比较粗犷,实现方式非常简单,拆分的数量与数据源的配置数量成正比,将数据源与传入的值进行绑定, sharding的精细度只能到schema级别,使用的SQL语句一定是固定的,可改造ibatis的数据源注入情况,达到ibatis的下应用的sharding。
依然假设我们有用户信息2000万或者更多,随着业务的增长我们需要根据userid将此sharding, SQL解析的模式是如何实现的?
第一步,定义数据库的shading方式,将数据库通过schema和表的方式切分开来,将6个schema分布到不通的物理机器上,并创建不同命名的表,表的个数是6*10=60。
machine1:
user00:
create table user00
(
userid bigint (12),
email varchar(256),
........
)
......
create table user09
(
userid bigint (12),
email varchar(256),
........
)
user01:
create table user10
(
userid bigint (12),
email varchar(256),
........
)
.....
create table user19
(
userid bigint (12),
email varchar(256),
........
)
machine2:
user02:
create table user20
(
userid bigint (12),
email varchar(256),
........
)
.....
create table user29
(
userid bigint (12),
email varchar(256),
........
)
user03:
create table user30
(
userid bigint (12),
email varchar(256),
........
)
.....
create table user39
(
userid bigint (12),
email varchar(256),
........
)
machine3:
user04:
create table user40
(
userid bigint (12),
email varchar(256),
........
)
.....
create table user49
(
userid bigint (12),
email varchar(256),
........
)
user05:
create table user50
(
userid bigint (12),
email varchar(256),
........
)
.....
create table user59
(
userid bigint (12),
email varchar(256),
........
)
第二步,定义数据源与具体逻辑数据库的关系,简单的XML代码如下:
<datasource-config>
<datasource name="user00" value="0">
<property name="driver" value="com.mysql.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://10.224.57.116:3306/user00"/>
<property name="user" value="test"/>
<property name="password" value="pass"/>
</datasource>
...
<datasource name="user02" value="2">
<property name="driver" value="com.mysql.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://10.224.57.117:3306/user03"/>
<property name="user" value="test"/>
<property name="password" value="pass"/>
</datasource>
...
<datasource name="user04" value="4">
<property name="driver" value="com.mysql.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://10.224.57.118:3306/user06"/>
<property name="user" value="test"/>
<property name="password" value="pass"/>
</datasource>
...
</datasource-config>
第三步,根据传入的USERID,进行数据源和表选择,代码逻辑如下:
(1)插入数据:
SQL语句为: insert into user(userid,email) values(1,'aa@sina.com');
插入逻辑:根据userid的value=1进行取模10的值=1,再减一,知道此时插入的数据源为user00,在根据userid末尾两位数据知道表名为user01,此时的SQL语句中表名已经更改,如果使用ibtais书写SQL,那么静态的SQL无法替换成动态的SQL语句,此时有两种做法,一种方法是在ibtais上层实现一套解析层,将SQL解析后并替换再传递给ibatis去执行,另一种方法是改写ibtais的代码,加入SQL解析程序,将SQL解析后并替换再传递给ibatis去执行插入操作,此时的难点在于SQL的语法解析,目前比较著名的淘宝TDDL就是采取第一种的方法,使用javacc去解析SQL语句。本人尝试了下第二种方法即ibatis的改写模式,在此将实现代码分享给大家。
a)在MappedStatement.java中增加下面代码,使用opensource工具JSqlParser:
/**
* add by philip zhong to replace the tables name.2010-12-15
* @param processedSql
* @return
* @throws JSQLParserException
* @throws Exception
*/
private String replaceSql(String processedSql,String replaceTableSuffixKey) throws JSQLParserException
{
CCJSqlParserManager pm = new CCJSqlParserManager();
net.sf.jsqlparser.statement.Statement statement = pm.parse(new StringReader(processedSql));
TablesNamesFinder tablesNamesFinder = new TablesNamesFinder();
List tableList = tablesNamesFinder.getTableList(statement);
String sql=processedSql;
for (int i=0;i<tableList.size();i++)
{
String tempSQL=sql;
String tableName=tableList.get(i).toString().trim();
String repTabName=tableList.get(i).toString().trim()+replaceTableSuffixKey.trim();
sql=tempSQL.replaceAll(tableName,repTabName);
//System.out.println("replaceSql="+sql);
}
return sql;
}
/**
* add by philip zhong to check has lookupkey in parameter object
* @param localSql
* @param map
* @return
*/
private String getTableSuffixKey(Object map)
{
String value = StaticBeanProbe.getObject(map, "lookupkey").toString();
return value;
}
b)再修改IBATIS代码,在MappedStatement.createStatement方法中添加下面代码:
//从传入的parameter中找到表的后缀关键字,例如表:user01,01就是后缀关键字。
String lookupkey=getTableSuffixKey(parameterObject);
//在执行前把sql语句对应的表替换掉,然后再执行
processedSql=replaceSql(processedSql,lookupkey);
c)需要在DAO代码中执行SQL的时间加上以下例子的代码,lookupkey是一个标签:
public long getUseridCount(long userid,String lookupkey) throws DaoException {
HashMap<String, Comparable> paraMap = new HashMap<String, Comparable>();
paraMap.put("userid", new Long(userid));
//for get table name
paraMap.put("lookupkey", lookupkey);
return ((Long) executeQueryForObject("getUseridCount",paraMap)).longValue();
}
(2)查询数据:
SQL语句为: select * from user where userid=12;
查询逻辑:根据userid的value=12进行取模10的值=2,再减一,知道此查询的数据源为user01,在根据userid末尾两位数据知道表名为user12,在经过SQL解析程序替换具体执行的SQL,然后获取SQL执行的结果。
总结:SQL解析模式sharding的精细度较高,在相同的资源下可支持的数据量大,对数据库的资源利用率较高,但实现方式比较复杂,需要实现SQL的语法解析程序,拆分的数量与数据源的配置数量以及表的拆分规则成正比, sharding的精细度到表级别。
至于sharding带来的维护以及异常case,后面blog的会尝试详细阐述。
你好,我发现这个文章有三处地方有点问题,估计是不是你写错啦?
ReplyDelete第一处:
“第一步,定义数据库的shading方式,将数据库通过schema和表的方式切分开来,将4个schema分布到不通的物理机器上,并创建不同命名的表,表的个数是4*10=40。”
依据你后面写的部分,这里的schema应该为6吧,所以表的个数应该为6*10=60。
第二处:
在“SQL语句为: insert into user(userid,email) values(1,'aa@sina.com');
插入逻辑:根据userid的value=1进行取模10的值=1,知道此时插入的数据源为user01,在根据userid末尾两位数据知道表名为user01,此时的SQL语句中表名已经更改,如果使用ibtais书写SQL,那么静态的SQL无法替换成动态的SQL语句,”中,
我个人觉得,根据userid末尾两位数据知道表名应该为user11吧?因为数据源user01中的表是从user10~user19并没有user01这个表,
第三处:
SQL语句为: select * from user where userid=12;
查询逻辑:根据userid的value=12进行取模10的值=2,知道此查询的数据源为user02,在根据userid末尾两位数据知道表名为user12,在经过SQL解析程序替换具体执行的SQL,然后获取SQL执行的结果。
这里也和第二处类似,在根据userid末尾两位数据知道表名应该为user22,理由也是一样,在数据源为user02中,表名的范围也是从user20~user29的,并不存在user12这个表的。
@rainly,非常感谢仔细的阅读,你指出的地方,的确有错误,数据源的关系的计算上需要减一。
ReplyDelete