多数据源、读写分离、分库分表

摘要:为了保证服务读写效率以及高可用性,通过读写分离、冷热分离解决数据库读并发的问题。


目录

[TOC]

数据库架构优化

为了保证服务读写效率以及高可用性,通过读写分离、冷热分离解决数据库读并发的问题,

随着业务量增长,单表的数据量达到性能瓶颈之后,通过分库分表(对数据库表进行水平拆分和垂直拆分)解决数据库存储压力的问题。

  • 具体如何进行合理的拆分,以及技术选型,这些和项目现有的表结构设计是息息相关的,要考虑后续的可拓展性,不能短期拆了一时爽,
  • 后续业务量增暴涨之后,服务器的性能不足以维持数据库的性能时,这时候要拆分服务器部署了。

如:

  1. 读写分离:主要是为了将数据库的读操作和写操作分散到不同的数据库节点上。部署多台数据库,主服务器负责写,从服务器负责读,通常一主多从。主从数据库间(通过主从复制)进行数据同步。需要尽量避免主从延迟。
  2. 分库分表
    1. 分库:将数据库中的数据分散到不同的数据库上。
    2. 分表:对单表的数据进行拆分,可以是垂直拆分,也可以是水平拆分。常见的分片算法。
    3. 引入分库分表之后,需要系统解决事务、分布式 id、无法 join 操作的问题。
  3. 冷热分离、使用缓存:根据数据的访问频率和业务重要性,将数据分为冷数据和热数据。使用缓存存储热点数据。

常见中间件

  1. Sharding-Sphere(分库分表的首选):是一款分布式的数据库生态系统, 可以将任意数据库转换为分布式数据库,并通过数据分片、弹性伸缩、加密等能力对原有数据库进行增强。
    1. 功能完善,除了支持读写分离和分库分表,还提供分布式事务、数据库治理、影子库、数据加密和脱敏等功能。
    2. 社区强大,已经进入 Apache 孵化。是当当捐入 Apache 的,目前主要由京东的一些巨佬维护。
    3. 在京东、当当等大型互联网公司落地使用,并且已经提供的有 100+ 企业的成功案例。
    4. 子项目 sharding-jdbc推荐首选方案,是一款轻量级 Java 框架,以 jar 包形式提供服务,不需做额外运维,且兼容性很好。
  2. MyCAT:基于 Cobar
  3. TSharding(蘑菇街)
  4. Cobar(阿里巴巴)

读写分离

读写分离:主要是为了将数据库的读操作和写操作分散到不同的数据库节点上。部署多台数据库(集群),主数据库负责写,从数据库负责读,通常一主多从。主从数据库间(通过主从复制)进行数据同步。需要尽量避免主从延迟。

  • 读写分离基于主从复制,MySQL 主从复制依赖于 binlog
  • 优点:
    1. 大幅提高读性能,小幅提高写的性能。
    2. 提高服务器的负载、并发能力,可以根据读请求的规模自由增加或者减少从库的数量。
    3. 数据备份,快速恢复。
  • 用途:因此更适合单机并发读写少读多请求较多的场景。如商品数据库。

img

img

常用实现方式

  1. 代理方式:代理层负责分离读写请求,再路由到对应的数据库中。提供类似功能的中间件有 MySQL Router(官方)、Atlas(基于 MySQL Proxy)、Maxscale、MyCat
  2. SDK 组件方式:引入第三方组件来帮助读写请求,推荐sharding-jdbc

主从复制原理

MySQL 的主从复制

MySQL 主从复制依赖于 binlog 日志文件

  • MySQL binlog(binary log 二进制日志文件) :主要记录了 MySQL 数据库中数据的所有变化(数据库执行的所有 DDL 和 DML 语句)。
  • 因此,根据主库的 binlog 日志就能够将主库的数据同步到从库中。

主从复制的具体过程:

  1. 主库将数据库中数据的变化(用 DML 命令)写入到 binlog(Binary Log);
  2. 从库连接主库;
  3. 从库创建一个 I/O 线程向主库请求更新的 binlog;
  4. 主库创建一个 binlog dump 线程来(读取数据库事件)发送 binlog ,从库中的 I/O 线程负责接收;
  5. 从库的 I/O 线程将接收的 binlog 写入到 relay log 中。
  6. 从库的 SQL 线程读取 relay log 同步数据到本地(即再执行一遍 SQL )。

当然,除了主从复制之外,binlog 还能帮助实现数据恢复。

图片

关于MySql的主从复制的实线可以参考:MySql主从复制,从原理到实践!

Redis 的主从复制

分布式缓存组件 Redis 也是通过主从复制实现的读写分离。

应用场景:电子商务网站上的商品,一般都是一次上传,无数次浏览的,也就是”多读少写”。

实现原理:

  • 一个Redis服务可以有多个该服务的复制品,这个Redis服务称为 Master 主库,其它复制称为 Slaves 从库。
  • 主库只负责写数据,每次有数据更新都将更新的数据同步到它所有的从库,而从库只负责读数据。

主从(同步)延迟

主从同步延迟:主库和从库的数据存在延迟

  • 比如写完主库之后,主库的数据同步到从库需要时间,这个时间差就导致了主库和从库的数据不一致的问题。

如何避免主从延迟?

  1. 强制将读请求路由到主库处理;虽然会增加主库的压力,但是,实现起来比较简单。如 Sharding-JDBC
  2. 延迟读取:对于一些对数据比较敏感的场景,可以在完成写请求之后,避免立即进行其它请求
    1. 比如支付成功之后,跳转到一个支付成功的页面,当点击返回之后才返回自己的账户。
    2. 方便,这样设计业务流程就会好很多。

Gaea

传统的MySql读写分离方案是通过在代码中根据SQL语句的类型动态切换数据源来实现的。

Gaea是小米中国区电商研发部研发的,基于MySql协议的数据库中间件,目前在小米商城大陆和海外得到广泛使用,包括订单、社区、活动等多个业务。

  • Gaea支持分库分表、SQL路由、自动读写分离等基本特性,其中分库分表方案兼容了mycat和kingshard两个项目的路由方式。

使用Gaea需要依赖MySql的主从复制环境。

读写分离测试思路:
  1. 首先我们关闭从实例的主从复制,然后通过Gaea代理来操作数据库,插入一条数据,如果主实例中有这条数据而从实例中没有,说明写操作是走的主库。
  2. 然后再通过Gaea代理查询该表数据,如果没有这条数据,表示读操作走的是从库,证明读写分离成功。

分库分表

通过读写分离、冷热分离解决数据库读并发的问题,通过分库分表解决数据库存储压力的问题。

