Внедрение транзакций с несколькими источниками данных

Java
Внедрение транзакций с несколькими источниками данных

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

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

На самом деле это требование для достижения распределенных транзакций, но наша сцена находится в сервисе, поэтому вы можете ее использовать.AOPДля достижения этого требования, если у вас есть несколько услуг, вам необходимо реализовать менеджер.

Выполнение

использовалspringВсем известно, что мы обычно используем@TransactionalАннотация, но эта аннотация может поддерживать только указанный источник данных в нескольких источниках данных (не указано по умолчанию).

Итак, мы создаем новую пользовательскую аннотацию:

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface MultiTransactional {

    String[] values() default "";
}

Определите еще один аспект:

/**
 * 多数据源事务
 *
 * @author 7le
 */
@Slf4j
@Aspect
@Order(-7)
@Component
public class MultiTransactionalAspect {

    @Autowired
    private ApplicationContext applicationContext;

    @Around(value = "@annotation(multiTransactional)")
    public Object around(ProceedingJoinPoint pjp, MultiTransactional multiTransactional) throws Throwable {
        Stack<DataSourceTransactionManager> dataSourceTransactionManagerStack = new Stack<>();
        Stack<TransactionStatus> transactionStatusStack = new Stack<>();
        try {
            if (!openTransaction(dataSourceTransactionManagerStack, transactionStatusStack, multiTransactional)) {
                return null;
            }
            Object ret = pjp.proceed();
            commit(dataSourceTransactionManagerStack, transactionStatusStack);
            return ret;
        } catch (Throwable e) {
            rollback(dataSourceTransactionManagerStack, transactionStatusStack);
            log.error(String.format(
                    "MultiTransactionalAspect catch exception class: %s method: %s detail:", pjp.getTarget().getClass().getSimpleName(),
                    pjp.getSignature().getName()), e);
            throw e;
        }
    }

    private boolean openTransaction(Stack<DataSourceTransactionManager> dataSourceTransactionManagerStack,
                                    Stack<TransactionStatus> transactionStatusStack, MultiTransactional multiTransactional) {
        String[] transactionMangerNames = multiTransactional.values();
        if (ArrayUtils.isEmpty(multiTransactional.values())) {
            return false;
        }
        for (String beanName : transactionMangerNames) {
            DataSourceTransactionManager dataSourceTransactionManager = (DataSourceTransactionManager) applicationContext
                    .getBean(beanName);
            TransactionStatus transactionStatus = dataSourceTransactionManager
                    .getTransaction(new DefaultTransactionDefinition());
            transactionStatusStack.push(transactionStatus);
            dataSourceTransactionManagerStack.push(dataSourceTransactionManager);
        }
        return true;
    }


    private void commit(Stack<DataSourceTransactionManager> dataSourceTransactionManagerStack,
                        Stack<TransactionStatus> transactionStatusStack) {
        while (!dataSourceTransactionManagerStack.isEmpty()) {
            dataSourceTransactionManagerStack.pop().commit(transactionStatusStack.pop());
        }
    }

    private void rollback(Stack<DataSourceTransactionManager> dataSourceTransactionManagerStack,
                          Stack<TransactionStatus> transactionStatusStack) {
        while (!dataSourceTransactionManagerStack.isEmpty()) {
            dataSourceTransactionManagerStack.pop().rollback(transactionStatusStack.pop());
        }
    }
}

Это сделано, просто добавьте нужный метод в@MultiTransactional({"xxxx","xxxxx"})

Реализованный код находится вspringcloud-gateway

Меры предосторожности

При использовании нужно обратить внимание на некоторые детали, добавить@EnableTransactionManagement.

а также@MultiTransactional({"xxxx","xxxxx"})даAOPКстати, это означает, что динамический прокси-сервер не работает следующим образом:

  • Частные, защищенные методы недействительны (Spring AOP использует JDK Dynamic Proxies или CGLIB для создания прокси для целевого объекта. Использование местных прокси Java не может прокси защищать и частные типы методов. Не рекомендуется к защищенным прокси и частным типам методов при использовании в AOP.)
  • Публичный метод в том же классе недействителен (@открывается транзакция Transactional, которая основана на интерфейсе или создается прокси класса. Поэтому в том же классе нетранзакционный метод вызывает другой транзакционный метод, который будет выполняться на эта ссылка. Вызов вместо проксирования, транзакции не будут работать.)
  • Аннотация написана на абстрактный метод родительского класса

скрытая опасность

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

Опишем это в псевдокоде, предполагая 2 источника данных

begin1
begin2
sql1
sql2
commit1
commit2

Это решение представляет собой откат исключения между SQL1 SQL2. Если commit1 будет отправлен успешно, Commit2 отправит ошибку (или тайм-аут), это вызовет несогласованность данных, хотя это так, но это также скрытая опасность.

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

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

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

Реализация распределенной транзакции

Потратив некоторое время, я реализовал промежуточное ПО для распределенных транзакций, которое можно использовать в некоторых сценариях, требующих высокой согласованности данных.shine-mqСоответствующие дизайнерские идеи есть вРаспределенные транзакции: на основе надежных служб обмена сообщениями

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

Ли Яньпэн.Справка.Архитектура распределенных сервисов: принцип, дизайн и изюминка [M].


GithubНе скупитесь на свою звезду ^.^Более захватывающе нажмите на меня