1、前言
通常来讲,随着业务的发展数据库的数据量会愈来愈多,当单表数据超过上千万时执行一些查询sql语句就会遇到性能问题。一开始能够用主从复制读写分离来减轻db压力,可是后面仍是要用分库分表把数据进行水平拆分和垂直拆分。
实现分库分表目前我知道的方式有两种,第一种是使用mycat中间件实现,第二种是使用sharding-jdbc实现。相比较而言,sharding-jdbc引入一个jar包便可使用更轻量级一些,它们之间的优缺点这里也不作比较,有兴趣的能够本身搜索相关资料。
不清楚分库分表原理的能够参考这篇博客,数据库之分库分表-垂直?水平?html
2、使用当当网的sharding-jdbc分库分表
2.1新建SpringBoot项目
新建项目sharding-jdbc-first,并在pom文件添加以下内容:java
- <parent>
- <groupId>org.springframework.boot</groupId>
- <artifactId>spring-boot-starter-parent</artifactId>
- <version>1.5.16.RELEASE</version>
- <relativePath/>
- </parent>
-
- <properties>
- <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
- <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
- <java.version>1.8</java.version>
- </properties>
-
- <dependencies>
- <dependency>
- <groupId>org.springframework.boot</groupId>
- <artifactId>spring-boot-starter-data-jpa</artifactId>
- </dependency>
- <dependency>
- <groupId>org.springframework.boot</groupId>
- <artifactId>spring-boot-starter-web</artifactId>
- </dependency>
-
- <dependency>
- <groupId>mysql</groupId>
- <artifactId>mysql-connector-java</artifactId>
- <scope>runtime</scope>
- </dependency>
- <dependency>
- <groupId>org.springframework.boot</groupId>
- <artifactId>spring-boot-starter-test</artifactId>
- <scope>test</scope>
- </dependency>
- <dependency>
- <groupId>com.dangdang</groupId>
- <artifactId>sharding-jdbc-core</artifactId>
- <version>1.4.2</version>
- </dependency>
- <dependency>
- <groupId>com.alibaba</groupId>
- <artifactId>druid</artifactId>
- <version>1.0.12</version>
- </dependency>
-
- <dependency>
- <groupId>com.dangdang</groupId>
- <artifactId>sharding-jdbc-self-id-generator</artifactId>
- <version>1.4.2</version>
- </dependency>
-
-
- </dependencies>
目前好像不支持SpringBoot2.0以上的版本。node
2.2编写实体类及建库建表
目标:
db0
├── t_order_0 user_id为偶数 order_id为偶数
├── t_order_1 user_id为偶数 order_id为奇数
db1
├── t_order_0 user_id为奇数 order_id为偶数
├── t_order_1 user_id为奇数 order_id为奇数mysql
- 建立两个数据库 ds_0 和 ds_1,编码类型UTF-8。
- 每一个库分表建立两个表t_order_0和t_order_1,sql语句以下:
DROP TABLE IF EXISTS t_order_0;
CREATE TABLE t_order_0 (
order_id bigint(20) NOT NULL,
user_id bigint(20) NOT NULL,
PRIMARY KEY (order_id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin;
- 新建类Order,代码以下
- package cn.sp.bean;
-
- import javax.persistence.Entity;
- import javax.persistence.Id;
- import javax.persistence.Table;
-
-
-
-
- @Entity
- @Table(name="t_order")
- public class Order {
- @Id
- private Long orderId;
-
- private Long userId;
-
- public Long getOrderId() {
- return orderId;
- }
-
- public void setOrderId(Long orderId) {
- this.orderId = orderId;
- }
-
- public Long getUserId() {
- return userId;
- }
-
- public void setUserId(Long userId) {
- this.userId = userId;
- }
- }
-
这里须要注意 @Id注解不要导错包,以前我就遇到过这个问题。
4.配置文件application.ymlgit
- server:
- port: 8000
- spring:
- jpa:
- database: mysql
- show-sql: true
- hibernate:
-
- ddl-auto: none
- application:
- name: sharding-jdbc-first
这里要注意的是spring-data-jpa默认会本身建表,这里咱们要手动创建,因此须要将ddl-auto属性设置为none。github
2.3自定义分库分表算法
1.分库算法类须要实现SingleKeyDatabaseShardingAlgorithm<T>接口,这是一个泛型接口,T表明分库依据的字段的类型,好比咱们根据userId%2来分库,userId是Long型的,这里的T就是Long。web
- public class ModuloDatabaseShardingAlgorithm implements SingleKeyDatabaseShardingAlgorithm<Long> {
- @Override
- public String doEqualSharding(Collection<String> availableDatabaseNames, ShardingValue<Long> shardingValue) {
- for(String databaseName : availableDatabaseNames){
- if (databaseName.endsWith(shardingValue.getValue() % 2 + "")){
-
- return databaseName;
- }
- }
- throw new IllegalArgumentException();
- }
-
- @Override
- public Collection<String> doInSharding(Collection<String> availableDatabaseNames, ShardingValue<Long> shardingValue) {
- Collection<String> result = new LinkedHashSet<>(availableDatabaseNames.size());
- for(Long value : shardingValue.getValues()){
- for(String name : availableDatabaseNames){
- if (name.endsWith(value%2 + "")){
- result.add(name);
- }
- }
- }
- return result;
- }
-
- @Override
- public Collection<String> doBetweenSharding(Collection<String> availableDatabaseNames, ShardingValue<Long> shardingValue) {
- Collection<String> result = new LinkedHashSet<>(availableDatabaseNames.size());
- Range<Long> range = shardingValue.getValueRange();
- for(Long i = range.lowerEndpoint() ; i < range.upperEndpoint();i++){
- for(String each : availableDatabaseNames){
- if (each.endsWith( i % 2+"")){
- result.add(each);
- }
- }
- }
-
- return result;
- }
- }
-
2.分表算法类须要实现SingleKeyTableShardingAlgorithm<T>接口。算法
-
-
-
-
- public class ModuloTableShardingAlgorithm implements SingleKeyTableShardingAlgorithm<Long> {
-
-
-
-
-
-
-
- @Override
- public String doEqualSharding(Collection<String> tableNames, ShardingValue<Long> shardingValue) {
- for (String tableName : tableNames) {
- if (tableName.endsWith(shardingValue.getValue() % 2 + "")) {
- return tableName;
- }
- }
-
- throw new IllegalArgumentException();
- }
-
-
-
-
-
-
-
-
-
-
- @Override
- public Collection<String> doInSharding(Collection<String> tableNames, ShardingValue<Long> shardingValue) {
- Collection<String> result = new LinkedHashSet<>(tableNames.size());
- for (Long value : shardingValue.getValues()) {
- for (String table : tableNames) {
- if (table.endsWith(value % 2 + "")) {
- result.add(table);
- }
- }
- }
- return result;
- }
-
-
-
-
-
-
- @Override
- public Collection<String> doBetweenSharding(Collection<String> tableNames, ShardingValue<Long> shardingValue) {
- Collection<String> result = new LinkedHashSet<>(tableNames.size());
- Range<Long> range = shardingValue.getValueRange();
- for (Long i = range.lowerEndpoint(); i < range.upperEndpoint(); i++) {
- for (String each : tableNames) {
- if (each.endsWith(i % 2 + "")) {
- result.add(each);
- }
- }
- }
-
- return result;
- }
- }
2.4配置数据源
数据源配置类DataSourceConfigspring
- @Configuration
- public class DataSourceConfig {
- @Bean
- public IdGenerator getIdGenerator(){
- return new CommonSelfIdGenerator();
- }
-
- @Bean
- public DataSource getDataSource() {
- return buildDataSource();
- }
-
-
- private DataSource buildDataSource() {
-
- Map<String, DataSource> dataSourceMap = new HashMap<>(2);
- dataSourceMap.put("ds_0", createDataSource("ds_0"));
- dataSourceMap.put("ds_1", createDataSource("ds_1"));
-
-
-
- DataSourceRule rule = new DataSourceRule(dataSourceMap, "ds_0");
-
-
- TableRule orderTableRule = TableRule.builder("t_order")
- .actualTables(Arrays.asList("t_order_0", "t_order_1"))
- .dataSourceRule(rule)
- .build();
-
- ShardingRule shardingRule = ShardingRule.builder()
- .dataSourceRule(rule)
- .tableRules(Arrays.asList(orderTableRule))
- .databaseShardingStrategy(new DatabaseShardingStrategy("user_id", new ModuloDatabaseShardingAlgorithm()))
- .tableShardingStrategy(new TableShardingStrategy("order_id", new ModuloTableShardingAlgorithm()))
- .build();
-
- DataSource dataSource = ShardingDataSourceFactory.createDataSource(shardingRule);
- return dataSource;
- }
-
- private static DataSource createDataSource(String dataSourceName) {
-
- DruidDataSource druidDataSource = new DruidDataSource();
- druidDataSource.setDriverClassName("com.mysql.jdbc.Driver");
- druidDataSource.setUrl(String.format("jdbc:mysql://localhost:3306/%s?characterEncoding=utf-8", dataSourceName));
- druidDataSource.setUsername("root");
- druidDataSource.setPassword("1234");
- return druidDataSource;
- }
- }
这里的一些配置信息url,username,password等能够优化下,从配置文件读取。sql
2.5测试
1.新建OrderRepository
- public interface OrderRepository extends CrudRepository<Order,Long> {
-
- }
2.controller层
-
-
-
- @RestController
- @RequestMapping("/order")
- public class OrderController {
-
- @Autowired
- private OrderRepository repository;
-
- @Autowired
- private IdGenerator idGenerator;
-
- @RequestMapping("/add")
- public String add(){
- for(int i=0;i<10;i++){
- Order order = new Order();
- order.setOrderId((long) i);
- order.setUserId((long) i);
- repository.save(order);
- }
-
-
-
-
- return "success";
- }
-
- @RequestMapping("/query")
- public List<Order> queryAll(){
- List<Order> orders = (List<Order>) repository.findAll();
- return orders;
- }
- }
-
3.访问http://localhost:8080/order/add,便可在数据库ds_0,ds_1发现多了一些数据。
访问http://localhost:8080/order/query能够查询刚刚添加的订单数据。
完整代码地址:https://github.com/2YSP/sharding-jdbc-first
3、使用sharding-jdbc-spring-boot-starter分库分表
3.1引入依赖
由于个人SpringBoot是2.X版本,因此引入最新的依赖。由于目前的maven仓库(包括阿里仓库)尚未对应的jar,须要本身去github下载源代码,而后执行 mvn clean install打包到本地maven仓库。
- <dependency>
- <groupId>io.shardingsphere</groupId>
- <artifactId>sharding-jdbc-spring-boot-starter</artifactId>
- <version>3.0.0.M4</version>
- </dependency>
3.2SpringBoot配置
在application.properties文件添加以下内容:
##########分库分表配置##########
sharding.jdbc.datasource.names=ds0,ds1
## 这里使用阿里的Druid链接池
sharding.jdbc.datasource.ds0.type=com.alibaba.druid.pool.DruidDataSource
sharding.jdbc.datasource.ds0.driver-class-name=com.mysql.jdbc.Driver
sharding.jdbc.datasource.ds0.url=jdbc:mysql://localhost:3306/ds_0
sharding.jdbc.datasource.ds0.username=root
sharding.jdbc.datasource.ds0.password=1234
sharding.jdbc.datasource.ds1.type=com.alibaba.druid.pool.DruidDataSource
sharding.jdbc.datasource.ds1.driver-class-name=com.mysql.jdbc.Driver
sharding.jdbc.datasource.ds1.url=jdbc:mysql://localhost:3306/ds_1
sharding.jdbc.datasource.ds1.username=root
sharding.jdbc.datasource.ds1.password=1234
##默认的分库策略:user_id为奇数-->数据库ds_1,user_id为偶数-->数据库ds_0
sharding.jdbc.config.sharding.default-database-strategy.inline.sharding-column=user_id
sharding.jdbc.config.sharding.default-database-strategy.inline.algorithm-expression=ds$->{user_id % 2}
## 这里的t_order是逻辑表,由数据源名 + 表名组成,以小数点分隔。多个表以逗号分隔,支持inline表达式
sharding.jdbc.config.sharding.tables.t_order.actual-data-nodes=ds$->{0..1}.t_order_$->{0..1}
## 行表达式分片策略
sharding.jdbc.config.sharding.tables.t_order.table-strategy.inline.sharding-column=order_id
sharding.jdbc.config.sharding.tables.t_order.table-strategy.inline.algorithm-expression=t_order_$->{order_id % 2}
这里还能够用Java配置,Yaml配置来代替,感兴趣的话能够访问github地址了解更多,上面有对应的中文文档。
4、总结
在分库分表的时候要根据实际状况来决定根据哪一个字段来分(不必定都是主键),须要分几个库几张表。 分库分表后遇到的问题: 1.不能像之前同样使用数据库自增的主键了,会出现主键重复的问题(可使用分布式主键来代替)。 2.不支持一些关键字。 3.在作一些统计查询的时候也更加困难,那时候可能须要引入搜索引擎ES了。 4.以前觉得sharding-jdbc不支持分页操做,那天测试了下居然能够。