分库分表:按照业务划分数据库、按照规则拆分表到不同数据库,垂直拆分列、水平拆分行。

  • 用于解决由于库、表数据量过大导致数据库性能持续下降的问题。

定义、常用方法

分库:将数据库中的数据分散到不同的数据库上。

  1. 垂直分库:把单一数据库按照业务进行划分。不同的业务使用不同的数据库,进而将一个数据库的压力分担到多个数据库。如:将数据库中的用户表、订单表和商品表分别单独拆分为用户数据库、订单数据库和商品数据库。
  2. 水平分库:把同一个表按一定规则拆分到不同的数据库中,每个库可以位于不同的服务器上。解决了单表的存储和性能瓶颈问题。如:订单表数据量太大,对订单表进行了水平切分,将切分后的 2 张订单表分别放在两个不同的数据库。

分表:对单表的数据进行拆分。可以避免单表数据量过大对性能造成影响。

  1. 垂直分表:是对数据表的拆分,把一张列比较多的表(一些列单独抽出来)拆分为多张表。如:可以将用户信息表中的作为一个表。(根据范式)
  2. 水平分表:是对数据表的拆分,把一张行比较多的表(一些行单独抽出来)拆分为多张表。
    • 为了提升性能,通常会选择将水平分表后的多张表放在不同的数据库中。即,水平分表通常和水平分库同时出现。

应用场景

  1. 单表的数据达到千万级别以上,数据库读写速度较慢(分表)。
  2. 数据库中的数据占用的空间越来越大,备份时间越来越长(分库)。
  3. 应用的并发量太大(分库)。

不过,分库分表的成本太高,如非必要尽量不要采用。

而且,并不一定是单表千万级数据量就要分表,毕竟每张表包含的字段不同,它们在不错的性能下能够存放的数据量也不同,还是要具体情况具体分析。

为什么要分库分表:

  1. 这个问题要结合读写分离、分区表来综合回答;
  2. 更进一步则要说清在具体的业务场景下,应该分库还是分表,还是说都要;
  3. 高端点则要将分库分表和其它中间件进行一个横向比较,站在一个集群模式的角度去讨论这个问题

设计原理

分库分表中间件的设计原理、步骤:

  1. 改写 SQL:给出一个逻辑 SQL 和相应的分库分表规则,你应该能够说清楚它的目标库和目标表
    • 改写 SQL 的目标是生成物理/目标 SQL。主要受到两方面的影响:
      1. 查询本身
      2. 分库分表规则
    • 举个典型例子来说,SELECT * FROM user_tab WHERE id < 10000 这条语句:
      1. 如果是哈希分库分表,那么大概率是广播,即将查询发到全部表上
      2. 如果是范围分表,那么就可以根据范围来计算出准确的目标表
      3. 而如果是 SELECT avg(age) FROM user_tab,那么不管是范围还是哈希分库分表,都要改写为 SELECT count(id), sum(age) FROM user_tab,然后在处理结果的时候根据 count 和 sum 来求平均值。
  2. 执行 SQL:重点要考虑
    • 目标 SQL 能不能并发执行
    • 修改数据类的查询,例如说 INSERT、UPDATE、DELETE 之类的语句,如果目标 SQL 有多个,那么如果出现部分失败的问题该怎么解决
    • 事务怎么处理
  3. 处理结果集:这个过程和前面改写 SQL 是很密切相关的,要考虑
    • 排序和分页
    • 聚合函数

实际中如何分库分表

  • 实际中如何分库分表:
    • 怎么挑选分库分表的键
    • 主键怎么生成:不同策略的优缺点和潜在问题
    • 从单裤单表开始分库分表,要怎么做
    • 怎么计算分库分表应该要分多少库、多少表
    • 如果中途发现分库分表需要扩容,怎么扩容:
      • 怎么做数据迁移
      • 怎么做数据校验
      • 怎么切换流量
      • 怎么做好回滚:即万一中间任何一个步骤出问题,能不能回滚,怎么回滚
  • 分库分表的性能问题
    • 排序查询性能问题以及优化方案
    • 分页查询性能问题以及优化方案
  • 非典型查询,是指不符合分库分表初衷的查询。例如在订单表中,典型的分库分表方案是使用买家 ID,那么如果卖家要查询自己卖了多少单,就很难查询

常见的分片算法

分片算法:主要解决了数据被水平分片(拆行)之后,数据该放在哪个表的问题。

常见的分片算法有:

  1. 哈希分片:求指定分片键的哈希,根据哈希值确定数据应被放在哪个表中。
    1. 可以使每个表的数据分布相对均匀,但对动态伸缩(例如新增一个表或者库)不友好。
    2. 比较适合随机读写的场景,不太适合经常需要范围查询的场景。
  2. 范围分片:按照特定的范围区间(比如时间区间、ID 区间)来分配数据。
    1. 比如 将 id1~299999 的记录分到第一个表, 300000~599999 的分到第二个表。
    2. 范围分片适合需要经常进行范围查找且数据分布均匀的场景,不太适合随机读写的场景(数据未被分散,容易出现热点数据的问题)。
  3. 映射表分片:使用一个单独的映射表来存储分片键和分片位置的对应关系。
    1. 可以支持任何类型的分片算法,如哈希分片、范围分片等。可以灵活地调整分片规则,不需要修改应用程序代码或重新分布数据。
    2. 不过,需要维护额外的表,还增加了查询的开销和复杂度。
  4. 一致性哈希分片:将哈希空间组织成一个哈希环,将分片键和节点(数据库或表)都映射到这个环上,根据顺时针的规则确定数据或请求应该分配到哪个节点上。解决了传统哈希对动态伸缩不友好的问题。另外,见负载均衡算法中的一致性哈希法
  5. 地理位置分片:根据地理位置(如城市、地域)来分配数据。
  6. 融合算法分片:灵活组合多种分片算法,比如将哈希分片和范围分片组合。
  7. ……

分片键如何选择?

分片键(Sharding Key):是数据分片的关键字段。分片键的选择非常重要,它关系着数据的分布和查询效率。

一般来说,分片键应该具备以下特点:

  • 具有共性: 能够覆盖绝大多数的查询场景,尽量减少单次查询所涉及的分片数量,降低数据库压力;
  • 具有离散性:能够将数据均匀地分散到各个分片上,避免数据倾斜和热点问题
  • 具有稳定性:分片键的值不会发生变化,避免数据迁移和一致性问题;
  • 具有扩展性:能够支持分片的动态增加和减少,避免数据重新分片的开销。

实际项目中,分片键很难满足上面提到的所有特点,需要权衡一下。

并且,分片键可以是表中多个字段的组合,例如取用户 ID 后四位作为订单 ID 后缀。

带来的问题

