一 简介官网
始于当当开源的shardingjdbc,功能较单一,2018进入Apache基金会孵化器。目前最新apache版本ShardingSphere是4.0.1。ShardingSphere一套开源的分布式数据库中间件解决方案组成的生态圈,它由Sharding-JDBC、Sharding-Proxy和Sharding-Sidecar(计划中)这3款相互独立的产品组成.常用核心功能有分表分库(行表达式)与读写分离(基于SQL语句类型路由DQL/DML),分布式事务(xa事务,默认atomicos,如果能接受2pc的性能损失,分分钟解决分布式事务)
二 依赖
<!--分表分库与读写分离-->
<dependency>
<groupId>org.apache.shardingsphere</groupId>
<artifactId>sharding-jdbc-spring-boot-starter</artifactId>
<version>4.0.1</version>
</dependency>
<!--XA分布式事务依赖-->
<dependency>
<groupId>org.apache.shardingsphere</groupId>
<artifactId>sharding-transaction-xa-core</artifactId>
<version>4.0.1</version>
</dependency>
三 配置文件
spring:
shardingsphere:
datasource:
names: ds0,ds0slave0,ds0slave1,ds1,ds1slave0,ds1slave1
ds0:
type: com.zaxxer.hikari.HikariDataSource
driverClassCame: com.mysql.jdbc.Driver
jdbcUrl: jdbc:mysql://192.168.203.132:3306/ds0
username: root
password: 123456
ds0slave0:
type: com.zaxxer.hikari.HikariDataSource
driverClassCame: com.mysql.jdbc.Driver
jdbcUrl: jdbc:mysql://192.168.203.132:3301/ds0
username: root
password: 123456
ds0slave1:
type: com.zaxxer.hikari.HikariDataSource
driverClassCame: com.mysql.jdbc.Driver
jdbcUrl: jdbc:mysql://192.168.203.132:3302/ds0
username: root
password: 123456
ds1:
type: com.zaxxer.hikari.HikariDataSource
driverClassCame: com.mysql.jdbc.Driver
jdbcUrl: jdbc:mysql://192.168.203.132:3306/ds1
username: root
password: 123456
ds1slave0:
type: com.zaxxer.hikari.HikariDataSource
driverClassCame: com.mysql.jdbc.Driver
jdbcUrl: jdbc:mysql://192.168.203.132:3301/ds1
username: root
password: 123456
ds1slave1:
type: com.zaxxer.hikari.HikariDataSource
driverClassCame: com.mysql.jdbc.Driver
jdbcUrl: jdbc:mysql://192.168.203.132:3302/ds1
username: root
password: 123456
sharding:
tables:
t_order:
actual-data-nodes: ms_ds$->{0..1}.t_order$->{0..1} #行表达式 ds1.t_order1,$->{}是为了区分spring 的${}
## 指定分库规则
database-strategy:
inline:
sharding-column: user_id
algorithm-expression: ms_ds$->{user_id % 2}
## 指定分表规则
table-strategy:
inline:
sharding-column: order_id
algorithm-expression: t_order$->{order_id % 2}
t_order_item:
actual-data-nodes: ms_ds$->{0..1}.t_order_item$->{0..1}
## 通过hint方式自定义分库规则,这里需要根据userid路由,但order_item表没有该路由键,固自定义hint,然后绑定主表与子表,避免关联查询导致的全数据源路由
database-strategy:
hint:
algorithmClassName: com.construn.vehicle.user.core.HintSharding
## 指定分表规则
table-strategy:
inline:
sharding-column: order_id
algorithm-expression: t_order_item$->{order_id % 2}
## 雪花算法主键生成
key-generator:
column: item_id
type: SNOWFLAKE
## 绑定主表与子表,避免关联查询导致的全数据源路由
binding-tables: t_order,t_order_item
## 配置广播表:以广播的形式增删改所有库(如果只涉及查询的话可以不配置,轮询查询)
broadcast-tables: t_config
## 读写分离
masterSlaveRules:
ms_ds0:
masterDataSourceName: ds0
slaveDataSourceNames:
- ds0slave0
- ds0slave1
loadBalanceAlgorithmType: ROUND_ROBIN
ms_ds1:
masterDataSourceName: ds1
slaveDataSourceNames:
- ds1slave0
- ds1slave1
loadBalanceAlgorithmType: ROUND_ROBIN
## 打印sql,显示当前sql路由的数据库
props:
sql:
show: true
四/测试。参考https://blog.csdn.net/u011212394/article/details/101101633
建表脚本
create table t_config
(
id int not null
primary key,
remark varchar(50) null,
create_time timestamp default CURRENT_TIMESTAMP not null,
last_modify_time timestamp default CURRENT_TIMESTAMP not null
on update CURRENT_TIMESTAMP
);
create table t_order0
(
order_id int not null
primary key,
user_id int not null,
config_id int not null,
remark varchar(50) null,
create_time datetime default CURRENT_TIMESTAMP not null,
last_modify_time timestamp default CURRENT_TIMESTAMP not null
on update CURRENT_TIMESTAMP
);
create table t_order1
(
order_id int not null
primary key,
user_id int not null,
config_id int not null,
remark varchar(50) null,
create_time timestamp default CURRENT_TIMESTAMP not null,
last_modify_time timestamp default CURRENT_TIMESTAMP not null
on update CURRENT_TIMESTAMP
);
create table t_order_item0
(
item_id bigint auto_increment
primary key,
order_id bigint not null,
remark varchar(50) null,
create_time timestamp default CURRENT_TIMESTAMP not null,
last_modify_time timestamp default CURRENT_TIMESTAMP not null
on update CURRENT_TIMESTAMP
);
create table t_order_item1
(
item_id bigint auto_increment
primary key,
order_id bigint not null,
remark varchar(50) null,
create_time timestamp default CURRENT_TIMESTAMP not null,
last_modify_time timestamp default CURRENT_TIMESTAMP not null
on update CURRENT_TIMESTAMP
);
Config.java
@Setter
@Getter
@ToString
public class Config {
private Integer id;
private String remark;
private Date createTime;
private Date lastModifyTime;
}
Order.java
@Getter
@Setter
@ToString
public class Order {
private Integer orderId;
private Integer userId;
private Integer configId;
private String remark;
private Date createTime;
private Date lastModifyTime;
}
OrderItem.java
@Getter
@Setter
@ToString
public class OrderItem {
private Long itemId;
private Integer orderId;
private String remark;
private Date createTime;
private Date lastModifyTime;
}
ConfigMapper.java
@Mapper
public interface ConfigMapper {
@Insert("insert into t_config(id,remark) values(#{id},#{remark})")
Integer save(Config config);
@Select("select * from t_config where id = #{id}")
Config selectById(Integer id);
}
OrderMapper.java
@Mapper
public interface OrderMapper {
@Insert("insert into t_order(order_id,user_id,config_id,remark) values(#{orderId},#{userId},#{configId},#{remark})")
Integer save(Order order);
@Select("select order_id orderId, user_id userId, config_id configId, remark from t_order where user_id = #{userId}")
Order selectByUserId(Integer userId);
@Select("select o.order_id orderId, o.user_id userId, o.config_id configId, o.remark from " +
"t_order o inner join t_order_item i on o.order_id = i.order_id " +
"where o.user_id =#{userId} and o.order_id =#{orderId}")
List<Order> selectOrderJoinOrderItem(Integer userId, Integer orderId);
@Select("select o.order_id orderId, o.user_id userId, o.config_id configId, o.remark " +
"from t_order o inner join t_config c on o.config_id = c.id " +
"where o.user_id =#{userId} and o.order_id =#{orderId}")
List<Order> selectOrderJoinConfig(Integer userId, Integer orderId);
}
OrderItemMapper.java
@Mapper
public interface OrderItemMapper {
@Insert("insert into t_order_item(order_id,remark) values(#{orderId},#{remark})")
Integer save(OrderItem orderItem);
}
OrderService.java
public interface OrderService {
Integer saveOrder(Order order);
Integer saveOrderItem(OrderItem orderItem, Integer userId);
Order selectByUserId(Integer userId);
List<Order> selectOrderJoinOrderItem(Integer userId, Integer orderId);
List<Order> selectOrderJoinOrderItemNoSharding(Integer userId, Integer orderId);
List<Order> selectOrderJoinConfig(Integer userId, Integer orderId);
Integer saveConfig(Config config);
Config selectConfig(Integer id);
}
OrderServiceImpl
@Service
public class OrderServiceImpl implements OrderService {
@Autowired
private OrderMapper orderMapper;
@Autowired
private OrderItemMapper orderItemMapper;
@Autowired
private ConfigMapper configMapper;
@Override
public Integer saveOrder(Order order) {
return orderMapper.save(order);
}
@Override
public Integer saveOrderItem(OrderItem orderItem, Integer userId) {
try (HintManager hintManager = HintManager.getInstance()) {
hintManager.addDatabaseShardingValue("t_order_item", userId);
return orderItemMapper.save(orderItem);
}
}
@Override
public Order selectByUserId(Integer userId) {
return orderMapper.selectByUserId(userId);
}
@Override
public List<Order> selectOrderJoinOrderItem(Integer userId, Integer orderId) {
try (HintManager hintManager = HintManager.getInstance()) {
hintManager.addDatabaseShardingValue("t_order_item", userId);
return orderMapper.selectOrderJoinOrderItem(userId, orderId);
}
}
@Override
public List<Order> selectOrderJoinOrderItemNoSharding(Integer userId, Integer orderId) {
return orderMapper.selectOrderJoinOrderItem(userId, orderId);
}
@Override
public List<Order> selectOrderJoinConfig(Integer userId, Integer orderId) {
return orderMapper.selectOrderJoinConfig(userId, orderId);
}
@Override
public Integer saveConfig(Config config) {
return configMapper.save(config);
}
@Override
public Config selectConfig(Integer id) {
return configMapper.selectById(id);
}
}
hint自定义分库路由
public class HintSharding implements HintShardingAlgorithm<Integer> {
@Override
public Collection<String> doSharding(Collection<String> availableTargetNames, HintShardingValue<Integer> hintShardingValue) {
Collection<String> result = new ArrayList<>();
for (String each : availableTargetNames) {
for (Integer value : hintShardingValue.getValues()) {
if (each.endsWith(String.valueOf(value % 2))) {
result.add(each);
}
}
}
return result;
}
}
单元测试
@RunWith(SpringRunner.class)
@SpringBootTest
public class VehicleUserApplicationTests {
@Autowired
private OrderService orderService;
@Test
@ShardingTransactionType(value = TransactionType.XA)
@Transactional(rollbackFor = Exception.class)
public void testSaveOrder() {
for (int i = 0; i < 10; i++) {
Integer orderId = 1000 + i;
Integer userId = 10 + i;
Order o = new Order();
o.setOrderId(orderId);
o.setUserId(userId);
o.setConfigId(i);
o.setRemark("save.order");
orderService.saveOrder(o);
if (orderId>1005) {
int a=1/0;
}
OrderItem oi = new OrderItem();
oi.setOrderId(orderId);
oi.setRemark("save.orderItem");
orderService.saveOrderItem(oi, userId);
}
}
/**
* 根据分片键查询
*
* @param
* @return void
* @author hujy
* @date 2019-09-20 11:26
*/
@Test
public void testSelectByUserId() {
Integer userId = 12;
HintManager.getInstance().setMasterRouteOnly();
Order o1 = orderService.selectByUserId(userId);
System.out.println(o1);
userId = 17;
Order o2 = orderService.selectByUserId(userId);
System.out.println(o2);
}
/**
* 与分片子表关联
*
* @param
* @return void
* @author hujy
* @date 2019-09-20 11:24
*/
@Test
public void testSelectOrderJoinOrderItem() {
// 指定了子表分片规则
List<Order> o1 = orderService.selectOrderJoinOrderItem(12, 1002);
System.out.println(o1);
// 未指定子表分片规则:导致子表的全路由
List<Order> o2 = orderService.selectOrderJoinOrderItemNoSharding(12, 1002);
System.out.println(o2);
}
/**
* 与广播表关联
*
* @param
* @return void
* @author hujy
* @date 2019-09-20 11:24
*/
@Test
public void testSelectOrderJoinConfig() {
List<Order> o1 = orderService.selectOrderJoinConfig(12, 1002);
System.out.println(o1);
List<Order> o2 = orderService.selectOrderJoinConfig(17, 1007);
System.out.println(o2);
}
/**
* 广播表保存
* 对所有数据源进行广播
*
* @param
* @return void
* @author hujy
* @date 2019-09-20 11:23
*/
@Test
public void testSaveConfig() {
for (int i = 0; i < 10; i++) {
Config config = new Config();
config.setId(i);
config.setRemark("config " + i);
orderService.saveConfig(config);
System.out.println();
}
}
/**
* 广播表查询
* 随机选择数据源
*
* @param
* @return void
* @author hujy
* @date 2019-09-20 11:23
*/
@Test
public void testSelectConfig() {
Config config1 = orderService.selectConfig(5);
System.out.println(config1);
Config config2 = orderService.selectConfig(7);
System.out.println(config2);
}
@Test
public void testUpdateConfig() {
for (int i = 0; i < 10; i++) {
Config config = new Config();
config.setId(i);
config.setRemark("configNEW " + i);
orderService.updateConfig(config);
}
}
}
五/其它注意事项
- 读写分离配置也可以通过java配置方式,详解官方文档
- 读写分离还是要依赖mysql的bin-log日志主从复制实现,主从复制有延迟,有时候必须主库读取,可以读数据前加入代码HintManager.getInstance().setMasterRouteOnly();强制路由到主库。主从复制数据源名貌似不能使用下划线,如ds0_slave0报错
-
shardingjdbc实现xa分布式事务只需要加入依赖(也支持seata等),增加注解
@ShardingTransactionType(value = TransactionType.XA) @Transactional(rollbackFor = Exception.class)