歡迎光臨
每天分享高質量文章

【死磕Sharding-jdbc】—強制路由

點選上方“Java技術驛站”,選擇“置頂公眾號”。

有內涵、有價值的文章第一時間送達!

位於 sharding-jdbc-core模組下的包 com.dangdang.ddframe.rdb.sharding.hint中,核心類HintManagerHolder的部分原始碼如下:

  1. /**

  2. * Hint manager holder.

  3. *

    Use thread-local to manage hint.

  4. * @author zhangliang

  5. */

  6. @NoArgsConstructor(access = AccessLevel.PRIVATE)

  7. public final class HintManagerHolder {

  8.    // hint特性儲存資料的核心變數,即儲存一個HintManager型別物件到ThreadLocal中

  9.    private static final ThreadLocal<HintManager> HINT_MANAGER_HOLDER = new ThreadLocal<>();

  10.    /**

  11.     * Set hint manager.

  12.     * @param hintManager hint manager instance

  13.     */

  14.    public static void setHintManager(final HintManager hintManager) {

  15.        Preconditions.checkState(null == HINT_MANAGER_HOLDER.get(), "HintManagerHolder has previous value, please clear first.");

  16.        HINT_MANAGER_HOLDER.set(hintManager);

  17.    }

  18.    public static boolean isUseShardingHint() {

  19.        // 判斷當前執行緒中是否使用了sharding hint--即HintManager中的shardingHint為true

  20.        return null != HINT_MANAGER_HOLDER.get() && HINT_MANAGER_HOLDER.get().isShardingHint();

  21.    }

  22.    public static Optional<ShardingValue>> getDatabaseShardingValue(final ShardingKey shardingKey) {

  23.        // 如果使用了sharding hint,那麼從ThreadLocal中取資料庫的sharding值

  24.        return isUseShardingHint() ? Optional.<ShardingValue>>fromNullable(HINT_MANAGER_HOLDER.get().getDatabaseShardingValue(shardingKey)) : Optional.<ShardingValue>>absent();

  25.    }

  26.    public static Optional<ShardingValue>> getTableShardingValue(final ShardingKey shardingKey) {

  27.        // 如果使用了sharding hint,那麼從ThreadLocal中取表的sharding值

  28.        return isUseShardingHint() ? Optional.<ShardingValue>>fromNullable(HINT_MANAGER_HOLDER.get().getTableShardingValue(shardingKey)) : Optional.<ShardingValue>>absent();

  29.    }

  30.    public static boolean isMasterRouteOnly() {

  31.        // 是否強制路由主庫--sharding-jdbc的特性之一:強制路由

  32.        return null != HINT_MANAGER_HOLDER.get() && HINT_MANAGER_HOLDER.get().isMasterRouteOnly();

  33.    }

  34.    public static boolean isDatabaseShardingOnly() {

  35.        // 是否只是資料庫sharding

  36.        return null != HINT_MANAGER_HOLDER.get() && HINT_MANAGER_HOLDER.get().isDatabaseShardingOnly();

  37.    }

  38.    /**

  39.     * Clear hint manager for current thread-local.

  40.     */

  41.    public static void clear() {

  42.        // ThreadLocal用完需要清理

  43.        HINT_MANAGER_HOLDER.remove();

  44.    }

  45. }

ThreadLocal中管理的HintManager定義如下:

  1. @NoArgsConstructor(access = AccessLevel.PRIVATE)

  2. public final class HintManager implements AutoCloseable {

  3.    // 資料庫強制路由的值

  4.    private final Map<ShardingKey, ShardingValue>> databaseShardingValues = new HashMap<>();

  5.    // 表強制路由的值

  6.    private final Map<ShardingKey, ShardingValue>> tableShardingValues = new HashMap<>();

  7.    // 即是否使用了強制路由特性

  8.    @Getter

  9.    private boolean shardingHint;

  10.    // 是否強制路由到主資料庫

  11.    @Getter

  12.    private boolean masterRouteOnly;

  13.    @Getter

  14.    private boolean databaseShardingOnly;

  15.    ... ...

  16.    @Override

  17.    public void close() {

  18.        HintManagerHolder.clear();

  19.    }

  20. }

sharding值儲存在ThreadLocal中,所以需要在操作結束時呼叫HintManager.close()來清除ThreadLocal中的內容。HintManager實現了AutoCloseable介面,推薦使用 trywithresource(JDK7新特性,參考Java 7中的Try-with-resources)自動關閉清理ThreadLocl執行緒中的資料。

如何使用