同一个数据库中的表分布在了不同的数据库中,可能会带来的问题:

  1. join 操作:无法使用 join 操作,导致需要手动进行数据的封装。
    1. 比如,在一个数据库中查询到一个数据之后,再根据这个数据去另外一个数据库中找对应的数据。
    2. 不过,很多大厂的资深 DBA 都是建议尽量不要使用 join 操作。因为 join 的效率低,并且会对分库分表造成影响。对于需要用到 join 操作的地方,可以采用多次查询业务层进行数据组装的方法。不过,需要考虑业务上多次查询的事务性的容忍度。
  2. 事务问题:如果单个操作涉及到多个数据库,那么数据库自带的事务就无法满足要求了。需要引入分布式事务

另外,可能会带来的问题:

  1. 分布式 id:分库之后, 数据遍布在不同服务器上的数据库,数据库的自增主键无法生成全局唯一主键。需要引入分布式 ID
  2. 跨库聚合查询问题:分库分表会导致常规聚合查询操作,如 group by,order by 等变得异常复杂。
    1. 因为这些操作需要在多个分片上进行数据汇总和排序。为了实现这些操作,需要编写复杂的业务代码,或者使用中间件来协调分片间的通信和数据传输。
    2. 这样会增加开发和维护的成本,以及影响查询的性能和可扩展性。

冷热分离

数据冷热分离:指根据数据的访问频率和业务重要性,将数据分为冷数据和热数据。冷数据一般存储在低成本、低性能的介质中,热数据存储在高性能存储介质中。

  • 热数据:指经常被访问和修改且需要快速访问的数据。缓存热点数据,存储在内存中,速度相当快。
  • 冷数据:指不经常访问,对当前项目价值较低,但需要长期保存的数据。
冷热分离的思想

冷热分离的思想:对数据进行分类,然后分开存储。

可以应用到很多领域和场景中,例如:

  • 邮件系统中,可以将近期的比较重要的邮件放在收件箱,将比较久远的不太重要的邮件存入归档
优缺点
  • 优点:
    1. 热数据的查询性能得到优化(用户的绝大部分操作体验会更好);
    2. 节约成本:根据冷热数据的不同存储需求,选择对应的数据库类型和硬件配置,比如将热数据放在 SSD 上,将冷数据放在 HDD 上。
  • 缺点:
    1. 系统复杂性和风险增加(需要分离冷热数据,数据错误的风险增加);
    2. 统计效率低。
冷数据如何存储?

冷数据的存储要求主要是容量大、成本低、可靠性高,访问速度可以适当牺牲。

冷数据存储方案:

  • 中小厂:直接使用 MySQL/PostgreSQL 即可(不改变数据库选型和项目当前使用的数据库保持一致),比如新增一张表来存储某个业务的冷数据、或者使用单独的冷库来存放冷数据(涉及跨库查询,增加了系统复杂性和维护难度)。
  • 大厂:Hbase(常用)、RocksDB、Doris、Cassandra

如果公司成本预算足的话,也可以直接上 TiDB 这种分布式关系型数据库,直接一步到位。

  • TiDB 6.0 正式支持数据冷热存储分离,可以降低 SSD 使用成本。
  • 使用 TiDB 6.0 的数据放置功能,可以在同一个集群实现海量数据的冷热存储,将新的热数据存入 SSD,历史冷数据存入 HDD。

数据库连接池

在项目中,数据库连接池基本是必不可少的组件。在目前数据库连接池的选型中,主要是

  1. Druid:为监控而生的数据库连接池。Alibaba 开源的高性能数据库连接池。加入了强大的监控功能,可实时观察数据库连接池和 SQL 的运行情况,帮用户及时排查出系统中存在的问题。
  2. HikariCP:号称性能最好的数据库连接池。Spring Boot 默认数据源。
  3. C3P0
  4. DBCP

至于怎么选择,两者都非常优秀,不用过多纠结。

  1. 阿里大规模采用 Druid 。
  2. Spring Boot 2.X 版本,默认采用 HikariCP 。

当然,如下有一些资料,胖友可以阅读参考:

HikariCP 和 Druid 的入门,会配置单数据源和多数据源情况下的连接池。

整合 HikariCP 连接池

依赖

配置文件

添加 HikariCP 配置:

  • spring.datasource 配置项下,可以添加数据源的通用配置。定义了 ordersusers 两个数据源的配置。
    • .hikari 配置项下,可以添加 HikariCP 连接池的自定义配置。然后 DataSourceConfiguration.Hikari 会自动化配置 HikariCP 连接池。

参考:HikariCP 更多配置项

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
spring:
  # datasource 数据源配置内容
  datasource:
    # 订单数据源配置
    orders:
      url: jdbc:mysql://127.0.0.1:3306/test_orders?useSSL=false&useUnicode=true&characterEncoding=UTF-8
      driver-class-name: com.mysql.jdbc.Driver
      username: root
      password:
      # HikariCP 自定义配置,对应 HikariConfig 配置属性类
      hikari:
        minimum-idle: 20 # 池中维护的最小空闲连接数,默认为 10 个。
        maximum-pool-size: 20 # 池中最大连接数,包括闲置和使用中的连接,默认为 10 个。
    
    # 用户数据源配置
    users:
      url: jdbc:mysql://127.0.0.1:3306/test_users?useSSL=false&useUnicode=true&characterEncoding=UTF-8
      driver-class-name: com.mysql.jdbc.Driver
      username: root
      password:
      # HikariCP 自定义配置,对应 HikariConfig 配置属性类
      hikari:
        minimum-idle: 15 # 池中维护的最小空闲连接数,默认为 10 个。
        maximum-pool-size: 15 # 池中最大连接数,包括闲置和使用中的连接,默认为 10 个。

数据源配置类

创建 DataSourceConfig 配置类。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
// DataSourceConfig.java

@Configuration
public class DataSourceConfig {

    /**
     * 创建 orders 数据源的配置对象
     */
    @Primary // 保证项目中有一个主的 DataSourceProperties Bean 。
    @Bean(name = "ordersDataSourceProperties")
    @ConfigurationProperties(prefix = "spring.datasource.orders") // 读取 spring.datasource.orders 配置到 DataSourceProperties 对象
    public DataSourceProperties ordersDataSourceProperties() {
        return new DataSourceProperties();
    }

    /**
     * 创建 orders 数据源
     */
    @Bean(name = "ordersDataSource") //创建一个名字为 `"ordersDataSource"` 的 HikariDataSource Bean
    @ConfigurationProperties(prefix = "spring.datasource.orders.hikari") // 读取 spring.datasource.orders 自定义配置项,逐个属性赋值给 HikariDataSource Bean 对象
    public DataSource ordersDataSource() {
        // <1.1> 获得 `orders` 数据源的 DataSourceProperties 对象
        DataSourceProperties properties =  this.ordersDataSourceProperties();
        // <1.2> 创建 HikariDataSource 对象。这样,`"spring.datasource.orders"` 配置项,逐个属性赋值给 HikariDataSource Bean 。
        return createHikariDataSource(properties);
    }

