登陆

极彩论坛2018-分布式业务怎么做?Spring Cloud Alibaba Seata告知你

admin 2019-10-29 186人围观 ,发现0个评论

假如觉得我文章对您有协助能够重视大众号:极客挖掘机,取得每日干货推送

Spring Cloud Alibaba | 微服务分布式事务之Seata

本篇实战所运用Spring有关版别:

SpringBoot:2.1.7.RELEASE

Spring Cloud:Greenwich.SR2

Spring CLoud Alibaba:2.1.0.RELEASE

1. 概述

在构建微服务的过程中,不管是运用什么结构、组件来构建,都绕不开一个问题,跨服务的事务操作怎么坚持数据共同性。

2. 什么是分布式事务?

首要,想象一个传统的单体运用,不管多少内部调用,终究终归是在同一个数据库上进行操作来完结一贯事务操作,如图:

跟着事务量的开展,事务需求和架构发生了巨大的改变,全体架构由原本的单体运用逐步拆分成为了微服务,原本的3个服务被从一个单体架构上拆开了,成为了3个独立的服务,别离运用独立的数据源,也不在之前同享同一个数据源了,详细的事务将由三个服务的调用来完结,如图:

此刻,每一个服务的内部数据共同性依然有本地事务来确保。可是面对整个事务流程上的事务应该怎么确保呢?这便是在微服务架构下面对的应战,怎么确保在微服务中的数据共同性。

3. 常见的分布式事务解决计划

3.1 两阶段提交计划/XA计划

所谓的 XA 计划,即两阶段提交,有一个事务办理器的概念,担任和谐多个数据库(资源办理器)的事务,事务办理器先问问各个数据库你预备好了吗?假如每个数据库都回复 ok,那么就正式提交事务,在各个数据库上履行操作;假如任何其间一个数据库答复不 ok,那么就回滚事务。

分布式体系的一个难点是怎么确保架构下多个节点在进行事务性操作的时分坚持共同性。为完结这个意图,二阶段提交算法的建立根据以下假定:

  • 该分布式体系中,存在一个节点作为和谐者(Coordinator),其他节点作为参与者(Cohorts)。且节点之间能够进行网络通信。
  • 一切节点都选用预写式日志,且日志被写入后即被坚持在牢靠的存储设备上,即便节点损坏不会导致日志数据的消失。
  • 一切节点不会永久性损坏,即便损坏后依然能够康复。

3.2 TCC 计划

TCC的全称是:Try、Confirm、Cancel。

  • Try 阶段:这个阶段说的是对各个服务的资源做检测以及对资源进行确定或许预留。
  • Confirm 阶段:这个阶段说的是在各个服务中履行实践的操作。
  • Cancel 阶段:假如任何一个服务的事务办法履行犯错,那么这儿就需求进行补偿,便是履行现已履行成功的事务逻辑的回滚操作。(把那些履行成功的回滚)

这种计划说实话简直很少人运用,可是也有运用的场景。因为这个事务回滚实践上是严峻依靠于你自己写代码来回滚和补偿了,会形成补偿代码巨大。

TCC的理论有点笼统,下面咱们凭借一个账务拆分这个实践事务场景对TCC事务的流程做一个描绘,期望对了解TCC有所协助。

事务流程:别离坐落三个不同分库的帐户A、B、C,A和B一同向C转帐共80元:

Try:测验履行事务。

完结一切事务检查(共同性):检查A、B、C的帐户状况是否正常,帐户A的余额是否不少于30元,帐户B的余额是否不少于50元。

预留有必要事务资源(准阻隔性):帐户A的冻住金额添加30元,帐户B的冻住金额添加50元,这样就确保不会呈现其他并发进程扣减了这两个帐户的余额而导致在后续的真实转帐操作过程中,帐户A和B的可用余额不行的状况。

Confirm:承认履行事务。

真实履行事务:假如Try阶段帐户A、B、C状况正常,且帐户A、B余额够用,则履行帐户A给账户C转账30元、帐户B给账户C转账50元的转帐操作。

不做任何事务检查:这时现已不需求做事务检查,Try阶段现已完结了事务检查。

只运用Try阶段预留的事务资源:只需求运用Try阶段帐户A和帐户B冻住的金额即可。

Cancel:撤销履行事务。

开释Try阶段预留的事务资源:假如Try阶段部分成功,比方帐户A的余额够用,且冻住相应金额成功,帐户B的余额不行而冻住失利,则需求对帐户A做Cancel操作,将帐户A被冻住的金额冻结掉。

4. Spring Cloud Alibaba Seata

Seata 的计划其实一个 XA 两阶段提交的改进版,详细差异如下:

架构的层面

XA 计划的 RM 实践上是在数据库层,RM 本质上便是数据库自身(经过供给支撑 XA 的驱动程序来供运用运用)。

而 Seata 的 RM 是以二方包的办法作为中心件层布置在运用程序这一侧的,不依靠与数据库自身对协议的支撑,当然也不需求数据库支撑 XA 协议。这点关于微服务化的架构来说是非常重要的:运用层不需求为本地事务和分布式事务两类不同场景来适配两套不同的数据库驱动。

这个规划,剥离了分布式事务计划对数据库在 协议支撑 上的要求。

两阶段提交

不管 Phase2 的抉择是 commit 仍是 rollback,事务性资源的锁都要坚持到 Phase2 完结才开释。

想象一个正常运转的事务,大概率是 90% 以上的事务终究应该是成功提交的,咱们是否能够在 Phase1 就将本地事务提交呢?这样 90% 以上的状况下,能够省去 Phase2 持锁的时刻,全体进步功率。

  • 分支事务中数据的 本地锁 由本地事务办理,在分支事务 Phase1 完毕时开释。
  • 一起,跟着本地事务完毕,衔接 也得以开释。
  • 分支事务中数据的 大局锁 在事务和谐器侧办理,在抉择 Phase2 大局提交时,大局锁立刻能够开释。只要在抉择大局回滚的状况下,大局锁 才被持有至分支的 Phase2 完毕。

这个规划,极大地减少了分支事务对资源(数据和衔接)的确定时刻,给全体并发和吞吐的提高供给了根底。

5. Seata实战事例

5.1 方针介绍

在本节,咱们将经过一个实战事例来详细介绍Seata的运用办法,咱们将模仿一个简略的用户购买产品下单场景,创立3个子工程,别离是 order-server (下单服务)、storage-server(库存服务)和 pay-server (付出服务),详细流程图如图:

5.2 环境预备

在本次实战中,咱们运用Nacos做为服务中心和装备中心,Nacos布置请参阅本书的第十一章,这儿不再赘述。

接下来咱们需求布置Seata的Server端,下载地址为:https://github.com/seata/seata/releases ,主张挑选最新版别下载,现在笔者看到的最新版别为 v0.8.0 ,下载 seata-server-0.8.0.tar.gz 解压后,翻开 conf 文件夹,咱们需对其间的一些装备做出修正。

5.2.1 registry.conf 文件修正,如下:

registry {
type = "nacos"
nacos {
serverAddr = "192.168.0.128"
namespace = "public"
cluster = "default"
}
}
config {
type = "nacos"
nacos {
serverAddr = "192.168.0.128"
namespace = "public"
cluster = "default"
}
}

这儿咱们挑选运用Nacos作为服务中心和装备中心,这儿做出对应的装备,一起能够看到Seata的注册服务支撑:file 、nacos 、eureka、redis、zk、consul、etcd3、sofa等办法,装备支撑:file、nacos 、apollo、zk、consul、etcd3等办法。

5.2.2 file.conf 文件修正

这儿咱们需求其间装备的数据库相关装备,详细如下:

## database store
db {
## the implement of javax.sql.DataSource, such as DruidDataSource(druid)/BasicDataSource(dbcp) etc.
datasource = "dbcp"
## mysql/oracle/h2/oceanbase etc.
db-type = "mysql"
driver-class-name = "com.mysql.jdbc.Driver"
url = "jdbc:mysql://192.168.0.128:3306/seata"
user = "root"
password = "123456"
min-conn = 1
max-conn = 3
global.table = "global_table"
branch.table = "branch_table"
lock-table = "lock_table"
query-limit = 100
}

这儿数据库默许是运用mysql,需求装备对应的数据库衔接、用户名和暗码等。

5.2.3 nacos-config.txt 文件修正,详细如下:

service.vgroup_mapping.spring-cloud-pay-server=default
service.vgroup_mapping.spring-cloud-order-server=default
service.vgroup_mapping.spring-cloud-storage-server=default

这儿的语法为: service.vgroup_mapping.${your-service-gruop}=default ,中心的 ${your-service-gruop}为自己界说的服务组称号,这儿需求咱们在程序的装备文件中装备,笔者这儿直接运用程序的 spring.application.name。

5.2.4 数据库初始化

需求在方才装备的数据库中履行数据初始脚本 db_store.sql ,这个是大局事务操控的表,需求提早初始化。

这儿咱们仅仅做演示,理论上上面三个事务服务应该分属不同的数据库,这儿咱们仅仅在同一台数据库下面创立三个 Schema ,别离为 dbaccount 、 dborder 和 db_storage ,详细如图:

5.2.5 服务发动

因为咱们是运用的Nacos作为装备中心,所以这儿需求先履行脚原本初始化Nacos的相关装备,指令如下:

cd conf
sh nacos-config.sh 192.168.0.128

履行成功后能够翻开Nacos的操控台,在装备列表中,能够看到初始化了许多 Group 为 SEATA_GROUP 的装备,如图:

初始化成功后,能够运用下面的指令发动Seata的Server端:

cd bin
sh seata-server.sh -p 8091 -m file

发动后在 Nacos 的服务列表下面能够看到一个名为 serverAddr 的服务

到这儿,咱们的环境预备工作就做完了,接下来开端代码实战。

5.3 代码实战

因为本示例代码偏多,这儿仅介绍中心代码和一些需求留意的代码,其他代码各位读者能够拜访本书配套的代码库房获取。

子工程common用来放置一些公共类,首要包括视图 VO 类和呼应类 OperationResponse.java。

5.3.1 父工程 seata-nacos-jpa 依靠 pom.xml 文件

代码清单:Alibaba/seata-nacos-jpa/pom.xml


 


org.springframework.boot
spring-boot-starter-actuator


org.springframework.boot
spring-boot-starter-data-jpa


org.springframework.boot
spring-boot-starter-web



com.alibaba.cloud
spring-cloud-starter-alibaba-nacos-discovery



com.alibaba.cloud
spring-cloud-starter-alibaba-nacos-config



com.alibaba.cloud
spring-cloud-starter-alibaba-seata


mysql
mysql-connector-java
runtime


org.projectl极彩论坛2018-分布式业务怎么做?Spring Cloud Alibaba Seata告知你ombok
lombok
true


org.springframework.boot
spring-boot-starter-test
test





org.springframework.cloud
spring-cloud-dependencies
${spring-cloud.version}
pom
import


com.alibaba.cloud
spring-cloud-alibaba-dependencies
${spring-cloud-alibaba.version}
pom
import



阐明:本示例是运用 JPA 作为数据库拜访 ORM 层, Mysql 作为数据库,需引进 JPA 和 Mysql 相关依靠, spring-cloud-alibaba-dependencies 的版别是 2.1.0.RELEASE , 其间有关Seata的组件版别为 v0.7.1 ,尽管和服务端版别不符,经简略测验,未发现问题。

5.3.2 数据源装备

Seata 是经过署理数据源完结事务分支,所以需求装备 io.seata.rm.datasource.DataSourceProxy 的 Bean,且是 @Primary默许的数据源,不然事务不会回滚,无法完结分布式事务,数据源装备类DataSourceProxyConfig.java如下:

代码清单:Alibaba/seata-nacos-jpa/order-server/src/main/java/com/springcloud/orderserver/config/DataSourceProxyConfig.java


@Configuration
public class DataSourceProxyConfig {
@Bean
@ConfigurationProperties(prefix = "spring.datasource")
public DruidDataSource druidDataSource() {
return new DruidDataSource();
}
@Primary
@Bean
public DataSourceProxy dataSource(DruidDataSource druidDataSource) {
return new DataSourceProxy(druidDataSource);
}
}

5.3.3 敞开大局事务

咱们在order-server服务中开端整个事务流程,需求在这儿的办法上添加大局事务的注解 @GlobalTransactional,详细代码如下:

代码清单:Alibaba/seata-nacos-jpa/order-server/src/main/java/com/springcloud/orderserver/service/impl/OrderServiceImpl.java


@Service
@Slf4j
public class OrderServiceImpl implements OrderService {


@Autowired
private RestTemplate restTemplate;


@Autowired
private OrderDao orderDao;


private final String STORAGE_SERVICE_HOST = "http://spring-cloud-storage-server/storage";
private final String PAY_SERVICE_HOST = "http://spring-cloud-pay-server/pay";


@Override
@GlobalTransactional
public OperationResponse placeOrder(PlaceOrderRequestVO placeOrderRequestVO) {
Integer amount = 1;
Integer price = placeOrderRequestVO.getPrice();


Order order = Order.builder()
.userId(placeOrderRequestVO.getUserId())
.productId(placeOrderRequestVO.getProductId())
.status(OrderStatus.INIT)
.payAmount(price)
.build();

order = orderDao.save(order);

log.info("保存订单{}", order.getId() != null ? "成功" : "失利");
log.info("当时 XID: {}", RootContext.getXID());


// 扣减库存
log.info("开端扣减库存");
ReduceStockRequestVO reduceStockRequestVO = ReduceStockRequestVO.builder()
.productId(placeOrderRequestVO.getProductId())
.amount(amount)
.build();
String storageReduceUrl = String.format("%s/reduceStock", STORAGE_SERVICE_HOST);
OperationResponse storageOperationResponse = restTemplate.postForObject(storageReduceUrl, reduceStockRequestVO, OperationResponse.class);
log.info("扣减库存成果:{}", storageOperationResponse);


// 扣减余额
log.info("开端扣减余额");
ReduceBalanceRequestVO reduceBalanceRequestVO = ReduceBalanceRequestVO.builder()
.userId(placeOrderRequestVO.getUserId())
.price(price)
.build();


String reduceBalanceUrl = String.format("%s/reduceBalance", PAY_SERVICE_HOST);
OperationResponse balanceOperationResponse = restTemplate.postForObject(reduceBalanceUrl, reduceBalanceRequestVO, OperationResponse.class);
log.info("扣减余额成果:{}", balanceOperationResponse);


Integer updateOrderRecord = orderDao.updateOrder(order.getId(), OrderStatus.SUCCESS);
log.info("更新订单:{} {}", order.getId(), updateOrderRecord > 0 ? "成功" : "失利");


return OperationResponse.builder()
.success(balanceOperationResponse.isSuccess() && storageOperationResponse.isSuccess())
.build();
}
}

其次,咱们需求在别的两个服务的办法中添加注解 @Transactional,表明敞开事务。

这儿的远端服务调用是经过 RestTemplate ,需求在工程发动时将 RestTemplate 注入 Spring 容器中办理。

5.3.4 装备文件

工程中需在 resources 目录下添加有关Seata的装备文件 registry.conf ,如下:

代码清单:Alibaba/seata-nacos-jpa/order-server/src/main极彩论坛2018-分布式业务怎么做?Spring Cloud Alibaba Seata告知你/resources/registry.conf


registry {
type = "nacos"
nacos {
serverAddr = "192.168.0.128"
namespace = "public"
cluster = "default"
}
}
config {
type = "nacos"
nacos {
serverAddr = "192.168.0.128"
namespace = "public"
cluster = "default"
}
}

在 bootstrap.yml 中的装备如下:

代码清单:Alibaba/seata-nacos-jpa/order-server/src/main/resources/bootstrap.yml


spring:
application:
name: spring-cloud-order-server
cloud:
nacos:
# nacos config
config:
server-addr: 192.168.0.128
namespace: public
group: SEATA_GROUP
# nacos discovery
discovery:
server-addr: 192.168.0.128
namespace: public
enabled: true
alibaba:
seata:
tx-service-group: ${spring.application.name}
  • spring.cloud.nacos.config.group :这儿的 Group 是 SEATAGROUP ,也便是咱们前面在运用 nacos-config.sh 生成 Nacos 的装备时生成的装备,它的 Group 是 SEATAGROUP。
  • spr极彩论坛2018-分布式业务怎么做?Spring Cloud Alibaba Seata告知你ing.cloud.alibaba.seata.tx-service-group :这儿是咱们之前在修正 Seata Server 端装备文件 nacos-config.txt 时里边装备的 service.vgroup_mapping.${your-service-gr中央政治局委员uop}=default 中心的 ${your-service-gruop} 。这两处装备请必须共同,不然在发动工程后会一向报错 noavailable server to connect 。

5.3.5 事务数据库初始化

数据库初始脚本坐落:Alibaba/seata-nacos-jpa/sql ,请别离在三个不同的 Schema 中履行。

5.3.6 测验

测验东西咱们挑选运用 PostMan ,发动三个服务,次序无关 order-server、pay-server 和 storage-server 。

运用 PostMan 发送测验恳求,如图:

数据库初始化余额为 10 ,这儿每次下单将会耗费 5 ,咱们能够正常下单两次,第三次应该下单失利,而且回滚 db_order 中的数据。数据库中数据如图:

咱们进行第三次下单操作,如图:

这儿看到直接报错500,检查数据库 db_order 中的数据,如图:

能够看到,这儿的数据并未添加,咱们看下子工程_rder-server的操控台打印:

日志现已过简化处理

Hibernate: insert into orders (pay_amount, product_id, status, user_id) values (?, ?, ?, ?)
c.s.b.c.service.impl.OrderServiceImpl : 保存订单成功
c.s.b.c.service.impl.OrderServiceImpl : 当时 XID: 192.168.0.102:8091:2021674307
c.s.b.c.service.impl.OrderServiceImpl : 开端扣减库存
c.s.b.c.service.impl.OrderServiceImpl : 扣减库存成果:OperationResponse(success=true, message=操作成功, data=null)
c.s.b.c.service.impl.OrderServiceImpl : 开端扣减余额
i.s.core.rpc.netty.RmMessageListener : onMessage:xid=192.168.0.102:8091:2021674307,branchId=2021674308,branchType=AT,resourceId=jdbc:mysql://192.168.0.128:3306/db_order,applicationData=null
io.seata.rm.AbstractRMHandler : Branch Rollbacking: 192.168.0.102:8091:2021674307 2021674308 jdbc:mysql://192.168.0.128:3306/db_order
i.s.rm.datasource.undo.UndoLogManager : xid 192.168.0.102:8091:2021674307 branch 2021674308, undo_log deleted with GlobalFinished
io.seata.rm.AbstractRMHandler : Branch Rollbacked result: PhaseTwo_Rollbacked
i.seata.tm.api.DefaultGlobalTransaction : [192.168.0.102:8091:2021674307] rollback status:Rollbacked

从日志中没有能够清楚的看到,在服务order-server是先履行了订单写入操作,而且调用扣减库存的接口,经过检查storage-server的日志也能够发现,相同是先履行了库存修正操作,直到扣减余额的时分发现余额缺乏,开端对 xid 为 192.168.0.102:8091:2021674307 履行回滚操作,而且这个操作是大局回滚。

6. 留意

现在在 Seata v0.8.0 的版别中,Server端没有支撑集群布置,不主张运用于出产环境,而且开源团队计划在 v1.0.0 版别的时分能够运用与出产环境,各位读者能够继续重视这个开源项目。

7. 示例代码

Github-示例代码:https://github.com/meteor1993/SpringCloudLearning/tree/master/Alibaba/seata-nacos-jpa

Gitee-示例代码:https://gitee.com/inwsy/SpringCloudLearning/tree/master/Alibaba/seata-nacos-jpa

请关注微信公众号
微信二维码
不容错过
Powered By Z-BlogPHP