.net mysql 分布式事务处理_通过LCN实现分布式事务

简介

为什么要使用分布式事务,就不详细介绍了,具体可以看CAP定律BASE理论和分布式事务解决方案,好了话不多说,一个字:干。

LCN介绍

官方宣称:LCN并不生产事务,LCN只是本地事务的协调工。

TX-LCN定位于一款事务协调性框架,框架其本身并不操作事务,而是基于对事务的协调从而达到事务一致性的效果。

LCN解决方案

在一个分布式系统下存在多个模块协调来完成一次业务。那么就存在一次业务事务下可能横跨多种数据源节点的可能。TX-LCN将可以解决这样的问题。

例如存在服务模块A 、B、 C。A模块是mysql作为数据源的服务,B模块是基于redis作为数据源的服务,C模块是基于mongo作为数据源的服务。若需要解决他们的事务一致性就需要针对不同的节点采用不同的方案,并且统一协调完成分布式事务的处理。

3e15d20ed308?utm_campaign=maleskine&utm_content=note&utm_medium=seo_notes&utm_source=recommendation

image.png

若采用TX-LCN分布式事务框架,则可以将A模块采用LCN模式、B/C采用TCC模式就能完美解决。

事务控制原理

TX-LCN由两大模块组成, TxClient、TxManager,TxClient作为模块的依赖框架,提供TX-LCN的标准支持,TxManager作为分布式事务的控制放。事务发起方或者参与反都由TxClient端来控制。

3e15d20ed308?utm_campaign=maleskine&utm_content=note&utm_medium=seo_notes&utm_source=recommendation

image.png

核心步骤

创建事务组

是指在事务发起方开始执行业务代码之前先调用TxManager创建事务组对象,然后拿到事务标示GroupId的过程。

加入事务组

添加事务组是指参与方在执行完业务方法以后,将该模块的事务信息通知给TxManager的操作。

通知事务组

是指在发起方执行完业务代码以后,将发起方执行结果状态通知给TxManager,TxManager将根据事务最终状态和事务组的信息来通知相应的参与模块提交或回滚事务,并返回结果给事务发起方。

实战之-TxManager

LCN官方文档中的快速开始,说实话,还真快速不了,首先咱们先看下官方文档怎么说的。

建中间件数据库,

名称为: tx-manager,并创建表。

CREATE TABLE `t_tx_exception` (

`id` bigint(20) NOT NULL AUTO_INCREMENT,

`group_id` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL,

`unit_id` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL,

`mod_id` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL,

`transaction_state` tinyint(4) NULL DEFAULT NULL,

`registrar` tinyint(4) NULL DEFAULT NULL,

`remark` varchar(4096) NULL DEFAULT NULL,

`ex_state` tinyint(4) NULL DEFAULT NULL COMMENT '0 未解决 1已解决',

`create_time` datetime(0) NULL DEFAULT NULL,

PRIMARY KEY (`id`) USING BTREE

) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci ROW_FORMAT = Dynamic;

TM服务搭建

3e15d20ed308?utm_campaign=maleskine&utm_content=note&utm_medium=seo_notes&utm_source=recommendation

image.png

将官方源码仓库中的txlcn-tm直接打包,然后运行即可,TM中间件就算搭起来了,如果直接把txlcn-tm拎出来,打包是打不成功的,因为txlcn-tm 依赖了其他的txlcn的其他服务,打包的话需要把其他包也一起打进去,我现在是直接本地导入IDE运行的,是可以的。

3e15d20ed308?utm_campaign=maleskine&utm_content=note&utm_medium=seo_notes&utm_source=recommendation

image.png

TM服务中 application.properties配置

spring.application.name=TransactionManager

server.port=7970

spring.datasource.driver-class-name=com.mysql.jdbc.Driver

spring.datasource.url=jdbc:mysql://127.0.0.1:3306/tx-manager?characterEncoding=UTF-8&usessl=false

spring.datasource.username=root

spring.datasource.password=root

spring.jpa.database-platform=org.hibernate.dialect.MySQL5InnoDBDialect

spring.jpa.hibernate.ddl-auto=update

#Redis配置

spring.redis.host=127.0.0.1

spring.redis.port=6379

spring.redis.password=

tx-lcn.manager.admin-key=lcn

#spring.application.name=TransactionManager

#server.port=7970

#

## JDBC 数据库配置

#spring.datasource.driver-class-name=com.mysql.jdbc.Driver

#spring.datasource.url=jdbc:mysql://127.0.0.1:3306/tx-manager?characterEncoding=UTF-8

