当前位置: 首页 > news >正文

SpringCloud集成分布式事务LCN (一)

前言

最近公司使用分布式框架搭建自己的服务(springCloud),就会遇到各位大神都会遇到的神级烦问题:分布式事务问题,近期研究了一下lcn框架的源码,觉得很不错,于是果断选用lcn控制分布式事务。选用原因如下:

1.完全开源,可以在官方的基础上做自己的各种修改
2.不仅支持springCloud,而且支持dubbo
3.数据库方面,支持mybatis,jdbc等

正如lcn官网所说: LCN并不生产事务,LCN只是本地事务的协调者
官网(http://www.txlcn.org)
情景分析

假如现在你要通过携程官网买上海到大理的机票,而正好没有上海直达大理的飞机,因此你需要从昆明中转。此时伟大的携程已经给你推荐了一条最优的中转路线
(东航)上海->昆明 + (南航) 昆明->大理
你觉得满意,于是屁颠屁颠的准备一键下单了....
我们来分析一下,你点击下单以后,携程做了哪些事:

(ps 如果携程这样做:
  1.先看东航有没有上海到昆明的票,有,扣票,付钱,提交事务
  2.再看南航有没有昆明到大理的票,有,扣票,付钱,提交事务
  但是,刚好有那么一次,做完1后,发现2居然没票了!!
  你就郁闷了,你本来想到大理,但是你只买了去昆明的票,你肯定不乐意
)

首先携程向东航尝试性发送扣票请求(不提交扣票的事务),如果有票,好,继续向南航发送尝试性扣票请求(不提交扣票的事务),如果都有票,那么一次性提交两边的事务,如果没有一边没票,则回滚两个事务.

综上,我们可不可以通过一个三方,来控制我们各个模块的事务呢,答案肯定是有的,LCN就是

下面,先阐述怎么集成,怎么使用,然后下一篇文章阐述源码分析

怎么集成

前提:springCloud服务是使用feign实现内部服务之间的通信

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});
        }

    }
}

这个包路径下,还要有这两个类:

/**
 *  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;
}

}

 //----------------------类分割线---------------------
/**
 * 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

相关文章:

  • 实验报告四 恶意代码技术
  • 转载的项目
  • OpenCASCADE Face Normals
  • PgAUT插件的原理
  • beetl的内置函数 (如strutil 工具类)
  • JDK命令行(jps、jstat、jinfo、jmap、jhat、jstack、jstatd、hprof)与JConsole
  • Aliyun ECS 重置系统
  • Composite组合模式(结构型模式)
  • SQL基础知识
  • 开放源代码库指南
  • WPF换肤之三:WPF中的WndProc
  • 【转】VUE 爬坑之旅-- 如何对公共JS,CSS进行统一管理,全局调用
  • 各个浏览器之间常见的兼容性问题
  • 为什么需要RPC,而不是简单的HTTP接口
  • 和开源硬件相关的几个词,免费、山寨、创客教育,以及未来 | COSCon'18
  • Create React App 使用
  • Docker下部署自己的LNMP工作环境
  • es6(二):字符串的扩展
  • HTTP中的ETag在移动客户端的应用
  • Java 23种设计模式 之单例模式 7种实现方式
  • Material Design
  • overflow: hidden IE7无效
  • python学习笔记 - ThreadLocal
  • Shadow DOM 内部构造及如何构建独立组件
  • Windows Containers 大冒险: 容器网络
  • 从0实现一个tiny react(三)生命周期
  • 关于 Cirru Editor 存储格式
  • 技术攻略】php设计模式(一):简介及创建型模式
  • 警报:线上事故之CountDownLatch的威力
  • 使用前端开发工具包WijmoJS - 创建自定义DropDownTree控件(包含源代码)
  • 试着探索高并发下的系统架构面貌
  • 译自由幺半群
  • 分布式关系型数据库服务 DRDS 支持显示的 Prepare 及逻辑库锁功能等多项能力 ...
  • ​决定德拉瓦州地区版图的关键历史事件
  • ​直流电和交流电有什么区别为什么这个时候又要变成直流电呢?交流转换到直流(整流器)直流变交流(逆变器)​
  • #pragma once
  • (22)C#传智:复习,多态虚方法抽象类接口,静态类,String与StringBuilder,集合泛型List与Dictionary,文件类,结构与类的区别
  • (C#)一个最简单的链表类
  • (env: Windows,mp,1.06.2308310; lib: 3.2.4) uniapp微信小程序
  • (七)Knockout 创建自定义绑定
  • (亲测成功)在centos7.5上安装kvm,通过VNC远程连接并创建多台ubuntu虚拟机(ubuntu server版本)...
  • (四)模仿学习-完成后台管理页面查询
  • (新)网络工程师考点串讲与真题详解
  • (转)shell中括号的特殊用法 linux if多条件判断
  • .bat批处理(十):从路径字符串中截取盘符、文件名、后缀名等信息
  • .NET 2.0中新增的一些TryGet,TryParse等方法
  • .NET 的静态构造函数是否线程安全?答案是肯定的!
  • .NET(C#) Internals: as a developer, .net framework in my eyes
  • .NET中使用Protobuffer 实现序列化和反序列化
  • .project文件
  • @Bean, @Component, @Configuration简析
  • @JoinTable会自动删除关联表的数据
  • [2021]Zookeeper getAcl命令未授权访问漏洞概述与解决
  • [AIGC] SQL中的数据添加和操作:数据类型介绍
  • [Android Pro] android 混淆文件project.properties和proguard-project.txt