Seata,阿里開源的分布式事務框架,多的我就不介紹了,了解詳細介紹,請看官網。seata spring boot入門,可以看我上一篇博客《Spring boot微服務如何集成fescar解決分布式事務問題?》(fescar后來更名為seata)。
本篇,將介紹,同時使用seata的tcc模式和at模式的一些問題。點擊demo,可查看相關源代碼。
第一個問題:數據源使用seata代理的數據源,同時使用TCC模式,將導致注冊到TC的分支事務多一倍
在上一篇博客中,我們說到,要讓分支事務加入全局事務,需要分支事務rm獲得全局事務的xid,所以我們通過feign將xid傳遞到下游的微服務。但是AT模式的rm在下游服務的代理數據源處,TCC模式的rm在上游服務的TccAction處做的代理。此時要解決分支事務重復注冊的問題,在使用TCC模式的時候就不能把xid傳遞到下游服務,這樣,下游服務數據源代理處判斷到這個數據庫操作不在全局事務中,就不會向TC注冊。
解決的辦法:我們在feign header里加入一個標識,標志此請求是不是TCC模式的請求,如果是,則不將xid傳遞到下游服務。
@Component public class RequestHeaderInterceptor implements RequestInterceptor { @Override public void apply(RequestTemplate template) { ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder .getRequestAttributes(); boolean seataTransactionATMode = true; if (attributes!=null) { HttpServletRequest request = attributes.getRequest(); Enumeration<String> headerNames = request.getHeaderNames(); if (headerNames != null) { Map<String, Collection<String>> resolvedHeaders = new CaseInsensitiveKeyMap<>(); resolvedHeaders.putAll(template.headers()); while (headerNames.hasMoreElements()) { String name = headerNames.nextElement(); if (!resolvedHeaders.containsKey(name)) { String values = request.getHeader(name); List<String> headers = new ArrayList<String>(); headers.addAll(Arrays.asList(values)); resolvedHeaders.put(name, headers); } } template.headers(null); template.headers(resolvedHeaders); } } Map<String, Collection<String>> headers = template.headers(); if(headers!=null){ Collection<String> values = headers.getOrDefault(SeataConstants.TRANSACTION_MODE_HEADER,null); if (values==null) { values = headers.getOrDefault(SeataConstants.TRANSACTION_MODE_HEADER.toLowerCase(),null); } if(values!=null&&values.contains("TCC")){ seataTransactionATMode = false; } } if(seataTransactionATMode) { String xid = RootContext.getXID(); if (StringUtils.isNotBlank(xid)) { template.header(SeataConstants.XID_HEADER, xid); } } } }
使用tcc模式有一個點需要注意,
@TwoPhaseBusinessAction(name = "CreateOrderTccAction" , commitMethod = "commit", rollbackMethod = "rollback") public boolean prepare(BusinessActionContext actionContext, List<SoMaster> soMasters, @BusinessActionContextParameter(paramName = "SoSysNos") String soSysNos) throws BusinessException;
那就是BusinessActionContextParameter盡量使用簡單類型,如果是復雜類型,在注冊分支事務時會被序列化成json字符串,把上下文數據存到session。提交或者重試的時候,從actionContext獲取參數的時候actionContext.getActionContext("your argument")返回的是個object對象,此對象是個jsonObject,無法直接轉為復雜類型,需要tostring,再json反序列化。
第二個問題:在目前的undolog序列化協議中,數據庫里bigint類型的數據,被序列化后,再在undo回滾時反序列化回object類型,真實的值類型變成了int型
{"branchId":2013531184,"sqlUndoLogs":[{"afterImage":{"rows":[{"fields":[{"keyType":"PrimaryKey","name":"sysno","type":-5,"value":1},{"keyType":"NULL","name":"available_qty","type":4,"value":999992},{"keyType":"NULL","name":"allocated_qty","type":4,"value":8}]}],"tableName":"inventory"},"beforeImage":{"rows":[{"fields":[{"keyType":"PrimaryKey","name":"sysno","type":-5,"value":1},{"keyType":"NULL","name":"available_qty","type":4,"value":999994},{"keyType":"NULL","name":"allocated_qty","type":4,"value":6}]}],"tableName":"inventory"},"sqlType":"UPDATE","tableName":"inventory"}],"xid":"172.16.4.137:8091:2013531176"}
如上,{"keyType":"PrimaryKey","name":"sysno","type":-5,"value":1},sysno,type -5表示這是一個bigint的類型,反序列化后,value的真正值類型是int型,這個問題可以參考issue 1139。
在目前的版本0.6.1,已經支持TC的高可用嗎,但這個bug還沒有解決,如果使用類型判斷去做轉換來修復這個bug,預計會寫很多if else。官方回復的解決辦法是,他們會修改序列化的協議來解決這個bug。期待官方盡快修復這個bug。