#spring.datasource.username=root

#spring.datasource.password=123456

#

## 数据库方言

#spring.jpa.database-platform=org.hibernate.dialect.MySQL5InnoDBDialect

#

## 第一次运行可以设置为: create, 为TM创建持久化数据库表

#spring.jpa.hibernate.ddl-auto=validate

#

## TM监听IP. 默认为 127.0.0.1

#tx-lcn.manager.host=127.0.0.1

#

## TM监听Socket端口. 默认为 ${server.port} - 100

#tx-lcn.manager.port=8070

#

## 心跳检测时间(ms). 默认为 300000

#tx-lcn.manager.heart-time=300000

#

## 分布式事务执行总时间(ms). 默认为36000

#tx-lcn.manager.dtx-time=8000

#

## 参数延迟删除时间单位ms 默认为dtx-time值

#tx-lcn.message.netty.attr-delay-time=${tx-lcn.manager.dtx-time}

#

## 事务处理并发等级. 默认为机器逻辑核心数5倍

#tx-lcn.manager.concurrent-level=160

#

## TM后台登陆密码,默认值为codingapi

#tx-lcn.manager.admin-key=codingapi

#

## 分布式事务锁超时时间 默认为-1,当-1时会用tx-lcn.manager.dtx-time的时间

#tx-lcn.manager.dtx-lock-time=${tx-lcn.manager.dtx-time}

#

## 雪花算法的sequence位长度,默认为12位.

#tx-lcn.manager.seq-len=12

#

## 异常回调开关。开启时请制定ex-url

#tx-lcn.manager.ex-url-enabled=false

#

## 事务异常通知(任何http协议地址。未指定协议时,为TM提供内置功能接口)。默认是邮件通知

#tx-lcn.manager.ex-url=/provider/email-to/***@**.com

#注意(NOTE)

#(1) TxManager所有配置均有默认配置,请按需覆盖默认配置。

#(2) 特别注意 TxManager进程会监听两个端口号,一个为TxManager端口,另一个是事务消息端口。TxClient默认连接事务消息端口是8070, 所以,为保证TX-LCN基于默认配置运行良好,请设置TxManager端口号为8069 或者指定事务消息端口为8070

#(3) 分布式事务执行总时间 a 与 TxClient通讯最大等待时间 b、TxManager通讯最大等待时间 c、微服务间通讯时间 d、微服务调用链长度 e 几个时间存在着依赖关系。 a >= 2c + (b + c + d) * (e - 1), 特别地,b、c、d 一致时,a >= (3e-1)b。你也可以在此理论上适当在减小a的值,发生异常时能更快得到自动补偿,即 a >= (3e-1)b - Δ(原因)。 最后,调用链小于等于3时,将基于默认配置运行良好

#(4) 若用tx-lcn.manager.ex-url=/provider/email-to/xxx@xx.xxx 这个配置,配置管理员邮箱信息(如QQ邮箱):

#spring.mail.host=smtp.qq.com

#spring.mail.port=587

#spring.mail.username=xxxxx@**.com

启动成功后界面

3e15d20ed308?utm_campaign=maleskine&utm_content=note&utm_medium=seo_notes&utm_source=recommendation

image.png

TxManager进程会监听两个端口号,一个为TxManager 7970 端口,另一个是事务消息端口 8070。

实战之-TxClient

场景简介

AB两个服务,A(producer)服务调用B(consumer)服务完毕后,抛出异常,B服务能回滚,确保幂等。

AB环境:MybatisPlus,Erueka,Feign,这些具体就不细讲了

服务结构

3e15d20ed308?utm_campaign=maleskine&utm_content=note&utm_medium=seo_notes&utm_source=recommendation

image.png

producer 服务

Pom.xml

xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">

org.springframework.boot

spring-boot-starter-parent

2.1.2.RELEASE

com.glj

producer

0.0.1-SNAPSHOT

producer

4.0.0

Demo project for Spring Boot

UTF-8

UTF-8

1.8

5.0.2.RELEASE

org.springframework.boot

spring-boot-starter-web

org.springframework.boot

spring-boot-starter-test

test

org.springframework.boot

spring-boot-starter-freemarker

org.springframework.cloud

spring-cloud-starter-netflix-eureka-client

org.springframework.cloud

spring-cloud-starter-openfeign

com.google.guava

guava

27.0-jre

mysql

mysql-connector-java

runtime

org.projectlombok

lombok

true

com.baomidou

