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набор текста