diff --git a/hmily-config/hmily-config-api/src/main/java/org/dromara/hmily/config/api/entity/HmilyConfig.java b/hmily-config/hmily-config-api/src/main/java/org/dromara/hmily/config/api/entity/HmilyConfig.java index 96a5eb08..6bbe666d 100644 --- a/hmily-config/hmily-config-api/src/main/java/org/dromara/hmily/config/api/entity/HmilyConfig.java +++ b/hmily-config/hmily-config-api/src/main/java/org/dromara/hmily/config/api/entity/HmilyConfig.java @@ -138,6 +138,16 @@ public class HmilyConfig extends AbstractConfig { * tac sqlRevert. */ private String sqlRevert = "default"; + + /** + * global lock retry interval(unit: ms). + */ + private int lockRetryInterval = 10; + + /** + * global lock retry times. + */ + private int lockRetryTimes = 30; @Override public String prefix() { diff --git a/hmily-demo/hmily-demo-tac/hmily-demo-tac-dubbo/hmily-demo-tac-dubbo-order/src/main/java/org/dromara/hmily/demo/tac/dubbo/order/enums/ReadCommittedTransactionEnum.java b/hmily-demo/hmily-demo-tac/hmily-demo-tac-dubbo/hmily-demo-tac-dubbo-order/src/main/java/org/dromara/hmily/demo/tac/dubbo/order/enums/ReadCommittedTransactionEnum.java new file mode 100644 index 00000000..bb7efaa2 --- /dev/null +++ b/hmily-demo/hmily-demo-tac/hmily-demo-tac-dubbo/hmily-demo-tac-dubbo-order/src/main/java/org/dromara/hmily/demo/tac/dubbo/order/enums/ReadCommittedTransactionEnum.java @@ -0,0 +1,22 @@ +package org.dromara.hmily.demo.tac.dubbo.order.enums; + +import lombok.Getter; +import lombok.RequiredArgsConstructor; + +/** + * The enum transaction enum. + * + * @author zhangzhi + */ +@RequiredArgsConstructor +@Getter +public enum ReadCommittedTransactionEnum { + + TRANSACTION_READ_WRITE(1, "读已提交隔离级别的事务, 包括更新、查询操作"), + + TRANSACTION_READ_ONLY(2, "读已提交隔离级别的事务, 只有查询操作"); + + private final int code; + + private final String desc; +} diff --git a/hmily-demo/hmily-demo-tac/hmily-demo-tac-dubbo/hmily-demo-tac-dubbo-order/src/main/java/org/dromara/hmily/demo/tac/dubbo/order/service/PaymentService.java b/hmily-demo/hmily-demo-tac/hmily-demo-tac-dubbo/hmily-demo-tac-dubbo-order/src/main/java/org/dromara/hmily/demo/tac/dubbo/order/service/PaymentService.java index 1a5f279f..93fd797e 100644 --- a/hmily-demo/hmily-demo-tac/hmily-demo-tac-dubbo/hmily-demo-tac-dubbo-order/src/main/java/org/dromara/hmily/demo/tac/dubbo/order/service/PaymentService.java +++ b/hmily-demo/hmily-demo-tac/hmily-demo-tac-dubbo/hmily-demo-tac-dubbo-order/src/main/java/org/dromara/hmily/demo/tac/dubbo/order/service/PaymentService.java @@ -17,6 +17,7 @@ package org.dromara.hmily.demo.tac.dubbo.order.service; import org.dromara.hmily.demo.common.order.entity.Order; +import org.dromara.hmily.demo.tac.dubbo.order.enums.ReadCommittedTransactionEnum; /** * The interface Payment service. @@ -97,6 +98,7 @@ public interface PaymentService { * 订单支付. * * @param order 订单实体 + * @param readCommittedTransactionEnum 事务类型 */ - String makePaymentWithReadCommitted(Order order); + String makePaymentWithReadCommitted(Order order, ReadCommittedTransactionEnum readCommittedTransactionEnum); } diff --git a/hmily-demo/hmily-demo-tac/hmily-demo-tac-dubbo/hmily-demo-tac-dubbo-order/src/main/java/org/dromara/hmily/demo/tac/dubbo/order/service/impl/OrderServiceImpl.java b/hmily-demo/hmily-demo-tac/hmily-demo-tac-dubbo/hmily-demo-tac-dubbo-order/src/main/java/org/dromara/hmily/demo/tac/dubbo/order/service/impl/OrderServiceImpl.java index 0b4b6cdf..aecc4a0d 100644 --- a/hmily-demo/hmily-demo-tac/hmily-demo-tac-dubbo/hmily-demo-tac-dubbo-order/src/main/java/org/dromara/hmily/demo/tac/dubbo/order/service/impl/OrderServiceImpl.java +++ b/hmily-demo/hmily-demo-tac/hmily-demo-tac-dubbo/hmily-demo-tac-dubbo-order/src/main/java/org/dromara/hmily/demo/tac/dubbo/order/service/impl/OrderServiceImpl.java @@ -17,10 +17,10 @@ package org.dromara.hmily.demo.tac.dubbo.order.service.impl; import org.dromara.hmily.common.utils.IdWorkerUtils; -import org.dromara.hmily.demo.common.account.api.AccountService; import org.dromara.hmily.demo.common.order.entity.Order; import org.dromara.hmily.demo.common.order.enums.OrderStatusEnum; import org.dromara.hmily.demo.common.order.mapper.OrderMapper; +import org.dromara.hmily.demo.tac.dubbo.order.enums.ReadCommittedTransactionEnum; import org.dromara.hmily.demo.tac.dubbo.order.service.OrderService; import org.dromara.hmily.demo.tac.dubbo.order.service.PaymentService; import org.slf4j.Logger; @@ -165,7 +165,23 @@ private Order saveOrder(Integer count, BigDecimal amount) { public String orderPayWithReadCommitted(Integer count, BigDecimal amount) { Order order = saveOrder(count, amount); long start = System.currentTimeMillis(); - paymentService.makePaymentWithReadCommitted(order); + // 开启一个事务 + new Thread(() -> { + try { + paymentService.makePaymentWithReadCommitted(order, ReadCommittedTransactionEnum.TRANSACTION_READ_WRITE); + } catch (Exception e) { + System.out.println(e.getMessage()); + } + }, "global trans2").start(); + try { + // 确保第一个事务先执行 + Thread.sleep(1000); + } catch (InterruptedException e) { + e.printStackTrace(); + } + // 开启另一个事务 + paymentService.makePaymentWithReadCommitted(order, ReadCommittedTransactionEnum.TRANSACTION_READ_ONLY); + System.out.println("切面耗时:" + (System.currentTimeMillis() - start)); return "success"; } diff --git a/hmily-demo/hmily-demo-tac/hmily-demo-tac-dubbo/hmily-demo-tac-dubbo-order/src/main/java/org/dromara/hmily/demo/tac/dubbo/order/service/impl/PaymentServiceImpl.java b/hmily-demo/hmily-demo-tac/hmily-demo-tac-dubbo/hmily-demo-tac-dubbo-order/src/main/java/org/dromara/hmily/demo/tac/dubbo/order/service/impl/PaymentServiceImpl.java index a026c83b..70bbd284 100644 --- a/hmily-demo/hmily-demo-tac/hmily-demo-tac-dubbo/hmily-demo-tac-dubbo-order/src/main/java/org/dromara/hmily/demo/tac/dubbo/order/service/impl/PaymentServiceImpl.java +++ b/hmily-demo/hmily-demo-tac/hmily-demo-tac-dubbo/hmily-demo-tac-dubbo-order/src/main/java/org/dromara/hmily/demo/tac/dubbo/order/service/impl/PaymentServiceImpl.java @@ -4,9 +4,9 @@ * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. @@ -27,19 +27,21 @@ import org.dromara.hmily.demo.common.order.entity.Order; import org.dromara.hmily.demo.common.order.enums.OrderStatusEnum; import org.dromara.hmily.demo.common.order.mapper.OrderMapper; +import org.dromara.hmily.demo.tac.dubbo.order.enums.ReadCommittedTransactionEnum; import org.dromara.hmily.demo.tac.dubbo.order.service.PaymentService; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; + /** * PaymentServiceImpl. * @author xiaoyu */ @Service public class PaymentServiceImpl implements PaymentService { - + private static final Logger LOGGER = LoggerFactory.getLogger(PaymentServiceImpl.class); private final OrderMapper orderMapper; @@ -56,7 +58,7 @@ public PaymentServiceImpl(final OrderMapper orderMapper, this.accountService = accountService; this.inventoryService = inventoryService; } - + @Override @HmilyTAC public void makePayment(final Order order) { @@ -66,7 +68,7 @@ public void makePayment(final Order order) { //进入扣减库存操作 inventoryService.decrease(buildInventoryDTO(order)); } - + @Override public void testMakePayment(final Order order) { updateOrderStatus(order, OrderStatusEnum.PAYING); @@ -87,7 +89,7 @@ public void makePaymentWithNested(final Order order) { //扣除用户余额 accountService.paymentWithNested(buildAccountNestedDTO(order)); } - + @Override @HmilyTAC public void makePaymentWithNestedException(final Order order) { @@ -99,7 +101,7 @@ public void makePaymentWithNestedException(final Order order) { //扣除用户余额 accountService.paymentWithNestedException(buildAccountNestedDTO(order)); } - + @Override @HmilyTAC public String mockPaymentInventoryWithTryException(final Order order) { @@ -109,7 +111,7 @@ public String mockPaymentInventoryWithTryException(final Order order) { inventoryService.mockWithTryException(buildInventoryDTO(order)); return "success"; } - + @Override @HmilyTAC public String mockPaymentInventoryWithTryTimeout(final Order order) { @@ -119,7 +121,7 @@ public String mockPaymentInventoryWithTryTimeout(final Order order) { inventoryService.mockWithTryTimeout(buildInventoryDTO(order)); return "success"; } - + @Override @HmilyTAC public String mockPaymentAccountWithTryException(final Order order) { @@ -127,7 +129,7 @@ public String mockPaymentAccountWithTryException(final Order order) { accountService.mockTryPaymentException(buildAccountDTO(order)); return "success"; } - + @Override @HmilyTAC public String mockPaymentAccountWithTryTimeout(final Order order) { @@ -135,7 +137,7 @@ public String mockPaymentAccountWithTryTimeout(final Order order) { accountService.mockTryPaymentTimeout(buildAccountDTO(order)); return "success"; } - + @Override @HmilyTAC public String mockPaymentInventoryWithConfirmTimeout(final Order order) { @@ -151,13 +153,24 @@ public String mockPaymentInventoryWithConfirmTimeout(final Order order) { @Override @HmilyTAC - public String makePaymentWithReadCommitted(Order order) { + public String makePaymentWithReadCommitted(Order order, ReadCommittedTransactionEnum transactionEnum) { + //第二个事务查询相同账户信息, 获取不到全局锁, 会进行回滚 + if (ReadCommittedTransactionEnum.TRANSACTION_READ_ONLY.equals(transactionEnum)) { + accountService.findByUserId(order.getUserId()); + return "success"; + } updateOrderStatus(order, OrderStatusEnum.PAY_SUCCESS); //扣除用户余额 accountService.payment(buildAccountDTO(order)); - //查询账户信息, 读已提交, 此时该事务未结束, 获取全局锁失败, 将会回滚 + //查询账户信息, 读已提交隔离级别, 但是在统一全局事务中, 所以可见 accountService.findByUserId(order.getUserId()); //进入扣减库存操作 + try { + // 延时第一个事务, 确保第一个事务还没结束, 第二个事务执行查询 + Thread.sleep(1200); + } catch (InterruptedException e) { + e.printStackTrace(); + } inventoryService.decrease(buildInventoryDTO(order)); return "success"; } @@ -166,14 +179,14 @@ private void updateOrderStatus(final Order order, final OrderStatusEnum orderSta order.setStatus(orderStatus.getCode()); orderMapper.update(order); } - + private AccountDTO buildAccountDTO(final Order order) { AccountDTO accountDTO = new AccountDTO(); accountDTO.setAmount(order.getTotalAmount()); accountDTO.setUserId(order.getUserId()); return accountDTO; } - + private AccountNestedDTO buildAccountNestedDTO(final Order order) { AccountNestedDTO nestedDTO = new AccountNestedDTO(); nestedDTO.setAmount(order.getTotalAmount()); @@ -182,7 +195,7 @@ private AccountNestedDTO buildAccountNestedDTO(final Order order) { nestedDTO.setCount(order.getCount()); return nestedDTO; } - + private InventoryDTO buildInventoryDTO(final Order order) { InventoryDTO inventoryDTO = new InventoryDTO(); inventoryDTO.setCount(order.getCount()); diff --git a/hmily-demo/hmily-demo-tac/hmily-demo-tac-springcloud/hmily-demo-tac-springcloud-order/src/main/java/org/dromara/hmily/demo/springcloud/order/enums/ReadCommittedTransactionEnum.java b/hmily-demo/hmily-demo-tac/hmily-demo-tac-springcloud/hmily-demo-tac-springcloud-order/src/main/java/org/dromara/hmily/demo/springcloud/order/enums/ReadCommittedTransactionEnum.java new file mode 100644 index 00000000..ab6346c2 --- /dev/null +++ b/hmily-demo/hmily-demo-tac/hmily-demo-tac-springcloud/hmily-demo-tac-springcloud-order/src/main/java/org/dromara/hmily/demo/springcloud/order/enums/ReadCommittedTransactionEnum.java @@ -0,0 +1,22 @@ +package org.dromara.hmily.demo.springcloud.order.enums; + +import lombok.Getter; +import lombok.RequiredArgsConstructor; + +/** + * The enum transaction enum. + * + * @author zhangzhi + */ +@RequiredArgsConstructor +@Getter +public enum ReadCommittedTransactionEnum { + + TRANSACTION_READ_WRITE(1, "读已提交隔离级别的事务, 包括更新、查询操作"), + + TRANSACTION_READ_ONLY(2, "读已提交隔离级别的事务, 只有查询操作"); + + private final int code; + + private final String desc; +} diff --git a/hmily-demo/hmily-demo-tac/hmily-demo-tac-springcloud/hmily-demo-tac-springcloud-order/src/main/java/org/dromara/hmily/demo/springcloud/order/service/PaymentService.java b/hmily-demo/hmily-demo-tac/hmily-demo-tac-springcloud/hmily-demo-tac-springcloud-order/src/main/java/org/dromara/hmily/demo/springcloud/order/service/PaymentService.java index 2478f9b4..4a89d98e 100644 --- a/hmily-demo/hmily-demo-tac/hmily-demo-tac-springcloud/hmily-demo-tac-springcloud-order/src/main/java/org/dromara/hmily/demo/springcloud/order/service/PaymentService.java +++ b/hmily-demo/hmily-demo-tac/hmily-demo-tac-springcloud/hmily-demo-tac-springcloud-order/src/main/java/org/dromara/hmily/demo/springcloud/order/service/PaymentService.java @@ -18,6 +18,7 @@ import org.dromara.hmily.demo.common.order.entity.Order; +import org.dromara.hmily.demo.springcloud.order.enums.ReadCommittedTransactionEnum; /** * PaymentService. @@ -100,6 +101,7 @@ public interface PaymentService { * 订单支付. * * @param order 订单实体 + * @param readCommittedTransactionEnum 读已提交事务类型 */ - String makePaymentWithReadCommitted(Order order); + String makePaymentWithReadCommitted(Order order, ReadCommittedTransactionEnum readCommittedTransactionEnum); } diff --git a/hmily-demo/hmily-demo-tac/hmily-demo-tac-springcloud/hmily-demo-tac-springcloud-order/src/main/java/org/dromara/hmily/demo/springcloud/order/service/impl/OrderServiceImpl.java b/hmily-demo/hmily-demo-tac/hmily-demo-tac-springcloud/hmily-demo-tac-springcloud-order/src/main/java/org/dromara/hmily/demo/springcloud/order/service/impl/OrderServiceImpl.java index 23c47da0..8883580c 100644 --- a/hmily-demo/hmily-demo-tac/hmily-demo-tac-springcloud/hmily-demo-tac-springcloud-order/src/main/java/org/dromara/hmily/demo/springcloud/order/service/impl/OrderServiceImpl.java +++ b/hmily-demo/hmily-demo-tac/hmily-demo-tac-springcloud/hmily-demo-tac-springcloud-order/src/main/java/org/dromara/hmily/demo/springcloud/order/service/impl/OrderServiceImpl.java @@ -20,6 +20,7 @@ import org.dromara.hmily.demo.common.order.entity.Order; import org.dromara.hmily.demo.common.order.enums.OrderStatusEnum; import org.dromara.hmily.demo.common.order.mapper.OrderMapper; +import org.dromara.hmily.demo.springcloud.order.enums.ReadCommittedTransactionEnum; import org.dromara.hmily.demo.springcloud.order.service.OrderService; import org.dromara.hmily.demo.springcloud.order.service.PaymentService; import org.slf4j.Logger; @@ -133,7 +134,23 @@ public void updateOrderStatus(Order order) { public String orderPayWithReadCommitted(Integer count, BigDecimal amount) { Order order = saveOrder(count, amount); long start = System.currentTimeMillis(); - paymentService.makePaymentWithReadCommitted(order); + // 开启一个事务 + new Thread(() -> { + try { + paymentService.makePaymentWithReadCommitted(order, ReadCommittedTransactionEnum.TRANSACTION_READ_WRITE); + } catch (Exception e) { + System.out.println(e.getMessage()); + } + }, "global trans2").start(); + try { + // 确保第一个事务先执行 + Thread.sleep(1000); + } catch (InterruptedException e) { + e.printStackTrace(); + } + // 开启另一个事务 + paymentService.makePaymentWithReadCommitted(order, ReadCommittedTransactionEnum.TRANSACTION_READ_ONLY); + System.out.println("切面耗时:" + (System.currentTimeMillis() - start)); return "success"; } diff --git a/hmily-demo/hmily-demo-tac/hmily-demo-tac-springcloud/hmily-demo-tac-springcloud-order/src/main/java/org/dromara/hmily/demo/springcloud/order/service/impl/PaymentServiceImpl.java b/hmily-demo/hmily-demo-tac/hmily-demo-tac-springcloud/hmily-demo-tac-springcloud-order/src/main/java/org/dromara/hmily/demo/springcloud/order/service/impl/PaymentServiceImpl.java index 51720988..e24141c4 100644 --- a/hmily-demo/hmily-demo-tac/hmily-demo-tac-springcloud/hmily-demo-tac-springcloud-order/src/main/java/org/dromara/hmily/demo/springcloud/order/service/impl/PaymentServiceImpl.java +++ b/hmily-demo/hmily-demo-tac/hmily-demo-tac-springcloud/hmily-demo-tac-springcloud-order/src/main/java/org/dromara/hmily/demo/springcloud/order/service/impl/PaymentServiceImpl.java @@ -17,7 +17,6 @@ package org.dromara.hmily.demo.springcloud.order.service.impl; import org.dromara.hmily.annotation.HmilyTAC; -import org.dromara.hmily.annotation.HmilyTCC; import org.dromara.hmily.common.exception.HmilyRuntimeException; import org.dromara.hmily.demo.common.account.dto.AccountDTO; import org.dromara.hmily.demo.common.account.dto.AccountNestedDTO; @@ -27,6 +26,7 @@ import org.dromara.hmily.demo.common.order.mapper.OrderMapper; import org.dromara.hmily.demo.springcloud.order.client.AccountClient; import org.dromara.hmily.demo.springcloud.order.client.InventoryClient; +import org.dromara.hmily.demo.springcloud.order.enums.ReadCommittedTransactionEnum; import org.dromara.hmily.demo.springcloud.order.service.PaymentService; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -154,13 +154,24 @@ public String mockPaymentInventoryWithConfirmTimeout(final Order order) { @Override @HmilyTAC - public String makePaymentWithReadCommitted(Order order) { + public String makePaymentWithReadCommitted(Order order, ReadCommittedTransactionEnum transactionEnum) { + //第二个事务查询相同账户信息, 获取不到全局锁, 会进行回滚 + if (ReadCommittedTransactionEnum.TRANSACTION_READ_ONLY.equals(transactionEnum)) { + accountClient.findByUserId(order.getUserId()); + return "success"; + } updateOrderStatus(order, OrderStatusEnum.PAY_SUCCESS); //扣除用户余额 accountClient.payment(buildAccountDTO(order)); - //查询账户信息, 读已提交, 此时该事务未结束, 获取全局锁失败, 将会回滚 + //查询账户信息, 读已提交隔离级别, 但是在统一全局事务中, 所以可见 accountClient.findByUserId(order.getUserId()); //进入扣减库存操作 + try { + // 延时第一个事务, 确保第一个事务还没结束, 第二个事务执行查询 + Thread.sleep(1200); + } catch (InterruptedException e) { + e.printStackTrace(); + } inventoryClient.decrease(buildInventoryDTO(order)); return "success"; } @@ -192,4 +203,5 @@ private AccountNestedDTO buildAccountNestedDTO(Order order) { nestedDTO.setCount(order.getCount()); return nestedDTO; } + } diff --git a/hmily-tac/hmily-tac-core/src/main/java/org/dromara/hmily/tac/core/exception/LockWaitTimeoutException.java b/hmily-tac/hmily-tac-core/src/main/java/org/dromara/hmily/tac/core/exception/LockWaitTimeoutException.java new file mode 100644 index 00000000..c4dba179 --- /dev/null +++ b/hmily-tac/hmily-tac-core/src/main/java/org/dromara/hmily/tac/core/exception/LockWaitTimeoutException.java @@ -0,0 +1,61 @@ +/* + * Copyright 2017-2021 Dromara.org + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.dromara.hmily.tac.core.exception; + +/** + *The type hmily lock wait timeout exception. + * + * @author zhangzhi + */ +public class LockWaitTimeoutException extends RuntimeException { + + private static final long serialVersionUID = 54809856411203804L; + + /** + * Instantiates a new Hmily lock wait timeout exception. + */ + public LockWaitTimeoutException() { + } + + /** + * Instantiates a new Hmily lock wait timeout exception. + * + * @param message the message + */ + public LockWaitTimeoutException(final String message) { + super(message); + } + + /** + * Instantiates a new Hmily lock wait timeout exception. + * + * @param message the message + * @param cause the cause + */ + public LockWaitTimeoutException(final String message, final Throwable cause) { + super(message, cause); + } + + /** + * Instantiates a new Hmily lock wait timeout exception. + * + * @param cause the cause + */ + public LockWaitTimeoutException(final Throwable cause) { + super(cause); + } +} diff --git a/hmily-tac/hmily-tac-core/src/main/java/org/dromara/hmily/tac/core/lock/HmilyLockManager.java b/hmily-tac/hmily-tac-core/src/main/java/org/dromara/hmily/tac/core/lock/HmilyLockManager.java index aa38769a..a23eaa93 100644 --- a/hmily-tac/hmily-tac-core/src/main/java/org/dromara/hmily/tac/core/lock/HmilyLockManager.java +++ b/hmily-tac/hmily-tac-core/src/main/java/org/dromara/hmily/tac/core/lock/HmilyLockManager.java @@ -17,12 +17,14 @@ package org.dromara.hmily.tac.core.lock; import lombok.extern.slf4j.Slf4j; +import org.dromara.hmily.common.utils.CollectionUtils; import org.dromara.hmily.core.repository.HmilyRepositoryStorage; import org.dromara.hmily.repository.spi.entity.HmilyLock; import org.dromara.hmily.repository.spi.exception.HmilyLockConflictException; import org.dromara.hmily.tac.core.cache.HmilyLockCacheManager; import java.util.Collection; +import java.util.Objects; import java.util.Optional; /** @@ -67,4 +69,23 @@ public void releaseLocks(final Collection hmilyLocks) { hmilyLocks.forEach(lock -> HmilyLockCacheManager.getInstance().removeByKey(lock.getLockId())); log.debug("TAC-release-lock ::: {}", hmilyLocks); } + + /** + * Check locks. + * + * @param hmilyLocks hmily locks + */ + public void checkLocks(final Collection hmilyLocks) { + if (CollectionUtils.isEmpty(hmilyLocks)) { + return; + } + for (HmilyLock lock : hmilyLocks) { + Optional hmilyLock = HmilyLockCacheManager.getInstance().get(lock.getLockId()); + if (hmilyLock.isPresent() && !Objects.equals(hmilyLock.get().getTransId(), lock.getTransId())) { + String message = String.format("current record [%s] has locked by transaction:[%s]", lock.getLockId(), hmilyLock.get().getTransId()); + log.error(message); + throw new HmilyLockConflictException(message); + } + } + } } diff --git a/hmily-tac/hmily-tac-core/src/main/java/org/dromara/hmily/tac/core/lock/LockRetryController.java b/hmily-tac/hmily-tac-core/src/main/java/org/dromara/hmily/tac/core/lock/LockRetryController.java new file mode 100644 index 00000000..dd4eb72d --- /dev/null +++ b/hmily-tac/hmily-tac-core/src/main/java/org/dromara/hmily/tac/core/lock/LockRetryController.java @@ -0,0 +1,45 @@ +package org.dromara.hmily.tac.core.lock; + +import lombok.extern.slf4j.Slf4j; +import org.dromara.hmily.config.api.ConfigEnv; +import org.dromara.hmily.config.api.entity.HmilyConfig; +import org.dromara.hmily.tac.core.exception.LockWaitTimeoutException; + +/** + * Lock retry controller. + * + * @author zhangzhi + */ +@Slf4j +public class LockRetryController { + + private int lockRetryInterval; + + private int lockRetryTimes; + + /** + * Instantiates a new Lock retry controller. + */ + public LockRetryController() { + HmilyConfig hmilyConfig = ConfigEnv.getInstance().getConfig(HmilyConfig.class); + this.lockRetryInterval = hmilyConfig.getLockRetryInterval(); + this.lockRetryTimes = hmilyConfig.getLockRetryTimes(); + } + + /** + * Sleep. + * @param e the e + * @throws LockWaitTimeoutException the lock wait timeout exception + */ + public void sleep(final Exception e) { + // prioritize the rollback of other transactions + if (--lockRetryTimes < 0) { + log.error("Global lock wait timeout"); + throw new LockWaitTimeoutException("Global lock wait timeout", e); + } + try { + Thread.sleep(lockRetryInterval); + } catch (InterruptedException ignore) { + } + } +} diff --git a/hmily-tac/hmily-tac-p6spy/src/main/java/org/dromara/hmily/tac/p6spy/executor/HmilyExecuteTemplate.java b/hmily-tac/hmily-tac-p6spy/src/main/java/org/dromara/hmily/tac/p6spy/executor/HmilyExecuteTemplate.java index 07b80b32..994de911 100644 --- a/hmily-tac/hmily-tac-p6spy/src/main/java/org/dromara/hmily/tac/p6spy/executor/HmilyExecuteTemplate.java +++ b/hmily-tac/hmily-tac-p6spy/src/main/java/org/dromara/hmily/tac/p6spy/executor/HmilyExecuteTemplate.java @@ -4,9 +4,9 @@ * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. @@ -28,12 +28,14 @@ import org.dromara.hmily.repository.spi.entity.HmilyDataSnapshot; import org.dromara.hmily.repository.spi.entity.HmilyLock; import org.dromara.hmily.repository.spi.entity.HmilyParticipantUndo; +import org.dromara.hmily.repository.spi.exception.HmilyLockConflictException; import org.dromara.hmily.tac.common.utils.DatabaseTypes; import org.dromara.hmily.tac.common.utils.ResourceIdUtils; import org.dromara.hmily.tac.core.cache.HmilyParticipantUndoCacheManager; import org.dromara.hmily.tac.core.cache.HmilyUndoContextCacheManager; import org.dromara.hmily.tac.core.context.HmilyUndoContext; import org.dromara.hmily.tac.core.lock.HmilyLockManager; +import org.dromara.hmily.tac.core.lock.LockRetryController; import org.dromara.hmily.tac.p6spy.threadlocal.AutoCommitThreadLocal; import org.dromara.hmily.tac.sqlcompute.HmilySQLComputeEngine; import org.dromara.hmily.tac.sqlcompute.HmilySQLComputeEngineFactory; @@ -53,12 +55,12 @@ */ @Slf4j public enum HmilyExecuteTemplate { - + /** * Instance hmily execute template. */ INSTANCE; - + /** * Sets auto commit. * @@ -78,13 +80,12 @@ public void beforeSetAutoCommit(final Connection connection) { e.printStackTrace(); } } - - + /** * Execute. * - * @param sql SQL - * @param parameters parameters + * @param sql SQL + * @param parameters parameters * @param connectionInformation connection information */ public void execute(final String sql, final List parameters, final ConnectionInformation connectionInformation) { @@ -100,16 +101,38 @@ public void execute(final String sql, final List parameters, final Conne } String resourceId = ResourceIdUtils.INSTANCE.getResourceId(connectionInformation.getUrl()); HmilySQLComputeEngine sqlComputeEngine = HmilySQLComputeEngineFactory.newInstance(statement); + // select SQL + if (statement instanceof HmilySelectStatement) { + executeSelect(sql, parameters, connectionInformation, sqlComputeEngine, resourceId); + return; + } HmilyDataSnapshot snapshot = sqlComputeEngine.execute(sql, parameters, connectionInformation.getConnection(), resourceId); log.debug("TAC-compute-sql ::: {}", snapshot); HmilyUndoContext undoContext = buildUndoContext(HmilyContextHolder.get(), snapshot, resourceId); HmilyLockManager.INSTANCE.tryAcquireLocks(undoContext.getHmilyLocks()); log.debug("TAC-try-lock ::: {}", undoContext.getHmilyLocks()); - if (!(statement instanceof HmilySelectStatement)) { - HmilyUndoContextCacheManager.INSTANCE.set(undoContext); + HmilyUndoContextCacheManager.INSTANCE.set(undoContext); + } + + private void executeSelect(final String sql, final List parameters, final ConnectionInformation connectionInformation, + final HmilySQLComputeEngine sqlComputeEngine, final String resourceId) { + LockRetryController lockRetryController = new LockRetryController(); + while (true) { + try { + HmilyDataSnapshot snapshot = sqlComputeEngine.execute(sql, parameters, connectionInformation.getConnection(), resourceId); + log.debug("TAC-compute-sql ::: {}", snapshot); + HmilyUndoContext undoContext = buildUndoContext(HmilyContextHolder.get(), snapshot, resourceId); + // check the global lock + HmilyLockManager.INSTANCE.checkLocks(undoContext.getHmilyLocks()); + log.debug("TAC-check-lock ::: {}", undoContext.getHmilyLocks()); + break; + } catch (HmilyLockConflictException hlce) { + // trigger retry + lockRetryController.sleep(hlce); + } } } - + private HmilyUndoContext buildUndoContext(final HmilyTransactionContext transactionContext, final HmilyDataSnapshot dataSnapshot, final String resourceId) { HmilyUndoContext result = new HmilyUndoContext(); result.setDataSnapshot(dataSnapshot); @@ -118,7 +141,7 @@ private HmilyUndoContext buildUndoContext(final HmilyTransactionContext transact result.setParticipantId(transactionContext.getParticipantId()); return result; } - + /** * Commit. * @@ -136,8 +159,7 @@ public void commit(final Connection connection) { log.debug("TAC-persist-undo ::: {}", undoList); clean(connection); } - - + /** * Rollback. * @@ -155,14 +177,14 @@ public void rollback(final Connection connection) { HmilyLockManager.INSTANCE.releaseLocks(locks); clean(connection); } - + @SneakyThrows private void clean(final Connection connection) { connection.setAutoCommit(AutoCommitThreadLocal.INSTANCE.get()); HmilyUndoContextCacheManager.INSTANCE.remove(); AutoCommitThreadLocal.INSTANCE.remove(); } - + private List buildUndoList() { List contexts = HmilyUndoContextCacheManager.INSTANCE.get(); return contexts.stream().map(context -> { @@ -175,9 +197,8 @@ private List buildUndoList() { undo.setStatus(HmilyActionEnum.TRYING.getCode()); return undo; }).collect(Collectors.toList()); - } - + private boolean check() { HmilyTransactionContext transactionContext = HmilyContextHolder.get(); return Objects.isNull(transactionContext) || !TransTypeEnum.TAC.name().equalsIgnoreCase(transactionContext.getTransType());