mybatis-plus-boot-starter

3.0.6

io.springfox

springfox-swagger2

2.8.0

org.mapstruct

mapstruct

io.springfox

springfox-swagger-ui

2.8.0

com.codingapi.txlcn

txlcn-tc

${codingapi.txlcn.version}

com.codingapi.txlcn

txlcn-txmsg-netty

${codingapi.txlcn.version}

org.springframework.boot

spring-boot-maven-plugin

org.springframework.cloud

spring-cloud-dependencies

Greenwich.RELEASE

pom

import

application.yml

spring:

application:

name: spring-cloud-producer

datasource:

url: jdbc:mysql://127.0.0.1:3306/spring_cloud_app?serverTimezone=GMT%2B8&characterEncoding=utf-8

password: root

username: root

driver-class-name: com.mysql.cj.jdbc.Driver

redis:

host: 127.0.0.1

port: 6379

server:

port: 8081

eureka:

client:

service-url:

defaultZone: http://glj:glj@127.0.0.1:2100/eureka/

instance:

prefer-ip-address: true

instance-id: ${spring.cloud.client.ipAddress}:${server.port}

#swagger

swagger2:

enable: true

tx-lcn:

client:

manager-address: 127.0.0.1:8070

服务入口

package com.glj.producer;

import com.codingapi.txlcn.tc.config.EnableDistributedTransaction;

import org.springframework.boot.SpringApplication;

import org.springframework.boot.autoconfigure.SpringBootApplication;

import org.springframework.cloud.client.discovery.EnableDiscoveryClient;

import org.springframework.cloud.openfeign.EnableFeignClients;

@SpringBootApplication

@EnableDiscoveryClient

@EnableFeignClients

@EnableDistributedTransaction

public class ProducerApplication {

public static void main(String[] args) {

SpringApplication.run(ProducerApplication.class, args);

}

}

package com.glj.producer.business.service.impl;

import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;

import com.codingapi.txlcn.tc.annotation.LcnTransaction;

import com.glj.producer.business.entity.UserPo;

import com.glj.producer.business.mapper.UserMapper;

import com.glj.producer.business.service.IUserService;

import com.glj.producer.client.ConsumerUserClinet;

import com.glj.producer.dto.ProducerRequst;

import com.google.common.base.Preconditions;

import lombok.extern.slf4j.Slf4j;

import org.apache.commons.lang.StringUtils;

import org.springframework.beans.factory.annotation.Autowired;

import org.springframework.stereotype.Service;

import org.springframework.transaction.annotation.Transactional;

/**

*

* 服务实现类

*

*

* @author gaoleijie

* @since 2019-05-21

*/

@Service

@Slf4j

public class UserServiceImpl extends ServiceImpl implements IUserService {

@Autowired

private ConsumerUserClinet consumerUserClinet;

@Autowired

private IUserService userService;

/**

* 使用分布式事务

* @param requst

* @return

*/

@Override

@LcnTransaction

@Transactional

public String aTob(ProducerRequst requst) {

log.info("开始调用B服务");

Boolean res = consumerUserClinet.saveUser(requst.getNickName(),requst.getId());

log.info("B服务return result{}",res);

UserPo userPo = userService.getById(requst.getId());

userPo.setUserName(requst.getNickName());

userService.saveOrUpdate(userPo);

Preconditions.checkArgument(StringUtils.isNotBlank(requst.getExFlag()),"exFlag is null");

if(res) {

return "scuess";

} else {

return "false";

}

}

/**

* 未使用分布式事务

* @param requst

* @return

*/

@Override

@Transactional

public String aTob1(ProducerRequst requst) {

log.info("开始调用B服务");

Boolean res = consumerUserClinet.saveUser1(requst.getNickName(),requst.getId());

log.info("B服务return result{}",res);

UserPo userPo = userService.getById(requst.getId());

userPo.setUserName(requst.getNickName());

userService.saveOrUpdate(userPo);

Preconditions.checkArgument(StringUtils.isNotBlank(requst.getExFlag()),"exFlag is null");

if(res) {

return "scuess";

} else {

return "false";

}

}

}

package com.glj.producer.business.controller;

import com.glj.producer.business.service.IUserService;

import com.glj.producer.dto.ProducerRequst;

import io.swagger.annotations.ApiOperation;

import org.springframework.beans.factory.annotation.Autowired;

import org.springframework.web.bind.annotation.*;

/**

*

* 前端控制器

*

*

* @author gaoleijie

* @since 2019-05-21

*/

