3.ShardingSphere-读写分离

老马 老马 | 517 | 2022-09-29

目的

上节我们已在准备了主从复制的数据库,本节实现读写分离,也就是说,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.测试

  • 插入

    image-20220929142150606

    可以看到永远是主库

  • 查询

    image-20220929142233790

    因为我们这里仅仅一个从库,还看不出来轮询的负载均衡策略,练习时可以设置多个

读写分离思考

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));
}

请求测试地址后

image-20220929143927918

这里明显可以看到,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";
}

请求测试地址后

image-20220929144141038

可以看到select和update都是对主库的操作了。

文章标签: SpringBoot
推荐指数:

真诚点赞 诚不我欺~

3.ShardingSphere-读写分离

点赞 收藏 评论