3.ShardingSphere-读写分离
目的
上节我们已在准备了主从复制的数据库,本节实现读写分离,也就是说,select语句走从库,而insert语句走主库。从而进一步提升数据库访问效率。而且主从数据库本身也是对数据安全多提供了一层保证。
这里采用的的ShardingSphere版本为4.1.1。5.x太新不太敢用。
ShardingSphere实现读写分离
1.搭建一个springboot基础项目
略
2.pom文件引入ShardingSphere依赖
<!-- ShardingSphere-JDBC -->
<dependency>
<groupId>org.apache.shardingsphere</groupId>
<artifactId>sharding-jdbc-spring-boot-starter</artifactId>
<version>4.1.1</version>
</dependency>
3.配置ShardingSphere
spring:
shardingsphere:
#显示sql
props:
sql:
show: true
datasource:
names: master,slave1
# 配置主库
master:
type: com.zaxxer.hikari.HikariDataSource
driver-class-name: com.mysql.jdbc.Driver
jdbc-url: jdbc:mysql://yourip:3306/springcloud_stock?useUnicode=true&characterEncoding=UTF-8&useSSL=false&serverTimezone=Asia/Shanghai
username: root
password: 123456
# 配置从库
slave1:
type: com.zaxxer.hikari.HikariDataSource
driver-class-name: com.mysql.jdbc.Driver
jdbc-url: jdbc:mysql://yourip:13306/springcloud_stock?useUnicode=true&characterEncoding=UTF-8&useSSL=false&serverTimezone=Asia/Shanghai
username: root
password: 123456
# 指定主从库
masterslave:
# 随意指定
name: ds_ms
#指定主库
master-data-source-name: master
#指定从库
slave-data-source-names: slave1
#配置从节点的负载均衡策略为采用轮询方式。默认为:RANDOM随机选取
load-balance-algorithm-type: round_robin
注意:
从库其实可以设置为多个的,这里演示的仅仅是一个从库
从节点默认是随机负载均衡策略,这里设置为轮询方式
显示sql开启,便于我们观察插入和查询走不同的库
4.写一个测试用的controller
@RestController
public class TestProjectController {
@Resource
private StockInfoMapper stockInfoMapper;
//测试写入走主库
@GetMapping("/insert.json")
public String testInsert() {
//创建一个StockInfo对象
StockInfo stockInfo = new StockInfo();
stockInfo.setProductId(IdUtil.getSnowflakeNextIdStr());
stockInfo.setProductName("测试商品"+ RandomUtil.randomNumbers(3));
stockInfoMapper.insert(stockInfo);
return "success";
}
//测试读库走主库
@GetMapping("/select.json")
public String testSelect() {
return JSON.toJSONString(this.stockInfoMapper.selectList(null));
}
}
5.测试
-
插入
可以看到永远是主库
-
查询
因为我们这里仅仅一个从库,还看不出来轮询的负载均衡策略,练习时可以设置多个
读写分离思考
1. 我的思考
有时,程序会用select for update的方式行锁记录。这种情况相信大家都有遇到的场景,如果用ShardingSphere进行了分库分表,select会走一个库;而insert走的另外一个库;这种情况就不会起到行锁的目的;还有另外一种情况,由于是两个不同的数据源进行同一个库的链接,但是他们是有独立的数据库连接池的,也就是不同的Connection,在一个方法中执行selec和insert或者update操作时,就可能出现死锁的情况。因为你第一句执行select for update的操作是要在方法执行完成后才被springboot进行事务提交,而在方法里的inert或upate语句就一直等待行锁释放,就造成死锁的情况。
然而,经过我的测试,至少在4.1.1版本的ShardingSphere是不存在这个问题的,因为select for update的语句是会走主库。所以我上面的担心就变得没有必要了。
2.验证过程
//stockInfoMapper 通过主键查询库存信息,行锁
@Select("select * from stock_info t where t.id = #{id} for update")
Optional<StockInfo> selectByPrimaryKeyForUpdate(Long id);
//controller中测试读库走主库,这里加上select for update的查询
@GetMapping("/select.json")
public String testSelect() {
Optional<StockInfo> stockInfoOptional = this.stockInfoMapper.selectByPrimaryKeyForUpdate(190L);
return JSON.toJSONString(this.stockInfoMapper.selectList(null));
}
请求测试地址后
这里明显可以看到,select for update走的主库,而普通的select走的是从库。
3.也可以人为指定操作主从库
//在controller加入测试方法。仅用于演示,实际开发中应放入service中实现
//测试select for update
@GetMapping("/forupdate.json")
@Transactional(rollbackFor = Exception.class)
public String testSelect2() {
HintManager.getInstance().setMasterRouteOnly();
Optional<StockInfo> stockInfoOptional = this.stockInfoMapper.selectByPrimaryKeyForUpdate(190L);
StockInfo stockInfo = stockInfoOptional.orElseThrow(() -> new RuntimeException("没有查询到数据"));
stockInfo.setProductName("测试商品aaa");
this.stockInfoMapper.updateByPrimaryKey(stockInfo);
return "success";
}
请求测试地址后
可以看到select和update都是对主库的操作了。
真诚点赞 诚不我欺~