ShardingJDBC的基本配置和使用


原創不易,如需轉載,請注明出處https://www.cnblogs.com/baixianlong/p/12644027.html,希望大家多多支持!!!

一、ShardingSphere介紹

  ShardingSphere是一套開源的分布式數據庫中間件解決方案組成的生態圈,它由Sharding-JDBC、Sharding-Proxy和Sharding-Sidecar(計划中)這3款相互獨立的產品組成。 他們均提供標准化的數據分片、分布式事務和數據庫治理功能,可適用於如Java同構、異構語言、雲原生等各種多樣化的應用場景。詳細一點的介紹直接看官網:https://shardingsphere.apache.org/document/current/cn/overview/
s1.png

  本章我們主要探討如何集成ShardingJDBC, 它使用客戶端直連數據庫,以jar包形式提供服務,無需額外部署和依賴,可理解為增強版的JDBC驅動,完全兼容JDBC和各種ORM框架。特點如下:

  • 適用於任何基於JDBC的ORM框架,如:JPA, Hibernate, Mybatis, Spring JDBC Template或直接使用JDBC。
  • 支持任何第三方的數據庫連接池,如:DBCP, C3P0, BoneCP, Druid, HikariCP等。
  • 支持任意實現JDBC規范的數據庫。目前支持MySQL,Oracle,SQLServer,PostgreSQL以及任何遵循SQL92標准的數據庫。

二、Springboot整合ShardingJDBC

方式一:基於配置文件集成,方便簡單但是不夠靈活,這種方式直接看代碼:https://github.com/xianlongbai/sharding-jdbc-boot-demo

	<!--主要有以下依賴,分庫分表策略直接在application.properties做相關配置即可-->
	<dependency>
		<groupId>io.shardingsphere</groupId>
		<artifactId>sharding-jdbc-spring-boot-starter</artifactId>
		<version>3.1.0.M1</version>
	</dependency>
	<dependency>
		<groupId>io.shardingsphere</groupId>
		<artifactId>sharding-jdbc-spring-namespace</artifactId>
		<version>3.1.0.M1</version>
	</dependency>

方式二:這里我們主要基於java config的方式來集成到springboot中,更適合學習和理解。直接上代碼:

//相關依賴
<dependency>
	<groupId>io.shardingsphere</groupId>
	<artifactId>sharding-jdbc-core</artifactId>
	<version>3.1.0</version>
</dependency>
<!--<dependency>
	<groupId>io.shardingsphere</groupId>
	<artifactId>sharding-transaction-2pc-xa</artifactId>
	<version>3.1.0</version>
</dependency>-->
<dependency>
	<groupId>io.shardingsphere</groupId>
	<artifactId>sharding-jdbc-orchestration</artifactId>
	<version>3.1.0</version>
</dependency>
<dependency>
	<groupId>io.shardingsphere</groupId>
	<artifactId>sharding-orchestration-reg-zookeeper-curator</artifactId>
	<version>3.1.0</version>
</dependency>

