Spring Cloud Alibaba使用Seata解决分布式事务全过程解析

eureka {

serviceUrl = “http://localhost:8761/eureka”

application = “default”

weight = “1”

}

redis {

serverAddr = “localhost:6379”

db = 0

password = “”

cluster = “default”

timeout = 0

}

zk {

cluster = “default”

serverAddr = “127.0.0.1:2181”

sessionTimeout = 6000

connectTimeout = 2000

username = “”

password = “”

}

consul {

cluster = “default”

serverAddr = “127.0.0.1:8500”

}

etcd3 {

cluster = “default”

serverAddr = “http://localhost:2379”

}

sofa {

serverAddr = “127.0.0.1:9603”

application = “default”

region = “DEFAULT_ZONE”

datacenter = “DefaultDataCenter”

cluster = “default”

group = “SEATA_GROUP”

addressWaitTime = “3000”

}

file {

name = “file.conf”

}

}

config {

file、nacos 、apollo、zk、consul、etcd3

使用nacos管理配置

type = “nacos”

nacos {

nacos ip

serverAddr = “127.0.0.1:8848”

所在命名空间

namespace = “7e3699fa-09eb-4d47-8967-60f6c98da94a”

所在分组

group = “EXAMPLE-GROUP”

username = “nacos”

password = “nacos”

}

consul {

serverAddr = “127.0.0.1:8500”

}

apollo {

appId = “seata-server”

apolloMeta = “http://192.168.1.204:8801”

namespace = “application”

}

zk {

serverAddr = “127.0.0.1:2181”

sessionTimeout = 6000

connectTimeout = 2000

username = “”

password = “”

}

etcd3 {

serverAddr = “http://localhost:2379”

}

file {

name = “file.conf”

}

}

以上内容主要修改了注册中心与配置中心为Nacos并且修改了Nacos地址与登录账号/登录密码,命名空间,分组;

配置部署到Nacos

==========

这里简化了下Nacos官网下载的config.txt内容,从官网下载的配置文本以下内容标记需要修改的需要关注

Copy#事务组 重点关注

service.vgroupMapping.my_test_tx_group=default

#服务段分组地址

service.default.grouplist=127.0.0.1:8091

#保持默认

service.enableDegrade=false

#保持默认

service.disableGlobalTransaction=false

#存储方式选择 db模式则数据库

store.mode=db

#需修改

store.lock.mode=db

#需修改

store.session.mode=db

store.publicKey=

#需修改

store.db.datasource=druid

#需修改

store.db.dbType=mysql

#需修改

store.db.driverClassName=com.mysql.jdbc.Driver

#需修改

store.db.url=jdbc:mysql://127.0.0.1:3306/seata?useUnicode=true&rewriteBatchedStatements=true

#需修改

store.db.user=root

#需修改

store.db.password=123456

store.db.minConn=5

store.db.maxConn=30

store.db.globalTable=global_table

store.db.branchTable=branch_table

store.db.distributedLockTable=distributed_lock

store.db.queryLimit=100

store.db.lockTable=lock_table

store.db.maxWait=5000

client.undo.dataValidation=true

#需修改

#jackson改为kryo 解决数据库Datetime类型问题

client.undo.logSerialization=kryo

client.undo.onlyCareUpdateColumns=true

server.undo.logSaveDays=7

server.undo.logDeletePeriod=86400000

client.undo.logTable=undo_log

client.undo.compress.enable=true

client.undo.compress.type=zip

client.undo.compress.threshold=64k

log.exceptionRate=100

transport.serialization=seata

transport.compressor=none

其中该配置需要重点关注service.vgroupMapping.my_test_tx_group=default这里的配置与微服务应用中的配置必须要一致后面会描述到。

由于有时间类型是Seata回滚反序列化Date类型无法成功反序列化,需要修改序列化方式解决该问题: client.undo.logSerialization=kryo

修改完所有配置运行从官网下载的nacos-config.sh文件将文本内容上次到nacos配置中心中:

Copy# -h ip -p 端口 -t 命名空间 -g 分组

sh nacos-config.sh -h localhost -p 8848 -t 7e3699fa-09eb-4d47-8967-60f6c98da94a -g EXAMPLE-GROUP

部署好配置文件之后在Nacos命名空间为7e3699fa-09eb-4d47-8967-60f6c98da94a(dev)的配置管理界面可以看到文本中的内容。

Spring Cloud Alibaba使用Seata解决分布式事务全过程解析

Seata数据库

========

按照config.txt中对应的数据库连接信息创建Seata数据库并且创建以下几张表

CopyCREATE TABLE IF NOT EXISTS global_table