    // 省略 users 数据源配置类

    private static HikariDataSource createHikariDataSource(DataSourceProperties properties) {
        // 创建 HikariDataSource 对象
        HikariDataSource dataSource = properties.initializeDataSourceBuilder().type(HikariDataSource.class).build();
        // 设置线程池名
        if (StringUtils.hasText(properties.getName())) {
            dataSource.setPoolName(properties.getName());
        }
        return dataSource;
    }

}

整合 Druid 连接池

依赖

1
2
3
4
5
6
<!--添加 druid 的 starter-->
<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>druid-spring-boot-starter</artifactId>
    <version>1.1.17</version>
</dependency>

数据源配置

application.yml 文件中添加数据源配置,会与 Druid 数据源中的属性进行绑定;

  • 将 Druid 的自定义配置,和 urldriver-class-name 等数据源的通用配置放在同一级,这样后续只需要使用 @ConfigurationProperties(prefix = "spring.datasource.orders") 的方式,就能完成 DruidDataSource 的配置属性设置。
  • spring.datasource.druid 配置项下,还配置了 filter.statstat-view-servlet 配置项,用于 Druid 监控功能。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
		######## JDBC 通用配置 ############### 
...
      	######## Druid 连接池的配置 ###############
spring:
  # datasource 数据源配置内容
  datasource:
    # 订单数据源配置
    orders:
      url: jdbc:mysql://127.0.0.1:3306/test_orders?useSSL=false&useUnicode=true&characterEncoding=UTF-8
      driver-class-name: com.mysql.jdbc.Driver
      username: root
      password:
      type: com.alibaba.druid.pool.DruidDataSource # 设置类型为 DruidDataSource
      # Druid 自定义配置,对应 DruidDataSource 中的 setting 方法的属性
      min-idle: 0 # 池中维护的最小空闲连接数,默认为 0 个。
      max-active: 20 # 池中最大连接数,包括闲置和使用中的连接,默认为 8 个。
    # 用户数据源配置
    users:
      url: jdbc:mysql://127.0.0.1:3306/test_users?useSSL=false&useUnicode=true&characterEncoding=UTF-8
      driver-class-name: com.mysql.jdbc.Driver
      username: root
      password:
      type: com.alibaba.druid.pool.DruidDataSource # 设置类型为 DruidDataSource
      # Druid 自定义配置,对应 DruidDataSource 中的 setting 方法的属性
      min-idle: 0 # 池中维护的最小空闲连接数,默认为 0 个。
      max-active: 20 # 池中最大连接数,包括闲置和使用中的连接,默认为 8 个。
      
      initial-size: 5 # 初始化连接大小
      max-wait: 60000 # 获取连接时最大等待时间,毫秒
      time-between-eviction-runs-millis: 60000 # 间隔多久检测一次需关闭的空闲连接,毫秒
      min-evictable-idle-time-millis: 300000 # 连接在池中最小生存时间,毫秒
      connection-properties: ’druid.stat.mergeSql=true;druid.stat.slowSqlMillis=5000‘ # 打开mergeSql功能;慢SQL记录
      
    # Druid 自定已配置
    druid:
      filter: # 配置扩展插件,常用的有stat:监控统计,wall:防sql注入
        stat: # 配置 StatFilter ,对应文档 https://github.com/alibaba/druid/wiki/%E9%85%8D%E7%BD%AE_StatFilter
          log-slow-sql: true # 开启慢查询记录
          slow-sql-millis: 5000 # 慢 SQL 的标准,单位:毫秒
      stat-view-servlet: # 配置 StatViewServlet ,对应文档 https://github.com/alibaba/druid/wiki/%E9%85%8D%E7%BD%AE_StatViewServlet%E9%85%8D%E7%BD%AE
        enabled: true # 是否开启 StatViewServlet
        login-username: yudaoyuanma # 账号
        login-password: javaniubi # 密码

数据源配置类

数据库连接池、数据源自动配置;DataSourceAutoConfiguration 类;

自定义方式需创建数据源配置类:配置类创建 Druid 数据源对象时,应避免将数据源信息(如 URL、username、password 等)硬编码到代码中,可通过 @ConfigurationProperties("spring.datasource") 注解,将数据源属性与配置文件中以 spring.datasource 开头的配置绑定。

创建 DataSourceConfig 配置类。

  • 因为在 「5.2 应用配置」 中,将 Druid 自定义的配置项,和数据源的通用配置放在了同一级,所以只需使用 @ConfigurationProperties(prefix = "spring.datasource.orders") 这样的方式即可。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
// DataSourceConfig.java

@Configuration
public class DataSourceConfig {

    /**
     * 创建 orders 数据源
     */
    @Primary
    @Bean(name = "ordersDataSource")
    @ConfigurationProperties(prefix = "spring.datasource.orders") // 读取 spring.datasource.orders 配置到 HikariDataSource 对象
    public DataSource ordersDataSource() {
        return DruidDataSourceBuilder.create().build();
    }

    /**
     * 创建 users 数据源
     */
    @Bean(name = "usersDataSource")
    @ConfigurationProperties(prefix = "spring.datasource.users")
    public DataSource usersDataSource() {
        return DruidDataSourceBuilder.create().build();
    }

}

监控功能

  1. 内置提供的名为 StatViewServletServlet,可开启 Druid 内置监控页面功能, 展示 Druid 的统计信息;需将该 Servlet 配置在 Web 应用中的 WEB-INF/web.xml 中;
  2. 内置提供的 StatFilter,可开启 Druid 的 SQL 监控功能;
  3. 内置提供了 WallFilter,可开启防火墙功能,防御 SQL 注入攻击。
  • 通过 spring.datasource.filter.stat 配置了 StatFilter ,统计监控信息。
  • 通过 spring.datasource.filter.stat-view-servlet 配置了 StatViewServlet ,提供监控信息的展示的 html 页面和 JSON API 。

所以在启动项目后,访问 http://127.0.0.1:8080/druid 地址,可以看到监控 html 页面。