//數據源、分庫分表總體配置
@Configuration
@MapperScan(basePackages = "com.bxl.dao.shardingDao", sqlSessionTemplateRef  = "shardingSqlSessionTemplate")
public class ShardingDataSourceConfig {

private static final Logger logger = LoggerFactory.getLogger(ShardingDataSourceConfig.class);

//這里直接注入你項目中配置的數據源
@Resource
private DataSource dataSourceOne;
@Resource
private DataSource dataSourceTwo;

//注釋掉的先不用看,后邊會介紹
@Bean(name = "shardingDataSource")
public DataSource dataSource() throws SQLException {

    TransactionTypeHolder.set(TransactionType.LOCAL);
    //1、指定需要分庫分表的數據源
    Map<String, DataSource> dataSourceMap = new HashMap<>();
    dataSourceMap.put("ds0", dataSourceOne);
    dataSourceMap.put("ds1",dataSourceTwo);
    //2、分庫分表配置
    ShardingRuleConfiguration shardingRuleConfig = new ShardingRuleConfiguration();
    //2.1、配置默認自增主鍵生成器
    shardingRuleConfig.setDefaultKeyGenerator(new DefaultKeyGenerator());
    //2.2、配置各個表的分庫分表策略,這里只配了一張表的就是t_order
    shardingRuleConfig.getTableRuleConfigs().add(getOrderTableRuleConfiguration());
    //2.3、配置綁定表規則列表,級聯綁定表代表一組表,這組表的邏輯表與實際表之間的映射關系是相同的
//        shardingRuleConfig.getBindingTableGroups().add("t_order","t_order_item");
    //2.4、配置廣播表規則列表,利用廣播小表提高性能
//        shardingRuleConfig.getBroadcastTables().add("t_config");
    //2.5、配置默認分表規則
    shardingRuleConfig.setDefaultTableShardingStrategyConfig(new NoneShardingStrategyConfiguration());
    //2.6、配置默認分庫規則(不配置分庫規則,則只采用分表規則)
    shardingRuleConfig.setDefaultDatabaseShardingStrategyConfig(new NoneShardingStrategyConfiguration());
    //2.7、配置默認數據源
    shardingRuleConfig.setDefaultDataSourceName("ds0");
    //2.8、配置讀寫分離規則
//        shardingRuleConfig.setMasterSlaveRuleConfigs();

    //3、屬性配置項,可以為以下屬性
    Properties propertie = new Properties();
    //是否打印SQL解析和改寫日志
    propertie.setProperty("sql.show",Boolean.TRUE.toString());
    //用於SQL執行的工作線程數量,為零則表示無限制
    propertie.setProperty("executor.size","4");
    //每個物理數據庫為每次查詢分配的最大連接數量
    propertie.setProperty("max.connections.size.per.query","1");
    //是否在啟動時檢查分表元數據一致性
    propertie.setProperty("check.table.metadata.enabled","false");

    //4、用戶自定義屬性
    Map<String, Object> configMap = new HashMap<>();
    configMap.put("effect","分庫分表");
    DataSource dataSource = ShardingDataSourceFactory.createDataSource(dataSourceMap, shardingRuleConfig,configMap,propertie);

    //5、數據治理
    //5.1、配置注冊中心
//        RegistryCenterConfiguration regConfig = new RegistryCenterConfiguration();
//        regConfig.setServerLists("localhost:2181");
//        regConfig.setNamespace("sharding-sphere-orchestration");
    //regConfig.setDigest("data-centre");
    //5.2、配置數據治理
//        OrchestrationConfiguration orchConfig = new OrchestrationConfiguration("orchestration-sharding-data-source", regConfig, true);
    //5.3、獲取數據源對象
//        DataSource dataSource = OrchestrationShardingDataSourceFactory.createDataSource(dataSourceMap, shardingRuleConfig, configMap, propertie, orchConfig);
    return dataSource;
}

@Bean(name = "shardingSqlSessionFactory")
public SqlSessionFactory sqlSessionFactorySharding(@Qualifier("shardingDataSource") DataSource dataSource) throws Exception {
    SqlSessionFactoryBean bean = new SqlSessionFactoryBean();
    bean.setDataSource(dataSource);
    bean.setConfigLocation(new PathMatchingResourcePatternResolver().getResource("classpath:mybatis/mybatis-config.xml"));
    bean.setMapperLocations(new PathMatchingResourcePatternResolver().getResources("classpath:mybatis/mapper/shardingMapper/**/*.xml"));
    return bean.getObject();
}

//本地事務
@Bean(name = "shardingTransactionManagerLOCAL")
public PlatformTransactionManager transactionManagerLocal(@Qualifier("shardingDataSource") DataSource dataSource) {
    return new DataSourceTransactionManager(dataSource);
}

//XA事務
//    @Bean(name = "shardingTransactionManagerXA")
//    public ShardingTransactionManager transactionManagerXA(@Qualifier("shardingDataSource") DataSource dataSource) {
//        return new AtomikosTransactionManager();
//    }

@Bean(name = "shardingSqlSessionTemplate")
public SqlSessionTemplate sqlSessionTemplateThree(@Qualifier("shardingSqlSessionFactory") SqlSessionFactory sqlSessionFactory) throws Exception {
    return new SqlSessionTemplate(sqlSessionFactory);
}


/**
 * Sharding提供了5種分片策略:
 *      StandardShardingStrategyConfiguration:標准分片策略, 提供對SQL語句中的=, IN和BETWEEN AND的分片操作支持
 *      ComplexShardingStrategyConfiguration:復合分片策略, 提供對SQL語句中的=, IN和BETWEEN AND的分片操作支持。
 *      InlineShardingStrategyConfiguration:Inline表達式分片策略, 使用Groovy的Inline表達式,提供對SQL語句中的=和IN的分片操作支持
 *      HintShardingStrategyConfiguration:通過Hint而非SQL解析的方式分片的策略
 *      NoneShardingStrategyConfiguration:不分片的策略
 * Sharding提供了以下4種算法接口:
 *      PreciseShardingAlgorithm
 *      RangeShardingAlgorithm
 *      HintShardingAlgorithm
 *      ComplexKeysShardingAlgorithm
 * @return
 */
TableRuleConfiguration getOrderTableRuleConfiguration() {
    TableRuleConfiguration result = new TableRuleConfiguration();
    //1、指定邏輯索引(oracle/PostgreSQL需要配置)
//        result.setLogicIndex("order_id");
    //2、指定邏輯表名
    result.setLogicTable("t_order");
    //3、指定映射的實際表名
    result.setActualDataNodes("ds${0..1}.t_order_${0..1}");
    //4、配置分庫策略,缺省表示使用默認分庫策略
    result.setDatabaseShardingStrategyConfig(new InlineShardingStrategyConfiguration("user_id","ds${user_id % 2}"));
    //result.setDatabaseShardingStrategyConfig(new HintShardingStrategyConfiguration(new OrderDataBaseHintShardingAlgorithm()));
    //5、配置分表策略,缺省表示使用默認分表策略
    result.setTableShardingStrategyConfig(new InlineShardingStrategyConfiguration("order_id", "t_order_${order_id % 2}"));
    //result.setTableShardingStrategyConfig(new StandardShardingStrategyConfiguration("order_id",new orderPreciseShardingAlgorithm(),new orderRangeShardingAlgorithm()));
    //result.setTableShardingStrategyConfig(new ComplexShardingStrategyConfiguration("order_id,user_id",new orderComplexKeysShardingAlgorithm()));
    //result.setTableShardingStrategyConfig(new HintShardingStrategyConfiguration(new OrderTableHintShardingAlgorithm()));
    //6、指定自增字段以及key的生成方式
    result.setKeyGeneratorColumnName("order_id");
    result.setKeyGenerator(new DefaultKeyGenerator());
    return result;
}


//PreciseShardingAlgorithm接口實現(用於處理 = 和 in 的路由)
class orderPreciseShardingAlgorithm implements PreciseShardingAlgorithm<Long> {
    @Override
    public String doSharding(Collection<String> collection, PreciseShardingValue<Long> preciseShardingValue) {
        logger.info("collection:" + JSON.toJSONString(collection) + ",preciseShardingValue:" + JSON.toJSONString(preciseShardingValue));
        for (String name : collection) {
            if (name.endsWith(preciseShardingValue.getValue() % collection.size() + "")) {
                logger.info("return name:"+name);
                return name;
            }
        }
        return null;
    }
}


//RangeShardingAlgorithm接口實現(用於處理BETWEEN AND分片),這里的核心是找出這個范圍的數據分布在那些表(庫)中
class orderRangeShardingAlgorithm implements RangeShardingAlgorithm<Long> {
    @Override
    public Collection<String> doSharding(Collection<String> collection, RangeShardingValue<Long> rangeShardingValue) {
        logger.info("Range collection:" + JSON.toJSONString(collection) + ",rangeShardingValue:" + JSON.toJSONString(rangeShardingValue));
        Collection<String> collect = new ArrayList<>();
        Range<Long> valueRange = rangeShardingValue.getValueRange();
        for (Long i = valueRange.lowerEndpoint(); i <= valueRange.upperEndpoint(); i++) {
            for (String each : collection) {
                if (each.endsWith(i % collection.size() + "")) {
                    collect.add(each);
                }
            }
        }
        return collect;
    }
}


//ComplexShardingStrategy支持多分片鍵
class orderComplexKeysShardingAlgorithm implements ComplexKeysShardingAlgorithm{
    @Override
    public Collection<String> doSharding(Collection<String> collection, Collection<ShardingValue> shardingValues) {
        logger.info("collection:" + JSON.toJSONString(collection) + ",shardingValues:" + JSON.toJSONString(shardingValues));
        Collection<Long> orderIdValues = getShardingValue(shardingValues, "order_id");
        Collection<Long> userIdValues = getShardingValue(shardingValues, "user_id");
        List<String> shardingSuffix = new ArrayList<>();
        /**例如:根據 user_id + order_id 雙分片鍵來進行分表*/
        //Set<List<Integer>> valueResult = Sets.cartesianProduct(userIdValues, orderIdValues);
        for (Long userIdVal : userIdValues) {
            for (Long orderIdVal : orderIdValues) {
                String suffix = userIdVal % 2 + "_" + orderIdVal % 2;
                collection.forEach(x -> {
                    if (x.endsWith(suffix)) {
                        shardingSuffix.add(x);
                    }
                });
            }
        }
        return shardingSuffix;
    }

