點(diǎn)擊Dbsplit進(jìn)入我們的項(xiàng)目主頁(yè)舞终,獲得更多信息和內(nèi)容。
什么是dbsplit?
Dbsplit擴(kuò)展了Spring的JdbcTemplate, 在JdbcTemplate上增加了分庫(kù)分表上陕,讀寫分離和失效轉(zhuǎn)移等功能硼莽,并與Spring JDBC保持相同的風(fēng)格懂鸵,簡(jiǎn)單實(shí)用套像,避免外部依賴周崭,不需要類似cobar的代理服務(wù)器,堪稱可伸縮的Spring JdbcTemplate磨取。
一方面凫岖,它對(duì)于單庫(kù)單表擴(kuò)展了JdbcTemplate模板, 使其成為一個(gè)簡(jiǎn)單的ORM框架,可以直接對(duì)領(lǐng)域?qū)ο竽P瓦M(jìn)行持久和搜索操作,并且實(shí)現(xiàn)了讀寫分離。
另一方面峭弟,對(duì)于分庫(kù)分表它與JdbcTemplate保持同樣的風(fēng)格,不但提供了一個(gè)簡(jiǎn)單的ORM框架,可以直接對(duì)領(lǐng)域?qū)ο竽P瓦M(jìn)行持久和搜索操作,還是先了數(shù)據(jù)分片和讀寫分離等高級(jí)功能朝巫。
另外,擴(kuò)展的Dbsplit保持與原有JdbcTemplate完全兼容揪荣,對(duì)于特殊需求,完全可以回溯到原有JdbcTemplate提供的功能请祖,即使用JDBC的方式來(lái)解決,這里面體現(xiàn)了通用和專用原則,通用原則解決80%的事情席纽,而專用原則解決剩余的20%的事情诈豌。
此項(xiàng)目也提供了一個(gè)方便的腳本彤蔽,可以一次性的建立多庫(kù)多表油够。
誰(shuí)應(yīng)該關(guān)注dbsplit揩悄?
特別適合想知道互聯(lián)網(wǎng)的分庫(kù)分表是怎么實(shí)現(xiàn)的,也適合那些想把分庫(kù)分表框架開箱即用的項(xiàng)目,更適合想學(xué)習(xí)互聯(lián)網(wǎng)的小伙伴們。
如果你在尋找數(shù)據(jù)庫(kù)分庫(kù)分表的輕量級(jí)解決方案发乔,請(qǐng)參考Dbsplit的實(shí)現(xiàn)和應(yīng)用場(chǎng)景起愈,它是一個(gè)兼容Spring JDBC的并且支持分庫(kù)分表的輕量級(jí)的數(shù)據(jù)庫(kù)中間件官觅,使用起來(lái)簡(jiǎn)單方便笛辟,性能接近于直接使用JDBC捷凄,并且能夠無(wú)縫的與Spring相結(jié)合,又具有很好的可維護(hù)性。
怎么使用dbsplit?
我們已經(jīng)完整的實(shí)現(xiàn)了一個(gè)具有分庫(kù)分表功能的框架dbsplit,現(xiàn)在,讓我們提供一個(gè)示例演示在我們的應(yīng)用中怎么來(lái)使用這個(gè)框架,大家也可以參考dbsplit項(xiàng)目中dbsplit-core/src/main/test中的源代碼。
首先购城,假設(shè)我們應(yīng)用中有個(gè)表需要增刪改查,它的DDL腳本如下:
drop table if exists TEST_TABLE_$I;
create table TEST_TABLE_$I
(
ID bigint not null,
NAME varchar(128) not null,
GENDER smallint default 0,
LST_UPD_USER varchar(128) default "SYSTEM",
LST_UPD_TIME timestamp default now(),
primary key(id),
unique key UK_NAME(NAME)
);
我們把這個(gè)DDL腳本保存到table.sql文件中,然后,我們需要準(zhǔn)備好一個(gè)Mysql的數(shù)據(jù)庫(kù)實(shí)例,實(shí)例端口為localhost:3307, 因?yàn)榄h(huán)境的限制,我們用著一個(gè)數(shù)據(jù)庫(kù)實(shí)例來(lái)模擬兩個(gè)數(shù)據(jù)庫(kù)實(shí)例,兩個(gè)數(shù)據(jù)庫(kù)實(shí)例使用同一個(gè)端口,我們?yōu)門EST_TABLE設(shè)計(jì)了2個(gè)數(shù)據(jù)庫(kù)實(shí)例瞳脓、每個(gè)實(shí)例2個(gè)數(shù)據(jù)庫(kù)哨啃、每個(gè)數(shù)據(jù)庫(kù)4個(gè)表审姓,共16個(gè)分片表。
我們使用腳本創(chuàng)建創(chuàng)建用于分片的多個(gè)數(shù)據(jù)庫(kù)和表,腳本代碼如下所示:
build-db-split.sh -i "localhost:3307,localhost:3307" -m test_db -n table.sql -x 2 -y 4 -a test_user -b test_password -c root -d youarebest -l localhost -t
這里骨宠,需要提供系統(tǒng)root用戶的用戶名和密碼。
然后相满,我們登錄Mysql的命令行客戶端诱篷,我們看到一共創(chuàng)建了4個(gè)數(shù)據(jù)庫(kù),前2個(gè)數(shù)據(jù)庫(kù)屬于數(shù)據(jù)庫(kù)實(shí)例1雳灵,后2個(gè)數(shù)據(jù)庫(kù)屬于數(shù)據(jù)庫(kù)實(shí)例2闸盔,每個(gè)數(shù)據(jù)庫(kù)有4個(gè)表悯辙。
mysql> show databases;
+--------------------+
| Database |
+--------------------+
| information_schema |
| test |
| test_db_0 |
| test_db_1 |
| test_db_2 |
| test_db_3 |
+--------------------+
6 rows in set (0.01 sec)
mysql> use test_db_0;
Database changed
mysql> show tables;
+---------------------+
| Tables_in_test_db_0 |
+---------------------+
| TEST_TABLE_0 |
| TEST_TABLE_1 |
| TEST_TABLE_2 |
| TEST_TABLE_3 |
+---------------------+
4 rows in set (0.00 sec)
因此,一共我們創(chuàng)建了16個(gè)分片表迎吵。
然后躲撰,我們定義對(duì)應(yīng)這個(gè)數(shù)據(jù)庫(kù)表的領(lǐng)域?qū)ο竽P停谶@個(gè)領(lǐng)域?qū)ο竽P椭谢鞣眩覀儾恍枰魏巫⒔饴5埃@是一個(gè)綠色的POJO。
public class TestTable {
private long id;
private String name;
public enum Gender {
MALE, FEMALE;
public static Gender parse(int value) {
for (Gender gender : Gender.values()) {
if (value == gender.ordinal())
return gender;
}
return null;
}
};
private Gender gender;
private String lstUpdUser;
private Date lstUpdTime;
public long getId() {
return id;
}
public void setId(long id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Gender getGender() {
return gender;
}
public void setGender(Gender gender) {
this.gender = gender;
}
public String getLstUpdUser() {
return lstUpdUser;
}
public void setLstUpdUser(String lstUpdUser) {
this.lstUpdUser = lstUpdUser;
}
public Date getLstUpdTime() {
return lstUpdTime;
}
public void setLstUpdTime(Date lstUpdTime) {
this.lstUpdTime = lstUpdTime;
}
@Override
public String toString() {
return JSON.toJSONString(this);
}
}
因?yàn)槲覀兊膽?yīng)用程序需要保存這個(gè)實(shí)體蔫巩,這就需要生成唯一的ID谆棱,發(fā)號(hào)器的設(shè)計(jì)和使用請(qǐng)參考第4章如何設(shè)計(jì)一款永不重復(fù)的高性能分布式發(fā)號(hào)器,這里我們需要配置一個(gè)發(fā)號(hào)器服務(wù)即可圆仔,代碼如下所示垃瞧。
<bean id="idService" class="com.robert.vesta.service.factory.IdServiceFactoryBean"
init-method="init">
<property name="providerType" value="PROPERTY" />
<property name="machineId" value="${vesta.machine}" />
</bean>
接下來(lái),我們?cè)赟pring環(huán)境中定義這個(gè)表的分片信息坪郭,這包括數(shù)據(jù)庫(kù)名稱个从、表名稱、數(shù)據(jù)庫(kù)分片數(shù)歪沃、表的分片數(shù)嗦锐,以及讀寫分離等信息,本例中我們制定數(shù)據(jù)庫(kù)前綴為test_db沪曙,數(shù)據(jù)庫(kù)表名為TEST_TABLE奕污,每個(gè)實(shí)例2個(gè)數(shù)據(jù)庫(kù),每個(gè)數(shù)據(jù)庫(kù)4張表液走,分片采用采用水平下標(biāo)策略菊值,并且打開讀寫分離外驱。
<bean name="splitTable" class="com.robert.dbsplit.core.SplitTable"
init-method="init">
<property name="dbNamePrefix" value="test_db" />
<property name="tableNamePrefix" value="TEST_TABLE" />
<property name="dbNum" value="2" />
<property name="tableNum" value="4" />
<property name="splitStrategyType" value="HORIZONTAL" />
<property name="splitNodes">
<list>
<ref bean="splitNode1" />
<ref bean="splitNode2" />
</list>
</property>
<property name="readWriteSeparate" value="true" />
</bean>
我們看到,這個(gè)splitTable引用了兩個(gè)數(shù)據(jù)庫(kù)實(shí)例節(jié)點(diǎn):splitNode1和splitNode2腻窒,他們的聲明如下:
<bean name="splitNode1" class="com.robert.dbsplit.core.SplitNode">
<property name="masterTemplate" ref="masterTemplate0" />
<property name="slaveTemplates">
<list>
<ref bean="slaveTemplate00"></ref>
</list>
</property>
</bean>
<bean name="splitNode2" class="com.robert.dbsplit.core.SplitNode">
<property name="masterTemplate" ref="masterTemplate1" />
<property name="slaveTemplates">
<list>
<ref bean="slaveTemplate10"></ref>
</list>
</property>
</bean>
每個(gè)數(shù)據(jù)庫(kù)實(shí)例節(jié)點(diǎn)都引用了一個(gè)數(shù)據(jù)庫(kù)主模板以及若干個(gè)數(shù)據(jù)庫(kù)從模板昵宇,這是用來(lái)實(shí)現(xiàn)讀寫分離的,因?yàn)槲覀兇蜷_了讀寫分離設(shè)置儿子,所有的讀操作將由dbsplit路由到數(shù)據(jù)庫(kù)的從模板上瓦哎,數(shù)據(jù)庫(kù)的主從模板的聲明引用到我們生命的數(shù)據(jù)庫(kù),因?yàn)槲覀兪窃诒镜刈鰷y(cè)試柔逼,這些數(shù)據(jù)源都指向了本地的Mysql數(shù)據(jù)庫(kù)localhost:3307蒋譬。
<bean id="masterTemplate0" class="org.springframework.jdbc.core.JdbcTemplate"
abstract="false" lazy-init="false" autowire="default"
dependency-check="default">
<property name="dataSource">
<ref bean="masterDatasource0" />
</property>
</bean>
<bean id="slaveTemplate00" class="org.springframework.jdbc.core.JdbcTemplate"
abstract="false" lazy-init="false" autowire="default"
dependency-check="default">
<property name="dataSource">
<ref bean="slaveDatasource00" />
</property>
</bean>
到現(xiàn)在為止,我們定義好了表的分片信息愉适,把我們把這個(gè)表加入到SplitTablesHolder的Bean中犯助,代碼如下所示:
<bean name="splitTablesHolder" class="com.robert.dbsplit.core.SplitTablesHolder"
init-method="init">
<property name="splitTables">
<list>
<ref bean="splitTable" />
</list>
</property>
</bean>
接下來(lái),我們就需要聲明我們的SimpleSplitJdbcTemplate的Bean维咸,它需要引用SplitTablesHolder的Bean剂买,以及配置讀寫分離的策略,配置代碼如下所示癌蓖,
<bean name="simpleSplitJdbcTemplate" class="com.robert.dbsplit.core.SimpleSplitJdbcTemplate">
<property name="splitTablesHolder" ref="splitTablesHolder" />
<property name="readWriteSeparate" value="${dbsplit.readWriteSeparate}" />
</bean>
我們有了SimpleSplitJdbcTemplate的Bean瞬哼,我們就可以把它導(dǎo)出給我們的服務(wù)層來(lái)使用了。這里我們通過(guò)一個(gè)測(cè)試用例來(lái)演示租副,在測(cè)試用例中初始化剛才我們配置的Spring環(huán)境坐慰,從Spring環(huán)境中獲取SimpleSplitJdbcTemplate的Bean simpleSplitJdbcTemplate,然后用僧,示例里面的方法插入TEST_TABLE的記錄结胀,然后,再把這條記錄查詢出來(lái)责循,代碼如下所示把跨。
public void testSimpleSplitJdbcTemplate() {
SimpleSplitJdbcTemplate simpleSplitJdbcTemplate = (SimpleSplitJdbcTemplate) applicationContext
.getBean("simpleSplitJdbcTemplate");
IdService idService = (IdService) applicationContext
.getBean("idService");
// Make sure the id generated is not align multiple of 1000
Random random = new Random(new Date().getTime());
for (int i = 0; i < random.nextInt(16); i++)
idService.genId();
long id = idService.genId();
System.out.println("id:" + id);
TestTable testTable = new TestTable();
testTable.setId(id);
testTable.setName("Alice-" + id);
testTable.setGender(Gender.MALE);
testTable.setLstUpdTime(new Date());
testTable.setLstUpdUser("SYSTEM");
simpleSplitJdbcTemplate.insert(id, testTable);
TestTable q = new TestTable();
TestTable testTable1 = simpleSplitJdbcTemplate.get(id, id,
TestTable.class);
AssertJUnit.assertEquals(testTable.getId(), testTable1.getId());
AssertJUnit.assertEquals(testTable.getName(), testTable1.getName());
AssertJUnit.assertEquals(testTable.getGender(), testTable1.getGender());
AssertJUnit.assertEquals(testTable.getLstUpdUser(),
testTable1.getLstUpdUser());
// mysql store second as least time unit but java stores miliseconds, so
// round up the millisends from java time
AssertJUnit.assertEquals(
(testTable.getLstUpdTime().getTime() + 500) / 1000 * 1000,
testTable1.getLstUpdTime().getTime());
System.out.println("testTable1:" + testTable1);
}
如何使用用于創(chuàng)建分庫(kù)分表的腳本?
這里介紹一個(gè)用于創(chuàng)建分庫(kù)分表的腳本沼死,這個(gè)腳本可以一次性的按照規(guī)則在多個(gè)mysql示例上創(chuàng)建多個(gè)數(shù)據(jù)庫(kù)和表着逐,以及在每一個(gè)數(shù)據(jù)庫(kù)實(shí)例上創(chuàng)建一個(gè)統(tǒng)一的用戶,并分配相應(yīng)的權(quán)限給此用戶意蛀。
1. 使用方法
Usage: $0 -i [INSTANCE_STR] -m [DB_PREFIX] -n [TABLE_SQL_FILE] -x [DB_SPLIT_NUM] -y [TABLE_SPLIT_NUM] -a [USER] -b [PASSWORD] -c [ROOT_USER] -d [ROOT_PASSWORD] -l [CONNECTION_HOST] -t
Descriptions:
-i : instances string.
-m : db name.
-n : table file name.
-x : db number.
-y : table number.
-a : user name to be created.
-b : password for the user name to be created.
-c : root user.
-d : password for root user.
-l : for the connection host.
-t : debug sql output.
2. 使用示例
Example1: $0 -i "localhost:3306,localhost:3306" -m test_db -n table.sql -x 2 -y 2 -a test_user -b test_password -c root -d youarebest -l localhost -t
Example2: $0 -i "localhost:3306,localhost:3306" -m test_db -n table.sql -x 2 -y 2 -a test_user -b test_password -c root -d youarebest -l localhost
3. 源碼
#!/bin/bash
insts=localhost:3306,localhost:3306
db_prefix=test_db
table_sql_file=table.sql
db_num=2
table_num=2
user_name=test_user
password=test_password
root_user_name=root
root_password=cool
debug=FALSE
conn_host=localhost
build_db() {
inst=$1
inst_arr=(${inst//:/ })
host=${inst_arr[0]}
port=${inst_arr[1]}
db=$2
db_no=$3
echo "info: building instance $inst db $db db no $db_no"
for ((k=0;k<$table_num;k++)); do
((table_no=$table_num*$db_no+$k))
echo "info: building instance $inst db $db db no $db_no table $table_no"
sql_command="sed 's/"'$index'"/$table_no/g' ./$table_sql_file | tr -t '\n' '\0'"
sql_create_table=`eval "$sql_command"`
if [[ $debug = 'TRUE' ]]; then
echo "Create Table SQL: $sql_create_table"
fi
mysql -u$root_user_name -p$root_password -e "$sql_create_table" $db 2> /dev/null
done
}
build_inst() {
inst=$1
inst_arr=(${inst//:/ })
host=${inst_arr[0]}
port=${inst_arr[1]}
inst_no=$2
echo "info: building instance $inst no $inst_no"
sql_delete_user="delete from mysql.user where user = '$user_name'; flush privileges"
if [[ $debug = 'TRUE' ]]; then
echo "Delete User SQL: $sql_delete_user"
fi
mysql -u$root_user_name -p$root_password -e "$sql_delete_user" 2> /dev/null
mysql -u$root_user_name -p$root_password -e "create user '$user_name'@'$conn_host' identified by '$password'"
for ((j=0;j<$db_num;j++)); do
((db_no=$db_num*$inst_no+$j))
create_database_sql="drop database if exists ${db_prefix}_${db_no};create database ${db_prefix}_${db_no}"
if [[ $debug = 'TRUE' ]]; then
echo "Create Database SQL: $create_database_sql"
fi
mysql -u$root_user_name -p$root_password -e "$create_database_sql" 2> /dev/null
assign_rights_sql="grant all privileges on ${db_prefix}_${db_no}.* to '$user_name'@'$conn_host' identified by '$password';flush privileges"
if [[ $debug = 'TRUE' ]]; then
echo "Assign Rights SQL: $assign_rights_sql"
fi
mysql -u$root_user_name -p$root_password -e "assign_rights_sql" 2> /dev/null
build_db $inst ${db_prefix}_${db_no} $db_no
done
}
main() {
echo "properties: insts=$insts db_prefix=$db_prefix table_sql_file=$table_sql_file db_num=$db_num table_num=$table_num user_name=$user_name password=$password root_user_name=$root_user_name root_password=$root_password"
insts_arr=(${insts//,/ })
insts_num=${#insts_arr[@]}
for ((i=0;i<$insts_num;i++)); do
build_inst ${insts_arr[$i]} $i
done
}
PrintUsage()
{
cat << EndOfUsageMessage
Usage: $0 -i [INSTANCE_STR] -m [DB_PREFIX] -n [TABLE_SQL_FILE] -x [DB_SPLIT_NUM] -y [TABLE_SPLIT_NUM] -a [USER] -b [PASSWORD] -c [ROOT_USER] -d [ROOT_PASSWORD] -l [CONNECTION_HOST] -t
Descriptions:
-i : instances string.
-m : db name.
-n : table file name.
-x : db number.
-y : table number.
-a : user name to be created.
-b : password for the user name to be created.
-c : root user.
-d : password for root user.
-l : for the connection host.
-t : debug sql output.
Example1: $0 -i "localhost:3306,localhost:3306" -m test_db -n table.sql -x 2 -y 2 -a test_user -b test_password -c root -d youarebest -l localhost -t
Example2: $0 -i "localhost:3306,localhost:3306" -m test_db -n table.sql -x 2 -y 2 -a test_user -b test_password -c root -d youarebest -l localhost
EndOfUsageMessage
}
InvalidCommandSyntaxExit()
{
echo "Invalid command\n`PrintUsage`"
exit;
}
if [ $# -eq 0 ]
then
echo "`PrintUsage`"
exit 1
fi
while getopts "i:m:n:x:y:a:b:c:d:l:t" arg
do
case $arg in
i)
insts=$OPTARG
;;
m)
db_prefix=$OPTARG
;;
n)
table_sql_file=$OPTARG
;;
x)
db_num=$OPTARG
;;
y)
table_num=$OPTARG
;;
a)
user_name=$OPTARG
;;
b)
password=$OPTARG
;;
c)
root_user_name=$OPTARG
;;
d)
root_password=$OPTARG
;;
l)
conn_host=$OPTARG
;;
t)
debug=TRUE
;;
?)
echo "`InvalidCommandSyntaxExit`"
exit 1
;;
esac
done
這個(gè)腳本僅僅是一個(gè)示例耸别,計(jì)劃中,這個(gè)腳本需要支持三種分庫(kù)分表的策略县钥,數(shù)據(jù)庫(kù)和表下標(biāo)累積的策略秀姐,數(shù)據(jù)庫(kù)和表下標(biāo)歸零的策略與兩種混合策略, 當(dāng)前腳本只支持第一種。
我們需要注意若贮,這個(gè)建庫(kù)腳本不支持建立主從關(guān)系省有,但是可以建立主庫(kù)和從庫(kù)后再手工建立主從關(guān)系痒留。
請(qǐng)掃描下面二維碼加入我們。