@RestController

@RequestMapping("/user")

public class UserController {

@Autowired

private IUserService userService;

@ApiOperation(value = "A 服务调用B服务(使用分布式事务)")

@PostMapping("/aTob")

public String aTob(@RequestBody ProducerRequst requst){

return userService.aTob(requst);

}

@ApiOperation(value = "A 服务调用B服务(未使用分布式事务)")

@PostMapping("/aTob1")

public String aTob1(@RequestBody ProducerRequst requst){

return userService.aTob1(requst);

}

}

其实就是这么简单,核心代码就三处

一处是服务入口加注解@EnableDistributedTransaction

一处是service方法上加注解 @LcnTransaction

另一处是application.yml 指定事务消息端口

consumer 服务

Pom.xml

xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">

4.0.0

org.springframework.boot

spring-boot-starter-parent

2.1.2.RELEASE

com.glj

consumer

0.0.1-SNAPSHOT

consumer

Demo project for Spring Boot

UTF-8

UTF-8

1.8

5.0.2.RELEASE

Greenwich.RELEASE

2.8.0

org.springframework.boot

spring-boot-starter-freemarker

com.baomidou

mybatis-plus-boot-starter

3.0.6

org.apache.tomcat

tomcat-jdbc

mysql

mysql-connector-java

runtime

org.projectlombok

lombok

true

org.springframework.boot

spring-boot-starter-test

test

org.springframework.boot

spring-boot-starter-web

org.springframework.cloud

spring-cloud-starter-netflix-eureka-client

org.springframework.cloud

spring-cloud-starter-netflix-hystrix

org.springframework.cloud

spring-cloud-starter-openfeign

io.springfox

springfox-swagger2

2.8.0

org.mapstruct

mapstruct

io.springfox

springfox-swagger-ui

${swagger-version}

com.codingapi.txlcn

txlcn-tc

${codingapi.txlcn.version}

com.codingapi.txlcn

txlcn-txmsg-netty

${codingapi.txlcn.version}

com.google.guava

guava

27.0-jre

org.springframework.boot

spring-boot-maven-plugin

org.springframework.cloud

spring-cloud-dependencies

${springcloud.version}

pom

import

服务入口

package com.glj.consumer;

import com.codingapi.txlcn.tc.config.EnableDistributedTransaction;

import org.springframework.boot.SpringApplication;

import org.springframework.boot.autoconfigure.SpringBootApplication;

import org.springframework.cloud.client.discovery.EnableDiscoveryClient;

@SpringBootApplication

@EnableDiscoveryClient

@EnableDistributedTransaction

public class ConsumerApplication {

public static void main(String[] args) {

SpringApplication.run(ConsumerApplication.class, args);

}

}

package com.glj.consumer.business.service.impl;

import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;

import com.codingapi.txlcn.tc.annotation.LcnTransaction;

import com.glj.consumer.business.entity.SysUserPo;

import com.glj.consumer.business.mapper.SysUserMapper;

import com.glj.consumer.business.service.ISysUserService;

import org.springframework.stereotype.Service;

import org.springframework.transaction.annotation.Transactional;

/**

*

* 服务实现类

*

*

* @author gaoleijie

* @since 2019-05-21

*/

@Service

public class SysUserServiceImpl extends ServiceImpl implements ISysUserService {

/**

* LCN实现分布式事务

* @param nickName

* @param Id

* @return

*/

@Override

@Transactional

@LcnTransaction

public Boolean saveUser(String nickName,Long Id){

SysUserPo user = this.getById(Id);

user.setNickname(nickName);

return this.saveOrUpdate(user);

}

/**

* 未实现分布式事务

* @param nickName

* @param Id

* @return

*/

@Override

@Transactional

public Boolean saveUser1(String nickName,Long Id){

SysUserPo user = this.getById(Id);

user.setNickname(nickName);

return this.saveOrUpdate(user);

}

}

还是那句话,就这么简单,跟生产者同样三处

效果

3e15d20ed308?utm_campaign=maleskine&utm_content=note&utm_medium=seo_notes&utm_source=recommendation

image.png

3e15d20ed308?utm_campaign=maleskine&utm_content=note&utm_medium=seo_notes&utm_source=recommendation

image.png

结果表明使用分布式以后,异常后数据回滚

没有使用分布式,异常后,AB数据不一致

结束语

LCN只是解决分布式事务的第三方,目前阿里的GTS 也很成熟了,后期将继续为大家分解通过GTS怎么解决分布式事务问题。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值