SpringCloud集成分佈式事務LCN (一)

前言

最近公司使用分佈式框架搭建本身的服務(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

相關文章
相關標籤/搜索