Если вы используете sharding-JDBC, советую посмотреть здесь, это очень важно

распределенный
Если вы используете sharding-JDBC, советую посмотреть здесь, это очень важно

Sharding-JDBC

Sharding-JDBC — это независимый продукт в ShardingSphere, позиционируемый как облегченный Java-фреймворк, предоставляющий дополнительные сервисы на уровне Java JDBC. Он использует клиент для прямого подключения к базе данных и предоставляет услуги в виде пакетов jar без дополнительного развертывания и зависимостей.Его можно понимать как расширенную версию драйвера JDBC, полностью совместимую с JDBC и различными ORM-фреймворками. ShardingSphere — это экосистема промежуточного ПО для распределенных баз данных с открытым исходным кодом, состоящая из трех независимых продуктов: Sharding-JDBC, Sharding-Proxy и Sharding-Sidecar (планируется). Все они обеспечивают стандартизированное разделение данных, распределенные транзакции и возможности управления базами данных.

ShardingSphere позиционируется как промежуточное ПО для реляционных баз данных, целью которого является полное и разумное использование вычислительных возможностей и возможностей хранения реляционных баз данных в распределенных сценариях, а не реализация новой реляционной базы данных.

ShardingSphere已经在2020年4月16日成为Apache顶级项目(Apache官方发布从4.0.0版本开始)。

Подводя итог, можно сказать, что Sharding-JBDC — это легкий встроенный компонент подтаблицы подбазы данных, включающий такие функции, как синтаксический анализ SQL, маршрутизация и слияние наборов результатов.分库分表Я не буду расширять введение здесь, вы можете проверить его, если вам интересно.

Поскольку это компонент подтаблицы подбазы данных, давайте поговорим о том, как он обрабатывает запросы на соединение, детскую обувь, которая использует или будет использовать Sharding-JDBC, обратите внимание, что这里有秘密要说,如果你的业务中存在跨分片查询场景,那么可能影响到你业务的正确性.

Совместный запрос между осколками

Существуют две существующие базы данных DB1 и DB2. В DB1 есть две таблицы t_order и t_order_item, а в DB2 есть таблица t_order. Эти две таблицы можно запросить с помощью ORDER_ID. SQL выглядит следующим образом.

 select a.name,b.age from t_order a left join t_item b on a.order_id = b.order_id 

Запрос по левому соединению, тогда результат должен соответствовать следующему рисунку

Результаты sharding-JDBC соответствуют ожиданиям.不一样, не удивлен

совместно используемая версия JDBC

    <dependency>
      <groupId>org.apache.shardingsphere</groupId>
      <artifactId>sharding-jdbc-core</artifactId>
      <version>4.0.0-RC2</version>
    </dependency>   

Стратегия разделения базы данных Sharding-JDBC

dataSources:
  ds_0: !!org.apache.commons.dbcp.BasicDataSource
    driverClassName: com.mysql.jdbc.Driver
    url: jdbc:mysql://localhost:3306/ds_0
    username: root
    password: root
  ds_1: !!org.apache.commons.dbcp.BasicDataSource
    driverClassName: com.mysql.jdbc.Driver
    url: jdbc:mysql://localhost:3306/ds_1
    username: root
    password: root

shardingRule:
  tables:
    t_order:
      actualDataNodes: ds_${0..1}.t_order
      tableStrategy:
        inline:
          shardingColumn: order_id
          algorithmExpression: t_order
      databaseStrategy:
        inline:
          shardingColumn: order_id
          algorithmExpression: ds_${order_id % 2}
    t_order_item:
      actualDataNodes: ds_0.t_order_item
      tableStrategy:
        inline:
          shardingColumn: order_id
          algorithmExpression: t_order_item
      databaseStrategy:
        inline:
          shardingColumn: order_id
          algorithmExpression: ds_0

Данные моделирования

Данные в таблице T_ORDS в DB1

INSERT INTO `ds_0`.`t_order` (`order_id`, `order_name`) VALUES ('1001', 'zhangsan');

Данные в t_order_item

INSERT INTO `ds_0`.`t_order_item` (`order_id`, `order_name`, `age`) VALUES ('1001', NULL, '10');

Данные в таблице t_order в DB2

INSERT INTO `ds_1`.`t_order` (`order_id`, `order_name`) VALUES ('1002', 'lisi');

проверить данные