监控界面

  • 在界面的顶部,提供了数据源、SQL 监控、SQL 防火墙等等功能。
  • 每个界面上,可以通过 View JSON API 获得数据的来源。同时,可以在 JSON API( http://127.0.0.1:8080/druid/api.html ) 菜单对应的界面中,看到 StatViewServlet 内置的监控信息的 JSON API 列表。
  • 因为监控信息是存储在 JVM 内存中,在 JVM 进程重启时,信息将会丢失。如果我们希望持久化到 MySQL、Elasticsearch、HBase 等存储器中,可以通过 StatViewServlet 提供的 JSON API 接口,采集监控信息。另外,有个 druid-aggregated-monitor 开源项目,提供了 集中监控分布式服务中的 Druid 连接池的方案和思路。
  • 如果 StatViewServlet 提供的 JSON API 接口,无法满足诉求,可以通过自定义 API 接口,使用 DruidStatManagerFacade 获得监控信息。使用示例 DruidStatController 代码。

测试类

Spring Boot 提供的默认测试类;

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
package net.biancheng.www;

@SpringBootTest
class SpringBootAdminexApplicationTests {
    // 数据源组件
    @Autowired
    DataSource dataSource;
    // 用于访问数据库的组件
    @Autowired
    JdbcTemplate jdbcTemplate;
    
    @Test
    void contextLoads() throws SQLException {
        System.out.println(默认数据源为:“ + dataSource.getClass());
        System.out.println(数据库连接实例:“ + dataSource.getConnection());
        // 访问数据库
        Integer cnt = jdbcTemplate.queryForObject(SELECT count(*) from `user`“, Integer.class);
        System.out.println(user 表中共有 + cnt + 条数据。“);
    }
}

数据权限

data-permisssion

多数据源

在项目中,可能会碰到需要多数据源的场景。

  • 多数据源(分库?):一个复杂的单体项目,因为没有拆分成不同的服务,需要连接多个业务的数据源。

  • 读写分离:数据库主节点压力比较大,需要增加从节点提供读操作,以减少压力。

    • 本质上,读写分离,仅仅是多数据源的一个场景,从节点是只提供读操作的数据源。所以只要实现了多数据源的功能,也就能够提供读写分离。

实现方式

目前,实现多数据源有三种方案:

基于 AbstractRoutingDataSource

基于 Spring AbstractRoutingDataSource 做拓展。

  1. 简单来说,通过继承 AbstractRoutingDataSource 抽象类,实现一个管理项目中多个 DataSource 的动态 DynamicRoutingDataSource 实现类
  2. 这样,Spring 在获取数据源时,可以通过 DynamicRoutingDataSource 返回实际的 DataSource 。
  3. 然后,可以自定义一个 @DS 注解,可以添加在 Service 方法、Dao 方法上,表示其实际对应的 DataSource 。

如此,整个过程就变成,执行数据操作时,通过配置的 @DS 注解,使用 DynamicRoutingDataSource 获得对应的实际的 DataSource 。之后,在通过该 DataSource 获得 Connection 连接,最后发起数据库操作。

如, 「3. baomidou 多数据源」「4. baomidou 读写分离」

不同操作类,固定数据源

整个配置过程会相对繁琐

以 MyBatis 举例子,假设有 ordersusers 两个数据源。

  1. 那么可以创建两个 SqlSessionTemplate ordersSqlSessionTemplateusersSqlSessionTemplate ,分别使用这两个数据源。
  2. 然后,配置不同的 Mapper 使用不同的 SqlSessionTemplate 。

如此,整个过程就变成,执行数据操作时,通过 Mapper 可以对应到其 SqlSessionTemplate ,进而获得对应的实际的 DataSource 。之后获得 Connection 连接,最后发起数据库操作。

如, 「5. MyBatis 多数据源」「6. Spring Data JPA 多数据源」「7. JdbcTemplate 多数据源」

缺点:在结合 Spring 事务的时候,也会存在无法切换数据源的问题。

分库分表中间件

对于分库分表的中间件,会解析我们编写的 SQL ,路由操作到对应的数据源。那么,它们天然就支持多数据源。如此,仅需配置好每个表对应的数据源,中间件就可以透明的实现多数据源、或者读写分离。

目前,Java 最好用的分库分表中间件,就是 Apache ShardingSphere ,没有之一。

无法切换数据源的问题

这种方式在结合 Spring 事务的时候,不会存在无法切换数据源的问题。

  • 在上述的方案一和方案二中,在 Spring 事务中,会获得对应的 DataSource ,再获得 Connection 进行数据库操作。而获得的 Connection 以及其上的事务,会通过 ThreadLocal 的方式和当前线程进行绑定。这样,就导致无法切换数据源。

  • 虽然分库分表中间件也需要 Connection 进行这些事情,但是不同的是 Connection 返回的实际是动态的 DynamicRoutingConnection ,它管理了整个请求(逻辑)过程中,使用的所有的 Connection ,而最终执行 SQL 时,它会解析 SQL ,获得表对应的真正的 Connection 执行 SQL 操作。

  • 难道方案一和方案二也可以这么做,前提是,他们要实现解析 SQL 的能力。

分库分表中间件(从一定程度上来说)就是多数据源的完美方案。

  • 但是,它需要解决多个 Connection 可能产生的多个事务的一致性问题,即分布式事务。不过相信,Sharding-JDBC 最终会解决分布式事务的难题,提供透明的多数据源的功能。

应用场景

实际场景下怎么选择呢?

  • 方案二:首先,基本排除了方案二【不同操作类,固定数据源】。配置繁琐,使用不变。
    • 这种方案,更加适合不同类型的数据源,例如说一个项目中,既有 MySQL 数据源,又有 MongoDB、Elasticsarch 等其它数据源。
  • 方案一:然后,对于大多数场景下,方案一【基于 SpringAbstractRoutingDataSource 做拓展】,基本能够满足。
    • 这种方案,目前是比较主流的方案,大多数项目都采用。
    • 在实现上,可以比较容易的自己封装一套,当然也可以考虑使用 dynamic-datasource-spring-boot-starter 开源项目。
  • 当然,方案一和方案二,会存在和 Spring 事务结合的时候,在事务中无法切换数据源。
    • 这是因为 Spring 事务会将 Connection 和当前线程变量绑定定,后续会通过线程变量重用该 Connection ,导致无法切换数据源。
    • 所以,方案一和方案二,可以理解成 DataSource 级别上实现的数据源方案。
  • 方案三:最后,【分库分表中间件】是完美解决方案,基本满足了所有的场景。
    • 个人强烈推荐使用 Apache ShardingSphereSharding-JDBC 组件,无论胖友是有多数据源,还是分库分表,还是读写分离,都能完美的匹配。
    • 并且,Apache ShardingSphere 已经提供多种分布式事务方案,也能解决在文章的开头,提到的分布式事务的问题。这种类型的方案,目前很多大厂都是这样去玩的。
  • 京东:采用 client 模式的读写分离和分库分表。
  • 美团:采用 client 模式的读写分离和分库分表。
  • 陌陌:采用 client 模式的读写分离和分库分表。

方案一

baomidou 多数据源

使用 test_orderstest_users 两个数据源作为两个数据源,然后实现在其上的 SQL 操作。并且,会结合在 Spring 事务的不同场景下,会发生的结果以及原因。

  • order 表、user 表分别位于不同的数据库。
  • 不分主从,没有备份。

依赖

Application

创建 Application.java

  • 添加 @MapperScan 注解,指定 Mapper 接口所在的包路径。
  • 添加 @EnableAspectJAutoProxy 注解,重点是配置 exposeProxy = true ,因为希望 Spring AOP 能将当前代理对象设置到 AopContext 中。
1
2
3
4
5
6
7
1
2
3
4
5
6
7
// Application.java

@SpringBootApplication
@MapperScan(basePackages = "cn.iocoder.springboot.lab17.dynamicdatasource.mapper")
@EnableAspectJAutoProxy(exposeProxy = true) // http://www.voidcn.com/article/p-zddcuyii-bpt.html
public class Application {
}

配置文件

resources 目录下,创建 application.yaml 配置文件。

主要是 spring.datasource.dynamic 配置项

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
spring:
  datasource:
    # dynamic-datasource-spring-boot-starter 动态数据源的配置内容
    dynamic:
      primary: users # 设置默认的数据源或者数据源组,默认值即为 master
      datasource: # 配置每个动态数据源。这里配置了 `orders`、`users` 两个
        # 订单 orders 数据源配置
        orders:
          url: jdbc:mysql://127.0.0.1:3306/test_orders?useSSL=false&useUnicode=true&characterEncoding=UTF-8
          driver-class-name: com.mysql.jdbc.Driver
          username: root
          password:
        # 用户 users 数据源配置
        users:
          url: jdbc:mysql://127.0.0.1:3306/test_users?useSSL=false&useUnicode=true&characterEncoding=UTF-8
          driver-class-name: com.mysql.jdbc.Driver
          username: root
          password:

# mybatis 配置内容
mybatis:
  config-location: classpath:mybatis-config.xml # 配置 MyBatis 配置文件路径
  mapper-locations: classpath:mapper/*.xml # 配置 Mapper XML 地址
  type-aliases-package: cn.iocoder.springboot.lab17.dynamicdatasource.dataobject # 配置数据库实体包路径

MyBatis 配置文件

resources 目录下,创建 mybatis-config.xml 配置文件。

  • 因为在数据库中表的字段,是使用下划线风格,而数据库实体的字段使用驼峰风格,所以通过 mapUnderscoreToCamelCase = true 来自动转换。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN" "http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>

    <settings>
        <!-- 使用驼峰命名法转换字段 -->
        <setting name="mapUnderscoreToCamelCase" value="true"/>
    </settings>

    <typeAliases>
        <typeAlias alias="Integer" type="java.lang.Integer"/>
        <typeAlias alias="Long" type="java.lang.Long"/>
        <typeAlias alias="HashMap" type="java.util.HashMap"/>
        <typeAlias alias="LinkedHashMap" type="java.util.LinkedHashMap"/>
        <typeAlias alias="ArrayList" type="java.util.ArrayList"/>
        <typeAlias alias="LinkedList" type="java.util.LinkedList"/>
    </typeAliases>

</configuration>

实体类

Mapper

@DS 注解,是 dynamic-datasource-spring-boot-starter 提供,可添加在 Service 或 Mapper 的类/接口上,或者方法上。在其 value 属性种,填写数据源的名字。

  • OrderMapper 接口上,添加了 @DS(DBConstants.DATASOURCE_ORDERS) 注解,访问 orders 数据源。
  • UserMapper 接口上,添加了 @DS(DBConstants.DATASOURCE_USERS) 注解,访问 users 数据源。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// OrderMapper.java
@Repository
@DS(DBConstants.DATASOURCE_ORDERS)
public interface OrderMapper {

    OrderDO selectById(@Param("id") Integer id);

}

// UserMapper.java
@Repository
@DS(DBConstants.DATASOURCE_USERS)
public interface UserMapper {

    UserDO selectById(@Param("id") Integer id);

}

baomidou 读写分离

示例代码对应仓库:lab-17-dynamic-datasource-baomidou-02

在绝大多数情况下,使用多数据源的目的,是为了实现读写分离。

  • 分主从库。
  • user 库?

其它与多数据源一致。

应用配置文件

resources 目录下,创建 application.yaml 配置文件。

  • 相比 「3.3 应用配置」 来说,配置了订单库的多个数据源:
    1. master :订单库的主库。
    2. slave_1 和 slave_2 :订单库的两个从库。
  • 在 dynamic-datasource-spring-boot-starter 中,多个相同角色的数据源可以形成一个数据源组。
    • 判断标准是,数据源名以下划线 _ 分隔后的首部即为组名。例如说,slave_1 和 slave_2 形成了 slave 组。
    • 可以使用 @DS("slave_1")@DS("slave_2") 注解,明确访问数据源组的指定数据源。
    • 也可以使用 @DS("slave") 注解,此时会负载均衡,选择分组中的某个数据源进行访问。目前,负载均衡默认采用轮询的方式。

因为本地并未搭建 MySQL 一主多从的环境,所以是通过创建了 test_orders_01test_orders_02 库,手动模拟作为 test_orders 的从库。

  • user 库?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
spring:
  datasource:
    # dynamic-datasource-spring-boot-starter 动态数据源的配置内容
    dynamic:
      primary: master # 设置默认的数据源或者数据源组,默认值即为 master
      datasource:
        # 订单 orders 主库的数据源配置
        master:
          url: jdbc:mysql://127.0.0.1:3306/test_orders?useSSL=false&useUnicode=true&characterEncoding=UTF-8
          driver-class-name: com.mysql.jdbc.Driver
          username: root
          password:
          
        # 订单 orders 从库数据源配置
        slave_1:
          url: jdbc:mysql://127.0.0.1:3306/test_orders_01?useSSL=false&useUnicode=true&characterEncoding=UTF-8
          driver-class-name: com.mysql.jdbc.Driver
          username: root
          password:
          
        # 订单 orders 从库数据源配置
        slave_2:
          url: jdbc:mysql://127.0.0.1:3306/test_orders_02?useSSL=false&useUnicode=true&characterEncoding=UTF-8
          driver-class-name: com.mysql.jdbc.Driver
          username: root
          password:

# mybatis 配置内容
mybatis:
  config-location: classpath:mybatis-config.xml # 配置 MyBatis 配置文件路径
  mapper-locations: classpath:mapper/*.xml # 配置 Mapper XML 地址
  type-aliases-package: cn.iocoder.springboot.lab17.dynamicdatasource.dataobject # 配置数据库实体包路径

OrderDO

只使用 「3.5 实体类」OrderDO.java 类。

OrderMapper

创建 OrderMapper.java 接口。

  • DBConstants.java 类,枚举了 DATASOURCE_MASTERDATASOURCE_SLAVE 两个数据源。
1
2
3
4
5
6
7
8
9
10
11
12
13
// OrderMapper.java

@Repository
public interface OrderMapper {

    // 读操作,访问从库
    @DS(DBConstants.DATASOURCE_SLAVE)
    OrderDO selectById(@Param("id") Integer id);

    // 写操作,访问主库
    @DS(DBConstants.DATASOURCE_MASTER)
    int insert(OrderDO entity);
}

resources/mapper 路径下,创建 OrderMapper.xml 配置文件。

方案二

MyBatis 多数据源

配置文件

resources 目录下,创建 application.yaml 配置文件。

  • 注释掉 mybatis 配置项,因为不使用 mybatis-spring-boot-starter 自动化配置 MyBatis ,而是自己写配置类,自定义配置 MyBatis 。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
spring:
  # datasource 数据源配置内容
  datasource:
    # 订单数据源配置
    orders:
      jdbc-url: jdbc:mysql://127.0.0.1:3306/test_orders?useSSL=false&useUnicode=true&characterEncoding=UTF-8
      driver-class-name: com.mysql.jdbc.Driver
      username: root
      password:
    # 用户数据源配置
    users:
      jdbc-url: jdbc:mysql://127.0.0.1:3306/test_users?useSSL=false&useUnicode=true&characterEncoding=UTF-8
      driver-class-name: com.mysql.jdbc.Driver
      username: root
      password:

# mybatis 配置内容
#mybatis:
#  config-location: classpath:mybatis-config.xml # 配置 MyBatis 配置文件路径
#  type-aliases-package: cn.iocoder.springboot.lab17.dynamicdatasource.dataobject # 配置数据库实体包路径

配置类

cn.iocoder.springboot.lab17.dynamicdatasource.config 包路径下,我们会分别创建:

两个 MyBatis 配置类代码是一致的,只是部分配置项的值不同。所以我们仅仅来看下 MyBatisOrdersConfig 配置类,而 MyBatisUsersConfig 配置类胖友自己看看即可。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
// MyBatisOrdersConfig.java

@Configuration
@MapperScan(basePackages = "cn.iocoder.springboot.lab17.dynamicdatasource.mapper.orders", sqlSessionTemplateRef = "ordersSqlSessionTemplate")
public class MyBatisOrdersConfig {

    /**
     * 创建 orders 数据源
     */
    @Bean(name = "ordersDataSource")
    @ConfigurationProperties(prefix = "spring.datasource.orders")
    public DataSource dataSource() {
        return DataSourceBuilder.create().build();
    }

    /**
     * 创建 MyBatis SqlSessionFactory
     */
    @Bean(name = "ordersSqlSessionFactory")
    public SqlSessionFactory sqlSessionFactory() throws Exception {
        SqlSessionFactoryBean bean = new SqlSessionFactoryBean();
        // <2.1> 设置 orders 数据源
        bean.setDataSource(this.dataSource());
        // <2.2> 设置 entity 所在包
        bean.setTypeAliasesPackage("cn.iocoder.springboot.lab17.dynamicdatasource.dataobject");
        // <2.3> 设置 config 路径
        bean.setConfigLocation(new PathMatchingResourcePatternResolver().getResource("classpath:mybatis-config.xml"));
        // <2.4> 设置 mapper 路径,拆分为 orders、users 路径
        bean.setMapperLocations(new PathMatchingResourcePatternResolver().getResources("classpath:mapper/orders/*.xml"));
        return bean.getObject();
    }

    /**
     * 创建 MyBatis SqlSessionTemplate
     * 其内部的 sqlSessionFactory 使用的就是对应 orders 数据源的 SqlSessionFactory 对象。
     */
    @Bean(name = "ordersSqlSessionTemplate")
    public SqlSessionTemplate sqlSessionTemplate() throws Exception {
        return new SqlSessionTemplate(this.sqlSessionFactory());
    }

    /**
     * 创建 orders 数据源的 TransactionManager 事务管理器
     */
    @Bean(name = DBConstants.TX_MANAGER_ORDERS)
    public PlatformTransactionManager transactionManager() {
        return new DataSourceTransactionManager(this.dataSource());
    }
}

