分布式事务框架seata
1 seata简介
Seata(原名Fescar) 是阿里18年开源的分布式事务的框架。Fescar的开源对分布式事务框架领域影响很大。作为开源大户,Fescar来自阿里的GTS,经历了好几次双十一的考验,一经开源便颇受关注。后来Fescar改名为Seata。 https://github.com/seata/seata
Fescar虽然是二阶段提交协议的分布式事务,但是其解决了XA的一些缺点:
- 单点问题:虽然目前Fescar(0.4.2)还是单server的,但是Fescar官方预计将会在0.5.x中推出HA-Cluster,到时候就可以解决单点问题。
- 同步阻塞:Fescar的二阶段,其再第一阶段的时候本地事务就已经提交释放资源了,不会像XA会再两个prepare和commit阶段资源都锁住,并且Fescar,commit是异步操作,也是提升性能的一大关键。
- 数据不一致:如果出现部分commit失败,那么fescar-server会根据当前的事务模式和分支事务的返回状态的结果来进行不同的重试策略。并且fescar的本地事务会在一阶段的时候进行提交,其实单看数据库来说在commit的时候数据库已经是一致的了。
- 只能用于单一数据库: Fescar提供了三种模式,AT和TCC和混合模式。在AT模式下事务资源可以是任何支持ACID的数据库,在TCC模式下事务资源没有限制,可以是缓存,可以是文件,可以是其他的等等。当然这两个模式也可以混用。
同时Fescar也保留了接近0业务入侵的优点,只需要简单的配置Fescar的数据代理和加个注解,加一个Undolog表,就可以达到我们想要的目的。
2 实现原理
Fescar将一个本地事务做为一个分布式事务分支,所以若干个分布在不同微服务中的本地事务共同组成了一个全局事务,结构如下。
官帮助文档: https://seata.io/zh-cn/index.html
TM:全局事务管理器,在标注开启fescar分布式事务的服务端开启,并将全局事务发送到TC事务控制端管理
TC:事务控制中心,控制全局事务的提交或者回滚。这个组件需要独立部署维护,目前只支持单机版本,后续迭代计划会有集群版本
RM:资源管理器,主要负责分支事务的上报,本地事务的管理
一段话简述其实现过程:服务起始方发起全局事务并注册到TC。在调用协同服务时,协同服务的事务分支事务会先完成阶段一的事务提交或回滚,并生成事务回滚的undo_log日志,同时注册当前协同服务到TC并上报其事务状态,归并到同一个业务的全局事务中。此时若没有问题继续下一个协同服务的调用,期间任何协同服务的分支事务回滚,都会通知到TC,TC在通知全局事务包含的所有已完成一阶段提交的分支事务回滚。如果所有分支事务都正常,最后回到全局事务发起方时,也会通知到TC,TC在通知全局事务包含的所有分支删除回滚日志。在这个过程中为了解决写隔离和度隔离的问题会涉及到TC管理的全局锁。
(增加订单(branchId=901),减库存(branchId=109)) xid=101
3 Fescar模式
Fescar对分布式事务的实现提供了3种模式,AT模式和TCC模式、saga模式:
3.1 AT模式
AT模式:主要关注多 DB 访问的数据一致性,实现起来比较简单,对业务的侵入较小,但性能没有TCC高,这种模式推荐大家使用。
AT模式部分代码如下:不需要关注执行状态,对业务代码侵入较小。
/**
* 此代码为示例代码, 不需要演示, 主要看AT和TCC代码的区别使用
*/
@GlobalTransactional(timeoutMills = 300000, name = "dubbo-demo-tx")
public void purchase(String userId, String commodityCode, int orderCount) {
LOGGER.info("purchase begin ... xid: " + RootContext.getXID());
storageService.deduct(commodityCode, orderCount);
orderService.create(userId, commodityCode, orderCount);
throw new RuntimeException("AT 模式发生异常,回滚事务");
}
AT模式的核心是对业务无侵入,是一种改进后的两阶段提交,其设计思路如图:
第一阶段:
核心在于对业务sql进行解析,转换成undolog,两阶段提交往往对资源的锁定需要持续到第二阶段实际的提交或者回滚操作,而有了回滚日志之后,可以在第一阶段释放对资源的锁定,降低了锁范围,提高效率,即使第二阶段发生异常需要回滚,只需找对undolog中对应数据并反解析成sql来达到回滚目的。Seata通过代理数据源将业务sql的执行解析成undolog来与业务数据的更新同时入库,达到了对业务无侵入的效果。
第二阶段:
如果决议是全局提交,此时分支事务此时已经完成提交,不需要同步协调处理(只需要异步清理回滚日志),Phase2 可以非常快速地完成。
如果决议是全局回滚,RM 收到协调器发来的回滚请求,通过 XID 和 Branch ID 找到相应的回滚日志记录,通过回滚记录生成反向的更新 SQL 并执行,以完成分支的回滚。
3.3 TCC模式
TCC模式:TCC补偿机制,对代码造成一定的侵入,实现难度较大,这种方式不推荐,不过TCC模式的特点是性能高。
TCC模式部分代码如下:可以看到执行事务回滚,都需要根据不同阶段执行的状态判断,侵入了业务代码。
/**
* 此代码为示例代码, 不需要演示, 主要看AT和TCC代码的区别使用
* 转账操作
* @param from 扣钱账户
* @param to 加钱账户
* @param amount 转账金额
* @return
*/
@Override
@GlobalTransactional
public boolean transfer(final String from, final String to, final double amount) {
//扣钱参与者,一阶段执行
boolean ret = firstTccAction.prepareMinus(null, from, amount);
if(!ret){
//扣钱参与者,一阶段失败; 回滚本地事务和分布式事务
throw new RuntimeException("账号:["+from+"] 预扣款失败");
}
//加钱参与者,一阶段执行
ret = secondTccAction.prepareAdd(null, to, amount);
if(!ret){
throw new RuntimeException("账号:["+to+"] 预收款失败");
}
System.out.println(String.format("transfer amount[%s] from [%s] to [%s] finish.", String.valueOf(amount), from, to));
return true;
}
3.3 Saga模式
详见 https://seata.io/zh-cn/docs/dev/mode/saga-mode.html
Seata案例
1准备工作
1导入资料中的changgou_common_fescar。注意总父工程中加入模块
<module>changgou_common_fescar</module>
2观察模块中的三个类。
3数据库changgou_order中的undo_log表为记录相关操作的表
4资料中的fescar-server-0.4.2解压,bin目录中双击fescar-server.bat。注意:这是fescar的服务,并且放到一个短目录才能执行。
2分布式事务错误演示
1order服务 com.changgou.order.service.impl 的 add方法中增加一个错误 本地事务控制注解增加@Transactional
//减库存
skuFeign.decrCount(order.getUsername());
int i=1/0;
// 5)删除购物车中数据
redisTemplate.delete("cart_"+order.getUsername());
2goods 服务 com.changgou.goods.service.impl decrCount减库存方法上增加本地事务控制注解@Transactional
3查看数据库 tb_order tb_order_item中都有一条数据。
tb_sku查看一条数据库存量 9920
4登陆购物车并登陆 http://localhost:8001/api/wcart/list
5往购物车添加数据 http://localhost:8001/api/cart/addCart?skuId=100000006163&num=10
6购物车页面点击结算到订单页面
7点击提交订单
8代码中 order服务报错
9观察数据库
tb_order tb_order_item没变化
tb_sku 100000006163商品库存减少了10
10 为什么
order goods服务是两个服务,即使加上@Transactional也是本地事务控制。
3 分布式事务正确演示
1 goods order 增加fescar依赖
<dependency>
<groupId>com.changgou</groupId>
<artifactId>changgou_common_fescar</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
2在订单微服务的OrderServiceImpl的add方法上增加@GlobalTransactional(name = “order_add”)注解
3order 服务重启 观察 fescar控制台输出
4goods 服务重启 观察 fescar控制台输出
5重新提交订单 依然失败 但库存不扣减了
消息队列实现分布式事务—整个项目的重点
1业务流程-重点
需求:(一次下单—》用户积分增加一次) 事务
1 order
1.1 order task.本地事务控制。
1.2 spring-task扫表 task-----》mq发消息
2user
2.1监听消息 6
2.2 8相当于锁 11释放锁
2.3 mq(另一个队列) task 积分加上了
3order
3.1监听消息 task_his 加上,后期查看,task 删除
思考:
1mq中几个交换机 几个队列?
2 8-11 redis user宕机了 集美没加上?redis 超时间
3 积分日志表 作用?本地事务控制user point 一起成功。
2代码实现
2.1准备
1order中添加两张表
tb_task 任务表
tb_task_his 历史任务表
2order-api中添加对应的实体类
package com.changgou.order.pojo;
import javax.persistence.Column;
import javax.persistence.Id;
import javax.persistence.Table;
import java.util.Date;
@Table(name = "tb_task")
public class Task {
@Id
private Long id;
@Column(name = "create_time")
private Date createTime;
@Column(name = "update_time")
private Date updateTime;
@Column(name = "delete_time")
private Date deleteTime;
@Column(name = "task_type")
private String taskType;
@Column(name = "mq_exchange")
private String mqExchange;
@Column(name = "mq_routingkey")
private String mqRoutingkey;
@Column(name = "request_body")
private String requestBody;
@Column(name = "status")
private String status;
@Column(name = "errormsg")
private String errormsg;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public Date getCreateTime() {
return createTime;
}
public void setCreateTime(Date createTime) {
this.createTime = createTime;
}
public Date getUpdateTime() {
return updateTime;
}
public void setUpdateTime(Date updateTime) {
this.updateTime = updateTime;
}
public Date getDeleteTime() {
return deleteTime;
}
public void setDeleteTime(Date deleteTime) {
this.deleteTime = deleteTime;
}
public String getTaskType() {
return taskType;
}
public void setTaskType(String taskType) {
this.taskType = taskType;
}
public String getMqExchange() {
return mqExchange;
}
public void setMqExchange(String mqExchange) {
this.mqExchange = mqExchange;
}
public String getMqRoutingkey() {
return mqRoutingkey;
}
public void setMqRoutingkey(String mqRoutingkey) {
this.mqRoutingkey = mqRoutingkey;
}
public String getRequestBody() {
return requestBody;
}
public void setRequestBody(String requestBody) {
this.requestBody = requestBody;
}
public String getStatus() {
return status;
}
public void setStatus(String status) {
this.status = status;
}
public String getErrormsg() {
return errormsg;
}
public void setErrormsg(String errormsg) {
this.errormsg = errormsg;
}
}
package com.changgou.order.pojo;
import javax.persistence.Column;
import javax.persistence.Id;
import javax.persistence.Table;
import java.util.Date;
@Table(name = "tb_task_his")
public class TaskHis {
@Id
private Long id;
@Column(name = "create_time")
private Date createTime;
@Column(name = "update_time")
private Date updateTime;
@Column(name = "delete_time")
private Date deleteTime;
@Column(name = "task_type")
private String taskType;
@Column(name = "mq_exchange")
private String mqExchange;
@Column(name = "mq_routingkey")
private String mqRoutingkey;
@Column(name = "request_body")
private String requestBody;
@Column(name = "status")
private String status;
@Column(name = "errormsg")
private String errormsg;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public Date getCreateTime() {
return createTime;
}
public void setCreateTime(Date createTime) {
this.createTime = createTime;
}
public Date getUpdateTime() {
return updateTime;
}
public void setUpdateTime(Date updateTime) {
this.updateTime = updateTime;
}
public Date getDeleteTime() {
return deleteTime;
}
public void setDeleteTime(Date deleteTime) {
this.deleteTime = deleteTime;
}
public String getTaskType() {
return taskType;
}
public void setTaskType(String taskType) {
this.taskType = taskType;
}
public String getMqExchange() {
return mqExchange;
}
public void setMqExchange(String mqExchange) {
this.mqExchange = mqExchange;
}
public String getMqRoutingkey() {
return mqRoutingkey;
}
public void setMqRoutingkey(String mqRoutingkey) {
this.mqRoutingkey = mqRoutingkey;
}
public String getRequestBody() {
return requestBody;
}
public void setRequestBody(String requestBody) {
this.requestBody = requestBody;
}
public String getStatus() {
return status;
}
public void setStatus(String status) {
this.status = status;
}
public String getErrormsg() {
return errormsg;
}
public void setErrormsg(String errormsg) {
this.errormsg = errormsg;
}
}
3 changgou_user新增积分日志表
4 changgou_service_user_api添加实体类 PointLog
package com.changgou.user.pojo;
import javax.persistence.Table;
@Table(name = "tb_point_log")
public class PointLog {
private String orderId;
private String userId;
private Integer point;
public String getOrderId() {
return orderId;
}
public void setOrderId(String orderId) {
this.orderId = orderId;
}
public String getUserId() {
return userId;
}
public void setUserId(String userId) {
this.userId = userId;
}
public Integer getPoint() {
return point;
}
public void setPoint(Integer point) {
this.point = point;
}
}
5rabbitMQ
1 order 服务 导包
<dependency>
<groupId>org.springframework.amqp</groupId>
<artifactId>spring-rabbit</artifactId>
</dependency>
2config 下 rabbitMQ配置类
package com.changgou.order.config;
import org.springframework.amqp.core.*;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class RabbitMQConfig {
//添加积分任务交换机
public static final String EX_BUYING_ADDPOINTUSER = "ex_buying_addpointuser";
//添加积分消息队列
public static final String CG_BUYING_ADDPOINT = "cg_buying_addpoint";
//完成添加积分消息队列
public static final String CG_BUYING_FINISHADDPOINT = "cg_buying_finishaddpoint";
//添加积分路由key
public static final String CG_BUYING_ADDPOINT_KEY = "addpoint";
//完成添加积分路由key
public static final String CG_BUYING_FINISHADDPOINT_KEY = "finishaddpoint";
//声明交换机
@Bean(EX_BUYING_ADDPOINTUSER)
public Exchange EX_BUYING_ADDPOINTUSER(){
return ExchangeBuilder.directExchange(EX_BUYING_ADDPOINTUSER).durable(true).build();
}
//声明队列
@Bean(CG_BUYING_ADDPOINT)
public Queue CG_BUYING_ADDPOINT(){
Queue queue = new Queue(CG_BUYING_ADDPOINT);
return queue;
}
@Bean(CG_BUYING_FINISHADDPOINT)
public Queue CG_BUYING_FINISHADDPOINT(){
Queue queue = new Queue(CG_BUYING_FINISHADDPOINT);
return queue;
}
//队列绑定交换机
@Bean
public Binding BINDING_CG_BUYING_ADDPOINT(@Qualifier(CG_BUYING_ADDPOINT) Queue queue, @Qualifier(EX_BUYING_ADDPOINTUSER)Exchange exchange){
return BindingBuilder.bind(queue).to(exchange).with(CG_BUYING_ADDPOINT_KEY).noargs();
}
@Bean
public Binding BINDING_CG_BUYING_FINISHADDPOINT(@Qualifier(CG_BUYING_FINISHADDPOINT) Queue queue,@Qualifier(EX_BUYING_ADDPOINTUSER)Exchange exchange){
return BindingBuilder.bind(queue).to(exchange).with(CG_BUYING_FINISHADDPOINT_KEY).noargs();
}
}
3配置文件 rabbitmq
rabbitmq:
host: 192.168.200.128
2.2 订单服务逻辑
需求:
1dao层 增加mapper
package com.changgou.order.dao;
import com.changgou.order.pojo.Task;
import tk.mybatis.mapper.common.Mapper;
public interface TaskMapper extends Mapper<Task> {
}
package com.changgou.order.dao;
import com.changgou.order.pojo.TaskHis;
import tk.mybatis.mapper.common.Mapper;
public interface TaskHisMapper extends Mapper<TaskHis> {
}
2下单逻辑中增加任务
@Autowired
TaskMapper taskMapper;
// int i=1/0;
//添加任务数据
System.out.println("向订单数据库中的任务表去添加任务数据");
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);
3定时任务,定时发送任务表信息到mq
启动类增加
@EnableScheduling //开启定时任务
新建task包,加入定时任务类
@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("订单服务向添加积分队列发送了一条消息");
}
}
}
}
taskmapper中自定义查询小于当前时间的方法
@Select("select * from tb_task where update_time<#{currentTime}")
@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);
2.3 用户服务逻辑
需求:
1配置文件增加redis rabbitMQ配置
spring:
application:
name: user
datasource:
driver-class-name: com.mysql.jdbc.Driver
url: jdbc:mysql://192.168.200.128:3306/changgou_user?useUnicode=true&characterEncoding=UTF-8&serverTimezone=UTC
username: root
password: root
main:
allow-bean-definition-overriding: true #当遇到同样名字的时候,是否允许覆盖注册
redis:
host: 192.168.200.128
rabbitmq:
host: 192.168.200.128
2导入mq依赖
<dependency>
<groupId>org.springframework.amqp</groupId>
<artifactId>spring-rabbit</artifactId>
</dependency>
3mq配置类
RabbitMQConfig ,从order服务copy
4添加依赖
<dependency>
<groupId>com.changgou</groupId>
<artifactId>changgou_service_order_api</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
4监听mq队列
com.changgou.user.listener创建AddPointListener
package com.changgou.user.listener;
import com.alibaba.fastjson.JSON;
import com.changgou.order.pojo.Task;
import com.changgou.user.config.RabbitMQConfig;
import com.changgou.user.service.UserService;
import org.apache.commons.lang.StringUtils;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;
@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;
}
}
5更新积分方法实现
1userService 定义接口
int updateUserPoint(Task task);
实现类
@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;
}
2dao层新建mapper,查询当前任务是否操作过
package com.changgou.user.dao;
import com.changgou.user.pojo.PointLog;
import org.apache.ibatis.annotations.Param;
import org.apache.ibatis.annotations.Select;
import tk.mybatis.mapper.common.Mapper;
public interface PointLogMapper extends Mapper<PointLog> {
@Select("select * from tb_point_log where order_id =#{orderId}")
PointLog findPointLogByOrderId(@Param("orderId") String orderId);
}
3userMapper新增一个修改用户积分方法
package com.changgou.user.dao;
import com.changgou.user.pojo.User;
import org.apache.ibatis.annotations.Param;
import org.apache.ibatis.annotations.Update;
import tk.mybatis.mapper.common.Mapper;
public interface UserMapper extends Mapper<User> {
@Update("update tb_user set points=points+#{point} where username=#{username}")
int updateUserPoint(@Param("username")String username, @Param("point") int point);
}
4AddPointListener完善
package com.changgou.user.listener;
import com.alibaba.fastjson.JSON;
import com.changgou.order.pojo.Task;
import com.changgou.user.config.RabbitMQConfig;
import com.changgou.user.service.UserService;
import org.apache.commons.lang.StringUtils;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;
@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("用户服务接收到了任务消息");
//1转换消息
Task task = JSON.parseObject(message, Task.class);
if (task == null || StringUtils.isEmpty(task.getRequestBody())){
return;
}
//2判断redis中当前的任务是否存在
Object value = redisTemplate.boundValueOps(task.getId()).get();
if (value != null){
return;
}
//3更新用户积分
int result = userService.updateUserPoint(task);
if (result == 0){
return;
}
//4向订单服务返回通知消息
rabbitTemplate.convertAndSend(RabbitMQConfig.EX_BUYING_ADDPOINTUSER,RabbitMQConfig.CG_BUYING_FINISHADDPOINT_KEY,JSON.toJSONString(task));
System.out.println("用户服务向完成添加积分队列发送了一条消息");
}
}
2.4订单服务收尾
需求:
建立监听类DelTaskListener
package com.changgou.order.listener;
import com.alibaba.fastjson.JSON;
import com.changgou.order.config.RabbitMQConfig;
import com.changgou.order.pojo.Task;
import com.changgou.order.service.TaskService;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
@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);
}
}
创建taskService写出删除任务的方法
public interface TaskService {
void delTask(Task task);
}
实现taskService
package com.changgou.order.service.impl;
import com.changgou.order.dao.TaskHisMapper;
import com.changgou.order.dao.TaskMapper;
import com.changgou.order.pojo.Task;
import com.changgou.order.pojo.TaskHis;
import com.changgou.order.service.TaskService;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.Date;
@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);
//2bean拷贝
TaskHis taskHis = new TaskHis();
BeanUtils.copyProperties(task,taskHis);
//3记录历史任务数据
taskHisMapper.insertSelective(taskHis);
//4删除原有任务数据
//taskMapper.deleteByPrimaryKey(id);
task.setId(id);
taskMapper.delete(task);
System.out.println("订单服务完成了添加历史任务并删除原有任务的操作");
}
}
2.5效果测试
order user服务以dubug打开。
从头到最后一步一步测试。关键点:
1order下单任务表中数据
2order 定时任务扫描表,发信息
3user收到信息,检查redis,修改积分
4order收到成功消息