Фон

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

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

Когда дело доходит до разделения чтения/записи, Apache ShardingSphere предоставляет пользователям два типа: статический и динамический, а также многочисленные алгоритмы балансировки нагрузки.

Функции шардинга и разделения чтения/записи ShardingSphere уже очень полезны, но сценарии постоянно меняются.

В качестве примера возьмем случай с несколькими арендаторами: пользователь ожидает сегментирования данных в соответствии с арендатором, которому принадлежит учетная запись для входа, но информация об арендаторе существует не в каждом бизнес-SQL. В этом случае алгоритм извлечения полей шардинга из SQL невозможен.

Кроме того, в большинстве сценариев разделения чтения/записи пользователи хотят направлять запросы к базе данных-получателю для выполнения, но в некоторых сценариях, требующих выполнения операций в реальном времени, пользователи хотят направлять SQL для выполнения в базу данных-источник. В настоящее время разделение чтения/записи не может соответствовать бизнес-требованиям.

Учитывая вышеупомянутые болевые точки, Apache ShardingSphere создала функцию Hint, позволяющую пользователям использовать другую логику, а не SQL, для реализации принудительной маршрутизации или сегментирования.

В настоящее время ShardingSphere предоставляет пользователям два метода Hint. Один из них — это метод ручного программирования с Java API, в котором используется HintManager для принудительной маршрутизации и сегментирования. Этот метод очень удобен для приложений, написанных с помощью JDBC, потому что разработчикам не нужно писать слишком много кода, и они могут легко реализовать независимые от SQL функции сегментирования или принудительной маршрутизации.

На основе распределенного SQL (DistSQL) компания ShardingSphere разработала SQL HINT и DistSQL HINT, чтобы предоставить пользователям функции сегментирования и принудительной маршрутизации, которые можно реализовать без программирования. Этот метод более удобен для администраторов баз данных (DBA).

Далее, давайте внимательно рассмотрим два метода.

Ручное программирование на основе HintManager

ShardingSphere может реализовать функции принудительного маршрута и шардинга через HintManager объектов. С HintManager пользователи могут выполнять сегментирование данных без SQL. Это также позволяет пользователям более гибко разделять данные или принудительно выполнять маршрутизацию, что значительно расширяет пользовательские сценарии.

На данный момент с помощью HintManager пользователи могут использовать встроенные или пользовательские алгоритмы Hint ShardingSphere для реализации функции сегментирования и могут установить указанный источник данных или заставить первичную базу данных выполнять разделение чтения/записи для реализации функции принудительной маршрутизации.

Сначала я хотел бы объяснить основной принцип его реализации, чтобы помочь вам лучше понять HintManager.

  • Реализация HintManager

Приведенный ниже фрагмент кода поможет вам быстро понять принцип HintManager.

@NoArgsConstructor(access = AccessLevel.PRIVATE)
public final class HintManager implements AutoCloseable {

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

Как показано выше, ShardingSphere реализует функцию HintManager с ThreadLocal: пока они находятся в одном потоке, пользовательские настройки сегментирования сохраняются. Таким образом, пользователю нужно только вызвать соответствующие HintManager функции перед выполнением операторов SQL, после чего ShardingSphere может получить условия сегментирования или обязательной маршрутизации, установленные пользователем в текущем потоке, чтобы выполнять операции сегментирования или маршрутизации.

Далее давайте научимся им пользоваться.

  • Как использовать HitManager
  1. Используйте HINT для разделения

Чтобы использовать Hint Sharding Algorithm, пользователи должны реализовать интерфейс org.apache.shardingsphere.sharding.api.sharding.hint.HintShardingAlgorithm. Когда Apache ShardingSphere выполняет маршрутизацию, он получает значения сегментов из HintManager для операций маршрутизации.

Конфигурация следующая:

rules:
- !SHARDING
  tables:
    t_order:
      actualDataNodes: demo_ds_${0..1}.t_order_${0..1}
      databaseStrategy:
        hint:
          algorithmClassName: xxx.xxx.xxx.HintXXXAlgorithm
      tableStrategy:
        hint:
          algorithmClassName: xxx.xxx.xxx.HintXXXAlgorithm
  defaultTableStrategy:
    none:
  defaultKeyGenerateStrategy:
    type: SNOWFLAKE
    column: order_id

props:
    sql-show: true

Получите экземпляр HintManager:

HintManager hintManager = HintManager.getInstance();

Добавить ключ сегмента:

  • Используйте hintManager.addDatabaseShardingValue, чтобы добавить ключ сегмента источника данных.
  • hintManager.addTableShardingValue используется для добавления ключа осколка таблицы

Примечание. В случае сегментирования базы данных без сегментирования таблиц при использовании HINT для принудительной маршрутизации в сегмент базы данных можно использовать hintManager.setDatabaseShardingValue для добавления Shard.

Удалить ключ сегмента:

Shard Key хранится в ThreadLocal, поэтому вам нужно вызвать hintManager.close() в конце операции, чтобы очистить содержимое в ThreadLocal

Пример полного фрагмента кода выглядит следующим образом:

String sql = "SELECT * FROM t_order";
try (HintManager hintManager = HintManager.getInstance();
     Connection conn = dataSource.getConnection();
     PreparedStatement preparedStatement = conn.prepareStatement(sql)) {
    hintManager.addDatabaseShardingValue("t_order", 1);
    hintManager.addTableShardingValue("t_order", 2);
    try (ResultSet rs = preparedStatement.executeQuery()) {
        while (rs.next()) {
            // ...
        }
    }
}

String sql = "SELECT * FROM t_order";
try (HintManager hintManager = HintManager.getInstance();
     Connection conn = dataSource.getConnection();
     PreparedStatement preparedStatement = conn.prepareStatement(sql)) {
    hintManager.setDatabaseShardingValue(3);
    try (ResultSet rs = preparedStatement.executeQuery()) {
        while (rs.next()) {
            // ...
        }
    }
}

2. Используйте HINT до Force Primary Database Route

Получить HintManager

Это то же самое, что и разделение данных на основе HINT, описанное выше.

Установить основной маршрут базы данных

Используйте hintManager.setWriteRouteOnly для завершения настройки.

Очистить значение ключа сегмента

Это то же самое, что и разделение данных на основе HINT, описанное выше.

Пример полного фрагмента кода выглядит следующим образом:

String sql = "SELECT * FROM t_order";
try (HintManager hintManager = HintManager.getInstance();
     Connection conn = dataSource.getConnection();
     PreparedStatement preparedStatement = conn.prepareStatement(sql)) {
    hintManager.setWriteRouteOnly();
    try (ResultSet rs = preparedStatement.executeQuery()) {
        while (rs.next()) {
            // ...
        }
    }
}

3. Используйте HINT для реализации маршрута к указанной базе данных

Получить HintManager

Это то же самое, что и разделение данных на основе HINT, описанное выше.

Установить маршрут к указанной базе данных

Используйте hintManager.setWriteRouteOnly для установки имени базы данных.

Пример полного фрагмента кода выглядит следующим образом:

String sql = "SELECT * FROM t_order";
try (HintManager hintManager = HintManager.getInstance();
     Connection conn = dataSource.getConnection();
     PreparedStatement preparedStatement = conn.prepareStatement(sql)) {
    hintManager.setDataSourceName("ds_0");
    try (ResultSet rs = preparedStatement.executeQuery()) {
        while (rs.next()) {
            // ...
        }
    }
}

Удалить значение принудительного маршрута

Это то же самое, что и разделение данных на основе HINT, описанное выше.

После понимания метода ручного программирования, основанного на HintManager, давайте взглянем на другое HINT решение, предоставляемое ShardingSphere на основе распределенного SQL.

СОВЕТ на основе DistSQL

DistSQL HINT, предоставляемый Apache ShardingSphere, состоит из двух функций: одна называется SQL HINT и основана на аннотациях SQL, а другая — функция, которая воздействует на HintManager через реализацию DistSQL.

СОВЕТ SQL

SQL HINT — это метод HINT для реализации принудительной маршрутизации путем добавления аннотаций к операторам SQL, что снижает стоимость модификации кода для пользователей. Это означает, что на него не распространяются ограничения Java API, и он доступен как в ShardingSphere-JDBC, так и в ShardingSphere-Proxy.

Возьмем в качестве примера следующую инструкцию SQL. Даже если пользователь настроит соответствующий алгоритм сегментирования для t_order, оператор SQL будет выполняться непосредственно в базе данных ds_0 и будет возвращен результат выполнения.

/* ShardingSphere hint: dataSourceName=ds_0 */
SELECT * FROM t_order;

С помощью аннотаций мы можем легко отправить оператор SQL непосредственно в указанную базу данных для выполнения без необходимости рассмотрения другой логики сегментирования.

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

Далее я хотел бы объяснить принцип реализации SQL HINT.

