一. 本地事务和分布式事务
1. ACID: 操作执行原子性(atomicity), 数据库状态一致性(consistency),多线程并发隔离性(isolation),durability(修改数据持久性)
2. @Transaction: 只在单体服务中有效, 即本地事务
3. 分布式事务: 事务参与者, 服务器,事务管理器在不同节点的不同应用-> 数据一致性
(1) 早期单体服务: 不涉及服务间的调用, 而是服务内操作到访问多个数据库资源
(2) 一个服务调用另外一个服务-> 跨服务的事务策略
(3) 跨服务事务中, 事务的发起者和提交是同一个(整个调用客户端=客户端最先调用的服务
二. CAP和BASE理论
1. CAP: 记忆 漫威哪里有事(事务), 哪里就有cap(美队)
(1) 一致性(consistency): 主从备份-> 机器越多时间越长-> 一致性效率越低
(2) 可用性(availability): 服务器的备用机-> 多台-> 高可用
(3) 分区容错性(partition tolerance): 分布式-> 多个子网(区)-> 区间通信可能失败-> 必然存在-> 一直成立
(4) 单体服务中可以保证CA: 一致性和高可用
2. BASE理论: ebay理论, 权衡CA
(1) basically available基本可用: 响应时间损失(时间延长)/功能损失 (页面降级)
(2) soft state软状态: 多个节点数据存在数据延迟
(3) eventually consistent最终一致性: 软状态最终达到数据一致
三. 分布式解决方案
1. 数据库XA协议-> 2PC两阶段提交2 phase commit:
(1) AP应用程序application program: 自己编写的代码-> 事务边界(开始和结束) (2) RM资源管理器resource manager: 计算机的共享资源(数据库, 文件系统,打印服务器) (3) TM事务管理器transaction manager: 分配id, 监控进度, 事务提交, 回滚, 恢复
2. 二阶段协议:
(1) 第一阶段: TM询问RM提交分支-> RM判断能否提交-> 可以则持久化返回OK-> 不行则返回NO-> RM回复NO且完成工作-> 丢弃事务分支
(2) 第二阶段: TM根据结果-> 决定提交或回滚-> RM全prepare成功则提交-> RM全回执NO则回滚
(3) 优点: 数据强一致性; 缺点: 实现复杂, 牺牲可用性, 不适合高并发
3. TCC补偿机制: 每个操作-> 注册确认和撤销操作
(1) Try: 业务系统检测和资源预留(转账前冻结钱)
(2) Confirm: 确认提交(转账成功则解冻, 失败则冻结接口)
(3) cancel: 执行出错(解冻方法)
(4) 优点: 可用性强-> 实现最终一致性 缺点: 每个微服务对三个阶段大量代码
4. 消息队列分布式事务-> 最终一致性
(1) 拆分成本地事务-> 通过队列不断发送消息-> 直到成功
(2) 优点: 最终一致性, 实现相对简单; 缺点: 消息表会耦合到业务系统-> 很多杂活
四. Seata: 原名fescar
1. 基于2PC, 解决XA缺点:
(1)单点问题: TM协调者在二阶段的提交中至关重要, TM出现故障会导致其他参与者处于事务资源的锁定状态; (2)同步阻塞: Fescar的一阶段本地事务就已经异步提交释放资源了, 不会向XA一样在prepare和commit阶段锁住资源; (3)数据不一致: commit时数据库的数据已经一致 (4)AT和MT: 混合模式 (5)Fescar代码零侵入
2. 原理: 比2PC多了一个TC(transaction coordinator)事务协调器
分布式事务(全局事务)-> 本地事务分支 (1) TC: 维护协调全局事务运行 (2) TM: 开启全局事务, 发起全局提交/回滚的决议 (3) RM: 事务分支的注册状态汇报 (4) 流程: TM向TC申请开启全局事务-> 生成唯一XID-> XID在微服务间传播 -> RM向TC注册分支(本地事务)-> 纳入XID对应事务-> TM向TC发起决议
3. 工作模式:
(1) AT模式: automatic transaction @GlobalTransactional 业务无侵入 第一阶段: 解析SQL-> 执行SQL-> 保存到undo log表-> RM注册到TM-> 提交-> 本地事务提交 第二阶段: 如果全局提交-> 分支事务已完成提交-> 无需同步-> 异步清理undo log 如果全局回滚-> RM收到TC的回滚请求-> 通过XID和分支ID找到undo log-> 反向更新SQL (2) MT模式: manual transaction 不支持XA协议 MT分支: prepare/commit/rollback RM注册TC-> 执行prepare-> RM检查资源是否可用-> 分支向TC汇报状态-> TC决定commit/rollback-> 通知RM (3) 混合AT&MT模式
4. 代码实现:
(1) 导入模块changgou_common_fescar:
(1)模块导入: changgou_common_fescar (2)fescar/config/FescarAutoConfiguration.java 三个bean: dataSource: 数据库代理类创建, 读取配置文件,获取数据库信息; globalTransactionScanner: 扫描@GlobalTransactional, 基于AOP控制事务 addFescarInterceptor: TM向TC申请XID, 每调用下一个微服务, 获取请求中的XID (3)过滤器: fescar/filter/FescarRMRequestFilter.java 一个方法: doFilterInternal: 给每个线程请求绑定XID (4)拦截器:fescar/interceptor/FescarRestInterceptor.java FescarRequestInterceptor implements RequestInterceptor: 实现请求拦截器(feignInterceptor), 判断请求头是否有XID, 有则传递到下一个微服务
(2) 日志表: undo log表 -> brand_id 和xid
(3) 启动fescar服务: fescar-server
(4) 给微服务配置fescar分布式事务
(1) 导入依赖: changgou_common_fescar (2) 在方法中添加: @GlobalTransactional(name="order_ad") //给不同的全局事务起名字
五. RabbitMQ实现分布式事务
1. 生成订单-> 任务表中添加数据-> 定时任务扫描任务表-> 获取最新订单信息-> 发送到MQ-> 用户服务接收消息-> 判断在redis是否存在-> 否则判断在DB中是否存在-> 否则存入redis-> 设置生存期防止本地事务回滚redis数据一直存在-> 修改用户积分-> 增加积分表日志-> 删除redis的任务信息-> 通知订单
2. 表:
(1)需要的表:
order_task表用来记录订单添加积分任务(用户名, 订单号, 积分);
order_task_history以后可用来统计一些订单数, 积分数据 //创建时间/删除时间/错误信息/交换机
user_point_log表: 记录增加积分日志
3. 实现代码:
(1) Orderf服务中的代码实现:
(1) order/config/RabbitMQConfig.java
绑定增加积分和完成增加积分的消息队列到同一个交换机, 定义路由键routingkey
(2) order/service/impl/OrderServiceImpl.java //添加任务数据
//在订单数据库中的任务表tb_task添加任务数据
Task task = new Task();
task.setCreateTime(new Date());
task.setUpdateTime(new Date());
task.setMqExchange(RabbitMQConfig.EX_BUYING_ADDPOINTUSER);
task.setMqRoutingkey(RabbitMQConfig.CG_BUYING_ADDPOINT_KEY);
Map map = new HashMap();
map.put("username", order.getUsername());
map.put("orderId", orderId);
map.put("point", order.getPayMoney());
task.setRequestBody(JSON.toJSONString(map));
taskMapper.insertSelective(task); //转换成jason
(3) order/dao/TaskMapper.java
public interface TaskMapper extends Mapper<Task> {
//定义查询方法, 获取小于当前时间的最新数据
@Select("select * from tb_task where update_time<#{currentTime}")
//tb_task表中列名 和 task实体类的属性不对应, 需要映射 - 传递映射关系
@Results({@Result(column = "create_time",property = "createTime"),
@Result(column = "update_time",property = "updateTime"),
@Result(column = "delete_time",property = "deleteTime"),
@Result(column = "task_type",property = "taskType"),
@Result(column = "mq_exchange",property = "mqExchange"),
@Result(column = "mq_routingkey",property = "mqRoutingkey"),
@Result(column = "request_body",property = "requestBody"),
@Result(column = "status",property = "status"),
@Result(column = "errormsg",property = "errormsg")})
List<Task> findTaskLessThanCurrentTime(Date currentTime);
}
(4) 开启定时任务: OrderApplication.java 添加 @EnableScheduling
(5)order/task/QueryPointTask.java
@Component
public class QueryPointTask {
@Autowired
private TaskMapper taskMapper;
@Autowired
private RabbitTemplate rabbitTemplate;
//查询任务表的最新数据
@Scheduled(cron = "0/2 * * * * ?")
public void queryTask() {
//1.获取小于系统当前时间的数据
List<Task> taskList = taskMapper.findTaskLessThanCurrentTime(new Date());
if (taskList != null&&taskList.size()>0){
//2. 发送任务到消息队列
for (Task task : taskList) {
rabbitTemplate.convertAndSend(RabbitMQConfig.EX_BUYING_ADDPOINTUSER,
RabbitMQConfig.CG_BUYING_ADDPOINT_KEY, JSON.toJSONString(task));
System.out.println("订单服务向添加积分队列发送了一条消息");
}
}
}
}
(2) User用户微服务
(1)user/listener/AddPointListener.java
@Component
public class AddPointListener {
@Autowired
private RedisTemplate redisTemplate;
@Autowired
private UserService userService;
@Autowired
private RabbitTemplate rabbitTemplate;
@RabbitListener(queues = RabbitMQConfig.CG_BUYING_ADDPOINT)
public void receiveAddPointMessage(String message) {
System.out.println("用户接收到了任务消息");
//转换消息
Task task = JSON.parseObject(message, Task.class);
if (task==null||StringUtils.isEmpty(task.getRequestBody())){
return ;
}
//判断redis中当前任务是否存在
Object value = redisTemplate.boundValueOps(task.getId()).get();
if (value != null){
return;
}
//更新用户积分
int result = userService.updateUserPoint(task);
if (result <=0){
return;
}
//返回通知消息
rabbitTemplate.convertAndSend(RabbitMQConfig.EX_BUYING_ADDPOINTUSER, RabbitMQConfig.CG_BUYING_FINISHADDPOINT_KEY, JSON.toJSONString(task));
}
}
(2)user/service/impl/UserServiceImpl.java
/**
* 更新用户积分
* @param task
* @return
*/
@Override
@Transactional
public int updateUserPoint(Task task) {
System.out.println("用户服务开始对任务进行处理");
//1. 从task中获取相关数据
Map map = JSON.parseObject(task.getRequestBody(), Map.class);
String username = map.get("username").toString();
String orderId = map.get("orderId").toString();
int point = (int) map.get("point");
//2. 判断当前任务是否操作过
PointLog pointLog = pointLogMapper.findPointLogByOrderId(orderId);
if (pointLog != null){
return 0;
}
//3. 将任务存入redis中
redisTemplate.boundValueOps(task.getId()).set("exist",30,TimeUnit.SECONDS);
//4. 修改用户积分
int result = userMapper.updateUserPoint(username,point);
if (result<=0){
return 0;
}
//5. 记录积分日志信息
pointLog = new PointLog();
pointLog.setUserId(username);
pointLog.setOrderId(orderId);
pointLog.setPoint(point);
result = pointLogMapper.insertSelective(pointLog);
if (result <= 0){
return 0;
}
//6. 删除redis中的任务信息
redisTemplate.delete(task.getId());
System.out.println("用户服务完成了更改用户积分的操作");
return 1;
}
(3) user/dao/PointLogMapper.java
public interface PointLogMapper extends Mapper<PointLog> {
@Select("select * from tb_point_log where order_id=#{orderId}")
PointLog findPointLogByOrderId(@Param("orderId")String orderId);
}
(4)user/dao/UserMapper.java
public interface UserMapper extends Mapper<User> {
/**
*增加用户积分
*/
@Update("UPDATE tb_user SET points=points+#{point} WHERE username=#{username}")
int addUserPoints(@Param("username")String username, @Param("point")Integer point);
//修改用户积分
@Update("update tb_user set points=points+#{point} where username=#{username}")
int updateUserPoint(@Param("username")String username, @Param("point")int point);
}
(4) order/task/DelTaskListener.java
@Component
public class DelTaskListener {
@Autowired
private TaskService taskService;
@RabbitListener(queues = RabbitMQConfig.CG_BUYING_FINISHADDPOINT)
public void receiveDelTaskMessage(String message) {
System.out.println("订单服务接收到了删除任务操作的消息");
Task task = JSON.parseObject(message, Task.class);
//删除原有任务数据, 并向历史任务表中添加记录
taskService.delTask(task);
}
}
(5) order/service/impl/TaskServiceImpl.java
@Service
public class TaskServiceImpl implements TaskService {
@Autowired
private TaskHisMapper taskHisMapper;
@Autowired
private TaskMapper taskMapper;
@Override
@Transactional
public void delTask(Task task) {
//1.记录删除的时间
task.setDeleteTime(new Date());
Long taskId = task.getId();
task.setId(null);
//2. bean拷贝 tb_task表 和 tb_task_his的属性一样
TaskHis taskHis = new TaskHis();
BeanUtils.copyProperties(task, taskHis);
//3. 记录历史任务数据
taskHisMapper.insertSelective(taskHis);
//4.删除原有任务数据
task.setId(taskId);
taskMapper.deleteByPrimaryKey(task);
System.out.println("当前的订单服务完成了添加历史任务并删除任务为的操作");
}
}