读写分离

按照这个思路,如果想要实现 MyBatis 读写分离。还是类似的思路。只是将从库作为一个“特殊”的数据源,需要做的是:

  1. 应用配置文件增加从库的数据源。
  2. 增加一套从库的 MyBatis 配置类。
  3. 增加一套从库相关的 MyBatis Mapper 接口、Mapper XML 文件。

相比方案一【基于 Spring AbstractRoutingDataSource 做拓展】来说,更加麻烦。并且,万一有多从呢?

所以呢,实际项目在选型时,方案一会于方案二,被更普遍的采用。

Spring Data JPA 多数据源

整个过程,和 「5. MyBatis 多数据源」 是类似的。

配置文件

resources 目录下,创建 application.yaml 配置文件。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
spring:
  # datasource 数据源配置内容
  datasource:
    # 订单数据源配置
    orders:
      jdbc-url: jdbc:mysql://127.0.0.1:3306/test_orders?useSSL=false&useUnicode=true&characterEncoding=UTF-8
      driver-class-name: com.mysql.jdbc.Driver
      username: root
      password:
    # 用户数据源配置
    users:
      jdbc-url: jdbc:mysql://127.0.0.1:3306/test_users?useSSL=false&useUnicode=true&characterEncoding=UTF-8
      driver-class-name: com.mysql.jdbc.Driver
      username: root
      password:
  jpa:
    show-sql: true # 打印 SQL 。生产环境,建议关闭
    # Hibernate 配置内容,对应 HibernateProperties 类
    hibernate:
      ddl-auto: none