Выполните следующий SQL через sharding-JDBC

    @Test
    public void testSelect() throws Exception {
        try (Connection connection = YamlConfigurationExample.getDataSource().getConnection();
             Statement ps = connection.createStatement();){
            final String sql = "select a.order_name,b.age from t_order a left join t_order_item b on a.order_id = b.order_id ";
            ResultSet rs = ps.executeQuery(sql);
            while (rs.next()) {
                System.out.println("order_name[" + rs.getString(1)
                        + "] age[" + rs.getString(2) +"]");
            }
        }
    }

Результаты

Неожиданно результаты запроса не совпали с тем, что мы ожидали.Если такая сцена действительно существует в бизнесе, она не умрет. Действительно ли есть ошибка? Не паникуйте, сегодня я предлагаю вам проанализировать sharding-JDBC с точки зрения исходного кода, чтобы найти основную причину проблемы.

анализировать проблему

Сначала проанализируйте проблему, набор результатов неверен, обычно эта проблема может появляться в двух местах:

  • Маршрутизация, результат маршрутизации может быть неправильным.Первоначально SQL отправлял DB1 и DB2, а результатом был DB1.
  • Слияние наборов результатов, слияние наборов результатов ошибочно

Во-первых, проследите исходный код с точки зрения маршрутизации и маршрутизируйте запись.

    private void shard(final String sql) {
        ShardingRuntimeContext runtimeContext = connection.getRuntimeContext();
        SimpleQueryShardingEngine shardingEngine = new SimpleQueryShardingEngine(runtimeContext.getRule(), runtimeContext.getProps(), runtimeContext.getMetaData(), runtimeContext.getDatabaseType(),runtimeContext.getParseEngine());
        sqlRouteResult = shardingEngine.shard(sql, Collections.emptyList());
    }

Эта часть включает в себя переписывание SQL, такое как перезапись имен таблиц в подтаблицах и т. д. Не по теме, продолжайте анализировать метод executeRoute.

    public SQLRouteResult shard(final String sql, final List<Object> parameters) {
        List<Object> clonedParameters = cloneParameters(parameters);
        //route
        SQLRouteResult result = executeRoute(sql, clonedParameters);
        //构建可执行单元
        result.getRouteUnits().addAll(HintManager.isDatabaseShardingOnly() ? convert(sql, clonedParameters, result) : rewriteAndConvert(sql, clonedParameters, result));
        if (shardingProperties.getValue(ShardingPropertiesConstant.SQL_SHOW)) {
            boolean showSimple = shardingProperties.getValue(ShardingPropertiesConstant.SQL_SIMPLE);
            SQLLogger.logSQL(sql, showSimple, result.getOptimizedStatement(), result.getRouteUnits());
        }
        return result;
    }

Хук регистрируется методом SPI, в случае сбоя маршрутизации пользователь может расширить внешнюю обработку. Этот фрагмент кода лично кажется очень мощным, вы можете извлечь из него уроки.

    private SQLRouteResult executeRoute(final String sql, final List<Object> clonedParameters) {
        routingHook.start(sql);
        try {
            SQLRouteResult result = route(sql, clonedParameters);
            routingHook.finishSuccess(result, metaData.getTable());
            return result;
            // CHECKSTYLE:OFF
        } catch (final Exception ex) {
            // CHECKSTYLE:ON
            routingHook.finishFailure(ex);
            throw ex;
        }
    }

Пропустите промежуточные шаги и, наконец, используйте механизм сложных запросов ComplexRoutingEngine.

    public RoutingResult route() {
        Collection<RoutingResult> result = new ArrayList<>(logicTables.size());
        Collection<String> bindingTableNames = new TreeSet<>(String.CASE_INSENSITIVE_ORDER);
        for (String each : logicTables) {
            Optional<TableRule> tableRule = shardingRule.findTableRule(each);
            if (tableRule.isPresent()) {
                if (!bindingTableNames.contains(each)) {
                    result.add(new StandardRoutingEngine(shardingRule, tableRule.get().getLogicTable(), optimizedStatement).route());
                }
                Optional<BindingTableRule> bindingTableRule = shardingRule.findBindingTableRule(each);
                if (bindingTableRule.isPresent()) {
                    bindingTableNames.addAll(Lists.transform(bindingTableRule.get().getTableRules(), new Function<TableRule, String>() {
                    <span class="hljs-meta" style="color: #4078f2; line-height: 26px;">@Override</span>
                    <span class="hljs-function" style="line-height: 26px;"><span class="hljs-keyword" style="color: #a626a4; line-height: 26px;">public</span> String <span class="hljs-title" style="color: #4078f2; line-height: 26px;">apply</span><span class="hljs-params" style="line-height: 26px;">(<span class="hljs-keyword" style="color: #a626a4; line-height: 26px;">final</span> TableRule input)</span> </span>{
                        <span class="hljs-keyword" style="color: #a626a4; line-height: 26px;">return</span> input.getLogicTable();
                    }
                }));
            }
        }
    }
    <span class="hljs-keyword" style="color: #a626a4; line-height: 26px;">if</span> (result.isEmpty()) {
        <span class="hljs-keyword" style="color: #a626a4; line-height: 26px;">throw</span> <span class="hljs-keyword" style="color: #a626a4; line-height: 26px;">new</span> ShardingException(<span class="hljs-string" style="color: #50a14f; line-height: 26px;">"Cannot find table rule and default data source with logic tables: '%s'"</span>, logicTables);
    }
    <span class="hljs-keyword" style="color: #a626a4; line-height: 26px;">if</span> (<span class="hljs-number" style="color: #986801; line-height: 26px;">1</span> == result.size()) {
        <span class="hljs-keyword" style="color: #a626a4; line-height: 26px;">return</span> result.iterator().next();
    }
    <span class="hljs-keyword" style="color: #a626a4; line-height: 26px;">return</span> <span class="hljs-keyword" style="color: #a626a4; line-height: 26px;">new</span> CartesianRoutingEngine(result).route();
}
скопировать код

