数据分片就是指分库分表
接入端:
Sharding JDBC(入侵业务代码,在JDBC层提供额外服务,使用客户端连接数据库,以jar包的方式提供服务,可理解为增强版JDBC,完全兼容JDBC和各种ORM框架)
Sharding-Proxy(中间件,类似于nginx一样的代理服务器,不需要入侵代码,但是需要单独部署)
Sharding-Sidecar(研发中)
开发生态:可以自己开发二次更改
两个MySQL,分别创建数据库mall_0和mall_1,然后创建相同的数据库表。
SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;
DROP TABLE IF EXISTS `t_order_0`;
CREATE TABLE `t_order_0` (
`order_id` bigint(20) NOT NULL,
`user_id` int(11) NOT NULL,
`status` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL,
PRIMARY KEY (`order_id`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci ROW_FORMAT = Dynamic;
DROP TABLE IF EXISTS `t_order_1`;
CREATE TABLE `t_order_1` (
`order_id` bigint(20) NOT NULL,
`user_id` int(11) NOT NULL,
`status` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL,
PRIMARY KEY (`order_id`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci ROW_FORMAT = Dynamic;
SET FOREIGN_KEY_CHECKS = 1;
然后创建maven项目,导入依赖,并创建applicationContext.xml配置Sharding-sphere分库分表的配置信息
<!-- sharding-jdbc 核心包-->
<dependency>
<groupId>org.apache.shardingsphere</groupId>
<artifactId>sharding-jdbc-core</artifactId>
<version>4.0.0-RC1</version>
</dependency>
<!-- sharding-jdbc 与spring 结合的组件:sharding-jdbc-spring-namespace -->
<dependency>
<groupId>org.apache.shardingsphere</groupId>
<artifactId>sharding-jdbc-spring-namespace</artifactId>
<version>4.0.0-RC1</version>
</dependency>
<!--spring的核心包-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.1.6.RELEASE</version>
</dependency>
<!--Java链接msyql驱动包-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.47</version>
</dependency>
<!--目前最快的链接池-->
<dependency>
<groupId>com.zaxxer</groupId>
<artifactId>HikariCP</artifactId>
<version>3.2.0</version>
</dependency>
<!--spring单元测试框架-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>5.1.6.RELEASE</version>
</dependency>
<!--junit测试框架-->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
</dependency>
<?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:sharding="http://shardingsphere.apache.org/schema/shardingsphere/sharding"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://shardingsphere.apache.org/schema/shardingsphere/sharding
http://shardingsphere.apache.org/schema/shardingsphere/sharding/sharding.xsd">
<!--JDBC数据源配置-->
<bean id="ds0" class="com.zaxxer.hikari.HikariDataSource">
<property name="driverClassName" value="com.mysql.jdbc.Driver"/>
<property name="jdbcUrl" value="jdbc:mysql://localhost:3306/mall_0?useSSL=false"/>
<property name="username" value="root"/>
<property name="password" value=""/>
</bean>
<!--JDBC数据源配置-->
<bean id="ds1" class="com.zaxxer.hikari.HikariDataSource">
<property name="driverClassName" value="com.mysql.jdbc.Driver"/>
<property name="jdbcUrl" value="jdbc:mysql://localhost:3306/mall_1?useSSL=false"/>
<property name="username" value="root"/>
<property name="password" value=""/>
</bean>
<!--分库策略:基于order.user_id值对2取余保存到ds0或ds1的数据库中-->
<sharding:inline-strategy id="databaseStrategy" sharding-column="user_id"
algorithm-expression="ds$->{user_id % 2}"/>
<!--分表策略:基于order.order_id对2取余决定存储在对应数据库的 t_order_0或者t_order_1表中-->
<sharding:inline-strategy id="orderTableStrategy" sharding-column="order_id"
algorithm-expression="t_order_$->{order_id % 2}"/>
<!--分库分表数据源策略信息配置-->
<sharding:data-source id="shardingDataSource">
<!--sharding:sharding-rule:定义分表规则,data-source-names="ds0,ds1" 说明有几个数据源-->
<sharding:sharding-rule data-source-names="ds0,ds1">
<sharding:table-rules>
<!--
sharding:table-rul 节点定义数据存储规则
logic-table="t_order" 代表数据库当中的逻辑表名
actual-data-nodes="ds$->{0..1}.t_order_$->{0..1} 说明数据分布在ds0与ds1两个数据源,物理表存在t_order_0与t_order_1
database-strategy-ref="databaseStrategy" 指向分库策略databaseStrategy
table-strategy-ref="orderTableStrategy" 指向分表策略orderTableStrategy
实例:insert into t_order(order_id , user_id,status) values( ? , ? , 'N');
不同情况的数据分布:
order_id user_id
1 2 -> ds0.order_1
2 2 -> ds0.order_0
3 1 -> ds1.order_1
4 1 -> ds1.order_0
-->
<sharding:table-rule logic-table="t_order"
actual-data-nodes="ds$->{0..1}.t_order_$->{0..1}"
database-strategy-ref="databaseStrategy"
table-strategy-ref="orderTableStrategy"
/>
</sharding:table-rules>
</sharding:sharding-rule>
</sharding:data-source>
</beans>
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import javax.annotation.Resource;
import javax.sql.DataSource;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.SQLException;
/**
* @author: SheepJin
* @date: 2022/9/18
* @description:
* @modifiedBy:
*/
/*使用SpringJUnit4ClassRunner创建运行环境*/
@RunWith(SpringJUnit4ClassRunner.class)
/*加载Sharding配置信息*/
@ContextConfiguration(locations = "classpath:applicationContext.xml")
public class ShardingTests {
/*指向applicationContext.xml配置文件中sharding:data-source标签的id(name值会爆红,idea的问题,不影响)*/
@Resource(name = "shardingDataSource")
private DataSource datasource;
@Test
public void testInsert() throws SQLException {
for (int index = 1; index <= 2; index++) {
Connection connection = datasource.getConnection();
int data = index % 2 == 0 ? 1 : 2;
String saveSql = "insert into t_order(order_id , user_id,status) values( " + data + " , " + data + " , 'N')";
PreparedStatement preparedStatement = connection.prepareStatement(saveSql);
preparedStatement.execute();
connection.close();
}
}
@Test
public void testQuery() throws SQLException {
Connection connection = datasource.getConnection();
String qyerySql = "select * from t_order";
PreparedStatement preparedStatement = connection.prepareStatement(qyerySql);
ResultSet resultSet = preparedStatement.executeQuery();
while (resultSet.next()) {
String queryResult = "{id:" + resultSet.getLong("order_id") + ",user_id:" + resultSet.getLong("user_id") + ",status:" + resultSet.getString("status") + "}";
System.out.println(queryResult);
}
connection.close();
}
}
插入结果:
# 插入时
# 我们的逻辑SQL:
insert into t_order(order_id , user_id,status) values(1,1,'N');
insert into t_order(order_id , user_id,status) values(2,2,'N');
# 分别被分解为:
insert into mall1.t_order_1(order_id , user_id,status) values(1,1,'N');
insert into mall0.t_order_0(order_id , user_id,status) values(2,2,'N');
# 查询时
# 我们的逻辑SQL:
select * from t_order;
# 被分解为
select * from mall0.t_order_0;
select * from mall1.t_order_1;
# 分别进行查询,然后将结果进行拼装后返回
# 需要注意的是如果逻辑SQL后有查询条件,会相应的拼接在分解后的SQL中
# 例如:
select * from t_order where order_id < 10;
# 分解后就是:
select * from mall0.t_order_0 where order_id < 10;
select * from mall1.t_order_1 where order_id < 10;
自增长主键会导致不同表的数据发生主键冲突,推荐使用分布式ID,Sharding-Sphere内置提供了UUID.randomUUID()、SNOWFLAKE、时钟回拨、LEAF等等主键生成器。
使用方法:
在applicationContext.xml当中添加sharding:key-genertor标签,声明逐渐生成策略,并且在具体的分库分表数据源配置当中指向该配置:
<sharding:key-generator id="orderKeyGenerator" type="SNOWFLAKE" column="order_id"/>
<sharding:table-rule logic-table="t_order"
actual-data-nodes="ds$->{0..1}.t_order_$->{0..1}"
database-strategy-ref="databaseStrategy"
table-strategy-ref="orderTableStrategy"
key-generator-ref="orderKeyGenerator"
/>
测试:
//自动生成主键
@Test
public void testInsertGeneratorKey() throws SQLException {
for (int index = 1; index <= 10; index++) {
Connection connection = datasource.getConnection();
int data = index % 2 == 0 ? 1 : 2;
String saveSql = "insert into t_order(order_id , user_id,status) values(" + data + " , 'N')";
PreparedStatement preparedStatement = connection.prepareStatement(saveSql);
preparedStatement.execute();
connection.close();
}
}
查询结果:
[mysqld]
port=
server-id=
log-bin=
# 创建主从同步的用户,@后则为允许该用户slave访问的远程服务器ip地址,也就是从属服务器(可以写全部,则为%),identified by则为该用户的密码
create user 'slave'@'%' identified by '12345678';
# 给用户授权,*.*为所有权限
grant replication slave on *.* to 'slave'@'%';
# 刷新权限
flush privileges;
# 查看最新bin-log信息
show master status;
# 查看bin-log当中的详细信息
show binlog events in 'binlog.000003';
# 配置主机信息
change master to
master_host = ''
,master_port =
,master_user = ''
,master_password = ''
,master_log_file = ''
,master_log_pos = ;
# 开启主从复制
start slave;
# 查看从节点的状态
show slave status;
# 结果中Slave_IO_Running和Slave_SQL_Running的结果均为Yes,即表示主从复制正在进行
show master status结果中的Position。完成上述操作,主服务器的任何操作即可复制到从从服务器当中。
同3、分库分表中,在applicationContext.xml中添加读写分离配置:
<!--
定义ShardingJDBC主从分离(读写)数据源
ds0是主服务器,用于写入操作,指向定义的数据源ID
ds1是从属服务器,用于读取操作,指向定义的数据源ID
其中,ds0也承担一部分查询职责.(生产环境不建议这样做)
-->
<master-slave:data-source id="msDataSouce"
master-data-source-name="ds0"
slave-data-source-names="ds0,ds1">
<!--主从节点配置信息-->
<master-slave:props>
<!--开启日志打印-->
<prop key="sql.show">true</prop>
</master-slave:props>
</master-slave:data-source>
代码测试:
@Test
public void testInsert() throws SQLException {
Connection connection = dataSource.getConnection();
PreparedStatement preparedStatement = connection.prepareStatement("insert into user(username) values('LI')");
preparedStatement.executeUpdate();
connection.close();
}
@Test
public void testQuery() throws SQLException {
Connection conn = dataSource.getConnection();
for(int i = 0 ; i < 10 ; i++) {
PreparedStatement pstmt = conn.prepareStatement("select * from user where user_id < " + i);
ResultSet resultSet = pstmt.executeQuery();
resultSet.close();
pstmt.close();
}
conn.close();
}
测试结果:
从SQL日志可以看出,Sharding-Jdbc是以轮训的方式将每条QuerySQL在不同的数据库当中进行数据读取,这就相对于传统单机MySQL服务器实例,主机的压力就会小很多,从而提高效率。
因篇幅问题不能全部显示,请点此查看更多更全内容