• 首页 首页 icon
  • 工具库 工具库 icon
    • IP查询 IP查询 icon
  • 内容库 内容库 icon
    • 快讯库 快讯库 icon
    • 精品库 精品库 icon
    • 问答库 问答库 icon
  • 更多 更多 icon
    • 服务条款 服务条款 icon

Spring Boot集成ShardingSphere实现读写分离 | Spring Cloud 43

武飞扬头像
gmHappy
帮助4

一、读写分离

1.1 背景

面对日益增加的系统访问量,数据库的吞吐量面临着巨大瓶颈。 对于同一时刻有大量并发读操作和较少写操作类型的应用系统来说,将数据库拆分为主库和从库,主库负责处理事务性的增删改操作,从库负责处理查询操作,能够有效的避免由数据更新导致的行锁,使得整个系统的查询性能得到极大的改善。

通过一主多从的配置方式,可以将查询请求均匀的分散到多个数据副本,能够进一步的提升系统的处理能力。 使用多主多从的方式,不但能够提升系统的吞吐量,还能够提升系统的可用性,可以达到在任何一个数据库宕机,甚至磁盘物理损坏的情况下仍然不影响系统的正常运行。

与将数据根据分片键打散至各个数据节点的水平分片不同,读写分离则是根据 SQL 语义的分析,将读操作和写操作分别路由至主库与从库。

学新通
读写分离的数据节点中的数据内容是一致的,而水平分片的每个数据节点的数据内容却并不相同。将水平分片和读写分离联合使用,能够更加有效的提升系统性能。

1.2 挑战

读写分离虽然可以提升系统的吞吐量和可用性,但同时也带来了数据不一致的问题。 这包括多个主库之间的数据一致性,以及主库与从库之间的数据一致性的问题。 并且,读写分离也带来了与数据分片同样的问题,它同样会使得应用开发和运维人员对数据库的操作和运维变得更加复杂。 下图展现了将数据分片与读写分离一同使用时,应用程序与数据库集群之间的复杂拓扑关系。
学新通

1.3 应用场景

许多系统通过采用主从数据库架构的配置来提高整个系统的吞吐量,但是主从的配置也给业务的使用带来了一定的复杂性。接入 ShardingSphere,可以利用读写分离功能管理主从数据库,实现透明化的读写分离功能,让用户像使用一个数据库一样使用主从架构的数据库。

1.4 核心概念

1.4.1 主库

添加、更新以及删除数据操作所使用的数据库,目前仅支持单主库。(可支持单主库多表数据分片)

1.4.2 从库

查询数据操作所使用的数据库,可支持多从库。

1.4.3 主从同步

将主库的数据异步的同步到从库的操作。 由于主从同步的异步性,从库与主库的数据会短时间内不一致。

1.4.4 负载均衡策略

通过负载均衡策略将查询请求疏导至不同从库。

1.5 使用限制

  • 不处理主库和从库的数据同步(数据同步需自行实现)
  • 不处理主库和从库的数据同步延迟导致的数据不一致
  • 不支持主库多写
  • 不处理主从库间的事务一致性。主从模型中,事务中的数据读写均用主库。

二、Mysql主从数据库同步

针对 1.5章节 提到的使用限制,自行实现主库和从库的数据同步,配置详情请见往期章节:Mysql8 数据库安装及主从配置 | Spring Cloud 2

三、读写分离示例示例

3.1 读写分开总体结构

3.1.1 主库数据源write_ds1

数据库地址 数据源名称 真实表名 逻辑表名称 业务描述
192.168.0.35 write_ds1 t_goods_0 t_goods 商品表-分表
192.168.0.35 write_ds1 t_goods_1 t_goods 商品表-分表

3.1.2 从库数据源read_ds1

数据库地址 数据源名称 真实表名 逻辑表名称 业务描述
192.168.0.45 read_ds1 t_goods_0 t_goods 商品表-分表
192.168.0.45 read_ds1 t_goods_1 t_goods 商品表-分表

3.1.3 从库数据源read_ds2

数据库地址 数据源名称 真实表名 逻辑表名称 业务描述
192.168.0.46 read_ds2 t_goods_0 t_goods 商品表-分表
192.168.0.46 read_ds1 t_goods_1 t_goods 商品表-分表

3.1.1 逻辑商品表 t_goods