Cartesian RoutingEngine, который метод принимает последним

    @Override
    public RoutingResult route() {
        RoutingResult result = new RoutingResult();
        for (Entry<String, Set<String>> entry : getDataSourceLogicTablesMap().entrySet()) {
            List<Set<String>> actualTableGroups = getActualTableGroups(entry.getKey(), entry.getValue());
            List<Set<TableUnit>> routingTableGroups = toRoutingTableGroups(entry.getKey(), actualTableGroups);
            result.getRoutingUnits().addAll(getRoutingUnits(entry.getKey(), Sets.cartesianProduct(routingTableGroups)));
        }
        return result;
    }

В основном посмотрите на метод getDataSourceLogicTablesMap, чтобы получить источник данных и таблицу.

 private Map<String, Set<String>> getDataSourceLogicTablesMap() {
        Collection<String> intersectionDataSources = getIntersectionDataSources();
        Map<String, Set<String>> result = new HashMap<>(routingResults.size());
        for (RoutingResult each : routingResults) {
            for (Entry<String, Set<String>> entry : each.getDataSourceLogicTablesMap(intersectionDataSources).entrySet()) {
                if (result.containsKey(entry.getKey())) {
                    result.get(entry.getKey()).addAll(entry.getValue());
                } else {
                    result.put(entry.getKey(), entry.getValue());
                }
            }
        }
        return result;
    }

Глядя на метод getIntersectionDataSources, видно по названию, что он берет пересечение источников данных, фронт маршрутизация по таблице, а это пересечение источников данных, поэтому проблема здесь, например, в примере Таблица SQL t_order находится в (ds_0, ds_1) t_item Таблица находится в (ds_0), тогда пересечение — ds_0, поэтому сегментирование-JDBC не разделяет SQL на каждый запрос к базе данных, а затем объединяет набор результатов.

    //取datasource交集
    private Collection<String> getIntersectionDataSources() {
        Collection<String> result = new HashSet<>();
        for (RoutingResult each : routingResults) {
            if (result.isEmpty()) {
                result.addAll(each.getDataSourceNames());
            }
            result.retainAll(each.getDataSourceNames());
        }
        return result;
    }

заключительные замечания

Я не думаю, что это недостаток дизайна. Это в основном связано с концепцией дизайна sharding-JDBC. Позиционирование sharding-JDBC представляет собой легкий встроенный компонент подтаблицы подбазы данных, который встроен в приложение, это означает, что он разделяет то же самое с приложением.Для ресурсов сервера, если вы выполняете сложный SQL, такой как упомянутый выше, вы должны разделить SQL.拆分为select * from t_order 在db1和db2中查询,select * from t_order_item在db1中查询,然后内存中合并结果集, который будет потреблять много серверов ресурсов и может перетащить приложение, поэтому Sharding-JDBC рекомендует, чтобы все запросы имели ключ Shard для уменьшения потерь.

Если в вашем приложении большое количество кросс-шардовых запросов, то вам необходимо проанализировать, подходит ли sharding-JDBC для вашего бизнеса? Нужно ли вам рассматривать Mycat и Dble (лично не рекомендую использовать Mycat, потому что вы знаете причину) или избегать его с помощью других средств, таких как глобальная таблица, таблица привязки, упомянутая в sharding-JDBC, на самом деле является ER в Таблица Mycat или Dble, широко известная как основная таблица, и другие методы, которых следует избегать.

В этой статье используетсяmdniceнабор текста