    private Collection<Long> getShardingValue(Collection<ShardingValue> shardingValues, final String key) {
        Collection<Long> valueSet = new ArrayList<>();
        Iterator<ShardingValue> iterator = shardingValues.iterator();
        while (iterator.hasNext()) {
            ShardingValue next = iterator.next();
            if (next instanceof ListShardingValue) {
                ListShardingValue value = (ListShardingValue) next;
                /**例如:根據user_id + order_id 雙分片鍵來進行分表*/
                if (value.getColumnName().equals(key)) {
                    return value.getValues();
                }
            }
        }
        return valueSet;
    }
}

//表的強制分片策略
class OrderTableHintShardingAlgorithm implements HintShardingAlgorithm {
    @Override
    public Collection<String> doSharding(Collection<String> collection, ShardingValue shardingValue) {
        logger.info("collection:" + JSON.toJSONString(collection) + ",shardingValues:" + JSON.toJSONString(shardingValue));
        Collection<String> result = new ArrayList<>();
        String logicTableName = shardingValue.getLogicTableName();
        ListShardingValue<Integer> listShardingValue = (ListShardingValue<Integer>) shardingValue;
        List<Integer> list = Lists.newArrayList(listShardingValue.getValues());
        String res = logicTableName + "_" + list.get(0);
        result.add(res);
        return result;
    }
}

//庫的強制分片策略
class OrderDataBaseHintShardingAlgorithm implements HintShardingAlgorithm {
    @Override
    public Collection<String> doSharding(Collection<String> collection, ShardingValue shardingValue) {
        logger.info("collection:" + JSON.toJSONString(collection) + ",shardingValues:" + JSON.toJSONString(shardingValue));
        Collection<String> result = new ArrayList<>();
        ListShardingValue<Integer> listShardingValue = (ListShardingValue<Integer>) shardingValue;
        List<Integer> list = Lists.newArrayList(listShardingValue.getValues());
        for (String db : collection) {
            if (db.endsWith(list.get(0).toString())) {
                result.add(db);
            }
        }
        return result;
    }
}

//分布式唯一主鍵
@Bean("myKeyGenerator")
public KeyGenerator keyGenerator() {
    return new DefaultKeyGenerator();
}

}

