1. 程式人生 > >Sharding-JDBC資料分庫分表實踐(水平分表)

Sharding-JDBC資料分庫分表實踐(水平分表)

摘要

範圍(range)分表也需要確切(precise)分表策略,這點很重要。 確切分表根據分表字段確定資料落在哪一個庫。 範圍分表策略可以根據分表字段的上下限決定從哪些表去查詢資料。

資料庫指令碼

DROP TABLE IF EXISTS `t_order201909`;
CREATE TABLE `t_order201909`  (
  `id` bigint(20) NOT NULL AUTO_INCREMENT,
  `user_id` bigint(32) NULL DEFAULT NULL,
  `order_id` bigint(32) NULL DEFAULT NULL,
  `title` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL,
  `content` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL,
  `create_time` datetime(0) NULL DEFAULT NULL,
  `update_time` datetime(0) NULL DEFAULT NULL,
  PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 4 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci ROW_FORMAT = Dynamic;

SET FOREIGN_KEY_CHECKS = 1;

DROP TABLE IF EXISTS `t_order201910`;
CREATE TABLE `t_order201910`  (
  `id` bigint(20) NOT NULL AUTO_INCREMENT,
  `user_id` bigint(32) NULL DEFAULT NULL,
  `order_id` bigint(32) NULL DEFAULT NULL,
  `title` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL,
  `content` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL,
  `create_time` datetime(0) NULL DEFAULT NULL,
  `update_time` datetime(0) NULL DEFAULT NULL,
  PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 8 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci ROW_FORMAT = Dynamic;

SET FOREIGN_KEY_CHECKS = 1;

實踐

確切分表(Precise分表)

分表策略

package com.zero.sharding.shardingrule;

import java.time.format.DateTimeFormatter;
import java.util.Collection;
import java.util.Date;
import java.util.Locale;

import org.apache.shardingsphere.api.sharding.standard.PreciseShardingAlgorithm;
import org.apache.shardingsphere.api.sharding.standard.PreciseShardingValue;
import org.joda.time.LocalDate;

import lombok.extern.slf4j.Slf4j;

/**
 * @author Michael Feng
 * @date 2019年9月19日
 * @description
 */
@Slf4j
public class DatePreciseShardingAlgorithm implements PreciseShardingAlgorithm<Date> {
	private static DateTimeFormatter sdf = DateTimeFormatter.ofPattern("yyyyMM", Locale.CHINA);

	@Override
	public String doSharding(Collection<String> availableTargetNames, PreciseShardingValue<Date> shardingValue) {
		String loginTableName = shardingValue.getLogicTableName();
		Date createTime = shardingValue.getValue();
		String yyyyMM= "201909";
		try{
			yyyyMM = LocalDate.fromDateFields(createTime).toString("yyyyMM", Locale.CHINA);
		}catch(Exception e){
			log.error("解析建立時間異常,分表失敗,進入預設表");
		}
		return loginTableName+yyyyMM;
	}

}

Springboot配置

#資料來源
spring.shardingsphere.datasource.names=sharding0,sharding1

#預設資料來源
spring.shardingsphere.sharding.default-data-source-name=sharding0

# 顯示sql
spring.shardingsphere.props.sql.show=true

#sharding0資料來源配置
spring.shardingsphere.datasource.sharding0.type=com.alibaba.druid.pool.DruidDataSource
spring.shardingsphere.datasource.sharding0.driver-class-name=com.mysql.cj.jdbc.Driver
spring.shardingsphere.datasource.sharding0.url=jdbc:mysql://139.196.229.195:3306/sharding0?useUnicode=true&characterEncoding=utf8&autoReconnect=true&allowMultiQueries=true&serverTimezone=Asia/Shanghai&zeroDateTimeBehavior=CONVERT_TO_NULL
spring.shardingsphere.datasource.sharding0.username=root
spring.shardingsphere.datasource.sharding0.password=%Pan120%
#sharding1 資料來源配置
spring.shardingsphere.datasource.sharding1.type=com.alibaba.druid.pool.DruidDataSource
spring.shardingsphere.datasource.sharding1.driver-class-name=com.mysql.cj.jdbc.Driver
spring.shardingsphere.datasource.sharding1.url=jdbc:mysql://139.196.229.195:3306/sharding1?useUnicode=true&characterEncoding=utf8&autoReconnect=true&allowMultiQueries=true&serverTimezone=Asia/Shanghai&zeroDateTimeBehavior=CONVERT_TO_NULL
spring.shardingsphere.datasource.sharding1.username=root
spring.shardingsphere.datasource.sharding1.password=%Pan120%

# 分庫配置
spring.shardingsphere.sharding.default-database-strategy.inline.sharding-column=user_id
spring.shardingsphere.sharding.default-database-strategy.inline.algorithm-expression=sharding$->{user_id % 2}



#確切水平分表
spring.shardingsphere.sharding.tables.t_order.table-strategy.standard.sharding-column=create_time
spring.shardingsphere.sharding.tables.t_order.table-strategy.standard.precise-algorithm-class-name=com.zero.sharding.shardingrule.DatePreciseShardingAlgorithm


																										  

	

測試示例

@Test
	public void testHorizonShardingWrite()  throws ParseException{
		Order order = new Order();
		order.setUserId(0l);
//		order.setOrderId(0l);
		order.setTitle("測試,userId:"+order.getUserId() + " orderId:" + order.getOrderId());
		order.setContent(order.getTitle());
		order.setCreateTime(DateUtils.parseDate("20191018", "yyyyMMdd"));
		Assert.assertEquals(1,orderMapper.insert(order));
	}
	
	@Test
	public void testHorizonShardingRead() throws ParseException{
		OrderExample ex = new OrderExample();
		ex.createCriteria().andUserIdEqualTo(0l).andCreateTimeEqualTo(DateUtils.parseDate("20191018", "yyyyMMdd"));
		List<Order> orders = orderMapper.selectByExample(ex);
		orders.stream().forEach(o->{
			System.out.println("userId:"+o.getUserId() + " orderId:" + o.getOrderId());
		});
	}

範圍分表 (range)

範圍分表策略需要結合確切分表策略一起使用。範圍分表策略可以確定範圍檢索涉及到哪些庫表,確切分表策略可以根據分表字段確定具體入哪個表。

分表策略

DatePreciseShardingAlgorithm

package com.zero.sharding.shardingrule;

import java.time.format.DateTimeFormatter;
import java.util.Collection;
import java.util.Date;
import java.util.Locale;

import org.apache.shardingsphere.api.sharding.standard.PreciseShardingAlgorithm;
import org.apache.shardingsphere.api.sharding.standard.PreciseShardingValue;
import org.joda.time.LocalDate;

import lombok.extern.slf4j.Slf4j;

/**
 * @author Michael Feng
 * @date 2019年9月19日
 * @description
 */
@Slf4j
public class DatePreciseShardingAlgorithm implements PreciseShardingAlgorithm<Date> {
	private static DateTimeFormatter sdf = DateTimeFormatter.ofPattern("yyyyMM", Locale.CHINA);

	@Override
	public String doSharding(Collection<String> availableTargetNames, PreciseShardingValue<Date> shardingValue) {
		String loginTableName = shardingValue.getLogicTableName();
		Date createTime = shardingValue.getValue();
		String yyyyMM= "201909";
		try{
			yyyyMM = LocalDate.fromDateFields(createTime).toString("yyyyMM", Locale.CHINA);
		}catch(Exception e){
			log.error("解析建立時間異常,分表失敗,進入預設表");
		}
		return loginTableName+yyyyMM;
	}

}

DateRangeShardingAlgorithm

package com.zero.sharding.shardingrule;

import java.util.Collection;
import java.util.Date;
import java.util.Locale;
import java.util.concurrent.atomic.AtomicInteger;

import org.apache.commons.lang.time.DateUtils;
import org.apache.shardingsphere.api.sharding.standard.RangeShardingAlgorithm;
import org.apache.shardingsphere.api.sharding.standard.RangeShardingValue;
import org.joda.time.LocalDate;

import com.google.common.collect.Range;
import com.google.common.collect.Sets;

import lombok.extern.slf4j.Slf4j;
import net.minidev.json.JSONObject;

/**
 * @author Michael Feng
 * @date 2019年9月19日
 * @description
 */
@Slf4j
public class DateRangeShardingAlgorithm implements RangeShardingAlgorithm<Date> {

	@Override
	public Collection<String> doSharding(Collection<String> availableTargetNames,
			RangeShardingValue<Date> shardingValue) {
		Collection<String> tableSet = Sets.newConcurrentHashSet();
		String logicTableName = shardingValue.getLogicTableName();
		Range<Date> dates = shardingValue.getValueRange();
		Date lowDate = dates.lowerEndpoint();
		Date upperDate = dates.upperEndpoint();
		AtomicInteger i = new AtomicInteger(0);
		while(DateUtils.addDays(lowDate, i.get()).compareTo(upperDate)<=0){
			tableSet.add(logicTableName+LocalDate.fromDateFields(DateUtils.addDays(lowDate, i.getAndAdd(1))).toString("yyyyMM", Locale.CHINA)  );
		}
		return tableSet;
	}

}

Springboot配置

#資料來源
spring.shardingsphere.datasource.names=sharding0,sharding1

#預設資料來源
spring.shardingsphere.sharding.default-data-source-name=sharding0

# 顯示sql
spring.shardingsphere.props.sql.show=true

#sharding0資料來源配置
spring.shardingsphere.datasource.sharding0.type=com.alibaba.druid.pool.DruidDataSource
spring.shardingsphere.datasource.sharding0.driver-class-name=com.mysql.cj.jdbc.Driver
spring.shardingsphere.datasource.sharding0.url=jdbc:mysql://139.196.229.195:3306/sharding0?useUnicode=true&characterEncoding=utf8&autoReconnect=true&allowMultiQueries=true&serverTimezone=Asia/Shanghai&zeroDateTimeBehavior=CONVERT_TO_NULL
spring.shardingsphere.datasource.sharding0.username=root
spring.shardingsphere.datasource.sharding0.password=%Pan120%
#sharding1 資料來源配置
spring.shardingsphere.datasource.sharding1.type=com.alibaba.druid.pool.DruidDataSource
spring.shardingsphere.datasource.sharding1.driver-class-name=com.mysql.cj.jdbc.Driver
spring.shardingsphere.datasource.sharding1.url=jdbc:mysql://139.196.229.195:3306/sharding1?useUnicode=true&characterEncoding=utf8&autoReconnect=true&allowMultiQueries=true&serverTimezone=Asia/Shanghai&zeroDateTimeBehavior=CONVERT_TO_NULL
spring.shardingsphere.datasource.sharding1.username=root
spring.shardingsphere.datasource.sharding1.password=%Pan120%

# 分庫配置
spring.shardingsphere.sharding.default-database-strategy.inline.sharding-column=user_id
spring.shardingsphere.sharding.default-database-strategy.inline.algorithm-expression=sharding$->{user_id % 2}



#範圍水平分表
spring.shardingsphere.sharding.tables.t_order.table-strategy.standard.precise-algorithm-class-name=com.zero.sharding.shardingrule.DatePreciseShardingAlgorithm
spring.shardingsphere.sharding.tables.t_order.table-strategy.standard.sharding-column=create_time
spring.shardingsphere.sharding.tables.t_order.table-strategy.standard.range-algorithm-class-name=com.zero.sharding.shardingrule.DateRangeShardingAlgorithm
																										  

測試示例

@Test
	public void testHorizonShardingWrite()  throws ParseException{
		Order order = new Order();
		order.setUserId(0l);
//		order.setOrderId(0l);
		order.setTitle("測試,userId:"+order.getUserId() + " orderId:" + order.getOrderId());
		order.setContent(order.getTitle());
		order.setCreateTime(DateUtils.parseDate("20191018", "yyyyMMdd"));
		Assert.assertEquals(1,orderMapper.insert(order));
	}
	
	@Test
	public void testHorizonShardingRead() throws ParseException{
		OrderExample ex = new OrderExample();
		ex.createCriteria().andUserIdEqualTo(0l).andCreateTimeEqualTo(DateUtils.parseDate("20191018", "yyyyMMdd"));
		List<Order> orders = orderMapper.selectByExample(ex);
		orders.stream().forEach(o->{
			System.out.println("userId:"+o.getUserId() + " orderId:" + o.getOrderId());
		});
	}
	
	@Test
	public void testHorizonShardingRangeRead() throws ParseException{
		OrderExample ex = new OrderExample();
		ex.createCriteria().andUserIdEqualTo(0l).andCreateTimeBetween(DateUtils.parseDate("20190918", "yyyyMMdd"),DateUtils.parseDate("20191018", "yyyyMMdd"));
		List<Order> orders = orderMapper.selectByExample(ex);
		orders.stream().forEach(o->{
			System.out.println("userId:"+o.getUserId() + " orderId:" + o.getOrderId());
		});
	}

總結

不總結

參考文件

分庫分表