分析了sharding-jdbc的強制路由實現的原始碼,接下來說說如何使用這一niubility特性,假定資料源定義如下:

  1. private static ShardingDataSource getShardingDataSource() throws SQLException {

  2.    DataSourceRule dataSourceRule = new DataSourceRule(createDataSourceMap());

  3.    TableRule orderTableRule = TableRule

  4.            .builder("t_order")

  5.            .actualTables(Arrays.asList("t_order_0", "t_order_1"))

  6.            .dataSourceRule(dataSourceRule)

  7.            .build();

  8.    TableRule orderItemTableRule = TableRule

  9.            .builder("t_order_item")

  10.            .actualTables(Arrays.asList("t_order_item_0", "t_order_item_1"))

  11.            .dataSourceRule(dataSourceRule)

  12.            .build();

  13.    ShardingRule shardingRule = ShardingRule.builder()

  14.            .dataSourceRule(dataSourceRule)

  15.            .tableRules(Arrays.asList(orderTableRule, orderItemTableRule))

  16.            .bindingTableRules(Collections.singletonList(new BindingTableRule(Arrays.asList(orderTableRule, orderItemTableRule))))

  17.            .databaseShardingStrategy(new DatabaseShardingStrategy("user_id", new ModuloDatabaseShardingAlgorithm()))

  18.            .tableShardingStrategy(new TableShardingStrategy("order_id", new ModuloTableShardingAlgorithm())).build();

  19.    return new ShardingDataSource(shardingRule);

  20. }

根據資料源定義可知,資料庫的sharding column為userid,表的sharding column為orderid;

1、強制路由資料庫

  • 如何使用

  1. private static void printHintSimpleSelect(final DataSource dataSource) throws SQLException {

  2.    // SQL陳述句並不涉及任何資料庫路由和表路由資訊(即where陳述句中沒有user_id條件和order_id條件)

  3.    String sql = "SELECT i.* FROM t_order o JOIN t_order_item i ON o.order_id=i.order_id";

  4.    try (

  5.            HintManager hintManager = HintManager.getInstance();

  6.            Connection conn = dataSource.getConnection();

  7.            PreparedStatement preparedStatement = conn.prepareStatement(sql)) {

  8.        // 強制路由:資料庫路由sharding column即user_id的值為10

  9.        hintManager.addDatabaseShardingValue("t_order", "user_id", 10);

  10.        try (ResultSet rs = preparedStatement.executeQuery()) {

  11.            //todo do something

  12.        }

  13.    }

  14. }

由於指定了強制路由資料庫的值user_id=10,所以只會輸出 ds_jdbc_0這個庫中符合條件的資料。而 ds_jdbc_1會被過濾;

  • 實現原理

  1. private Collection<String> routeDataSources(final TableRule tableRule) {

  2.    // 首先得到資料庫sharding策略,例如:資料庫按照列user_id進行sharding

  3.    DatabaseShardingStrategy strategy = shardingRule.getDatabaseShardingStrategy(tableRule);

  4.    // 然後從ThreadLocal中取出sharding的值

  5.    List<ShardingValue>> shardingValues = HintManagerHolder.isUseShardingHint() ? getDatabaseShardingValuesFromHint(strategy.getShardingColumns())

  6.            : getShardingValues(strategy.getShardingColumns());

  7.    Collection<String> result = strategy.doStaticSharding(tableRule.getActualDatasourceNames(), shardingValues);

  8.    Preconditions.checkState(!result.isEmpty(), "no database route info");

  9.    return result;

  10. }

2、強制路由表

  • 如何使用

  1. private static void printHintSimpleSelect(final DataSource dataSource) throws SQLException {

  2.    // SQL陳述句並不涉及任何資料庫路由和表路由資訊(即where陳述句中沒有user_id條件和order_id條件)

  3.    String sql = "SELECT i.* FROM t_order o JOIN t_order_item i ON o.order_id=i.order_id";

  4.    try (

  5.            HintManager hintManager = HintManager.getInstance();

  6.            Connection conn = dataSource.getConnection();

  7.            PreparedStatement preparedStatement = conn.prepareStatement(sql)) {

  8.        // 強制路由:表路由sharding column即order_id的值為1000

  9.        hintManager.addTableShardingValue("t_order", "order_id", 1000);

  10.        try (ResultSet rs = preparedStatement.executeQuery()) {

  11.            //todo do something

  12.        }

  13.    }

  14. }

由於指定了強制路由表的值order_id=1000,所以只會輸出所有庫中與 t_order_0 匹配的資料。而與 t_order_1匹配的資料會被過濾;

  • 實現原理

  1. private Collection<String> routeTables(final TableRule tableRule, final String routedDataSource) {

  2.    // 首先得到表的sharding策略,例如:表按照列order_id進行sharding

  3.    TableShardingStrategy strategy = shardingRule.getTableShardingStrategy(tableRule);

  4.    // 然後從ThreadLocal中取出sharding的值

  5.    List<ShardingValue>> shardingValues = HintManagerHolder.isUseShardingHint() ? getTableShardingValuesFromHint(strategy.getShardingColumns())

  6.            : getShardingValues(strategy.getShardingColumns());

  7.    Collection<String> result = tableRule.isDynamic() ? strategy.doDynamicSharding(shardingValues) : strategy.doStaticSharding(tableRule.getActualTableNames(routedDataSource), shardingValues);

  8.    Preconditions.checkState(!result.isEmpty(), "no table route info");

  9.    return result;

  10. }

3、強制路由主庫

  • 如何使用

  1. HintManager hintManager = HintManager.getInstance();

  2. hintManager.setMasterRouteOnly();

END

贊(0)

分享創造快樂

© 2024 知識星球   網站地圖