Транзакционный Транзакционный в Spring, это действительно правильно?

Java задняя часть

Это 16-й день моего участия в ноябрьском испытании обновлений.Подробности о событии:Вызов последнего обновления 2021 г.

Управление транзакциями в Spring очень простое, достаточно добавить аннотации к методу.@Transactional, Spring может автоматически помочь нам открывать, фиксировать и откатывать транзакции. Даже многие люди уже связали транзакции Spring с@TransactionalЗнак равенства помечен, пока есть операции, связанные с базой данных, напрямую добавьте метод в метод@Transactionalаннотация.

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

@Transactionalпроизводственная авария

В 2019 году я делал внутренний реимбурсный проект в компании, там такая бизнес-логика:

1. Сотрудники, работающие сверхурочно, могут брать такси напрямую через Didi Chuxing Enterprise Edition, а оплата такси на следующий день может быть напрямую синхронизирована с нашей платформой возмещения расходов.

2. Сотрудники могут проверить свои собственные расходы на такси на платформе возмещения и создать форму возмещения для возмещения.При создании формы возмещения будет создан поток утверждения (унифицированная платформа процессов), чтобы руководитель мог утвердить

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

 /**
 * 保存报销单并创建工作流
 */
@Transactional(rollbackFor = Exception.class)
public void save(RequestBillDTO requestBillDTO){
     //调用流程HTTP接口创建工作流
    workflowUtil.createFlow("BILL",requestBillDTO);
    
    //转换DTO对象
    RequestBill requestBill = JkMappingUtils.convert(requestBillDTO, RequestBill.class);
    requestBillDao.save(requestBill);
    //保存明细表
    requestDetailDao.save(requestBill.getDetail())
}

Код очень простой и "изящный". Сначала через http-интерфейс вызывается движок рабочего процесса для создания потока утверждения, а затем сохраняется форма возмещения. Чтобы обеспечить транзакцию операции, добавляется весь метод .@TransactionalАннотации (подумайте об этом, действительно ли это гарантирует транзакции?).

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

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

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

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

Анализ причин аварии

Благодаря анализу журнала мы можем легко определить, что причиной сбоя является метод save () сохранения формы возмещения, и виновником является то, что@Transactionalаннотация.

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

Когда Spring встречает эту аннотацию, он автоматически получает соединение из пула соединений с базой данных, запускает транзакцию, а затем привязывает ее к ThreadLocal.Для всего метода, обернутого аннотацией @Transactional, используется одно и то же соединение. Если у нас есть трудоемкие операции, такие как вызовы стороннего интерфейса, сложная бизнес-логика и крупномасштабная обработка данных, это заставит нас занять это соединение в течение длительного времени, а соединение с базой данных было занято и не освобождено. . Когда подобных операций будет слишком много, пул соединений с базой данных будет исчерпан.

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

Что такое долгий бизнес?

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

Какие проблемы могут вызвать длинные транзакции?

Общие опасности, вызванные длинными транзакциями:

  1. Пул соединений с базой данных заполнен, и приложение не может получить ресурсы соединения;
  2. Легко вызвать взаимоблокировку базы данных;
  3. Откат базы данных занимает много времени;
  4. В архитектуре master-slave задержка master-slave будет увеличиваться.

Как избежать длинных транзакций?

Теперь, когда вы знаете об опасностях длинных транзакций, как избежать проблем с длинными транзакциями в разработке?

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

Теперь, когда упоминается гранулярность транзакций, давайте рассмотрим, как Spring управляет транзакциями.

декларативная сделка

Сначала нам нужно знать, что с помощью метода@TransactionalОперация аннотации для управления транзакциями называется декларативной транзакцией.

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

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

Противоположностью декларативным транзакциям являются программные транзакции.

На основе базового API разработчики вручную управляют транзакциями, такими как открытие, фиксация и откат операций в коде. Может использоваться в весенних проектахTransactionTemplateОбъект класса, который вручную управляет транзакцией.

@Autowired 
private TransactionTemplate transactionTemplate; 
 
... 

public void save(RequestBill requestBill) { 
    transactionTemplate.execute(transactionStatus -> {
        requestBillDao.save(requestBill);
        //保存明细表
        requestDetailDao.save(requestBill.getDetail());
        return Boolean.TRUE; 
    });
} 

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

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

Некоторые студенты скажут,@TransactionalЭто так просто в использовании, есть ли способ использовать оба@Transactionalи избежать длинных транзакций?

Затем вам нужно разделить метод, чтобы отделить логику, не требующую управления транзакциями, от операции транзакции:

@Service
public class OrderService{

    public void createOrder(OrderCreateDTO createDTO){
        query();
        validate();
        saveData(createDTO);
    }
  
  //事务操作
    @Transactional(rollbackFor = Throwable.class)
    public void saveData(OrderCreateDTO createDTO){
        orderDao.insert(createDTO);
    }
}

query()иvalidate()Нет необходимости в транзакциях, мы совмещаем это с методом транзакцийsaveData()открыть.

Конечно, этот раскол ударил бы по использованию@TransactionalКлассический сценарий, когда транзакция не вступает в силу после аннотации, очень легко для многих новичков сделать эту ошибку.@TransactionalДекларативная транзакция аннотации работает через spring aop, а spring aop нужно генерировать прокси-объект.Исходный объект по-прежнему используется для вызовов методов непосредственно в том же классе, и транзакция не вступает в силу. Несколько других распространенных сценариев, когда транзакции не вступают в силу:

"

  • @Transactional применяется к непублично оформленным методам
  • Распространение атрибута аннотации @Transactional задано неправильно
  • Откат атрибута аннотации @TransactionalПри ошибке настройки
  • Вызовы методов в том же классе приводят к сбою @Transactional
  • Исключение, пойманное catch, приводит к сбою @Transactional

"

Правильный метод разделения должен использовать следующие два:

  1. Вы можете поместить методы в другой класс, например, добавивmanager层, инжектируемый через пружину, что соответствует условиям вызова между объектами.
@Service
public class OrderService{
  
    @Autowired
   private OrderManager orderManager;

    public void createOrder(OrderCreateDTO createDTO){
        query();
        validate();
        orderManager.saveData(createDTO);
    }
}

@Service
public class OrderManager{
  
    @Autowired
   private OrderDao orderDao;
  
  @Transactional(rollbackFor = Throwable.class)
    public void saveData(OrderCreateDTO createDTO){
        orderDao.saveData(createDTO);
    }
}
  1. Добавить класс запуска@EnableAspectJAutoProxy(exposeProxy = true), используемый в методеAopContext.currentProxy()Получите класс прокси, используйте транзакцию.
SpringBootApplication.java

@EnableAspectJAutoProxy(exposeProxy = true)
@SpringBootApplication
public class SpringBootApplication {}
OrderService.java
  
public void createOrder(OrderCreateDTO createDTO){
    OrderService orderService = (OrderService)AopContext.currentProxy();
    orderService.saveData(createDTO);
}

резюме

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