配置类

创建 HibernateConfig.java 配置类。

  • 目的是获得 Hibernate Vendor 相关配置。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// HibernateConfig.java

@Configuration
public class HibernateConfig {

    @Autowired
    private JpaProperties jpaProperties;
    @Autowired
    private HibernateProperties hibernateProperties;

    /**
     * 获取 Hibernate Vendor 相关配置
     */
    @Bean(name = "hibernateVendorProperties")
    public Map<String, Object> hibernateVendorProperties() {
        return hibernateProperties.determineHibernateProperties(
                jpaProperties.getProperties(), new HibernateSettings());
    }

}

分别创建:

  • JpaOrdersConfig 配置类,配置使用 orders 数据源的 Spring Data JPA 配置。
  • JpaUsersConfig 配置类,配置使用 users 数据源的 Spring Data JPA 配置。

JdbcTemplate 多数据源

整个过程,和 「5. MyBatis 多数据源」 是类似的

方案三

Sharding-JDBC 多数据源

Sharding-JDBCApache ShardingSphere 下,基于 JDBC 的分库分表组件。对于 Java 语言来说,推荐选择 Sharding-JDBC 优于 Sharding-Proxy ,主要原因是:

  • 减少一层 Proxy 的开销,性能更优。
  • 去中心化,无需多考虑一次 Proxy 的高可用。

