一、概述
- 分库分表介绍:当数据量变大以后,单库单表已经不能满足需求。此时就需要进行拆分,拆分纬度分为垂直拆分和水平拆分。
- 水平拆分:比如 服务器1 上有 user_0, order_0; 服务器2 上有 user_1, order_1。此时 user_0 和 user_1 一起组成了用户表。
- 垂直拆分:用户表 放在服务器1上,订单表 放在服务器2上。
- 此处模拟使用两个数据库,每个数据库建两张表。库的拆分使用 city 字段(按城市存不同的库),表的拆分使用 id 取模。
二、数据准备
- 引入 pom :
<dependency> <groupId>com.baomidou</groupId> <artifactId>mybatis-plus-boot-starter</artifactId> <version>3.3.1.tmp</version> </dependency> <dependency> <groupId>com.alibaba</groupId> <artifactId>druid</artifactId> <version>1.1.12</version> </dependency> <dependency> <groupId>com.dangdang</groupId> <artifactId>sharding-jdbc-config-spring</artifactId> <version>1.5.4.1</version> </dependency>
- 新建两个数据库(可以同一个服务器上,也可以两个服务器上)。
CREATE DATABASE `xjf_0` CHARACTER SET 'utf8' COLLATE 'utf8_general_ci' CREATE DATABASE `xjf_1` CHARACTER SET 'utf8' COLLATE 'utf8_general_ci'
- 在两个库中分别建如下两张表:
CREATE TABLE `user_0` ( `id` BIGINT(64) NOT NULL, `city` VARCHAR(20) NOT NULL, `name` varchar(20) NOT NULL, PRIMARY KEY (`id`) ) ENGINE=INNODB DEFAULT CHARSET=utf8; CREATE TABLE `user_1` ( `id` BIGINT(64) NOT NULL, `city` VARCHAR(20) NOT NULL, `name` varchar(20) NOT NULL, PRIMARY KEY (`id`) ) ENGINE=INNODB DEFAULT CHARSET=utf8;
三、分库分表配置
- 在 resource 目录下新建 sharding.xml。配置如下,记得数据库连接修改为你自己的:
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context" xmlns:rdb="http://www.dangdang.com/schema/ddframe/rdb" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd http://www.dangdang.com/schema/ddframe/rdb http://www.dangdang.com/schema/ddframe/rdb/rdb.xsd "> <!--======================================================分库分表===开始=================================================--> <!-- inline 表达式报错解决:在 Spring 的配置文件中,由于 inline 表达式使用了 Groovy 语法, Groovy 语法的变量符与 Spring 默认占位符 同为 ${} ,因此需要在配置文件中增加下面这行来解决解析报错问题--> <context:property-placeholder ignore-unresolvable="true" /> <!-- 第一个数据库 --> <bean id="ds_0" class="com.alibaba.druid.pool.DruidDataSource" destroy-method="close" primary="true"> <property name="driverClassName" value="com.mysql.jdbc.Driver" /> <property name="url" value="jdbc:mysql://localhost:3306/xjf_0?autoReconnect=true&useUnicode=true&characterEncoding=utf-8&useSSL=false" /> <property name="username" value="root" /> <property name="password" value="123456" /> </bean> <!-- 第二个数据库 --> <bean id="ds_1" class="com.alibaba.druid.pool.DruidDataSource" destroy-method="close"> <property name="driverClassName" value="com.mysql.jdbc.Driver" /> <property name="url" value="jdbc:mysql://localhost:3306/xjf_1?autoReconnect=true&useUnicode=true&characterEncoding=utf-8&useSSL=false" /> <property name="username" value="root" /> <property name="password" value="123456" /> </bean> <!-- 配置分库规则: 根据 city 来分库,同一个城市的数据存同一个数据库中 --> <rdb:strategy id="databaseShardingStrategy" sharding-columns="city" algorithm-class="com.xjf.sharding.algorithm.MySingleKeyDbShardingAlgorithm" /> <!-- 配置分表规则 --> <rdb:strategy id="tableShardingStrategy" sharding-columns="id" algorithm-class="com.xjf.sharding.algorithm.MyUserSingleKeyTableShardingAlgorithm" /> <!-- 配置分库分表数据源 --> <rdb:data-source id="dataSource"> <rdb:sharding-rule data-sources="ds_0, ds_1"> <rdb:table-rules> <rdb:table-rule logic-table="user" actual-tables="user_${0..1}" database-strategy="databaseShardingStrategy" table-strategy="tableShardingStrategy"> <!-- 使用 Sharding-JDBC 的默认 ID 生成器,基于雪花算法。--> <rdb:generate-key-column column-name="id" /> </rdb:table-rule> </rdb:table-rules> </rdb:sharding-rule> </rdb:data-source> <!--======================================================分库分表===结束=================================================--> <!-- 给 MyBatis-Plus 配置数据源 --> <bean id="mybatisSqlSessionFactoryBean" class="com.baomidou.mybatisplus.extension.spring.MybatisSqlSessionFactoryBean"> <property name="dataSource" ref="dataSource"/> </bean> </beans>
- 在启动类上添加对应注解,引入 sharding.xml。
@ImportResource(locations = "classpath:sharding.xml") @MapperScan("com.xjf.sharding.mapper") @SpringBootApplication public class ShardingApplication { public static void main(String[] args) { SpringApplication.run(ShardingApplication.class, args); } }
四、自定义分库和分表算法
- 分库算法,使用 city 来区分:
public class MySingleKeyDbShardingAlgorithm implements SingleKeyDatabaseShardingAlgorithm<String> { private static Map<String, List<String>> shardingMap = new ConcurrentHashMap<>(); static { shardingMap.put("ds_0", Arrays.asList("上海")); shardingMap.put("ds_1", Arrays.asList("杭州")); } @Override public String doEqualSharding(Collection<String> collection, ShardingValue<String> shardingValue) { for (String each : collection) { System.err.println("数据库:" + each); System.err.println("添加数据的城市:" + shardingValue.getValue()); if (shardingMap.get(each).contains(shardingValue.getValue())){ return each; } } // 默认保存在数据库 "ds_0" 中 return "ds_0"; } @Override public Collection<String> doInSharding(Collection<String> collection, ShardingValue<String> shardingValue) { Collection<String> result = new LinkedHashSet<>(collection.size()); for (String each : collection) { if (shardingMap.get(each).contains(shardingValue.getValue())){ result.add(each); }else { result.add("ds_0"); } } return result; } @Override public Collection<String> doBetweenSharding(Collection<String> collection, ShardingValue<String> shardingValue) { Collection<String> result = new LinkedHashSet<>(collection.size()); for (String each : collection) { if (shardingMap.get(each).contains(shardingValue.getValue())){ result.add(each); }else { result.add("ds_0"); } } return result; } }
- 分表算法,id 取模:
public class MyUserSingleKeyTableShardingAlgorithm implements SingleKeyTableShardingAlgorithm<Long> { /** * 在 where 使用 = 作为条件分片键 */ @Override public String doEqualSharding(Collection<String> collection, ShardingValue<Long> shardingValue) { System.err.println("运行方法: doEqualSharding"); for (String each : collection) { System.err.println("表:" + each); System.err.println("shardingValue.getValue: " + shardingValue.getValue()); // 配合测试分库分表,取模是只有 2 张表。在测试不分库只分表时是 4 张表。分别对应使用 // if (each.endsWith(shardingValue.getValue() % 4 +"")){ if (each.endsWith(shardingValue.getValue() % 2 +"")){ return each; } } throw new IllegalArgumentException(); } /** * 在 where 使用 in 作为条件分片键 */ @Override public Collection<String> doInSharding(Collection<String> collection, ShardingValue<Long> shardingValue) { System.err.println("运行方法: doInSharding"); Collection<String> result = new LinkedHashSet<>(collection.size()); for (Long value : shardingValue.getValues()) { for (String tableName : collection) { if (tableName.endsWith(value % 4 + "")){ result.add(tableName); } } } return result; } /** * 在 where 使用 between 作为条件分片键 */ @Override public Collection<String> doBetweenSharding(Collection<String> collection, ShardingValue<Long> shardingValue) { System.err.println("运行方法: doBetweenSharding"); Collection<String> result = new LinkedHashSet<>(collection.size()); Range<Long> range = shardingValue.getValueRange(); for (Long i = range.lowerEndpoint(); i <= range.upperEndpoint(); i++) { for (String each : collection) { if (each.endsWith( i % 4 + "")){ result.add(each); } } } return result; } }
五、测试
- 在 controller 中添加插入数据的方法。id 的生成使用 Sharding-JDBC 默认的分布式主键(基于雪花算法),当程序在多个服务器上时,需要分别为机器在系统环境变量中设置 sharing-jdbc.default.key.generator.worker.id :
@GetMapping("/add3") public String add3(){ for (int i = 0; i < 100; i++) { User user = new User(); // 不设置 ID,在 sharding.xml 配置了 Sharding-JDBC 的默认分布式主键生成,是采用雪花算法实现的。 // 在类 com.dangdang.ddframe.rdb.sharding.keygen.DefaultKeyGenerator 中 // user.setId(Long.valueOf(i)); // 随机设置城市 int random = new Random().nextInt(); if (random % 2 == 0){ user.setCity("上海"); }else { user.setCity("杭州"); } user.setName("嘉文四世"); userMapper.insert(user); } return "success"; }
- 调用方法,可以在分别的两个数据库,四张表中查看数据。其中数据总数加起来刚好 100 条。
看《Spring Cloud微服务入门、实战与进阶》