目录
sharding-jdbc官方demo测试
官方demo github:
https://github.com/apache/incubator-shardingsphere-example/tree/master
一、准备工作
github下载源码
第一步:
查看pom.xml信息,判断它这个example是针对哪个版本的sharding-jdbc。
如:
<sharding-sphere.version>3.1.0</sharding-sphere.version>
第二步:
查看 readme.md, 得知 给的 数据库demo sql的位置。
在项目的根目录下的src/resources下
The manual schema initial script is in `https://github.com/sharding-sphere/sharding-sphere-example/blob/dev/src/resources/manual_schema.sql`,
please execute it before you first run the example.
第三步:
执行sql,配置好相关的sql环境,方便后面测试。
查看demo’源码
sharding-jdbc-example/spring-namespace-nodep-example/spring-namespace-nodep-mybatis-example
它把分片分成2大类,一个精确分片,一个是范围分片。
precise 英 [prɪˈsaɪs]
美 [prɪˈsaɪs]
adj. 准确的; 确切的; 精确的; 明确的; (强调时间或方式等) 就,恰好; 细致的; 精细的; 认真的; 一丝不苟的;
二、精确分库测试(spring+mybatis环境)
两个数据库demo_ds_0, demo_ds_1
第一步:创建数据库
使用如下命令,创建这两个数据库
CREATE SCHEMA IF NOT EXISTS demo_ds_0;
CREATE SCHEMA IF NOT EXISTS demo_ds_1;
第二步:application-sharding-databases-precise.xml 配置文件修改
修改为你数据库账号密码以及端口号
<?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"
xmlns:tx="http://www.springframework.org/schema/tx"
xmlns:sharding="http://shardingsphere.io/schema/shardingsphere/sharding"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd
http://shardingsphere.io/schema/shardingsphere/sharding
http://shardingsphere.io/schema/shardingsphere/sharding/sharding.xsd">
<import resource="classpath:META-INF/shardingTransaction.xml"/>
<context:component-scan base-package="io.shardingsphere.example.repository.mybatis" />
<bean id="demo_ds_0" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
<property name="driverClassName" value="com.mysql.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:13306/demo_ds_0"/>
<property name="username" value="root"/>
<property name="password" value="root"/>
</bean>
<bean id="demo_ds_1" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
<property name="driverClassName" value="com.mysql.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:13306/demo_ds_1"/>
<property name="username" value="root"/>
<property name="password" value="root"/>
</bean>
<sharding:inline-strategy id="databaseStrategy" sharding-column="user_id" algorithm-expression="demo_ds_${user_id % 2}" />
<sharding:data-source id="shardingDataSource">
<sharding:sharding-rule data-source-names="demo_ds_0, demo_ds_1">
<sharding:table-rules>
<sharding:table-rule logic-table="t_order" database-strategy-ref="databaseStrategy" generate-key-column-name="order_id" />
<sharding:table-rule logic-table="t_order_item" database-strategy-ref="databaseStrategy" generate-key-column-name="order_item_id" />
</sharding:table-rules>
</sharding:sharding-rule>
</sharding:data-source>
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="shardingDataSource" />
</bean>
<tx:annotation-driven />
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="dataSource" ref="shardingDataSource"/>
<property name="mapperLocations" value="classpath*:META-INF/mappers/*.xml"/>
</bean>
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<property name="basePackage" value="io.shardingsphere.example.repository.mybatis.repository"/>
<property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"/>
</bean>
</beans>
第三步: 执行SpringNamespaceExample
执行SpringNamespaceExample 根据打印跟踪。
精确分库测试demo代码逻辑流程分析总结
- 初始化spring容器
根据你选择分片类型 精确数据库,加载特定的xml到 spring上下文 。
此步骤会把xml的一些配置,加载到spring 的bean中,让spring管理。
- 通过 spring 上下文获取业务bean
SpringPojoServiceImpl 类
1)使用@Resource注入
注入Mapper接口 mybatis-repository,这里它的Mapper接口都是以Repository结尾。
一般Mapper接口都是以Mapper做结尾,方便辨识,这里为什么用Repository结尾呢?
分析:方便抽象,因为这里会测试三种数据库访问方式 JDBC、JPA、MyBatis,比如在业务类的父抽象类public abstract class CommonServiceImpl中,我们可以getOrderRepository()、 getOrderItemRepository()获取子类对的数据库访问实例,不一定是Mybatis的Mapper实例。
@Mapper
public interface MybatisOrderItemRepository extends OrderItemRepository {
}
@Mapper
public interface MybatisOrderRepository extends OrderRepository {
}
2)继承抽象类 CommonServiceImpl
继承抽象类 CommonServiceImpl 实现了方法
initEnvironment() 初始化环境: 创建表
@Service
@Transactional
public class SpringPojoServiceImpl extends CommonServiceImpl implements SpringPojoService {
@Resource
private OrderRepository orderRepository;
@Resource
private OrderItemRepository orderItemRepository;
- 调业务类初始化方法 commonService.initEnvironment();
该方法内容如下:
getOrderRepository获取实现子类注入的Mapper实例,进行数据库操作,主要做创建表和清空表。
public abstract class CommonServiceImpl implements CommonService {
@Override
public void initEnvironment() {
getOrderRepository().createTableIfNotExists();
getOrderItemRepository().createTableIfNotExists();
getOrderRepository().truncateTable();
getOrderItemRepository().truncateTable();
}
- 调processSuccess 测试成功插入和成功删除记录过程。
至此,真正开始测试精确分库,insertData方法插入数据库,返回 订单主键。
后面又根据 订单主键传入deleteData方法进行删除。
@Transactional
@Override
public void processSuccess(final boolean isRangeSharding) {
System.out.println("-------------- Process Success Begin ---------------");
List<Long> orderIds = insertData();
printData(isRangeSharding);
deleteData(orderIds);
printData(isRangeSharding);
System.out.println("-------------- Process Success Finish --------------");
}
插入以及删除,都要利用分片规则去特定的库中插入和删除。
因为插入和删除在一个事务中,你在insert完还没有deleteData时,事务还没有结束,你在插入完数据后加断点,你用navicat也看不到插入的数据,无法判断它是否正确按照分片规则,分库存储。
因此,注调
deleteData(orderIds);
printData(isRangeSharding);
断点加在processSuccess方法执行完,观察入库情况,进行判断入库是否正确。
spring读取xml中的分片规则如下:
分片策略:内部策略,进行分片的列 user_id ,算法表达式: demo_ds_${user_id % 2} 即用户id与2取模,决定它存储到那个库。
指定访问这些表的逻辑表名为:logic-table=“t_order”、logic-table=“t_order_item”
<sharding:inline-strategy id="databaseStrategy" sharding-column="user_id" algorithm-expression="demo_ds_${user_id % 2}" />
<sharding:data-source id="shardingDataSource">
<sharding:sharding-rule data-source-names="demo_ds_0, demo_ds_1">
<sharding:table-rules>
<sharding:table-rule logic-table="t_order" database-strategy-ref="databaseStrategy" generate-key-column-name="order_id" />
<sharding:table-rule logic-table="t_order_item" database-strategy-ref="databaseStrategy" generate-key-column-name="order_item_id" />
</sharding:table-rules>
</sharding:sharding-rule>
</sharding:data-source>
观察完入库后,我们再观察删记录,查看删记录代码,
private void deleteData(final List<Long> orderIds) {
System.out.println("---------------------------- Delete Data ----------------------------");
for (Long each : orderIds) {
getOrderRepository().delete(each);
getOrderItemRepository().delete(each);
}
}
跟Mapper实例的delete sql,可以发现它 不用写库名,直接使用逻辑表名,传入order_id,它就会根据分库规则自动到对应的数据库删除相应记录。
<delete id="delete">
DELETE FROM t_order WHERE order_id = #{orderId,jdbcType=INTEGER};
</delete>
至此,官方demo,分库测试代码,分析完毕。
三、读写分离测试(只读写分离、不分片场景)
【死磕Sharding-jdbc】—–读写分离
参考URL: http://cmsblogs.com/?p=2550#
根据代码分析,它分成分片的读写分离和不分片的读写分离,本节测试ShardingType.MASTER_SLAVE;
private static ShardingType type = ShardingType.MASTER_SLAVE;
// private static ShardingType type = ShardingType.SHARDING_MASTER_SLAVE;
读写分离支持项
- 提供了一主多从的读写分离配置,可独立使用,也可配合分库分表使用。
- 同一线程且同一数据库连接内,如有写入操作,以后的读操作均从主库读取,用于保证数据一致性。
- Spring命名空间。
- 基于Hint的强制主库路由。
第一步:执行sql配置测试数据库环境
CREATE SCHEMA IF NOT EXISTS demo_ds_master;
CREATE SCHEMA IF NOT EXISTS demo_ds_slave_0;
CREATE SCHEMA IF NOT EXISTS demo_ds_slave_1;
第二步:调试demo源码测试
如下,注释掉删除函数 deleteData, 加入如下2个断点,我们可以观察到插入和查询printData都正常。
说明:同一线程且同一数据库连接内,如有写入操作,以后的读操作均从主库读取,用于保证数据一致性。
继续在方法外,加断点,执行printData(isRangeSharding); 发现报错如下,Table ‘demo_ds_slave_1.t_order’ doesn’t exist 说明它从从表读数据。
关于spring xml读写分离配置核心如下:
Spring事务管理Transaction Manager配置 ref为masterSlaveDataSource
在id=“masterSlaveDataSource” 中 定义了 主数据源名字、从数据源名字,以及策略引用strategy-ref=“randomStrategy”
在id=“randomStrategy” 中,我们定义了具体策略算法实现类,这里选择的算法是随机负载均衡算法。
<bean id="randomStrategy" class="io.shardingsphere.api.algorithm.masterslave.RandomMasterSlaveLoadBalanceAlgorithm" />
<master-slave:data-source id="masterSlaveDataSource" master-data-source-name="demo_ds_master" slave-data-source-names="demo_ds_slave_0, demo_ds_slave_1" strategy-ref="randomStrategy" />
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="masterSlaveDataSource" />
</bean>
<tx:annotation-driven />
四、其它测试
分布式主键
[推荐]数据库分库分表中间件 Sharding-JDBC 源码分析 —— 分布式主键
参考URL: https://www.cnblogs.com/yunai/p/7588528.html
跟我学shardingjdbc之分布式主键及其自定义
参考URL: http://wuwenliang.net/2019/03/25/跟我学shardingjdbc之分布式主键及其自定义/
雪花算法(SnoWflake)是Twitter公布的分布式主键生成算法,也是ShardingSphere默认提供的配置分布式主键生成策略方式。在ShardingSphere的类路径为:io.shardingsphere.core.keygen.DefaultKeyGenerator
官方code 提供了相应的单元测试,查看运行单元测试即可
如下单元测试代码: 多个线程同时调generateKey方法,执行taskNumber次,返回的id放到set中,判断set中的元素个数是否是taskNumber个,从而判断多线程执行时,是否有重复id。
public void assertGenerateKeyWithMultipleThreads() {
int threadNumber = Runtime.getRuntime().availableProcessors() << 1;
System.out.println("threadNumber: " + threadNumber);
ExecutorService executor = Executors.newFixedThreadPool(threadNumber);
int taskNumber = threadNumber << 2;
System.out.println("taskNumber: " + taskNumber);
final DefaultKeyGenerator keyGenerator = new DefaultKeyGenerator();
Set<Number> actual = new HashSet<>();
for (int i = 0; i < taskNumber; i++) {
actual.add(executor.submit(new Callable<Number>() {
@Override
public Number call() {
return keyGenerator.generateKey();
}
}).get());
}
assertThat(actual.size(), is(taskNumber));
}
DefaultKeyGeneratorTest
同时针对id生成器,官方代码提供了一个工厂类,根据类名string反射获取KeyGenerator 实例。
public final class KeyGeneratorFactory {
/**
* Create key generator.
*
* @param keyGeneratorClassName key generator class name
* @return key generator instance
*/
public static KeyGenerator newInstance(final String keyGeneratorClassName) {
try {
return (KeyGenerator) Class.forName(keyGeneratorClassName).newInstance();
} catch (final ReflectiveOperationException ex) {
throw new IllegalArgumentException(String.format("Class %s should have public privilege and no argument constructor", keyGeneratorClassName));
}
}
}
官网:ShardingSphere提供的默认分布式自增主键策略为什么是不连续的,且尾数大多为偶数?
ShardingSphere采用snowflake算法作为默认的分布式分布式自增主键策略,用于保证分布式的情况下可以无中心化的生成不重复的自增序列。因此自增主键可以保证递增,但无法保证连续。
而snowflake算法的最后4位是在同一毫秒内的访问递增值。因此,如果毫秒内并发度不高,最后4位为零的几率则很大。因此并发度不高的应用生成偶数主键的几率会更高。
在3.1.0版本中,尾数大多为偶数的问题已彻底解决,参见:https://github.com/sharding-sphere/sharding-sphere/issues/1617