Spring Boot微服務如何整合fescar解決分散式事務?
什麼是fescar?
關於fescar的詳細介紹,請參閱 fescar wiki 。
傳統的2PC提交協議,會持有一個全域性性的鎖,所有區域性事務預提交成功後一起提交,或有一個區域性事務預提交失敗後一起回滾,最後釋放全域性鎖。鎖持有的時間較長,會對併發造成較大的影響,死鎖的風險也較高。
fescar的創新之處在於,每個區域性事務執行完立即提交,釋放本地鎖;它會去解析你程式碼中的sql,從資料庫中獲得事務提交前的事務資源即資料,存放到undo_log中,全域性事務協調器在回滾的時候直接使用undo_log中的資料覆蓋你提交的資料。
Spring Boot如何整合fescar?
我們可以從 官方程式碼庫 中看到,fescar目前提供的示例是針對使用dubbo的服務,那Spring Boot的專案如何整合fescar呢?
和很多2PC提交協議(如 tx_lcn )的解決方案一樣,fescar也是在資料來源處做了代理,和事務協調器進行通訊,來決定本地事務是否回滾。所以,第一步,在你的spring boot專案中,首先應使用fescar提供的代理資料來源作為你的資料來源,例如:
DruidDataSource dataSource = initDataSource(dataSourceProps.get("url").toString(), dataSourceProps.get("username").toString(), dataSourceProps.get("password").toString()); DataSourceProxy proxy = new DataSourceProxy(dataSource);
然後,你需要建立一個Feign攔截器,把RootContext中的XID(XID用於標識一個區域性事務屬於哪個全域性事務,需要在呼叫鏈路的上下文中傳遞)傳遞到上層呼叫鏈路。
@Component public class RequestHeaderInterceptor implements RequestInterceptor { @Override public void apply(RequestTemplate template) { String xid = RootContext.getXID(); if(StringUtils.isNotBlank(xid)){ template.header("Fescar-Xid",xid); } } }
最後,你需要建立一個Http Rest請求攔截器,用於把當前上下文中獲取到的XID放到RootContext。
import com.alibaba.fescar.core.context.RootContext; import org.apache.commons.lang.StringUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.web.filter.OncePerRequestFilter; import javax.servlet.FilterChain; import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; public class FescarXidFilter extends OncePerRequestFilter { protected Logger logger = LoggerFactory.getLogger(FescarXidFilter.class); @Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { String xid = RootContext.getXID(); String restXid = request.getHeader("Fescar-Xid"); boolean bind = false; if(StringUtils.isBlank(xid)&&StringUtils.isNotBlank(restXid)){ RootContext.bind(restXid); bind = true; if (logger.isDebugEnabled()) { logger.debug("bind[" + restXid + "] to RootContext"); } } try{ filterChain.doFilter(request, response); } finally { if (bind) { String unbindXid = RootContext.unbind(); if (logger.isDebugEnabled()) { logger.debug("unbind[" + unbindXid + "] from RootContext"); } if (!restXid.equalsIgnoreCase(unbindXid)) { logger.warn("xid in change during http rest from " + restXid + " to " + unbindXid); if (unbindXid != null) { RootContext.bind(unbindXid); logger.warn("bind [" + unbindXid + "] back to RootContext"); } } } } } }
這樣就完成了fescar的整合。
開始使用吧!
首先在專案中初始化兩個Bean:
@Bean public FescarXidFilter fescarXidFilter(){ return new FescarXidFilter(); } @Bean public GlobalTransactionScanner scanner(){ GlobalTransactionScanner scanner = new GlobalTransactionScanner("fescar-test","my_test_tx_group"); return scanner; }
然後寫兩個服務,服務A呼叫服務B,並在A服務的呼叫方法上打上@GlobalTransactional標籤:
@GlobalTransactional(timeoutMills = 300000, name = "fescar-test-tx") public void testFescar() throws BusinessException { DictionVO dictionVO = new DictionVO(); dictionVO.setCode("simidatest"); dictionVO.setValue("1"); dictionVO.setDesc("simidatest"); dictionVO.setAppId("sso"); commonService.createDiction(dictionVO);//遠端呼叫服務B areaMapper.deleteAreaBySysNo(2);//本地事務 throw new BusinessException("主動報錯"); }
最後,兩個專案中新增application.conf檔案,用於告訴客戶端如何與分散式協調器通訊,官方示例中有這個檔案,就不在此貼程式碼啦, application.conf傳送門
啟動事務協調器, sh fescar-server.sh 8091 ~/dksl/git/fescar/data,啟動你的專案,開始測試吧!
last thing
分散式事務作為微服務應用中的老大難問題,在現有的解決方案中,個人認為fescar是目前最輕量並且代價最小的一種解決方案。目前的版本,事務協調器還不能分散式部署,官方給出的路線圖是在三月底會有第一個生產可用版本。讓我們一起參與到fescar的社群中,共同推動fescar生態建設,讓落地微服務不必再擔心分散式事務的問題。