-- ----------------------------
-- Table structure for t_goods_0
-- ----------------------------
DROP TABLE IF EXISTS `t_goods_0`;
CREATE TABLE `t_goods_0`  (
  `goods_id` bigint NOT NULL,
  `goods_name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '商品名称',
  `main_class` bigint NULL DEFAULT NULL COMMENT '商品大类数据字典',
  `sub_class` bigint NULL DEFAULT NULL COMMENT '商品小类数据字典',
  PRIMARY KEY (`goods_id`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci ROW_FORMAT = Dynamic;

-- ----------------------------
-- Table structure for t_goods_1
-- ----------------------------
DROP TABLE IF EXISTS `t_goods_1`;
CREATE TABLE `t_goods_1`  (
  `goods_id` bigint NOT NULL,
  `goods_name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '商品名称',
  `main_class` bigint NULL DEFAULT NULL COMMENT '商品大类数据字典',
  `sub_class` bigint NULL DEFAULT NULL COMMENT '商品小类数据字典',
  PRIMARY KEY (`goods_id`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci ROW_FORMAT = Dynamic;

3.2 项目结构

3.2.1 项目总体结构

学新通

3.2.2 Maven 依赖

shading-sphere/shading-readwrite/pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <parent>
        <artifactId>shading-sphere</artifactId>
        <groupId>com.gm</groupId>
        <version>0.0.1-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>shading-readwrite</artifactId>

    <properties>
        <maven.compiler.source>8</maven.compiler.source>
        <maven.compiler.target>8</maven.compiler.target>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    </properties>

    <dependencies>
        <dependency>
            <groupId>com.mysql</groupId>
            <artifactId>mysql-connector-j</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>org.apache.shardingsphere</groupId>
            <artifactId>shardingsphere-jdbc-core-spring-boot-starter</artifactId>
            <version>5.2.1</version>

        </dependency>
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-boot-starter</artifactId>
            <version>3.5.3.1</version>
        </dependency>

        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-generator</artifactId>
            <version>3.5.3.1</version>
        </dependency>
        <dependency>
            <groupId>org.apache.velocity</groupId>
            <artifactId>velocity-engine-core</artifactId>
            <version>2.3</version>
        </dependency>


        <dependency>
            <groupId>org.yaml</groupId>
            <artifactId>snakeyaml</artifactId>
            <version>1.33</version>
        </dependency>

    </dependencies>

</project>
  • shardingsphere-jdbc-core-spring-boot-starter使用版本5.2.1

  • JDBCORM 框架选用mybatis-plus

3.2.3 配置文件

server:
  port: 8844

spring:
  application:
    name: @artifactId@
  shardingsphere:
    # 数据源配置
    datasource:
      names: write_ds1,read_ds1,read_ds2
      write_ds1:
        type: com.zaxxer.hikari.HikariDataSource
        driver-class-name: com.mysql.cj.jdbc.Driver
        url: jdbc:mysql://192.168.0.35:3306/db1?useUnicode=true&characterEncoding=UTF-8&rewriteBatchedStatements=true&allowMultiQueries=true&serverTimezone=Asia/Shanghai
        username: root
        password: '1qaz@WSX'
      read_ds1:
        type: com.zaxxer.hikari.HikariDataSource
        driver-class-name: com.mysql.cj.jdbc.Driver
        url: jdbc:mysql://192.168.0.45:3306/db1?useUnicode=true&characterEncoding=UTF-8&rewriteBatchedStatements=true&allowMultiQueries=true&serverTimezone=Asia/Shanghai
        username: root
        password: '1qaz@WSX'
      read_ds2:
        type: com.zaxxer.hikari.HikariDataSource
        driver-class-name: com.mysql.cj.jdbc.Driver
        url: jdbc:mysql://192.168.0.46:3306/db1?useUnicode=true&characterEncoding=UTF-8&rewriteBatchedStatements=true&allowMultiQueries=true&serverTimezone=Asia/Shanghai
        username: root
        password: '1qaz@WSX'
    # 定义规则
    rules:
      sharding:
        # 数据分片规则配置
        tables:
          # 指定某个表的分片配置,逻辑表名
          t_goods:
            # 这个配置是告诉sharding有多少个库和多少个表及所在实际的数据库节点,由数据源名   表名组成(参考 Inline 语法规则)
            actual-data-nodes: readwrite_ds.t_goods_$->{0..1} # 此处使用下方定义的读写分离数据源
            # 配置表分片策略
            table-strategy:
              # 用于单分片键的标准分片场景
              standard:
                # 分片列名称
                sharding-column: main_class
                # 分片算法名称
                sharding-algorithm-name: t_goods_table_inline
            # 分布式序列策略
            key-generate-strategy:
              # 自增列名称,缺省表示不使用自增主键生成器
              column: goods_id
              # 分布式序列算法名称
              key-generator-name: snowflake
        # 分片算法配置
        sharding-algorithms:
          # 分片算法名称
          t_goods_table_inline:
            # 分片算法类型
            type: INLINE
            # 分片算法属性配置
            props:
              algorithm-expression: t_goods_${main_class % 2}
        # 分布式序列算法配置(如果是自动生成的,在插入数据的sql中就不要传id,null也不行,直接插入字段中就不要有主键的字段)
        keyGenerators:
          # 分布式序列算法名称
          snowflake:
            # 分布式序列算法类型
            type: SNOWFLAKE
      readwrite-splitting:
        dataSources:
          readwrite_ds: # 此处定义的数据源名称在上分分表中使用
            staticStrategy:
              writeDataSourceName: write_ds1
              readDataSourceNames:
                - read_ds1
                - read_ds2
            # dynamicStrategy:
            loadBalancerName: myBalancer
        load-balancers:
          myBalancer:
            type: RANDOM
            props:
              transactionalReadQueryStrategy: PRIMARY
    props:
      sql-show: true #显示sql

mybatis:
  configuration:
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl

配置简要说明:

商品逻辑表t_goods

  • 按照商品大类取模分片算法进行分表
  • 使用RANDOM算法进行读写分离配置

读写分离规则中定义的数据源名称readwrite_ds,在逻辑表t_goods的数据分片规则中得用应用(actual-data-nodes属性)

ShardingSphere-JDBC读写分离实现包括:静态读写分离动态读写分离两种方式,示例采用静态读写分离

关于动态读写分离使用配置,请见官网:
https://shardingsphere.apache.org/document/5.2.1/cn/user-manual/shardingsphere-jdbc/yaml-config/rules/ha/
https://shardingsphere.apache.org/document/5.2.1/cn/user-manual/shardingsphere-jdbc/spring-boot-starter/rules/ha/

ShardingSphere内置提供了多种负载均衡算法,请见官网:
https://shardingsphere.apache.org/document/5.2.1/cn/user-manual/common-config/builtin-algorithm/load-balance/

3.2.4 实体定义

com/gm/shading/readwrite/entity/Goods.java

import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;

@Data
@TableName("t_goods")
public class Goods {

    @TableId(type = IdType.ASSIGN_ID)
    private Long goodsId;

    private String goodsName;

    private Long mainClass;

    private Long subClass;

}

3.2.5 定义mapper

com/gm/shading/readwrite/mapper/GoodsMapper.java

import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.gm.shading.readwrite.entity.Goods;
import org.apache.ibatis.annotations.Mapper;

@Mapper
public interface GoodsMapper extends BaseMapper<Goods> {
    void save(Goods goods);
}

src/main/resources/mapper/GoodsMapper.xml

<?xml version="1.0" encoding="UTF-8"?>

<!--
  ~ Copyright (c) 2020 mttsmart4cloud Authors. All Rights Reserved.
  ~
  ~ Licensed under the Apache License, Version 2.0 (the "License");
  ~ you may not use this file except in compliance with the License.
  ~ You may obtain a copy of the License at
  ~
  ~     http://www.apache.org/licenses/LICENSE-2.0
  ~
  ~ Unless required by applicable law or agreed to in writing, software
  ~ distributed under the License is distributed on an "AS IS" BASIS,
  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  ~ See the License for the specific language governing permissions and
  ~ limitations under the License.
  -->

<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">

<mapper namespace="com.gm.shading.readwrite.mapper.GoodsMapper">
    <insert id="save" parameterType="com.gm.shading.readwrite.entity.Goods">
        insert into t_goods (goods_name, main_class, sub_class)
        values (#{goodsName}, #{mainClass}, #{subClass})
    </insert>
</mapper>

3.2.6 单元测试

src/test/java/com/gm/shading/readwrite/ShadingReadWriteApplicationTests.java

package com.gm.shading.readwrite;

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.gm.shading.readwrite.entity.Goods;
import com.gm.shading.readwrite.mapper.GoodsMapper;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import java.util.List;
import java.util.Random;

@SpringBootTest
public class ShadingReadWriteApplicationTests {

    @Autowired
    GoodsMapper goodsMapper;

    // 分布式队列不生效,使用数据库递增
    @Test
    void addGoods() throws InterruptedException {
        for (int i = 1; i <= 10; i  ) {
            Goods goods = new Goods();

            Random random = new Random();
            int mainClass = random.nextInt(100)   1;
            int subClass = random.nextInt(100)   1;
            goods.setMainClass((long) mainClass);
            goods.setSubClass((long) subClass);
            goods.setGoodsName("商品"   i);
            goodsMapper.insert(goods);
        }

        Thread.sleep(2000);
        for (int i = 1; i <= 10; i  ) {
            QueryWrapper<Goods> queryWrapper = new QueryWrapper<>();
            List<Goods> list = goodsMapper.selectList(queryWrapper.orderByAsc("goods_id"));
            for (Goods goods : list) {
                System.out.println(goods.toString());
            }
        }
    }

    // 分布式队列生效,使用雪花算法生成ID
    @Test
    void addGoods2() throws InterruptedException {
        for (int i = 1; i <= 10; i  ) {
            Goods goods = new Goods();

            Random random = new Random();
            int mainClass = random.nextInt(100)   1;
            int subClass = random.nextInt(100)   1;
            goods.setMainClass((long) mainClass);
            goods.setSubClass((long) subClass);
            goods.setGoodsName("商品"   i);
            goodsMapper.save(goods);
        }

        Thread.sleep(2000);
        for (int i = 1; i <= 10; i  ) {
            QueryWrapper<Goods> queryWrapper = new QueryWrapper<>();
            List<Goods> list = goodsMapper.selectList(queryWrapper.orderByAsc("goods_id"));
            for (Goods goods : list) {
                System.out.println(goods.toString());
            }
        }
    }
}

3.2.7 数据分片及读写分离效果

学新通
学新通

这篇好文章是转载于:学新通技术网

  • 版权申明: 本站部分内容来自互联网,仅供学习及演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,请提供相关证据及您的身份证明,我们将在收到邮件后48小时内删除。
  • 本站站名: 学新通技术网
  • 本文地址: /boutique/detail/tanhfejagf
系列文章
更多 icon
同类精品
更多 icon
继续加载