(

xid VARCHAR(128) NOT NULL,

transaction_id BIGINT,

status TINYINT NOT NULL,

application_id VARCHAR(32),

transaction_service_group VARCHAR(32),

transaction_name VARCHAR(128),

timeout INT,

begin_time BIGINT,

application_data VARCHAR(2000),

gmt_create DATETIME,

gmt_modified DATETIME,

PRIMARY KEY (xid),

KEY idx_gmt_modified_status (gmt_modified, status),

KEY idx_transaction_id (transaction_id)

) ENGINE = InnoDB

DEFAULT CHARSET = utf8;

– the table to store BranchSession data

CREATE TABLE IF NOT EXISTS branch_table

(

branch_id BIGINT NOT NULL,

xid VARCHAR(128) NOT NULL,

transaction_id BIGINT,

resource_group_id VARCHAR(32),

resource_id VARCHAR(256),

branch_type VARCHAR(8),

status TINYINT,

client_id VARCHAR(64),

application_data VARCHAR(2000),

gmt_create DATETIME(6),

gmt_modified DATETIME(6),

PRIMARY KEY (branch_id),

KEY idx_xid (xid)

) ENGINE = InnoDB

DEFAULT CHARSET = utf8;

– the table to store lock data

CREATE TABLE IF NOT EXISTS lock_table

(

row_key VARCHAR(128) NOT NULL,

xid VARCHAR(96),

transaction_id BIGINT,

branch_id BIGINT NOT NULL,

resource_id VARCHAR(256),

table_name VARCHAR(32),

pk VARCHAR(36),

gmt_create DATETIME,

gmt_modified DATETIME,

PRIMARY KEY (row_key),

KEY idx_branch_id (branch_id)

) ENGINE = InnoDB

DEFAULT CHARSET = utf8;

部署Seata Server

==============

以上工作准备就绪,进入bin目录运行seata-server.bat(windows用户)/seata-server.sh(linux用户)即可。

Seata应用场景模拟#

============

这里做一个用户服务用户登录成功后调用会员服务增加会员积分场景案例。

父工程改造

=====

工程名称:spring-cloud-alibaba-version-parent,增加mybatis,seata序列化等依赖版本管理。

Copy

<mybatis.plus.version>3.4.2</mybatis.plus.version>

<mybatis.plus.ds.version>2.5.4</mybatis.plus.ds.version>

<seata.serializer.kryo.version>1.3.0</seata.serializer.kryo.version>

com.baomidou

mybatis-plus-boot-starter

${mybatis.plus.version}

io.seata

seata-serializer-kryo

${seata.serializer.kryo.version}

会员服务工程改造

========

工程名称:spring-cloud-alibaba-service-member,增加数据库与Seata依赖,增加用户会员积分接口。

pom.xml

Copy

com.alibaba.cloud

spring-cloud-starter-alibaba-seata

io.seata

seata-serializer-kryo

com.baomidou

mybatis-plus-boot-starter

mysql

mysql-connector-java

bootstrap.yaml

Copy#注意,此处省略之前配置的信息…

#注意,此处省略之前配置的信息…

#注意,此处省略之前配置的信息…

#注意,此处省略之前配置的信息…

#数据库信息配置

spring:

datasource:

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

url: jdbc:mysql://localhost:3306/member_db?serverTimezone=Asia/Shanghai&useUnicode=true&characterEncoding=utf-8&useSSL=false&allowPublicKeyRetrieval=true

username: root

password: 123456

#Seata配置

seata:

enabled: true

application-id: ${spring.application.name}

#对应nacos配置 service.vgroupMapping.my_test_tx_group

tx-service-group: ‘my_test_tx_group’

service:

vgroup-mapping:

#对应nacos配置 service.vgroupMapping.my_test_tx_group 的值 default

my_test_tx_group: ‘default’

registry:

type: nacos

nacos:

server-addr: ${spring.cloud.nacos.discovery.server-addr}

namespace: ${spring.cloud.nacos.discovery.namespace}

group: ${spring.cloud.nacos.discovery.group}

#cluster: ${spring.cloud.nacos.discovery.cluster}

config:

type: nacos

nacos:

server-addr: ${spring.cloud.nacos.discovery.server-addr}

namespace: ${spring.cloud.nacos.discovery.namespace}

group: ${spring.cloud.nacos.discovery.group}

注意事项:

  1. bootstrap.yaml中seata.tx-service-group 配置项一定要配置nacos配置中心中service.vgroupMapping对应的my_test_tx_group。也就是说一定要保持一致。

  2. bootstrap.yaml中seata.service.vgroup-mapping.my_test_tx_group配置项一定要配置nacos配置中心对应service.vgroupMapping.my_test_tx_group配置祥的值。

