背景:对于cloud分布式架构,最难避免的就是多服务调用问题,然而在某些难以避免的情况下会发生一次请求会协调多个服务调用去完成这一个事件的任务,正式这种情况的存在,才会造成先调用的服务已经对数据库操作完成了,后面的对于数据库操作却失败了,在这种情况下就会存在数据库的一次操作数据处理残缺,所以才引入了今天我们要说的分布式事务。
官方解释:事务是由一组操作构成的可靠的独立的工作单元,事务具备ACID的特性,即原子性、一致性、隔离性和持久性。但是lcntx不生产事务,只是事务的协调者!
在一个分布式系统下存在多个模块协调来完成一次业务。那么就存在一次业务事务下可能横跨多种数据源节点的可能。TX-LCN将可以解决这样的问题。
例如存在服务模块A 、B、 C。A模块是mysql作为数据源的服务,B模块是基于redis作为数据源的服务,C模块是基于mongo作为数据源的服务。若需要解决他们的事务一致性就需要针对不同的节点采用不同的方案,并且统一协调完成分布式事务的处理。接下来进入正题
一.新建一个module,作为分布式事务服务(前面文章已经讲述如何创建,不再重复阐述),引入jar包
<!-- lcnTx start -->
<dependency>
<groupId>com.codingapi.txlcn</groupId>
<artifactId>txlcn-tm</artifactId>
<version>5.0.2.RELEASE</version>
</dependency>
<!-- lcnTx end -->
建表:
/*
Navicat Premium Data Transfer
Source Server : local
Source Server Type : MySQL
Source Server Version : 100309
Source Host : localhost:3306
Source Schema : tx-manager
Target Server Type : MySQL
Target Server Version : 100309
File Encoding : 65001
Date: 29/12/2018 18:35:59
*/
CREATE DATABASE IF NOT EXISTS
`tx-manager` DEFAULT CHARSET utf8 COLLATE utf8_general_ci;/*
tx-manager修改成为自己的数据库名
*/
USE `tx-manager`;
SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;
-- ----------------------------
-- Table structure for t_tx_exception
-- ----------------------------
DROP TABLE IF EXISTS `t_tx_exception`;
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,
`ex_state` tinyint(4) NULL DEFAULT NULL COMMENT '0 待处理 1已处理',
`remark` varchar(10240) NULL DEFAULT NULL COMMENT '备注',
`create_time` datetime(0) NULL DEFAULT NULL,
PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT
= 967 CHARACTER SET = utf8mb4 COLLATE =
utf8mb4_general_ci ROW_FORMAT = Dynamic;
SET FOREIGN_KEY_CHECKS = 1
二.配置文件有两种方案,第一种采用properties,这种是官方给出的配置文件格式
spring.application.name=TransactionManager
server.port=7970
spring.datasource.driver-class-name=com.mysql.jdbc.Driver
spring.datasource.url=jdbc:mysql://{hostname}:{port}/internethsp?characterEncoding=UTF-8&useUnicode=true&useSSL=true&serverTimezone=UTC
spring.datasource.username=root
spring.datasource.password=
spring.jpa.database-platform=org.hibernate.dialect.MySQL5InnoDBDialect
spring.jpa.hibernate.ddl-auto=none
#redis配置
spring.redis.host=10.10.10.242
spring.redis.password=
spring.redis.port=6379
spring.redis.database=0
spring.redis.timeout=60000
#事物日志配置
tx-lcn.logger.enabled=true
tx-lcn.logger.driver-class-name=${spring.datasource.driver-class-name}
tx-lcn.logger.jdbc-url=${spring.datasource.url}
tx-lcn.logger.username=${spring.datasource.username}
tx-lcn.logger.password=${spring.datasource.password}
第二种:因为我们已经搭建了统一配置管理中心,所以我们这次采用yml的配置文件格式,但是有一个问题就是如果只留下yml配置文件,将会启动失败,这或许是个bug,也或许是笔者技术太菜,不太清楚怎么处理这个,所以我用了一个另类的解决方案,
用了两个配置文件,但是properties的这个配置文件里面所有的东西都注释掉了,然后在用yml来配置,配置如下
spring:
application:
name: serviceConfig
profiles:
active: git
cloud:
config:
label: master
#这里的profile和远程的配置文件名字一致
profile: consult-master
uri: http://localhost:9527/serviceConfig
discovery:
enabled: true
service-id: fool-cloud-config
eureka:
instance:
prefer-ip-address: true
client:
serviceUrl:
defaultZone: http://admin:admin@127.0.0.1:9526/eureka/eureka
enabled: true
三.远程配置文件,第二篇文章说到我们的配置文件都将上远程配置,所以我们的远程库里面的配置文件为
server:
#端口号
port: 9529
spring:
application:
name: fool-cloud-lcn
datasource:
driver-class-name: com.mysql.jdbc.Driver
url: jdbc:mysql://{hostname}:{name}/internethsp?characterEncoding=UTF-8&useUnicode=true&useSSL=true&serverTimezone=UTC
username: root
password:
jpa:
database-platform: org.hibernate.dialect.MySQL5InnoDBDialect
hibernate.ddl-auto: none
#redis host名字
redis:
host: {hostname}
#redis端口号
port: 6379
#redis 密码
password:
# 心跳检测时间(ms). 默认为 30000
tx-lcn:
manager:
heart-time: 30000
# 分布式事务执行总时间(ms). 默认为36000
dtx-time: 8000 # 事务处理并发等级. 默认为机器逻辑核心数5倍
concurrent-level: 160
# 分布式事务锁超时时间 默认为-1,当-1时会用tx-lcn.manager.dtx-time的时间
dtx-lock-time: ${tx-lcn.manager.dtx-time} # 雪花算法的sequence位长度,默认为12位.
seq-len: 12 # 异常回调开关。开启时请制定ex-url
ex-url-enabled: false # TM后台登陆密码,默认值为123456
admin-key: 123456
#注册中心配置
#邮箱配置
#tx-lcn.manager.ex-url=/provider/email-to/18628960476@163.com
#TM监听IP. 默认为 127.0.0.1
host: 127.0.0.1
#TM监听
port: 8070 # 开启日志,默认为false
logger:
enabled: true
driver-class-name: ${spring.datasource.driver-class-name}
jdbc-url: ${spring.datasource.url}
username: ${spring.datasource.username}
password: ${spring.datasource.password}
logging:
level:
com:
codingapi:
txlcn: DEBUG
eureka:
instance:
prefer-ip-address: true
client:
serviceUrl:
defaultZone: http://admin:admin@127.0.0.1:9526/eureka/eureka/
enabled: true #spring.mail.host=smtp.163.com
#spring.mail.port=587
#spring.mail.username=18628960476@163.com
#spring.mail.password=334353
当然这里为了支持datasource还需要引入jar
<properties>
<lcn.version>5.0.2.RELEASE</lcn.version>
<config.version>2.1.1.RELEASE</config.version>
<jdbc.version>5.2.2.RELEASE</jdbc.version>
<mysql.version>5.1.6</mysql.version>
</properties>
<!-- lcnTx end -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>${mysql.version}</version>
<scope>runtime</scope>
</dependency>
<!-- jdbc start -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>${jdbc.version}</version>
</dependency>
<!-- jdbc end -->
<!-- jdbc start -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-config-client</artifactId>
<version>${config.version}</version>
</dependency>
<!-- jdbc end -->
四.接下来启动我们的统一配置中心,并且访问hostname+port会发现
配置成功了,再输入我们刚刚配置的初始密码登录就会进入到事务的后台管理了,这样统一事务管理中心就搭建完成了,然后我们原来的example服务上增加配置,来模拟事务客户端。
五.需要引入的jar,因为需要用到持久层,所以这里选用的mysql和jdbc链接
<properties>
<config.version>2.1.1.RELEASE</config.version>
<tc.version>5.0.2.RELEASE</tc.version>
<tc.netty.version>5.0.2.RELEASE</tc.netty.version>
<jdbc.version>5.2.2.RELEASE</jdbc.version>
<mysql.version>5.1.6</mysql.version>
</properties>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>${mysql.version}</version>
<scope>runtime</scope>
</dependency>
<!--事务管理 start-->
<dependency>
<groupId>com.codingapi.txlcn</groupId>
<artifactId>txlcn-tc</artifactId>
<version>${tc.version}</version>
</dependency>
<dependency>
<groupId>com.codingapi.txlcn</groupId>
<artifactId>txlcn-txmsg-netty</artifactId>
<version>${tc.netty.version}</version>
</dependency>
<!--事务管理 end-->
<!-- jdbc start -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>${jdbc.version}</version>
</dependency>
<!-- jdbc end -->
然后在启动类增加@EnableDistributedTransaction注解! 你以为这样就完了么?兄弟莫慌,快成功了,我们客户端肯定要配置注册到哪个事务管理中心撒,在example的远程配置文件增加如下配置
tx-lcn:
client:
manager-address: 127.0.0.1:8070
ribbon:
loadbalancer:
dtx:
enabled: true
logger:
enabled: true
driver-class-name: ${spring.datasource.driver-class-name}
jdbc-url: ${spring.datasource.url}
username: ${spring.datasource.username}
password: ${spring.datasource.password}
接下来可以启动了,有没有发现启动成功了,然后我们再来看第四步的登录事务管理后台,登录进去会发现有一个事务客户端注册进来,证明我们的搭建成功了。如果想试试分布式事务的效果,可以多搭建几个服务相互调用来测试,之前笔者采用的是cloud完整架构请求来测试的。
今天我们的搭建分布式事务管理就搭建成功了,下期将开始搭建gateway负载均衡式转发了,敬请期待!