# 水平分库分表

介绍如何通过shardingjdbc实现水平分库,水平分表,数据库读写分离功能。

# 水平分库分表

要想实现数据库的水平分库,水平分表,读写分离功能,需要同时改造MySQL数据库以及Java项目,MySQL数据库需要实现主从复制(基于docker搭建mysql主从集群),读写分离(对mysql主从集群做读写分离)功能。而Java项目也需要改造,对于读写分离,需要代码层面判断是读操作还是写操作,对于不同的操作,使用不同的数据库。而对于水平分库分表,则需要人为判断数据需要在哪个库哪个表读写。

# ShardingJDBC介绍

ShardingJDBC是由当当网开源的一个轻量级的数据库分库分表,读写分离框架。利用该框架,我们可以很轻松的实现分布式系统的水平分库,水平分表,读写分离功能。

# SpringBoot+ShardingJDBC实现水平分库分表,读写分离

# 前期准备

我们以一个主从同步,读写分离架构的Mysql服务集群来演示ShardingJDBC的水平分库,水平分表,读写分离功能,因此需要预先搭建一个MySQL一主一从的服务集群,参见基于docker搭建mysql主从集群对mysql主从集群做读写分离

# 建库建表

在Mysql主节点上创建水平分库db_order1以及db_order2,在db_oder1以及db_order2中分别创建t_order_1以及t_order2。可以在从节点看到相同的库表同步到了从节点。