如果没有注意上方两点将会导致启动时报:no available service ‘default’ found, please make sure registry config correct。

创建member_db数据库

其中undo_log表为Seata回滚日志表,需要在每个使用到Seata的业务服务数据库中都需要创建。

CopySET NAMES utf8mb4;

SET FOREIGN_KEY_CHECKS = 0;


– Table structure for t_member_integral


DROP TABLE IF EXISTS t_member_integral;

CREATE TABLE t_member_integral (

ID bigint(20) NOT NULL COMMENT ‘主键’,

USERNAME varchar(55) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT ‘用户名称’,

INTEGRAL int(11) DEFAULT NULL COMMENT ‘积分’,

CREDATE datetime(0) DEFAULT NULL COMMENT ‘时间’,

PRIMARY KEY (ID) USING BTREE

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


– Table structure for undo_log


DROP TABLE IF EXISTS undo_log;

CREATE TABLE undo_log (

id bigint(20) NOT NULL AUTO_INCREMENT,

branch_id bigint(20) NOT NULL,

xid varchar(100) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL,

context varchar(128) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL,

rollback_info longblob NOT NULL,

log_status int(11) NOT NULL,

log_created datetime(0) NOT NULL,

log_modified datetime(0) NOT NULL,

ext varchar(100) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL,

PRIMARY KEY (id) USING BTREE,

UNIQUE INDEX ux_undo_log(xid, branch_id) USING BTREE

) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;

SET FOREIGN_KEY_CHECKS = 1;

新增会员积分CRUD

我这里新增以下类,具体内容大家都比较熟悉。

CopyMemberIntegralController.java

IMemberIntegralBiz.java

IMemberIntegralBizImpl.java

MemberIntegralMapper.java

MemberIntegral.xml

在这里所有增加会员积分的逻辑都写在同一个类中 MemberIntegralController.java

Copyimport com.baomidou.mybatisplus.core.toolkit.IdWorker;

import com.gitee.eample.member.service.biz.IMemberIntegralBiz;

import com.gitee.eample.member.service.domain.MemberIntegral;

import com.gtiee.example.common.exception.Response;

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

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

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

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

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

import java.util.Date;

/**

  • 用户积分

  • @author wentao.wu

*/

@RestController

@RequestMapping(“/member/integral”)

public class MemberIntegralController {

@Autowired

private IMemberIntegralBiz memberIntegralBiz;

@PostMapping(“/login/{username}”)

public Response login(@PathVariable(“username”) String username) {

// 每天第一次登录则增加积分,我这里就不判断了,每次调用都新增一条积分记录了

MemberIntegral memberIntegral = new MemberIntegral();

memberIntegral.setId(IdWorker.getId());

memberIntegral.setIntegral(10);//固定10积分

memberIntegral.setUsername(username);

memberIntegral.setCredate(new Date());

memberIntegralBiz.save(memberIntegral);

return Response.createOk(“登录新增会员积分成功!”, true);

}

}

运行MemberServiceApplication.java启动服务,如果想知道有没有注册成功:

第一可以看Seata Server端有没有日志输出,该日志内容主要为注册的业务服务的数据库信息。

第二可以看业务服务有没有输出以下日志,有输出以下日志则Seata Server端注册成功

Copy2021-11-05 09:56:30.962 INFO 16420 — [ main] i.s.c.r.netty.NettyClientChannelManager : will connect to 2.0.4.58:8091

2021-11-05 09:56:30.962 INFO 16420 — [ main] i.s.c.rpc.netty.RmNettyRemotingClient : RM will register :jdbc:mysql://localhost:3306/member_db

2021-11-05 09:56:30.967 INFO 16420 — [ main] i.s.core.rpc.netty.NettyPoolableFactory : NettyPool create channel to transactionRole:RMROLE,address:2.0.4.58:8091,msg:< RegisterRMRequest{resourceIds=‘jdbc:mysql://localhost:3306/member_db’, applicationId=‘service-member’, transactionServiceGroup=‘my_test_tx_group’} >

用户服务工程改造

========

工程名称:spring-cloud-alibaba-service-member,增加数据库与Seata依赖,增加用户登录接口,增加调用会员服务积分接口feign。

由于内容一致此处省略pom.xml,bootstrap.xml(里面注意数据库要修改为用户服务的数据库)。

创建user_db数据库

其中undo_log表为Seata回滚日志表,需要在每个使用到Seata的业务服务数据库中都需要创建。

Copy

SET NAMES utf8mb4;

SET FOREIGN_KEY_CHECKS = 0;


– Table structure for t_user


DROP TABLE IF EXISTS t_user;

CREATE TABLE t_user (

ID bigint(20) NOT NULL COMMENT ‘主键’,

USERNAME varchar(55) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT ‘用户名’,

PWD varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT ‘密码’,

ADDR varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT ‘地址’,

LAST_LOGIN_DATE datetime(0) DEFAULT NULL COMMENT ‘最后登录时间’,

PRIMARY KEY (ID) USING BTREE

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


– Records of t_user


INSERT INTO t_user VALUES (1, ‘test1’, ‘123456’, ‘123’, NULL);


– Table structure for undo_log


DROP TABLE IF EXISTS undo_log;

CREATE TABLE undo_log (

id bigint(20) NOT NULL AUTO_INCREMENT,

branch_id bigint(20) NOT NULL,

xid varchar(100) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL,

context varchar(128) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL,

rollback_info longblob NOT NULL,

log_status int(11) NOT NULL,

log_created datetime(0) NOT NULL,

log_modified datetime(0) NOT NULL,

ext varchar(100) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL,

PRIMARY KEY (id) USING BTREE,

UNIQUE INDEX ux_undo_log(xid, branch_id) USING BTREE

) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;

SET FOREIGN_KEY_CHECKS = 1;

新增用户登录CRUD

我这里新增以下类,具体内容大家都比较熟悉。

CopyUserController.java

IUserBiz.java

IUserBizImpl.java

UserMapper.java

UserMapper.xml

MemberInfoControllerClient.java

MemberInfoControllerClient.java

Copy/**

  • service-member服务远程调用接口

  • @author wentao.wu

*/

@FeignClient(name = “service-member”)

public interface MemberInfoControllerClient {

/**

  • 登录送积分

  • @param username

  • @return

*/

@PostMapping(“/member/integral/login/{username}”)

Response login(@PathVariable(“username”)String username);

}

IUserBiz.java

Copypublic interface IUserBiz extends IService {

/**

  • 用户登录并且赠送第一次登录积分

  • @param command

  • @return

*/

boolean login(UserLoginCommand command);

}

IUserBizImpl.java

Copypackage com.gitee.eample.user.service.biz;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;

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

import com.gitee.eample.user.service.controller.command.UserLoginCommand;

import com.gitee.eample.user.service.dao.UserMapper;

import com.gitee.eample.user.service.domain.User;

import com.gitee.eample.user.service.feign.MemberInfoControllerClient;

import com.gtiee.example.common.exception.Response;

import io.seata.spring.annotation.GlobalTransactional;

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

import org.springframework.stereotype.Service;

import org.springframework.util.ObjectUtils;

import java.util.Date;

@Service

public class IUserBizImpl extends ServiceImpl<UserMapper, User> implements IUserBiz {

@Autowired

private MemberInfoControllerClient client;

@GlobalTransactional(name = “login_add_member_intergral”,rollbackFor = Exception.class)//开启分布式事务

@Override

public boolean login(UserLoginCommand command) {

LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>();

wrapper.eq(User::getUsername, command.getUsername())

.eq(User::getPwd, command.getPwd());

User loginUser = getOne(wrapper);

if (ObjectUtils.isEmpty(loginUser)) {

return false;

}

//调用会员登录接口增加积分

Response response = client.login(command.getUsername());

if (response.isOk()) {//增加积分成功,或已增加积分

//调用积分接口成功,修改当前用户登录时间

loginUser.setLastLoginDate(new Date());

updateById(loginUser);

//假设此处发生异常,不但修改当前用户登录时间需要回滚并且新增的会员积分信息也回滚才算正常

int i = 0 / 0;

return true;

} else {

//增加积分失败

return false;

}

}

}

UserController.java

Copyimport com.gitee.eample.user.service.biz.IUserBiz;

import com.gitee.eample.user.service.controller.command.UserLoginCommand;

import com.gtiee.example.common.exception.Response;

import org.slf4j.Logger;

import org.slf4j.LoggerFactory;

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

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

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

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

/**

  • User Business Controller

  • @author wentao.wu

*/

@RestController

@RequestMapping(“/users/”)

public class UserController {

private Logger logger = LoggerFactory.getLogger(UserController.class);

@Autowired

private IUserBiz userBiz;

@PostMapping(“/login”)

public Response login(UserLoginCommand command) {

try {

boolean result = userBiz.login(command);

if (result) {

return Response.createOk(“登录并赠送积分成功!”, result);

}else{

return Response.createError(“账号或密码不存在!”, result);

}

} catch (Exception e) {

logger.error(“登录失败!”, e);

return Response.createError(“服务器繁忙请稍后再试!”, false);

}

}

}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值