前言
最近公司使用分布式框架搭建本身的服务(springCloud),就会遇到各位大神都会遇到的神级烦问题:分布式事务问题,近期研究了一下lcn框架的源码,以为很不错,因而果断选用lcn控制分布式事务。选用缘由以下:git
1.彻底开源,能够在官方的基础上作本身的各类修改 2.不只支持springCloud,并且支持dubbo 3.数据库方面,支持mybatis,jdbc等 正如lcn官网所说: LCN并不生产事务,LCN只是本地事务的协调者 官网(http://www.txlcn.org)
情景分析
假如如今你要经过携程官网买上海到大理的机票,而正好没有上海直达大理的飞机,所以你须要从昆明中转。此时伟大的携程已经给你推荐了一条最优的中转路线
(东航)上海->昆明 + (南航) 昆明->大理
你以为满意,因而屁颠屁颠的准备一键下单了....
咱们来分析一下,你点击下单之后,携程作了哪些事:github
(ps 若是携程这样作: 1.先看东航有没有上海到昆明的票,有,扣票,付钱,提交事务 2.再看南航有没有昆明到大理的票,有,扣票,付钱,提交事务 可是,恰好有那么一次,作完1后,发现2竟然没票了!! 你就郁闷了,你原本想到大理,可是你只买了去昆明的票,你确定不乐意 )
首先携程向东航尝试性发送扣票请求(不提交扣票的事务),若是有票,好,继续向南航发送尝试性扣票请求(不提交扣票的事务),若是都有票,那么一次性提交两边的事务,若是没有一边没票,则回滚两个事务.spring
综上,咱们可不能够经过一个三方,来控制咱们各个模块的事务呢,答案确定是有的,LCN就是数据库
下面,先阐述怎么集成,怎么使用,而后下一篇文章阐述源码分析
(5.0.2版本集成:https://segmentfault.com/a/11...)segmentfault
怎么集成
前提:springCloud服务是使用feign实现内部服务之间的通讯api
pom.xml坐标(今天是2018.10.17,最新的是4.1.0版本): (<lcn.last.version>4.1.0</lcn.last.version>) <dependency> <groupId>com.codingapi</groupId> <artifactId>transaction-springcloud</artifactId> <version>${lcn.last.version}</version> <exclusions> <exclusion> <groupId>org.slf4j</groupId> <artifactId>*</artifactId> </exclusion> </exclusions> </dependency> <dependency> <groupId>com.codingapi</groupId> <artifactId>tx-plugins-db</artifactId> <version>${lcn.last.version}</version> <exclusions> <exclusion> <groupId>org.slf4j</groupId> <artifactId>*</artifactId> </exclusion> </exclusions> </dependency>
咱们知道,feign是基于RequestTemplate模拟的http发送的请求,若是有研究feign的源码,咱们能够发如今SynchronousMethodHandler 类下面,有一个targetRequest(RequestTemplate template)方法,这个方法里面会循环的调用interceptor.apply(template);
而lcn正是重写了apply方法,将本身的group-id传递到了下一方:
TransactionRestTemplateInterceptor.apply()
而若是咱们项目使用了oauth2或者其余安全框架,使用feign调用的时候,
就会出现401的问题,意思是没有权限访问下一个模块,所以,咱们须要将token封装在请求头里面,而后再封装。这个类的完成代码以下:安全
*****必定要注意,这个类的包路径,要是这个: package com.codingapi.tx.springcloud.feign ****即在本身项目新建一个这个绝对路径的类,以此来让jvm走咱们自定义的类 public class TransactionRestTemplateInterceptor implements RequestInterceptor { private Logger logger = LoggerFactory.getLogger(TransactionRestTemplateInterceptor.class); public TransactionRestTemplateInterceptor() { } public void apply(RequestTemplate requestTemplate) { TxTransactionLocal txTransactionLocal = TxTransactionLocal.current(); String groupId = txTransactionLocal == null ? null : txTransactionLocal.getGroupId(); this.logger.info("LCN-SpringCloud TxGroup info -> groupId:" + groupId); RequestAttributes requestAttributes = RequestContextHolder.currentRequestAttributes(); HttpServletRequest request = requestAttributes == null ? null : ((ServletRequestAttributes) requestAttributes).getRequest(); Object attribute = request.getAttribute("OAuth2AuthenticationDetails.ACCESS_TOKEN_VALUE"); String token = attribute == null ? null : attribute.toString(); requestTemplate.header("Authorization", "Bearer " + token); if (txTransactionLocal != null) { requestTemplate.header("tx-group", new String[]{groupId}); } } }
这个包路径下,还要有这两个类:mybatis
/** * Created by liuliang on 2018/10/10. */ @Service public class TxManagerHttpRequestServiceImpl implements TxManagerHttpRequestService{ @Override public String httpGet(String url) { System.out.println("httpGet-start"); String res = HttpUtils.get(url); System.out.println("httpGet-end"); return res; } @Override public String httpPost(String url, String params) { System.out.println("httpPost-start"); String res = HttpUtils.post(url,params); System.out.println("httpPost-end"); return res; }
}app
//----------------------类分割线---------------------
/** * Created by liuliang on 2018/10/10. */ @Service public class TxManagerTxUrlServiceImpl implements TxManagerTxUrlService{ @Value("${tm.manager.url}") private String url; @Override public String getTxUrl() { System.out.println("load tm.manager.url "); return url; }
}负载均衡
作好这些以后,咱们须要将三方控制事务的代码(tx-manager)拿过来,启动并注册到咱们本身的eureka上面。这边给出官方code:https://github.com/codingapi/...
咱们拉下来之后,启动tx-manager,并注意如下三个配置:
#服务端口 server.port=7000 #tx-manager不得修改!!!!!! spring.application.name=tx-manager #eureka 地址(注册到本身的eureka) eureka.client.service-url.defaultZone=http://127.0.0.1:8880/eureka/
ok,如今咱们启动了tx-manager,再回头看咱们本身的项目配置,在本身项目配置(.yml)里面加上以下:
init-db: true #txmanager地址 tm: manager: url: http://127.0.0.1:7000/tx/manager/ logging: level: com: codingapi: debug ##Ribbon的负载均衡策略 ribbon: NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule MaxAutoRetriesNextServer: 0
最后,咱们还须要加上一个配置文件,加到项目的根目录下面:
tx.properties
url=http://127.0.0.1:7000/tx/manager/
怎么使用
//事务发起方:@TxTransaction(isStart = true) /** * 测试分布式事务 */ @TxTransaction(isStart = true) @Transactional public void testM() { MallUser user = mallUserMapper.selectList(new EntityWrapper<MallUser>()).get(0); Random random = new Random(); int i = random.nextInt(100); user.setName(i + "FF"); mallUserMapper.updateById(user); log.info("i:" + i); MallItem mallItem = itemFeign.getItemById(1 + ""); mallItem.setItemDetail(i + "FF"); Boolean aBoolean = itemFeign.updateItemById(mallItem); log.info("item:" + aBoolean); throw new RuntimeException("333"); } //事务参与方@TxTransaction @Transactional @TxTransaction public Boolean updateItemById(MallItem mallItem){ boolean b = this.updateById(mallItem); // throw new RuntimeException("33"); return b; }
好了,经过以上步骤,分布式事务框架就集成成功了,固然一次就成功基本上不太可能,若是你们在集成的过程当中,碰到什么问题,能够上lcn分布式事务框架的官网(http://www.txlcn.org)
最后附上上面安装须要的代码,配置等:
连接:https://pan.baidu.com/s/1ulBb... 提取码:cftf