  • Реализация SQL HINT

Если вы уже слышали об Apache ShardingSphere, вы должны быть знакомы с его движком SQL Parser. Первым шагом к реализации SQL HINT является извлечение аннотаций SQL.

С каналом доступа в ANTLR4 аннотацию SQL можно отправлять отдельно на определенный скрытый канал. ShardingSphere также использует эту функцию для извлечения аннотаций в скрытом канале при создании результата синтаксического анализа.

Конкретная реализация показана в следующем фрагменте кода:

  • Поток комментариев SQL в скрытый канал:
lexer grammar Comments;

import Symbol;

BLOCK_COMMENT:  '/*' .*? '*/' -> channel(HIDDEN);
INLINE_COMMENT: (('-- ' | '#') ~[\r\n]* ('\r'? '\n' | EOF) | '--' ('\r'? '\n' | EOF)) -> channel(HIDDEN);
  • Получите доступ к дереву синтаксиса и добавьте извлечение аннотации
public <T> T visit(final ParseContext parseContext) {
    ParseTreeVisitor<T> visitor = SQLVisitorFactory.newInstance(databaseType, visitorType, SQLVisitorRule.valueOf(parseContext.getParseTree().getClass()), props);
    T result = parseContext.getParseTree().accept(visitor);
    appendSQLComments(parseContext, result);
    return result;
}

private <T> void appendSQLComments(final ParseContext parseContext, final T visitResult) {
    if (!parseContext.getHiddenTokens().isEmpty() && visitResult instanceof AbstractSQLStatement) {
        Collection<CommentSegment> commentSegments = parseContext.getHiddenTokens().stream().map(each -> new CommentSegment(each.getText(), each.getStartIndex(), each.getStopIndex()))
                .collect(Collectors.toList());
        ((AbstractSQLStatement) visitResult).getCommentSegments().addAll(commentSegments);
    }
}

После извлечения информации аннотаций SQL нам необходимо выполнить соответствующую обязательную маршрутизацию на основе этой информации. Для маршрутизации обычно используется механизм маршрутизатора Apache ShardingSphere.

Мы внесли некоторые изменения для HINT в движок маршрутизатора.

public RouteContext route(final LogicSQL logicSQL, final ShardingSphereMetaData metaData) {
    RouteContext result = new RouteContext();
    Optional<String> dataSourceName = findDataSourceByHint(logicSQL.getSqlStatementContext(), metaData.getResource().getDataSources());
    if (dataSourceName.isPresent()) {
        result.getRouteUnits().add(new RouteUnit(new RouteMapper(dataSourceName.get(), dataSourceName.get()), Collections.emptyList()));
        return result;
    }
    for (Entry<ShardingSphereRule, SQLRouter> entry : routers.entrySet()) {
        if (result.getRouteUnits().isEmpty()) {
            result = entry.getValue().createRouteContext(logicSQL, metaData, entry.getKey(), props);
        } else {
            entry.getValue().decorateRouteContext(result, logicSQL, metaData, entry.getKey(), props);
        }
    }
    if (result.getRouteUnits().isEmpty() && 1 == metaData.getResource().getDataSources().size()) {
        String singleDataSourceName = metaData.getResource().getDataSources().keySet().iterator().next();
        result.getRouteUnits().add(new RouteUnit(new RouteMapper(singleDataSourceName, singleDataSourceName), Collections.emptyList()));
    }
    return result;
}

ShardingSphere сначала находит аннотации SQL, соответствующие определению, и после проверки напрямую возвращает результат маршрутизации, указанный пользователем, тем самым реализуя функцию принудительной маршрутизации.

Далее я хотел бы продемонстрировать, как использовать SQL HINT.