相关建库建表语句
CREATE DATABASE `db_order1`;
CREATE DATABASE `db_order2`;
-- db_order1.t_order_1 definition
CREATE TABLE `t_order_1` (
  `order_id` bigint(20) NOT NULL COMMENT '订单id',
  `price` decimal(10,2) NOT NULL COMMENT '订单价格',
  `user_id` bigint(20) NOT NULL COMMENT '下单用户id',
  `status` varchar(50) NOT NULL COMMENT '订单状态',
  PRIMARY KEY (`order_id`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8 ROW_FORMAT=DYNAMIC;
-- db_order1.t_order_2 definition
CREATE TABLE `t_order_2` (
  `order_id` bigint(20) NOT NULL COMMENT '订单id',
  `price` decimal(10,2) NOT NULL COMMENT '订单价格',
  `user_id` bigint(20) NOT NULL COMMENT '下单用户id',
  `status` varchar(50) NOT NULL COMMENT '订单状态',
  PRIMARY KEY (`order_id`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8 ROW_FORMAT=DYNAMIC;

# Java服务创建

需要创建springboot项目,引入shardingjdbc相关依赖,同时提供分库分表配置,在业务代码里实现分库分表操作(项目运行在jdk8+mysql5.7环境)。

# 依赖pom文件

在pom依赖文件里,我们需要引入springboot,mybatis,druid,mysql-connector,lombok依赖。

完整pom文件
<?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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.1.3.RELEASE</version>
    </parent>

    <groupId>com.howl</groupId>
    <artifactId>sharding-jdbc</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>sharding-jdbc</name>
    <description>Demo project for Spring Boot</description>
    <properties>
        <java.version>1.8</java.version>
    </properties>


    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
            <version>2.0.5.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>1.18.26</version>
        </dependency>
                <dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</artifactId>
                    <version>2.0.0</version>
        </dependency>
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid-spring-boot-starter</artifactId>
            <version>1.1.16</version>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>5.1.47</version>
        </dependency>

        <dependency>
            <groupId>org.apache.shardingsphere</groupId>
            <artifactId>sharding-jdbc-spring-boot-starter</artifactId>
            <version>4.0.0-RC1</version>
        </dependency>
        <dependency>
            <groupId>org.mybatis</groupId>
            <artifactId>mybatis</artifactId>
            <version>3.5.10</version>
        </dependency>

    </dependencies>


    <repositories>
        <repository>
            <id>Central Repository</id>
            <url>https://repo1.maven.org/maven2/</url>
        </repository>
    </repositories>
    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>
</project>

# 配置文件修改

在配置文件里,我们需要配置shardingsphere数据源,读写分离策略,水平分库策略,水平分表策略,分布式主键生成策略。

application.yml
server:
  port: 8082
spring:
  application:
    name: sharding-jdbc-simple-demo
  main:
    allow-bean-definition-overriding: true
  shardingsphere:
    datasource:
      names: m1,m2,s1,s2
      m1:
        type: com.alibaba.druid.pool.DruidDataSource
        driverClassName: com.mysql.jdbc.Driver
        url: jdbc:mysql://43.139.88.150:3366/db_order1?useUnicode=true&useSSL=false
        username: howl
        password: 123456
      m2:
        type: com.alibaba.druid.pool.DruidDataSource
        driverClassName: com.mysql.jdbc.Driver
        url: jdbc:mysql://43.139.88.150:3366/db_order2?useUnicode=true&useSSL=false
        username: howl
        password: 123456
      s1:
        type: com.alibaba.druid.pool.DruidDataSource
        driverClassName: com.mysql.jdbc.Driver
        url: jdbc:mysql://43.139.88.150:3377/db_order1?useUnicode=true&useSSL=false
        username: howl
        password: 123456
      s2:
        type: com.alibaba.druid.pool.DruidDataSource
        driverClassName: com.mysql.jdbc.Driver
        url: jdbc:mysql://43.139.88.150:3377/db_order2?useUnicode=true&useSSL=false
        username: howl
        password: 123456
    sharding:
      master-slave-rules:
        ds1:
          master-data-source-name: m1
          slave-data-source-names: s1
        ds2:
          master-data-source-name: m2
          slave-data-source-names: s2
      tables:
        t_order:
          actualDataNodes: ds$->{1..2}.t_order_$->{1..2}
          databaseStrategy:
            inline:
              shardingColumn: user_id
              algorithm‐expression: m$->{user_id % 2 + 1}
          tableStrategy:
            inline:
              shardingColumn: order_id
              algorithmExpression: t_order_$->{order_id % 2 + 1}
          keyGenerator:
            type: SNOWFLAKE
            column: order_id
    props:
      sql:
        show: true
mybatis:
  configuration:
    map-underscore-to-camel-case: true
logging:
  level:
    root: info
    org.springframework.web: info
    com.itheima.dbsharding: debug
    druid.sql: debug

# 项目代码编写-Application入口

在应用入口,我们只需要使用@SpringBootApplication注解标记入口类即可。

应用入口
package com.dyh.shardingJdbc;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

/**
 * @author howl-xu
 * @version 1.0
 * Create by 2024/3/22
 */
@SpringBootApplication
public class Application {
    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }
}

# 项目代码编写-Controller层代码编写

因为是demo项目,我们直接跳过service层,直接在controller层访问Mapper层。

controller层代码
package com.dyh.shardingJdbc.controller;

import com.dyh.shardingJdbc.entity.Order;
import com.dyh.shardingJdbc.entity.dto.InsertOrderDto;
import com.dyh.shardingJdbc.mapper.OrderMapper;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import javax.annotation.Resource;
import java.util.List;
import java.util.Map;

/**
 * @author howl-xu
 * @version 1.0
 * Create by 2024/3/22
 */
@Slf4j
@RestController
@RequestMapping("/v1/sharding/")
public class OrderController {

    @Resource
    private OrderMapper orderMapper;

    @PostMapping("/insert")
    public void insertOrder(@RequestBody InsertOrderDto dto) {
        orderMapper.insertOrder(dto.getPrice(),dto.getUserId(),dto.getStatus());
    }

    @PostMapping("/select")
    public List<Order> selectOrder() {
        List<Order> list = orderMapper.selectOrderbyIds();
        return list;
    }
}

# 项目代码编写-mapper及entity

mapper及entity的代码可以参考详情。

mapper及entity
package com.dyh.shardingJdbc.mapper;

import com.dyh.shardingJdbc.entity.Order;
import org.apache.ibatis.annotations.Insert;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import org.apache.ibatis.annotations.Select;
import org.springframework.stereotype.Component;

import java.math.BigDecimal;
import java.util.List;
import java.util.Map;

/**
 * 订单Mapper接口
 *
 * @author howl-xu
 * @since 2024/3/22
 **/
@Mapper
@Component
public interface OrderMapper {
    /**
     * 新增订单
     *
     * @param price  订单价格
     * @param userId 用户id
     * @param status 订单状态
     * @return
     */
    @Insert("insert into t_order(price,user_id,status) value(#{price},#{userId},#{status})")
    int insertOrder(@Param("price") Double price, @Param("userId") Long userId,
                    @Param("status") String status);

    /**
     * 根据id列表查询多个订单
     *
     * @return
     */
    @Select("select * from t_order")
    List<Order> selectOrderbyIds();

}

package com.dyh.shardingJdbc.entity;

import java.math.BigDecimal;
import java.io.Serializable;
import lombok.Data;
import lombok.EqualsAndHashCode;

/**
 * <p>
 *
 * </p>
 *
 * @author howl-xu
 * @since 2024-03-22
 */
@Data
@EqualsAndHashCode(callSuper = false)
public class Order implements Serializable {

    private static final long serialVersionUID = 1L;

    private Long orderId;

    /**
     * 订单价格
     */
    private BigDecimal price;

    /**
     * 下单用户id
     */
    private Long userId;

    /**
     * 订单状态
     */
    private String status;


}

# 验证水平分库分表

测试demo的水平分库策略是根据用户的id做分库,如用户id为偶数,则分配到db_order1库中,如用户id为奇数,则分配到db_order2中。
测试demo的水平分表策略是根据order_id进行分表,订单id使用雪花算法生成,如订单id是偶数,则分配到t_order_1表,如是奇数,则分配到t_order_2表中。
我们使用一个用户id为偶数的用户做订单创建操作,如下:

查找数据库,这次雪花算法生成的id是一个奇数,数据写入到了db_order_1库的t_order_2表中。

# 验证读写分离

对创建的订单信息进行查询,如下:

可以在项目的日志中看到,此时查询操作查询了s1,s2两个数据库的t_order_1以及t_order_2表。

# 完整项目地址

水平分库分表读写分离项目demo (opens new window)