实习生学习记录(1)
什么是分布式?什么是事务?什么是分布式事务?
分布式:建立在网络之上的软件系统。
事务:是指访问并可能更新数据库中各种数据项的一个程序执行单元。
名人名言 “要么都执行,要么都不执行” !
分布式事务:事务的参与者、支持事务的服务器、资源管理器以及事务管理器分别位于不同的分布式系统的不同节点之上。
一、分布式事务的解决方案
有四种:
两阶段提交(2PC)
补偿事务(TCC)
本地消息表(异步确保)
MQ事务消息(消息中间件)
懂得不多,浅浅谈谈2PC和TCC
二、给定场景
场景一:
在单体应用中,我们的某些操作需要同时对两个数据库进行操作,如用户支付了订单,需要对订单表库中的订单表增加一条新订单,同时需要对库存库中的库存表减少该物品的一个库存。
(注:理解本地事务与跨库事务的区别)
存在问题:支付不成功,反而减库存成功了;支付成功,减库存失败(库存不够减、库存库挂了)。
分析问题:支付不成功、反而减库存成功,一般不存在这个问题,单体应用中两个操作在同一方法中,前一sql抛 异常后一sql自然不会执行。
支付成功,减库存失败,问题就是出现在后一sql。
解决问题:采用2PC方式确保分布式事务一致。
谈谈2PC
概念:两阶段提交(Two-phase Commit,2PC),通过引入协调者(Coordinator)来协调参与者的行为,并最终决定这些参与者是否要真正执行事务。图解如下:
将原本sql的执行过程拆分,分为两个阶段:
第一阶段:将两sql执行结果给到事务管理器,但数据库层并不commit操作。
第二阶段:当两sql结果都正确则执行commit操作,否则都进行rollback操作。
import com.atomikos.icatch.jta.UserTransactionImp;
import com.atomikos.jdbc.AtomikosDataSourceBean;
import javax.transaction.UserTransaction;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.Statement;
import java.util.Properties;
public class AtomikosJTATest {
private static AtomikosDataSourceBean createAtomikosDataSourceBean(String dbName) {
//连接池基本属性
Properties p = new Properties();
p.setProperty("url","jdbc:mysql://localhost:3306/" + dbName);
p.setProperty("user", "root");
p.setProperty("password", "your password");
//使用AtomikosDataSourceBean封装com.mysql.jdbc.jdbc2.optional.MysqlXADataSource
AtomikosDataSourceBean ds = new AtomikosDataSourceBean();
ds.setUniqueResourceName(dbName);
ds.setXaDataSourceClassName("com.mysql.jdbc.jdbc2.optional.MysqlXADataSource");
ds.setXaProperties(p);
return ds;
}
public static void main(String[] args) throws Exception {
AtomikosDataSourceBean ds1 = createAtomikosDataSourceBean("db_user");
AtomikosDataSourceBean ds2 = createAtomikosDataSourceBean("db_account");
Connection conn1 = null;
Connection conn2 = null;
PreparedStatement ps1 = null;
PreparedStatement ps2 = null;
UserTransaction userTransaction = new UserTransactionImp();
try {
//开启事务
userTransaction.begin();
//执行db1上的sql
conn1 = ds1.getConnection();
ps1 = conn1.prepareStatement("INSERT into user(name) VALUE (?)", Statement.RETURN_GENERATED_KEYS);
ps1.setString(1, "demo");
ps1.executeUpdate();
//模拟异常,直接进入catch代码块,2个都不会提交
//int i=1/0;
//执行db2上的sql
conn2 = ds2.getConnection();
ps2 = conn2.prepareStatement("INSERT into account(name) VALUE (?)");
ps2.setString(1, "demo");
ps2.executeUpdate();
//跨系统调用接口
//两阶段提交
userTransaction.commit();
}catch (Exception e) {
e.printStackTrace();
userTransaction.rollback();
}finally {
try {
ps1.close();
ps2.close();
conn1.close();
conn2.close();
ds1.close();
ds2.close();
}catch (Exception e) {
e.printStackTrace();
}
}
}
}
补充:假如第二个过程失败,可采取的手段有:重试操作、记录日志后台定时任务补偿操作或通知人工补偿操作。
场景二:
在微服务架构下,我们的某些操作需要牵扯到不同系统上的不同服务。如,用户下单的同时,需要对库存系统、积分系统和物流系统同步都进行操作。
存在问题:支付成功,减库存成功,积分增加服务却失败了等。
分析问题:网络抖动导致。
解决问题:采用TCC方式确保分布式事务一致。
谈谈TCC
概念:TCC 它是Try、Confirm和Cancel三个单词的首字母。其实就是采用的补偿机制,其核心思想是:针对每个操作,都要注册一个与其对应的确认和补偿(撤销)操作。它分为三个阶段:
Try 阶段主要是对业务系统做检测及资源预留
Confirm 阶段主要是对业务系统做确认提交,Try阶段执行成功并开始执行 Confirm阶段时,默认 Confirm阶段是不会出错的。即:只要Try成功,Confirm一定成功。
Cancel 阶段主要是在业务执行错误,需要回滚的状态下执行的业务取消,预留资源释放。
第一阶段:预留资源,将资源冻结,并不是真实的扣减,而是锁定。
stock frozen
10 1
第二阶段:确认资源,将冻结的资源真是扣减。
stock frozen
9 0
如确认资源失败,将冻结资源释放。
stock frozen
10 0
比较复杂的是,需要开发三套API(冻结、生效、撤销)对此进行操作。
还有很多其他问题:如接口幂等性问题等,下篇见!
三、总结
① 2PC:资源层面的分布式事务,强一致性
,在两阶段提交的整个过程中,一直会持有资源的锁;
② TCC:业务层面的分布式事务,最终一致性
,不会一直持有资源的锁;
③ TCC相对于2PC的优点,锁的粒度变小了。在2PC中,锁住了许多资源,在高并发场景下的表现很差。TCC方案分不同的阶段锁住资源,减小了锁的粒度,在性能上表现的更好。
但实现TCC的相关功能比较复杂,推荐使用开源框架。
Atomikos、tcc-transaction、ByteTcc、支付宝GTS(蚂蚁金服)
链接:https://github.com/liuyangming/ByteTCC (ByteTcc)
推荐阅读:https://linux.cn/article-11164-1.html (蚂蚁金服)