什么是 fescar?
关于 fescar 的详细介绍, 请参阅 fescar wiki.
传统的 2PC 提交协议, 会持有一个全局性的锁, 所有局部事务预提交成功后一起提交, 或有一个局部事务预提交失败后一起回滚, 最后释放全局锁. 锁持有的时间较长, 会对并发造成较大的影响, 死锁的风险也较高.
fescar 的创新之处在于, 每个局部事务执行完立即提交, 释放本地锁; 它会去解析你代码中的 sql, 从数据库中获得事务提交前的事务资源即数据, 存放到 undo_log 中, 全局事务协调器在回滚的时候直接使用 undo_log 中的数据覆盖你提交的数据.
Spring Boot 如何集成 fescar?
我们可以从官方代码库 https://github.com/alibaba/fescar 中看到, fescar 目前提供的示例是针对使用 dubbo 的服务, 那 Spring Boot 的项目如何集成 fescar 呢?
和很多 2PC 提交协议 (如 tx_lcn https://github.com/codingapi/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;
- }
- @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("主动报错");
- }
来源: http://www.bubuko.com/infodetail-2939055.html