  • Как использовать подсказку SQL

SQL HINT легко использовать с ShardingSphere-JDBC и ShardingSphere-Proxy.

Шаг 1. Включите анализатор аннотаций и установите для параметра sqlCommentParseEnabled значение true.

Шаг 2. Добавьте комментарии SQL. В настоящее время SQL HINT поддерживает указание маршрутизации источника данных и маршрутизации первичной базы данных.

  • Маршрутизация, указанная источником данных: в настоящее время поддерживается только маршрутизация к одному источнику данных. Формат комментария пока поддерживает только /* */ и начинается с ShardingSphere hint: с именем атрибута dataSourceName.
/* ShardingSphere hint: dataSourceName=ds_0 */
SELECT * FROM t_order;
  • Маршрутизация первичной базы данных: формат комментариев пока поддерживает только /* */. Содержимое должно начинаться с ShardingSphere hint: , а имя атрибута — writeRouteOnly.
/* ShardingSphere hint: writeRouteOnly=true */
SELECT * FROM t_order;

СОВЕТ DistSQL

DistSQL также предоставляет HINT функций, позволяющих пользователям реализовывать сегментирование и принудительную маршрутизацию через ShardingSphere-Proxy.

  • Принцип реализации DistSQL ПОДСКАЗКА

Давайте сначала рассмотрим принцип реализации DistSQL Hint.

Принцип реализации DistSQL HINT очень прост: это функция HINT, реализованная с помощью операции HintManager.

В качестве примера возьмем разделение чтения/записи HINT. Когда пользователь выполняет следующий SQL-запрос с помощью ShardingSphere-Proxy, ShardingSphere фактически выполняет операции (как показано ниже) с оператором SQL:

  • Принудительное чтение-запись первичной базы данных
    set readwrite_splitting hint source = write
set readwrite_splitting hint source = write
@RequiredArgsConstructor
public final class SetReadwriteSplittingHintExecutor extends AbstractHintUpdateExecutor<SetReadwriteSplittingHintStatement> {
private final SetReadwriteSplittingHintStatement sqlStatement;
@Override
public ResponseHeader execute() {
HintSourceType sourceType = HintSourceType.typeOf(sqlStatement.getSource());
switch (sourceType) {
case AUTO:
HintManagerHolder.get().setReadwriteSplittingAuto();
break;
case WRITE:
HintManagerHolder.get().setWriteRouteOnly();
break;
default:
break;
}
return new UpdateResponseHeader(new EmptyStatement());
}
}
@NoArgsConstructor(access = AccessLevel.PRIVATE)
public final class HintManagerHolder {
private static final ThreadLocal<HintManager> HINT_MANAGER_HOLDER = new ThreadLocal<>();
/**
* Get an instance for {@code HintManager} from {@code ThreadLocal},if not exist,then create new one.
*
* @return hint manager
*/
public static HintManager get() {
if (HINT_MANAGER_HOLDER.get() == null) {
HINT_MANAGER_HOLDER.set(HintManager.getInstance());
}
return HINT_MANAGER_HOLDER.get();
}
/**
* remove {@code HintManager} from {@code ThreadLocal}.
*/
public static void remove() {
HINT_MANAGER_HOLDER.remove();
}
}

После того, как пользователь выполнит оператор SQL, механизм синтаксического анализатора DistSQL сначала определит, что оператор SQL имеет подсказку разделения чтения/записи, и извлечет поля, которые пользователь хочет автоматически направить или принудительно направить в базу данных записи.

После этого он будет использоватьSetReadwriteSplittingHintExecutorдля выполнения оператора SQL, чтобы задать правильную работу в HintManager, реализуя функцию принудительной маршрутизации первичной базы данных.

  • Как использовать DistSQL HINT

Ниже приведены соответствующие операторы DistSQL HINT.

В этом блоге подробно представлены два метода и основные принципы HINT. Как только вы разовьете базовое понимание HINT, вы сможете лучше выбрать наиболее подходящий метод.

Ссылки на проект Apache ShardingSphere:

ШардингСфера Гитхаб

ШардингСфера Твиттер

ShardingSphere Slack

Руководство автора

Автор

Чусин ЧЕНЬ

Инженер промежуточного программного обеспечения SphereEx и коммиттер Apache ShardingSphere

В настоящее время Чен в основном занимается разработкой модуля ядра Apache ShardingSphere.