以上就是springboot基於mybatis整合ShardingJdbc的基本配置,其中就是關於t_order表的分庫分表最簡單的一個配置,使用和平常一樣。關於mybatis的配置就不多說了,我簡單粘一下我的dao層和xml。只需要注意一點就是我們在寫sql的時候,表明使用的是邏輯表明,像這里就是使用的t_order,而實際在我的庫里存的是t_order_0,t_order_1

//編寫我們的dao層接口,具體對應的對應的sql寫在xml中
@Repository
public interface OrderDao {
    void saveToOne(Order order);
    List<Order> findAllData();
    List<Order> findByIds(List<Long> list);
    List<Order> findByBetween(Map<String, Object> map);
    List<Order> findByOrderIdAndUserId(Map<String, Object> param);
	void updateByOrderIds(Map<String, Object> map);
	void deleteByOrderIds(long[] longs);
}

//對應的OrderMapper.xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.bxl.dao.shardingDao.OrderDao" >

    <insert id="saveToOne" parameterType="com.bxl.entity.Order" useGeneratedKeys="true" keyProperty="orderId">
        insert into t_order(`order_id`, `user_id`)
        values (#{orderId}, #{userId})
    </insert>

    <select id="findAllData" resultType="com.bxl.entity.Order">
        SELECT
        `order_id` orderId,
        `user_id` userId
        FROM t_order
    </select>

    <select id="findByIds" resultType="com.bxl.entity.Order" parameterType="java.lang.Long">
        SELECT
        `order_id` orderId,
        `user_id` userId
        FROM t_order where order_id in 
        <foreach collection="list" item="item" index="index" open="(" separator="," close=")">
            #{item}
        </foreach>
    </select>

    <select id="findByBetween" resultType="com.bxl.entity.Order" parameterType="java.util.Map">
      SELECT
        `order_id` orderId,
        `user_id` userId
        FROM t_order where order_id BETWEEN #{start} AND #{end}
    </select>
    
    <select id="findByOrderIdAndUserId" resultType="com.bxl.entity.Order" parameterType="java.util.Map">
      SELECT
        `order_id` orderId,
        `user_id` userId
        FROM t_order where order_id = #{orderId} and user_id = #{userId}
    </select>

    <update id="updateByOrderIds" parameterType="java.util.Map">
        update t_order set user_id=#{userId} where order_id in
        <foreach collection="orderIds" item="item" index="index" open="(" separator="," close=")" >
            #{item}
        </foreach>
    </update>

    <delete id="deleteByOrderIds" parameterType="java.lang.Long">
        delete from t_order where order_id in
        <foreach collection="array" item="item" index="index" open="(" separator="," close=")" >
            #{item}
        </foreach>
    </delete>

</mapper>

//建表語句
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

這個簡單的樣例我們是基於InlineShardingStrategyConfiguration分片策略來做的,它可以通過使用Groovy的Inline表達式來配置自己的分片策略,提供對SQL語句中的=和IN的分片操作支持,但不支持between,但可以轉化為>、<來完成類似的查詢。

三、ShardingJDBC提供了5種分片策略及分片算法

  分片策略包含分片鍵和分片算法,由於分片算法的獨立性,將其獨立抽離。真正可用於分片操作的是分片鍵 + 分片算法,也就是分片策略。目前提供5種分片策略。

  • StandardShardingStrategyConfiguration:標准分片策略, 提供對SQL語句中的=, IN和BETWEEN AND的分片操作支持
  • ComplexShardingStrategyConfiguration:復合分片策略, 提供對SQL語句中的=, IN和BETWEEN AND的分片操作支持。
  • InlineShardingStrategyConfiguration:Inline表達式分片策略, 使用Groovy的Inline表達式,提供對SQL語句中的=和IN的分片操作支持
  • HintShardingStrategyConfiguration:通過Hint而非SQL解析的方式分片的策略
  • NoneShardingStrategyConfiguration:不分片的策略

  分片鍵用於分片的數據庫字段,是將數據庫(表)水平拆分的關鍵字段。例:將訂單表中的訂單主鍵的尾數取模分片,則訂單主鍵為分片字段。 SQL中如果無分片字段,將執行全路由,性能較差。 除了對單分片字段的支持,ShardingSphere也支持根據多個字段進行分片。

  分片算法支持通過=、>=、<=、>、<、BETWEEN和IN分片。分片算法需要應用方開發者自行實現,可實現的靈活度非常高。目前提供4種分片算法。由於分片算法和業務實現緊密相關,因此並未提供內置分片算法,而是通過分片策略將各種場景提煉出來,提供更高層級的抽象,並提供接口讓應用開發者自行實現分片算法。四種算法接口如下:

  • PreciseShardingAlgorithm:精確分片算法,用於處理使用單一鍵作為分片鍵的=與IN進行分片的場景。需要配合StandardShardingStrategy使用。
  • RangeShardingAlgorithm:范圍分片算法,用於處理使用單一鍵作為分片鍵的BETWEEN AND、>、<、>=、<=進行分片的場景。需要配合StandardShardingStrategy使用。
  • HintShardingAlgorithm:Hint分片算法,用於處理使用Hint行分片的場景。需要配合HintShardingStrategy使用。
  • ComplexKeysShardingAlgorithm:復合分片算法,用於處理使用多鍵作為分片鍵進行分片的場景,包含多個分片鍵的邏輯較復雜,需要應用開發者自行處理其中的復雜度。需要配合ComplexShardingStrategy使用。

具體的使用特點及注意事項:

  1. 使用Inline表達式分片策略,使用相對簡單的Groovy表達式就可以實現分片功能,但適合一些分片簡單的場景,而且這種方式不支持between...and...的查詢方式。

  2. 使用標准分片策略(常用方式),需要我們自己實現兩個分片算法,即PreciseShardingAlgorithm和RangeShardingAlgorithm,這種方式就比較靈活多了,而且支持between...and...的查詢方式。

  3. 使用復合分片策略,需要自己實現ComplexKeysShardingAlgorithm復合分片算法,這種方式一般用於處理使用多鍵作為分片鍵進行分片的場景。

  4. 通過強制(Hint)分片的策略,主要是為了應對分片字段不存在SQL中、數據庫表結構中,而存在於外部業務邏輯,或者是為了強制在主庫進行某些數據操作。這塊的代碼編寫有點特殊:

    //首先配置實現自己的強制分片算法,包括庫和表,具體看上邊代碼
    result.setDatabaseShardingStrategyConfig(new HintShardingStrategyConfiguration(new OrderDataBaseHintShardingAlgorithm()));
    result.setTableShardingStrategyConfig(new HintShardingStrategyConfiguration(new OrderTableHintShardingAlgorithm()));
    
    //業務層代碼,其中分片的值可以不是當前表的字段,例如可以是系統緩存的用戶id等等
    public void addservice() {
        //基於強制路由算法進行分庫分表插入數據
        for (int i = 0; i < 100; i++) {
    		//生成分布式主鍵,這里利用的就是snowflake算法生成
    		//order.setOrderId(KeyGenerator.generateKey().longValue());
            Order order = new Order();
            order.setOrderId((long)i+1);
            int number = new Random().nextInt(500) + 1;
            order.setUserId((long) number);
            try (HintManager hintManager = HintManager.getInstance()) {
                //添加數據源分片鍵值
            	hintManager.addDatabaseShardingValue("t_order", 1);
                //添加表分片鍵值
                hintManager.addTableShardingValue("t_order", 1);
                orderDao.saveToOne(order);
            } catch (Exception e){
                System.out.println("插入數據發生異常!!!");
            }
        }
    }
    
    //查詢業務
    public List<Order> findAllDataByHint() {
        List<Order> res = new ArrayList<>();
        try (HintManager hintManager = HintManager.getInstance()) {
            //分庫不分表情況下,強制路由至某一個分庫時,可使用hintManager.setDatabaseShardingValue方式添加分片。通過此方式添加分片鍵值后,將跳過SQL解析和改寫階段,從而提高整體執行效率。
            //hintManager.setDatabaseShardingValue(1);
    		hintManager.addDatabaseShardingValue("t_order", 1);
            hintManager.addTableShardingValue("t_order", 1);
            //使用hintManager.setMasterRouteOnly設置主庫路由,強制讀主庫
            //在配置了主從庫時,那么我們的查詢邏輯會落到從庫上,但有些場景必須查主庫,這是就需要強制走主庫查詢
            hintManager.setMasterRouteOnly();
            res = orderDao.findAllData();
        }
        return res;
    }
    
    //刪除業務
    public void deleteData() {
        //強制路由下的刪除操作
        try (HintManager hintManager = HintManager.getInstance()) {
            hintManager.addDatabaseShardingValue("t_order", 0);
            hintManager.addTableShardingValue("t_order", 1);
            orderDao.deleteAll();
        }
    }
    

    注意:ShardingJdbc使用ThreadLocal管理分片鍵值進行Hint強制路由。可以通過編程的方式向HintManager中添加分片值,該分片值僅在當前線程內生效,所以需要在操作結束時調用hintManager.close()來清除ThreadLocal中的內容。由於hintManager實現了AutoCloseable接口,推薦使用try with resource自動關閉。

四、綁定表和廣播表

1、綁定表

配置方式:

//在配置分庫分表策略中添加如下配置,就表明將t_order和t_order_item進行了綁定
shardingRuleConfig.getBindingTableGroups().add("t_order", "t_order_item");

  指分片規則一致的主表和子表。例如:t_order表和t_order_item表,均按照order_id分片,則此兩張表互為綁定表關系。綁定表之間的多表關聯查詢不會出現笛卡爾積關聯,關聯查詢效率將大大提升。舉例說明,如果SQL為:

SELECT i.* FROM t_order o JOIN t_order_item i ON o.order_id=i.order_id WHERE o.order_id in (10, 11);

在不配置綁定表關系時,假設分片鍵order_id將數值10路由至第0片,將數值11路由至第1片,那么路由后的SQL應該為4條,它們呈現為笛卡爾積:

SELECT i.* FROM t_order_0 o JOIN t_order_item_0 i ON o.order_id=i.order_id WHERE o.order_id in (10, 11);
SELECT i.* FROM t_order_0 o JOIN t_order_item_1 i ON o.order_id=i.order_id WHERE o.order_id in (10, 11);
SELECT i.* FROM t_order_1 o JOIN t_order_item_0 i ON o.order_id=i.order_id WHERE o.order_id in (10, 11);
SELECT i.* FROM t_order_1 o JOIN t_order_item_1 i ON o.order_id=i.order_id WHERE o.order_id in (10, 11);

在配置綁定表關系后,路由的SQL應該為2條:

SELECT i.* FROM t_order_0 o JOIN t_order_item_0 i ON o.order_id=i.order_id WHERE o.order_id in (10, 11);
SELECT i.* FROM t_order_1 o JOIN t_order_item_1 i ON o.order_id=i.order_id WHERE o.order_id in (10, 11);

其中t_order在FROM的最左側,ShardingSphere將會以它作為整個綁定表的主表。 所有路由計算將會只使用主表的策略,那么t_order_item表的分片計算將會使用t_order的條件。故綁定表之間的分區鍵要完全相同。

2、廣播表

  指所有的分片數據源中都存在的表,表結構和表中的數據在每個數據庫中均完全一致。適用於數據量不大且需要與海量數據的表進行關聯查詢的場景,例如:字典表。

//配置如下;
shardingRuleConfig.getBroadcastTables().add("t_config");

五、分布式主鍵

既然涉及到分庫分表,那么如何生成一個分布式主鍵,這里提供幾種方式供大家參考:

  1. 采用UUID.randomUUID()的方式產生分布式主鍵。
  2. 使用雪花算法(snowflake)生成64bit的長整型數據,雪花算法是由Twitter公布的分布式主鍵生成算法,它能夠保證不同進程主鍵的不重復性,以及相同進程主鍵的有序性。shardingjdbc默認支持snowflake。
  3. 可以采用一個集中式ID生成器,它可以是Redis,也可以是ZooKeeper,甚至是DB,缺點是復雜性太高,需要嚴重依賴第三方服務。

六、使用規范

  • 路由至單數據節點:100%全兼容(目前僅MySQL,其他數據庫完善中)。

  • 路由至多數據節點:

    1. 全面支持DML、DDL、DCL、TCL和部分DAL。支持分頁、去重、排序、分組、聚合、關聯查詢(不支持跨庫關聯)。
    2. 不支持CASE WHEN、HAVING、UNION (ALL),有限支持子查詢。
    3. 運算表達式和函數中的分片鍵會導致全路由。
      由於ShardingSphere只能通過SQL字面提取用於分片的值,因此當分片鍵處於運算表達式或函數中時,ShardingSphere無法提前獲取分片鍵位於數據庫中的值,從而無法計算出真正的分片值。當出現此類分片鍵處於運算表達式或函數中的SQL時,ShardingSphere將采用全路由的形式獲取結果。
    4. 查詢偏移量過大的分頁會導致數據庫獲取數據性能低下,如(mysql):SELECT * FROM t_order ORDER BY id LIMIT 1000000, 10

七、關於分庫分表的思考

1、分庫分表為了什么?

分庫分表就是為了解決由於數據量過大而導致數據庫性能(IO瓶頸,CPU瓶頸)降低的問題,將原來獨立的數據庫拆分成若干數據庫組成 ,將數據大表拆分成若干數據表組成,使得單一數據庫、單一數據表的數據量變小,從而達到提升數據庫性能的目的。

2、數據庫的分區、垂直分庫、垂直分表、水平分庫、水平分表各自解決那些問題?

  • 分區表

    • 分區表的分區方式有range、list、hash、key四種方式,但常用的是range、list方式
    • 分區表可以單獨對分區數據進行操作,在特定的場景下,方便對數據的老化和查詢
    • 分區表可以提高單表的存儲,並且數據還可以分布在不同的物理設備上
  • 垂直分庫

    • 解決業務層面的耦合,業務清晰
    • 能對不同業務的數據進行分級管理、維護、監控、擴展等
    • 高並發場景下,垂直分庫一定程度的提升IO、數據庫連接數、降低單機硬件資源的瓶頸
  • 垂直分表(將一張表拆成多張表)

    • 為了避免IO爭搶並減少鎖表的幾率
    • 充分發揮熱數據的操作效率
    • 可以把不常用的字段單獨放在一張表,如一些詳細信息以及text,blob等大字段
  • 水平分庫

    • 解決了單庫大數據,高並發的性能瓶頸
    • 提高了系統的穩定性及可用性
  • 水平分表

    • 優化單一表數據量過大而產生的性能問題
    • 避免IO爭搶並減少鎖表的幾率

3、分庫分表的策略一般在什么情況下使用,使用哪種?

  • 首先一般來說,在系統設計階段就應該根據業務耦合松緊來確定垂直分庫、垂直分表的方案。
  • 當數據量隨着業務涼的提升不斷增大,但訪問壓力還不是特別大的情況下,我們首先考慮緩存、讀寫分離、索引等技術方案。
  • 當數據量增長到特別大且持續增長時,即將或者已經出現性能瓶頸時,再考慮水平分庫水平分表的方案。

4、水平分庫表如何解決擴容、熱點問題?

  • 最好的方式莫過於設計前期對數據量的正確預估和業務場景的判斷,盡量避免后期出現熱點問題和擴容問題。
  • 對於通過hash取模方案,沒有熱點問題,但會出現擴容問題,解決方案有:
    • 停服遷移
    • 升級從庫
    • 雙寫遷移
  • 對於通過range方案,無需遷移數據,但肯能出現熱點問題。所以在實際業務中,我們可以通過range+hash的配合使用來達到即支持擴容又盡可能避免熱點。

5、分庫分表如何解決跨庫事務?

  • 2PC兩階段提交協議
  • TCC事務補償機制
  • 最終一致性方案
  • 最大努力通知型

個人博客地址:

cnblogs:https://www.cnblogs.com/baixianlong
github:https://github.com/xianlongbai


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM