Spring Cloud Alibaba(三)Seata

Seate是一款开源的分布式事务解决方案,致力于在微服务架构下提供高性能和简单易用的分布式事务服务。

Seata将为用户提供了AT、TCC、SAGA和XA事务模型,为用户打造一站式的分布式解决方案。

img

项目场景搭建

逻辑约定

图书借阅流程:调用图书服务书籍数量减1->添加借阅记录->调用用户服务可借阅数减1

约束:每个用户最多同时借阅2本不用的书,图书馆中的所有书都有且仅有3本

数据库修改

用户表

添加字段book_count记录用户可借阅书籍数量

image-20220725104408740

书籍表

添加字段count记录书籍剩余数量

image-20220725104307273

服务修改

borrow-service

dao层
BorrowMapper.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
@Mapper
public interface BorrowMapper {
/**
* 根据条件查询借阅列表
* @param uid
* @param bid
* @return
*/
List<Borrow> getBorrow(Long uid, Long bid);

/**
* 新增借阅记录
* @param uid
* @param bid
* @return
*/
int addBorrow(Long uid, Long bid);
}
BorrowMapper.xml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.test.dao.BorrowMapper">
<select id="getBorrow" resultType="com.test.entity.Borrow">
SELECT * FROM borrow_info
<where>
<if test="uid != null"> uid = #{uid}</if>
<if test="bid != null"> and bid = #{bid}</if>
</where>
</select>
<insert id="addBorrow" parameterType="com.test.entity.Borrow">
INSERT INTO borrow_info (id, uid, bid)
VALUES (null, #{uid}, #{bid})
</insert>
</mapper>
Service层
BorrowService.java
1
2
3
4
5
6
7
8
9
public interface BorrowService {
UserBorrowDetail getBorrowByUser(Long uid);

BookBorrowDetail getBorrowByBook(Long bid);

BorrowDetail getBorrow(Long uid, Long bid);

boolean borrow(Long uid, Long bid);
}
BorrowServiceImpl.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
@SuppressWarnings("AliControlFlowStatementWithoutBraces")
@Service
@Slf4j
public class BorrowServiceImpl implements BorrowService {
@Resource
BorrowMapper borrowMapper;
@Resource
UserService userService;
@Resource
BookService bookService;

@Override
public boolean borrow(Long uid, Long bid) {
// 查询用户余量
User user = userService.getUserById(uid);
log.info("" + user);
if (user.getCount() < 1) throw new RuntimeException("用户借阅数达到上限");
// 查询图书余量
Book book = bookService.getBookById(bid);
if (book.getCount() < 1) throw new RuntimeException("图书数量不足");
// 调用图书服务:图书数量减1
if (!bookService.borrow(bid)) throw new RuntimeException("调用图书服务出错");
// 调用借阅服务:先查后插
if (!borrowMapper.getBorrow(uid, bid).isEmpty()) throw new RuntimeException("用户已经借阅过此书");
if (borrowMapper.addBorrow(uid, bid) < 1) throw new RuntimeException("借阅信息添加失败");
// 调用用户服务:可借阅数减1
if (!userService.borrow(bid)) throw new RuntimeException("调用用户服务出错");
return true;
}
}
Controller层
BorrowController.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
@Api(tags = "UserController", description = "用户信息管理")
@RestController
//@RefreshScope
public class UserController {
@Resource
UserService userService;

public static final Logger logger = LoggerFactory.getLogger(UserController.class);

@ApiOperation("图书借阅")
@PostMapping(value = "/{uid}/{bid}", produces = "application/json;charset=UTF-8")
public JSONObject borrow(@PathVariable("uid") @ApiParam("用户id") Long uid,
@PathVariable("bid") @ApiParam("图书id") Long bid) {
// 添加借阅信息
boolean result = borrowService.borrow(uid, bid);
// 创建JSON对象并返回
JSONObject jsonObject = new JSONObject();
if (result) {
jsonObject.put("code", 200);
jsonObject.put("success", true);
jsonObject.put("message", "借阅成功");
return jsonObject;
}
jsonObject.put("code", 100);
jsonObject.put("success", false);
jsonObject.put("message", "借阅失败");
return jsonObject;
}
}

分布式事务解决方案

XA-2PC

XA是一种典型的两阶段提交(2PC,Two-phase commit protocal)

两阶段提交协议 ,它分为两个阶段,一个是准备一个是提交。

整个过程的参与者一共有两个角色,一个是事务的执行者,一个是事务的协调者,实际上整个分布式事务的运作都需要依靠协调者来维持。

为了实现二阶段提交算法的成立基于以下假设:

  • 该分布式系统中,存在一个节点作为协调者(Coordinator),其他节点作为参与者(Cohorts),节点之间可以进行网络通信
  • 所有节点都采用预写式日志,且日志被写入后即保持在可靠的存储设备上,即使损坏不会导致日志数据的消失
  • 所有节点不会永久性损坏,即使损坏后仍然可以恢复

第一阶段(投票阶段)

1、协调者节点向所有参与者节点询问是否可以执行提交操作(vote),并开始等待各参与者节点的响应

2、参与者节点执行询问发起为止的所有事务操作,并将Undo信息和Redo信息写入日志。(注意:若成功这里其实每个参与者已经执行了事务操作)

3、各参与者节点响应协调者节点发起的询问。如果参与者节点的事务操作实际执行成功,则它返回一个“同意”消息;如果参与者节点的事务操作失败实际执行失败,则它返回一个“中止”消息

第二阶段(提交执行阶段)

当协调者节点从所有参与者节点获得响应消息都为“同意”时:

  1. 协调者节点向所有参与者节点发出”正式提交(commit)”的请求。
  2. 参与者节点正式完成操作,并释放在整个事务期间内占用的资源。
  3. 参与者节点向协调者节点发送”完成”消息。
  4. 协调者节点受到所有参与者节点反馈的”完成”消息后,完成事务。

如果任一参与者节点在第一阶段返回的响应消息为”中止”,或者 协调者节点在第一阶段的询问超时之前无法获取所有参与者节点的响应消息时:

  1. 协调者节点向所有参与者节点发出”回滚操作(rollback)”的请求。
  2. 参与者节点利用之前写入的Undo信息执行回滚,并释放在整个事务期间内占用的资源。
  3. 参与者节点向协调者节点发送”回滚完成”消息。
  4. 协调者节点受到所有参与者节点反馈的”回滚完成”消息后,取消事务。

3PC

TTC

SAGA