前言碎語java
樓主以前推薦過2pc的分佈式事務框架LCN。今天來詳細聊聊TCC事務協議。git
2pc實現:https://github.com/codingapi/tx-lcngithub
tcc實現:https://github.com/yu199195/hmilyspring
首先咱們瞭解下什麼是tcc,以下圖api
tcc分佈式事務協議控制總體業務事務分爲三個階段。框架
try:執行業務邏輯異步
confirm:肯定業務邏輯執行無誤後,肯定業務邏輯執行完成分佈式
cancel:假如try階段有問題,執行cancel階段邏輯,取消try階段的數據ide
這就須要咱們在設計業務時,在try階段多想一想業務處理的折中狀態,好比,處理中,支付中,進行中等,在confirm階段變動爲處理完成,或者在cancel階段變動爲處理失敗。性能
下面以電商下單爲例子:.
假設咱們有一個電商下單的業務,有三個服務組成,訂單服務處理下單邏輯,庫存服務處理減庫存邏輯,支付服務處理減帳戶餘額邏輯。在下單服務裏前後調用減庫存和減餘額的方法。若是使用tcc分佈式事務來協調事務,咱們服務就要作以下設計:
訂單服務:
庫存服務:
多加一個鎖定庫存的字段記錄,用於記錄業務處理中狀態
支付服務:
多加一個凍結金額的字段記錄,用於記錄業務處理中狀態
tcc分佈式事務在這裏起到了一個事務協調者的角色。真實業務只須要調用try階段的方法。confirm和cancel階段的額方法由tcc框架來幫咱們調用完成最終業務邏輯。下面咱們假設以下三個場景的業務狀況,看tcc如何協調業務最終一致的。
hmily事務框架怎麼作的?
經過上面對tcc事務協議說明你們應該都瞭解了tcc的處理協調機制,下面咱們來看看hmily是怎麼作到的,咱們以接入支持dubbo服務爲例。
概要:首先最基礎兩個應用點是aop和dubbo的filter機制,其次針對一組事務,定義了啓動事務處理器,參與事務處理器去協調處理不一樣的事務單元。外加一個disruptor+ScheduledService處理事務日誌,補償處理失敗的事務。
hmily框架以@Hmily註解爲切入點,定義了一個環繞織入的切面,註解必填兩個參數confirmMethod和cancelMethod,也就是tcc協調的兩個階段方法。在須要tcc事務的方法上面加上這個註解,也就託管了tcc三個階段的處理流程。下面是aspect切面的抽象類,不一樣的RPC框架支持會有不一樣的實現 。其中真正處理業務邏輯須要實現HmilyTransactionInterceptor接口
@Aspect public abstract class AbstractHmilyTransactionAspect { private HmilyTransactionInterceptor hmilyTransactionInterceptor; protected void setHmilyTransactionInterceptor(final HmilyTransactionInterceptor hmilyTransactionInterceptor) { this.hmilyTransactionInterceptor = hmilyTransactionInterceptor; } /** * this is point cut with {@linkplain Hmily }. */ @Pointcut("@annotation(org.dromara.hmily.annotation.Hmily)") public void hmilyInterceptor() { } /** * this is around in {@linkplain Hmily }. * @param proceedingJoinPoint proceedingJoinPoint * @return Object * @throws Throwable Throwable */ @Around("hmilyInterceptor()") public Object interceptTccMethod(final ProceedingJoinPoint proceedingJoinPoint) throws Throwable { return hmilyTransactionInterceptor.interceptor(proceedingJoinPoint); } /** * spring Order. * * @return int */ public abstract int getOrder(); }
dubbo的aspect抽象實現
@Aspect @Component public class DubboHmilyTransactionAspect extends AbstractHmilyTransactionAspect implements Ordered { @Autowired public DubboHmilyTransactionAspect(final DubboHmilyTransactionInterceptor dubboHmilyTransactionInterceptor) { super.setHmilyTransactionInterceptor(dubboHmilyTransactionInterceptor); } @Override public int getOrder() { return Ordered.HIGHEST_PRECEDENCE; } }
dubbo的HmilyTransactionInterceptor實現
@Component public class DubboHmilyTransactionInterceptor implements HmilyTransactionInterceptor { private final HmilyTransactionAspectService hmilyTransactionAspectService; @Autowired public DubboHmilyTransactionInterceptor(final HmilyTransactionAspectService hmilyTransactionAspectService) { this.hmilyTransactionAspectService = hmilyTransactionAspectService; } @Override public Object interceptor(final ProceedingJoinPoint pjp) throws Throwable { final String context = RpcContext.getContext().getAttachment(CommonConstant.HMILY_TRANSACTION_CONTEXT); HmilyTransactionContext hmilyTransactionContext; //判斷dubbo上下文中是否攜帶了tcc事務,若是有就取出反序列化爲事務上下文對象 if (StringUtils.isNoneBlank(context)) { hmilyTransactionContext = GsonUtils.getInstance().fromJson(context, HmilyTransactionContext.class); RpcContext.getContext().getAttachments().remove(CommonConstant.HMILY_TRANSACTION_CONTEXT); } else { //若是dubbo上下文中沒有,就從當前上下文中獲取。若是是事務發起者,這裏其實也獲取不到事務 hmilyTransactionContext = HmilyTransactionContextLocal.getInstance().get(); } return hmilyTransactionAspectService.invoke(hmilyTransactionContext, pjp); } }
這裏主要判斷了dubbo上下文中是否攜帶了tcc事務。若是沒有就從當前線程上下文中獲取,若是是事務的發起者,這裏其實獲取不到事務上下文對象的。在invoke裏有個獲取事務處理器的邏輯,若是事務上下文入參 爲null,那麼獲取到的就是啓動事務處理器。啓動事務處理器處理邏輯以下
public Object handler(final ProceedingJoinPoint point, final HmilyTransactionContext context) throws Throwable { System.err.println("StarterHmilyTransactionHandler"); Object returnValue; try { HmilyTransaction hmilyTransaction = hmilyTransactionExecutor.begin(point); try { //execute try returnValue = point.proceed(); hmilyTransaction.setStatus(HmilyActionEnum.TRYING.getCode()); hmilyTransactionExecutor.updateStatus(hmilyTransaction); } catch (Throwable throwable) { //if exception ,execute cancel final HmilyTransaction currentTransaction = hmilyTransactionExecutor.getCurrentTransaction(); executor.execute(() -> hmilyTransactionExecutor .cancel(currentTransaction)); throw throwable; } //execute confirm final HmilyTransaction currentTransaction = hmilyTransactionExecutor.getCurrentTransaction(); executor.execute(() -> hmilyTransactionExecutor.confirm(currentTransaction)); } finally { HmilyTransactionContextLocal.getInstance().remove(); hmilyTransactionExecutor.remove(); } return returnValue; }
真正業務處理方法,point.proceed();被try,catch包起來了,若是try裏面的方法出現異常,就會走hmilyTransactionExecutor.cancel(currentTransaction)的邏輯,若是成功,就走hmilyTransactionExecutor.confirm(currentTransaction)邏輯。其中cancel和confirm裏都有協調參與者事務的處理邏輯,以confirm邏輯爲例。
public void confirm(final HmilyTransaction currentTransaction) throws HmilyRuntimeException { LogUtil.debug(LOGGER, () -> "tcc confirm .......!start"); if (Objects.isNull(currentTransaction) || CollectionUtils.isEmpty(currentTransaction.getHmilyParticipants())) { return; } currentTransaction.setStatus(HmilyActionEnum.CONFIRMING.getCode()); updateStatus(currentTransaction); final ListhmilyParticipants = currentTransaction.getHmilyParticipants(); ListfailList = Lists.newArrayListWithCapacity(hmilyParticipants.size()); boolean success = true; if (CollectionUtils.isNotEmpty(hmilyParticipants)) { for (HmilyParticipant hmilyParticipant : hmilyParticipants) { try { HmilyTransactionContext context = new HmilyTransactionContext(); context.setAction(HmilyActionEnum.CONFIRMING.getCode()); context.setRole(HmilyRoleEnum.START.getCode()); context.setTransId(hmilyParticipant.getTransId()); HmilyTransactionContextLocal.getInstance().set(context); executeParticipantMethod(hmilyParticipant.getConfirmHmilyInvocation()); } catch (Exception e) { LogUtil.error(LOGGER, "execute confirm :{}", () -> e); success = false; failList.add(hmilyParticipant); } finally { HmilyTransactionContextLocal.getInstance().remove(); } } executeHandler(success, currentTransaction, failList); } }
能夠看到executeParticipantMethod(hmilyParticipant.getConfirmHmilyInvocation()),這裏執行了事務參與者的confirm方法。同理cancel裏面也有相似代碼,執行事務參與者的cancel方法。那麼事務參與者的信息是怎麼獲取到的呢?咱們須要回到一開始提到的dubbo的filter機制。
@Activate(group = {Constants.SERVER_KEY, Constants.CONSUMER}) public class DubboHmilyTransactionFilter implements Filter { private HmilyTransactionExecutor hmilyTransactionExecutor; /** * this is init by dubbo spi * set hmilyTransactionExecutor. * * @param hmilyTransactionExecutor {@linkplain HmilyTransactionExecutor } */ public void setHmilyTransactionExecutor(final HmilyTransactionExecutor hmilyTransactionExecutor) { this.hmilyTransactionExecutor = hmilyTransactionExecutor; } @Override @SuppressWarnings("unchecked") public Result invoke(final Invoker invoker, final Invocation invocation) throws RpcException { String methodName = invocation.getMethodName(); Class clazz = invoker.getInterface(); Class[] args = invocation.getParameterTypes(); final Object[] arguments = invocation.getArguments(); converterParamsClass(args, arguments); Method method = null; Hmily hmily = null; try { method = clazz.getMethod(methodName, args); hmily = method.getAnnotation(Hmily.class); } catch (NoSuchMethodException e) { e.printStackTrace(); } if (Objects.nonNull(hmily)) { try { final HmilyTransactionContext hmilyTransactionContext = HmilyTransactionContextLocal.getInstance().get(); if (Objects.nonNull(hmilyTransactionContext)) { if (hmilyTransactionContext.getRole() == HmilyRoleEnum.LOCAL.getCode()) { hmilyTransactionContext.setRole(HmilyRoleEnum