Application

「3.2 Application」 是完全一致的。

创建 Application.java

1
2
3
4
5
6
7
1
2
3
4
5
6
7
// Application.java

@SpringBootApplication
@MapperScan(basePackages = "cn.iocoder.springboot.lab17.dynamicdatasource.mapper")
@EnableAspectJAutoProxy(exposeProxy = true) // http://www.voidcn.com/article/p-zddcuyii-bpt.html
public class Application {
}

配置文件

resources 目录下,创建 application.yaml 配置文件。

  • 结合使用 Hikari 数据库连接池
  • spring.shardingsphere.datasource 配置项下,配置了 ds_ordersds_users 两个数据源。
  • spring.shardingsphere.sharding 配置项下,配置了分片规则
    1. orders 逻辑表的操作路由到 ds-orders 数据源的 orders 真实表
    2. users 逻辑表的操作路由到 ds-users 数据源的 users 真实表
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
spring:
  # ShardingSphere 配置项
  shardingsphere:
    datasource:
      # 所有数据源的名字
      names: ds-orders, ds-users
      # 订单 orders 数据源配置
      ds-orders:
        type: com.zaxxer.hikari.HikariDataSource # 使用 Hikari 数据库连接池
        driver-class-name: com.mysql.jdbc.Driver
        jdbc-url: jdbc:mysql://127.0.0.1:3306/test_orders?useSSL=false&useUnicode=true&characterEncoding=UTF-8
        username: root
        password:
        
      # 订单 users 数据源配置
      ds-users:
        type: com.zaxxer.hikari.HikariDataSource # 使用 Hikari 数据库连接池
        driver-class-name: com.mysql.jdbc.Driver
        jdbc-url: jdbc:mysql://127.0.0.1:3306/test_users?useSSL=false&useUnicode=true&characterEncoding=UTF-8
        username: root
        password:
        
    # 分片规则
    sharding:
      tables:
        # orders 表配置
        orders:
          actualDataNodes: ds-orders.orders # 映射到 ds-orders 数据源的 orders 表
        # users 表配置
        users:
          actualDataNodes: ds-users.users # 映射到 ds-users 数据源的 users 表

# mybatis 配置内容
mybatis:
  config-location: classpath:mybatis-config.xml # 配置 MyBatis 配置文件路径
  mapper-locations: classpath:mapper/*.xml # 配置 Mapper XML 地址
  type-aliases-package: cn.iocoder.springboot.lab17.dynamicdatasource.dataobject # 配置数据库实体包路径

Sharding-JDBC 读写分离

Sharding-JDBC 已经提供了读写分离的支持,可以看看如下两个文档:

未提及的部分与 Sharding-JDBC 多数据源一致

配置文件

resources 目录下,创建 application.yaml 配置文件。

  • spring.shardingsphere
    1. datasource 配置项下,配置了 一个主数据源 ds-master 、两个从数据源 ds-slave-1ds-slave-2
    2. masterslave 配置项下:配置了读写分离。对于从库来说,Sharding-JDBC 提供了多种负载均衡策略,默认为轮询。
  • mybatis 配置项,设置 mybatis-spring-boot-starter MyBatis 的配置内容。

因为本地并未搭建 MySQL 一主多从的环境,所以是通过创建了 test_orders_01test_orders_02 库,手动模拟作为 test_orders 的从库。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
spring:
  # ShardingSphere 配置项
  shardingsphere:
    # 数据源配置
    datasource:
      # 所有数据源的名字
      names: ds-master, ds-slave-1, ds-slave-2
      
      # 订单 orders 主库的数据源配置
      ds-master:
        type: com.zaxxer.hikari.HikariDataSource # 使用 Hikari 数据库连接池
        driver-class-name: com.mysql.jdbc.Driver
        jdbc-url: jdbc:mysql://127.0.0.1:3306/test_orders?useSSL=false&useUnicode=true&characterEncoding=UTF-8
        username: root
        password:
        
      # 订单 orders 从库数据源配置
      ds-slave-1:
        type: com.zaxxer.hikari.HikariDataSource # 使用 Hikari 数据库连接池
        driver-class-name: com.mysql.jdbc.Driver
        jdbc-url: jdbc:mysql://127.0.0.1:3306/test_orders_01?useSSL=false&useUnicode=true&characterEncoding=UTF-8
        username: root
        password:
        
      # 订单 orders 从库数据源配置
      ds-slave-2:
        type: com.zaxxer.hikari.HikariDataSource # 使用 Hikari 数据库连接池
        driver-class-name: com.mysql.jdbc.Driver
        jdbc-url: jdbc:mysql://127.0.0.1:3306/test_orders_02?useSSL=false&useUnicode=true&characterEncoding=UTF-8
        username: root
        password:
        
    # 读写分离配置,对应 YamlMasterSlaveRuleConfiguration 配置类
    masterslave:
      name: ms # 名字,任意,需要保证唯一
      master-data-source-name: ds-master # 主库数据源
      slave-data-source-names: ds-slave-1, ds-slave-2 # 从库数据源

# mybatis 配置内容
#mybatis:
#  config-location: classpath:mybatis-config.xml # 配置 MyBatis 配置文件路径
#  mapper-locations: classpath:mapper/*.xml # 配置 Mapper XML 地址
#  type-aliases-package: cn.iocoder.springboot.lab17.dynamicdatasource.dataobject # 配置数据库实体包路径
  
# mybatis-plus 配置内容。
# 使用 MyBatis-Plus 替换掉原生 MyBatis,进一步简化该示例
mybatis-plus:
  configuration:
    map-underscore-to-camel-case: true # 虽然默认为 true ,但是还是显示去指定下。
  global-config:
    db-config:
      id-type: none # 虽然 MyBatis Plus 也提供 ID 生成策略,但是还是使用 Sharding-JDBC 的
      logic-delete-value: 1 # 逻辑已删除值(默认为 1)
      logic-not-delete-value: 0 # 逻辑未删除值(默认为 0)
  mapper-locations: classpath*:mapper/*.xml
  type-aliases-package: cn.iocoder.springboot.lab18.shardingdatasource.dataobject

Mapper

0%