分布式事务相关概念
1.什么是分布式系统?
部署在不同结点上的系统通过网络交互来完成协同工作的系统。
2.什么是本地事务?
本地事务就是用关系型数据库来控制事务,关系型数据库通常都具有ACID特性
3.什么是分布式事务?
一次事务操作涉及多个分布式系统通过网络来协同完成功能的过程称为分布式事务。
4.分布式事务的应用场景
电商系统下订单,银行系统转账,教育系统选课
CAP理论:
C:Consistency,一致性,存储数据的节点和它的副本之间数据必须时刻保持一致
A:Availability,可用性,为一个节点建立多个副本提高可用性
P:Partition Tolerance,分区容忍性,允许系统通过网络协同工作
分布式系统能否兼顾C、A、P?
在保证分区容忍性的前提下一致性和可用性无法兼顾,如果要提高系统的可用性就要增加多个结点,如果要保证数
据的一致性就要实现每个结点的数据一致,结点越多可用性越好,但是数据一致性越差。所以,在进行分布式系统设计时,同时满足“一致性”、“可用性”和“分区容忍性”三者是几乎不可能的。
CAP组合方式:
1.CA:放弃分区容忍性,加强一致性和可用性,相当于直接放弃分布式系统
2.AP:放弃即时一致性,允许临时数据不一致,只需要最终能够保持一致性即可;比如支付宝转账的24小时内到账
3.CP:放弃可用性;比如银行转账,5s内一定要求转账双方都成功
分布式事务解决方案
解决方案一: 两阶段提交协议,2PC 2 Phase Commitment Protocol
阶段1,准备阶段
协调者通知参与者开始操作,参与者操作完成后(先不commit/rollback),参与者向协调者回应yes/no
阶段2,commit/rollback
协调者收到所有参与者的回应都为yes时,发起commit通知;只要有一个回应no,就发起rollback通知
强一致性,但是同步阻塞,性能低
3PC,准备阶段改进为canCommit,preCommit,效率更低
解决方案二: 事物补偿机制,TCC Try/Confirm/Cancel
Try阶段:检查数据库资源是否可用
Confirm阶段:执行CRUD操作,并commit
Cancel:如果有一个操作出了异常,取消全部的操作,比如:删除新增的订单,对刚刚-1的库存+1
注意:TCC的try/confirm/cancel接口都要实现幂等性(操作之前在业务方法进行判断如果执行过了就不再执行)
幂等性是指同一个操作无论请求多少次,其结果都相同。
强一致性,灵活度高,但是开发成本高,逻辑复杂
解决方案三: 消息队列实现最终一致
场景:电商下订单,生成订单+减少库存
原理:将分布式事务拆分成本地式事务
1.用户下订单后,生成订单,并在tb_task表插入订单ID;
2.定时任务定时去tb_task中查询当前时间1小时内的数据,然后将所有的订单ID发送给MQ;
3.MQ消费者监听到消息后,先根据订单ID从tb_has_decrease表中查询当前订单的库存是否已经减少过;
4.如果已经减少过库存,跳过减少库存的步骤,向MQ发送已减少库存的消息;如果没有查询到数据,就将库存减少,并
在tb_has_decrease表中插入一条已经减少库存的数据,然后发送MQ,已经减少库存
5.生成订单服务监听到减少库存库存服务发送的已经减少库存的消息后,根据订单ID,从tb_task表中删除记录;
6.最终,如果tb_task中仍然有超过1小时没被处理的数据,开发人员手动来处理
bug:
一但微服务工程搭了集群,tb_task中的数据将会被集群中的每一个分片都查询并执行一遍,虽然在减少库存库存一方做了判断,但是依然浪费资源,影响性能
解决方案: 乐观锁取任务
在tb_task加一个version字段,初始值为1,定义一个接口方法,sql: update tb_task set version = version
+1 where orderId = ? and version = ?;查询到tb_task中的记录后,遍历发送MQ前进行调用这个sql的方法,
int updateVersion(String orderId,Integer version),返回值是影响的行数;如果多个服务同时开始遍历tb_task中的记录,当version第一次被修改后,其他服务的version不满足条件,将不会再发送MQ,即便修改version发生了并行,由于对结果没有影响,也可以忽略
for(Task task : taskList){
if(updateVersion(task.getOrderId(),task.getVersion()) > 0 ){
}
}
SpringTask并行任务
import org.springframework.aop.interceptor.AsyncUncaughtExceptionHandler;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.annotation.AsyncConfigurer;
import org.springframework.scheduling.annotation.EnableScheduling;
import org.springframework.scheduling.annotation.SchedulingConfigurer;
import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler;
import org.springframework.scheduling.config.ScheduledTaskRegistrar;
import java.util.concurrent.Executor;
@Configuration
@EnableScheduling
public class AsyncTaskConfig implements SchedulingConfigurer, AsyncConfigurer {
private int corePoolSize = 5;
@Bean
public ThreadPoolTaskScheduler taskScheduler()
{
ThreadPoolTaskScheduler scheduler = new ThreadPoolTaskScheduler();
scheduler.initialize();
scheduler.setPoolSize(corePoolSize);
return scheduler;
}
@Override
public Executor getAsyncExecutor() {
Executor executor = taskScheduler();
return executor;
}
@Override
public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() {
return null;
}
@Override
public void configureTasks(ScheduledTaskRegistrar scheduledTaskRegistrar) {
scheduledTaskRegistrar.setTaskScheduler(taskScheduler());
}
}