1.什么是分布式事务?
在分布式系统中一次操作由多个系统协同完成,这种一次事务操作涉及多个系统通过网络协同完成的过程称为分布式事务。这里强调的是多个系统通过网络协同完成一个事务的过程,并不强调多个系统访问了不同的数据库,即使多个系统访问的是同一个数据库也是分布式事务,如下图:
另外一种分布式事务的表现是,一个应用程序使用了多个数据源连接了不同的数据库,当一次事务需要操作多个数据源,此时也属于分布式事务,当系统作了数据库拆分后会出现此种情况。
上面两种分布式事务表现形式以第一种据多。
2.分布式事务有哪些场景?
1) 电商系统中的下单扣库存
电商系统中,订单系统和库存系统是两个系统,一次下单的操作由两个系统协同完成
2)教育系统中下单选课业务
在线教育系统中,用户购买课程,下单支付成功后学生选课成功,此事务由订单系统和选课系统协同完成。
3. CAP理论
如何进行分布式事务控制?CAP理论是分布式事务处理的理论基础,了解了CAP理论有助于我们研究分布式事务的
处理方案。
CAP理论是:分布式系统在设计时只能在一致性(Consistency)、可用性(Availability)、分区容忍性(PartitionTolerance)中满足两种,无法兼顾三种。
通过下图理解CAP理论:
一致性(Consistency):服务A、B、C三个结点都存储了用户数据, 三个结点的数据需要保持同一时刻数据一致性。
可用性(Availability):服务A、B、C三个结点,其中一个结点宕机不影响整个集群对外提供服务,如果只有服务A结点,当服务A宕机整个系统将无法提供服务,增加服务B、C是为了保证系统的可用性。
分区容忍性(Partition Tolerance):分区容忍性就是允许系统通过网络协同工作,分区容忍性要解决由于网络分区导致数据的不完整及无法访问等问题。
举个例子,假设一个电商网站采用分布式架构,将用户数据和订单数据分别存储在不同的节点上。如果其中一个节点发生故障,导致用户数据无法访问,但订单数据仍然可以正常访问,那么系统仍然可以继续处理订单,保证网站的正常运行。这就是分区容错性的体现。
4.分布式系统能否兼顾C、A、P?
在保证分区容忍性的前提下一致性和可用性无法兼顾,如果要提高系统的可用性就要增加多个结点,如果要保证数据的一致性就要实现每个结点的数据一致,结点越多可用性越好,但是数据一致性越差。所以,在进行分布式系统设计时,同时满足“一致性”、“可用性”和“分区容忍性”三者是几乎不可能的。
CAP有哪些组合方式?
1、CA:放弃分区容忍性,加强一致性和可用性,关系数据库按照CA进行设计。
2、AP:放弃一致性,加强可用性和分区容忍性,追求最终一致性,很多NoSQL数据库按照AP进行设计。
说明:这里放弃一致性是指放弃强一致性,强一致性就是写入成功立刻要查询出最新数据。追求最终一致性是指允许暂时的数据不一致,只要最终在用户接受的时间内数据 一致即可。
3、CP:放弃可用性,加强一致性和分区容忍性,一些强一致性要求的系统按CP进行设计,比如跨行转账,一次转账请求要等待双方银行系统都完成整个事务才算完成。
说明:由于网络问题的存在CP系统可能会出现待等待超时,如果没有处理超时问题则整理系统会出现阻塞。
总结:在分布式系统设计中AP的应用较多,即保证分区容忍性和可用性,牺牲数据的强一致性(写操作后立刻读取到最新数据),保证数据最终一致性。比如:订单退款,今日退款成功,明日账户到账,只要在预定的用户可以接受的时间内退款事务走完即可
5. 解决方案
一、2PC
1、第一阶段:准备阶段
2PC,两阶段提交,将事务的提交过程分为资源准备和资源提交两个阶段,并且由事务协调者来协调所有事务参与者,如果准备阶段所有事务参与者都预留资源成功,则进行第二阶段的资源提交,否则事务协调者回滚资源。
- ① 协调者向所有参与者发送事务内容,询问是否可以提交事务,并等待答复
- ② 各参与者执行本地事务操作,将 undo 和 redo 信息记入事务日志中(但不提交事务)
- ③ 如参与者执行成功,给协调者反馈同意,否则反馈中止,表示事务不可以执行
2、第二阶段:提交阶段
(1)事务提交:
当第一阶段所有参与者都反馈同意时,协调者发起正式提交事务的请求,当所有参与者都回复同意时,则意味着完成事务,具体流程如下:
① 协调者节点向所有参与者节点发出正式提交的 commit 请求。
② 收到协调者的 commit 请求后,参与者正式执行事务提交操作,并释放在整个事务期间内占用的资源。
③ 参与者完成事务提交后,向协调者节点发送ACK消息。
④ 协调者节点收到所有参与者节点反馈的ACK消息后,完成事务。
(2)事务回滚:
如果任意一个参与者节点在第一阶段返回的消息为中止,或者协调者节点在第一阶段的询问超时之前无法获取所有参与者节点的响应消息时,那么这个事务将会被回滚,具体流程如下:
- ① 协调者向所有参与者发出 rollback 回滚操作的请求
- ② 参与者利用阶段一写入的undo信息执行回滚,并释放在整个事务期间内占用的资源
- ③ 参与者在完成事务回滚之后,向协调者发送回滚完成的ACK消息
- ④ 协调者收到所有参与者反馈的ACK消息后,取消事务
3、2PC的缺点:
1、整个事务的执行需要由协调者在多个节点之间去协调,增加了事务的执行时间,性能低下。
2、2PC非常依赖协调者,当协调者发生故障时,尤其是第二阶段,那么所有的参与者就会都处于锁定事务资源的状态中,而无法继续完成事务操作(如果是协调者挂掉,可以重新选举一个协调者,但是无法解决因为协调者宕机导致的参与者处于阻塞状态的问题)
3、数据一致性问题:在阶段二中,当协调者向参与者发送commit请求之后,发生了局部网络异常或者在发送commit请求过程中协调者发生了故障,这回导致只有一部分参与者接受到了commit请求。而在这部分参与者接到commit请求之后就会执行commit操作。但是其他部分未接到commit请求的机器则无法执行事务提交。于是整个分布式系统便出现了数据部一致性的现象。
二、TCC事务补偿
TCC事务补偿是基于2PC实现的业务层事务控制方案,它是Try、Confirm和Cancel三个单词的首字母,含义如下:
1、Try 检查及预留业务资源完成提交事务前的检查,并预留好资源。
2、Confirm 确定执行业务操作对try阶段预留的资源正式执行。
3、Cancel 取消执行业务操作对try阶段预留的资源释放。
1、TCC如何保证最终一致性?
- TCC 事务机制以 Try 为中心的,Confirm 确认操作和 Cancel 取消操作都是围绕 Try 而展开。因此,Try 阶段中的操作,其保障性是最好的,即使失败,仍然有 Cancel 取消操作可以将其执行结果撤销。
- Try阶段执行成功并开始执行 Confirm 阶段时,默认 Confirm 阶段是不会出错的,也就是说只要 Try 成功,Confirm 一定成功(TCC设计之初的定义)
- Confirm 与 Cancel 如果失败,由TCC框架进行重试补偿
- 存在极低概率在CC环节彻底失败,则需要定时任务或人工介入
优点:最终保证数据的一致性,在业务层实现事务控制,灵活性好。
缺点:开发成本高,每个事务操作每个参与者都需要实现try/confirm/cancel三个接口。
注意:TCC的try/confirm/cancel接口都要实现幂等性,在为在try、confirm、cancel失败后要不断重试。
什么是幂等性?
幂等性是指同一个操作无论请求多少次,其结果都相同。
幂等操作实现方式有:
1、操作之前在业务方法进行判断如果执行过了就不再执行。
2、缓存所有请求和处理的结果,已经处理的请求则直接返回结果。
3、在数据库表中加一个状态字段(未处理,已处理),数据操作时判断未处理时再处
三. 消息队列实现最终一致
本方案是将分布式事务拆分成多个本地事务来完成,并且由消息队列异步协调完成
例如以下案例:
1、支付成功后,订单服务向本地数据库更新订单状态并向消息表写入“添加选课消息”,通过本地数据库保证订单
2、定时任务扫描消息任务表,取出“添加选课任务“并发向MQ。
状态和添加选课消息的事务。
3、学习服务接收到添加选课的消息,先查询本地数据库的历史消息表是否存在消息,存在则说明已经添加选课,否则向本地数据库添加选课,并向历史任务消息表添加选课消息。这里选课表和历史消息表在同一个数据库,通过本地事务保证。
4、学习服务接收到添加选课的消息,通过查询消息表判断如果已经添加选课也向MQ发送“完成添加选课任务的消息”,否则则添加选课,完成后向MQ发送“完成添加选课任务的消息”,
5、订单服务接收到完成选课的消息后删除订单数据库中消息表的“添加选课消息”,为保证后期对账将消息表的消息先添加到历史消息表再删除消息,表示此消息已经完成。
4.订单服务定时发送消息
4.1 配置异步任务
创建异步任务配置类,需要配置线程池实现多线程调度任务。
@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());
}
}
将@EnableScheduling添加到此配置类上,SpringBoot启动类上不用再添加@EnableScheduling
4.2 RabbitMQ配置
@Configuration
public class RabbitMQConfig {
//添加选课任务交换机
public static final String EX_LEARNING_ADDCHOOSECOURSE = "ex_learning_addchoosecourse";
//完成添加选课消息队列
public static final String XC_LEARNING_FINISHADDCHOOSECOURSE = "xc_learning_finishaddchoosecourse";
//添加选课消息队列
public static final String XC_LEARNING_ADDCHOOSECOURSE = "xc_learning_addchoosecourse";
//添加选课路由key
public static final String XC_LEARNING_ADDCHOOSECOURSE_KEY = "addchoosecourse";
//完成添加选课路由key
public static final String XC_LEARNING_FINISHADDCHOOSECOURSE_KEY = "finishaddchoosecourse";
/**
* 交换机配置
* @return the exchange
*/
@Bean(EX_LEARNING_ADDCHOOSECOURSE)
public Exchange EX_DECLARE() {
return ExchangeBuilder.directExchange(EX_LEARNING_ADDCHOOSECOURSE).durable(true).build();
}
//声明队列完成添加选课队列
@Bean(XC_LEARNING_FINISHADDCHOOSECOURSE)
public Queue QUEUE_DECLARE() {
Queue queue = new Queue(XC_LEARNING_FINISHADDCHOOSECOURSE,true,false,true);
return queue;
}
//声明队列 添加选课队列
@Bean(XC_LEARNING_ADDCHOOSECOURSE)
public Queue QUEUE_DECLARE_2() {
Queue queue = new Queue(XC_LEARNING_ADDCHOOSECOURSE,true,false,true);
return queue;
}
/**
* 绑定完成添加选课队列到交换机 .
* @param queue the queue
* @param exchange the exchange
* @return the binding
*/
@Bean
public Binding binding_finishaddchoose_processtask(@Qualifier("xc_learning_finishaddchoosecourse") Queue queue, @Qualifier(EX_LEARNING_ADDCHOOSECOURSE) Exchange exchange) {
return BindingBuilder.bind(queue).to(exchange).with(XC_LEARNING_FINISHADDCHOOSECOURSE_KEY).noargs();
}
/**
* 绑定添加选课队列到交换机 .
* @param queue the queue
* @param exchange the exchange
* @return the binding
*/
@Bean
public Binding binding_addchoose_processtask(@Qualifier("xc_learning_addchoosecourse") Queue queue, @Qualifier(EX_LEARNING_ADDCHOOSECOURSE) Exchange exchange) {
return BindingBuilder.bind(queue).to(exchange).with(XC_LEARNING_ADDCHOOSECOURSE_KEY).noargs();
}
}
4.3 查询任务表中前N条任务
4.3.1 DAO
public interface XcTaskRepository extends JpaRepository<XcTask, String> {
//取出指定时间之前的记录
Page<XcTask> findByUpdateTimeBefore(Pageable pageable,Date updateTime);
//更新updateTime
@Modifying
@Query("update XcTask t set t.updateTime = :updateTime where t.id = :id")
public int updateTaskTime(@Param(value = "id") String id,@Param(value = "updateTime") Date updateTime);
@Modifying
@Query("update XcTask t set t.version = :version+1 where t.id = :id and t.version = :version")
public int updateTaskVersion(@Param(value = "id") String id,@Param(value = "version") int version);
}
4.3.2 Service
@Service
public class TaskService {
@Autowired
XcTaskRepository xcTaskRepository;
@Autowired
RabbitTemplate rabbitTemplate;
//查询前n条任务
public List<XcTask> findXcTaskList(Date updateTime,int size){
//设置分页参数
Pageable pageable = new PageRequest(0,size);
//查询前n条任务
Page<XcTask> all = xcTaskRepository.findByUpdateTimeBefore(pageable, updateTime);
List<XcTask> list = all.getContent();
return list;
}
//发布消息
public void publish(XcTask xcTask,String ex,String routingKey){
Optional<XcTask> optional = xcTaskRepository.findById(xcTask.getId());
if(optional.isPresent()){
rabbitTemplate.convertAndSend(ex,routingKey,xcTask);
//更新任务时间
XcTask one = optional.get();
one.setUpdateTime(new Date());
xcTaskRepository.save(one);
}
}
//获取任务
@Transactional
public int getTask(String id,int version){
//通过乐观锁的方式来更新数据表,如果结果大于0说明取到任务
int count = xcTaskRepository.updateTaskVersion(id, version);
return count;
}
//完成任务
@Transactional
public void finishTask(String taskId){
Optional<XcTask> optionalXcTask = xcTaskRepository.findById(taskId);
if(optionalXcTask.isPresent()){
//当前任务
XcTask xcTask = optionalXcTask.get();
//历史任务
XcTaskHis xcTaskHis = new XcTaskHis();
BeanUtils.copyProperties(xcTask,xcTaskHis);
xcTaskHisRepository.save(xcTaskHis);
xcTaskRepository.delete(xcTask);
}
}
}
4.4 编写任务类
编写任务类,每分钟执行任务,启动订单工程,观察定时发送消息日志,观察rabbitMQ队列中是否有消息,代码如下:
@Component
public class ChooseCourseTask {
private static final Logger LOGGER = LoggerFactory.getLogger(ChooseCourseTask.class);
@Autowired
TaskService taskService;
@RabbitListener(queues = RabbitMQConfig.XC_LEARNING_FINISHADDCHOOSECOURSE)
public void receiveFinishChoosecourseTask(XcTask xcTask){
if(xcTask!=null && StringUtils.isNotEmpty(xcTask.getId())){
taskService.finishTask(xcTask.getId());
}
}
@Scheduled(cron="0/3 * * * * *")
//定时发送加选课任务
public void sendChoosecourseTask(){
//得到1分钟之前的时间
Calendar calendar = new GregorianCalendar();
calendar.setTime(new Date());
calendar.set(GregorianCalendar.MINUTE,-1);
Date time = calendar.getTime();
List<XcTask> xcTaskList = taskService.findXcTaskList(time, 100);
System.out.println(xcTaskList);
//调用service发布消息,将添加选课的任务发送给mq
for(XcTask xcTask:xcTaskList){
//取任务
//考虑订单服务将来会集群部署,为了避免任务在1分钟内重复执行,这里使用乐观锁,实现
//思路如下:
//1) 每次取任务时判断当前版本及任务id是否匹配,如果匹配则执行任务,如果不匹配则取消执行。
//2) 如果当前版本和任务Id可以匹配到任务则更新当前版本加1.
//1、在Dao中增加校验当前版本及任务id的匹配方法
if(taskService.getTask(xcTask.getId(),xcTask.getVersion())>0){
String ex = xcTask.getMqExchange();//要发送的交换机
String routingKey = xcTask.getMqRoutingkey();//发送消息要带routingKey
taskService.publish(xcTask,ex,routingKey);
}
}
}
//定义任务调试策略
// @Scheduled(cron="0/3 * * * * *")//每隔3秒去执行
// @Scheduled(fixedRate = 3000) //在任务开始后3秒执行下一次调度
// @Scheduled(fixedDelay = 3000) //在任务结束后3秒后才开始执行
public void task1(){
LOGGER.info("===============测试定时任务1开始===============");
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
LOGGER.info("===============测试定时任务1结束===============");
}
//定义任务调试策略
// @Scheduled(cron="0/3 * * * * *")//每隔3秒去执行
// @Scheduled(fixedRate = 3000) //在任务开始后3秒执行下一次调度
// @Scheduled(fixedDelay = 3000) //在任务结束后3秒后才开始执行
public void task2(){
LOGGER.info("===============测试定时任务2开始===============");
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
LOGGER.info("===============测试定时任务2结束===============");
}
}
5.自动添加选课开发
5.1RabbitMQ配置
学习服务监听MQ的添加选课队列,并且声明完成选课队列,配置代码同订单服务中RabbitMQ配置
@Configuration
public class RabbitMQConfig {
//添加选课任务交换机
public static final String EX_LEARNING_ADDCHOOSECOURSE = "ex_learning_addchoosecourse";
//完成添加选课消息队列
public static final String XC_LEARNING_FINISHADDCHOOSECOURSE = "xc_learning_finishaddchoosecourse";
//添加选课消息队列
public static final String XC_LEARNING_ADDCHOOSECOURSE = "xc_learning_addchoosecourse";
//添加选课路由key
public static final String XC_LEARNING_ADDCHOOSECOURSE_KEY = "addchoosecourse";
//完成添加选课路由key
public static final String XC_LEARNING_FINISHADDCHOOSECOURSE_KEY = "finishaddchoosecourse";
/**
* 交换机配置
* @return the exchange
*/
@Bean(EX_LEARNING_ADDCHOOSECOURSE)
public Exchange EX_DECLARE() {
return ExchangeBuilder.directExchange(EX_LEARNING_ADDCHOOSECOURSE).durable(true).build();
}
//声明队列完成添加选课队列
@Bean(XC_LEARNING_FINISHADDCHOOSECOURSE)
public Queue QUEUE_DECLARE() {
Queue queue = new Queue(XC_LEARNING_FINISHADDCHOOSECOURSE,true,false,true);
return queue;
}
//声明队列 添加选课队列
@Bean(XC_LEARNING_ADDCHOOSECOURSE)
public Queue QUEUE_DECLARE_2() {
Queue queue = new Queue(XC_LEARNING_ADDCHOOSECOURSE,true,false,true);
return queue;
}
/**
* 绑定完成添加选课队列到交换机 .
* @param queue the queue
* @param exchange the exchange
* @return the binding
*/
@Bean
public Binding binding_finishaddchoose_processtask(@Qualifier("xc_learning_finishaddchoosecourse") Queue queue, @Qualifier(EX_LEARNING_ADDCHOOSECOURSE) Exchange exchange) {
return BindingBuilder.bind(queue).to(exchange).with(XC_LEARNING_FINISHADDCHOOSECOURSE_KEY).noargs();
}
/**
* 绑定添加选课队列到交换机 .
* @param queue the queue
* @param exchange the exchange
* @return the binding
*/
@Bean
public Binding binding_addchoose_processtask(@Qualifier("xc_learning_addchoosecourse") Queue queue, @Qualifier(EX_LEARNING_ADDCHOOSECOURSE) Exchange exchange) {
return BindingBuilder.bind(queue).to(exchange).with(XC_LEARNING_ADDCHOOSECOURSE_KEY).noargs();
}
5.2 学生选课Dao:
学生选课Dao:
public interface XcLearningCourseRepository extends JpaRepository<XcLearningCourse, String> {
//根据用户和课程查询选课记录,用于判断是否添加选课
XcLearningCourse findXcLearningCourseByUserIdAndCourseId(String userId, String courseId);
}
历史任务Dao:
public interface XcTaskHisRepository extends JpaRepository<XcTaskHis,String> {
}
5.3 添加选课方法学生选课Service
@Service
public class LearningService {
@Autowired
XcLearningCourseRepository xcLearningCourseRepository;
@Autowired
XcTaskHisRepository xcTaskHisRepository;
//添加选课
@Transactional
public ResponseResult addcourse(String userId, String courseId, String valid, Date startTime, Date endTime, XcTask xcTask){
if (StringUtils.isEmpty(courseId)) {
ExceptionCast.cast(LearningCode.LEARNING_GETMEDIA_ERROR);
}
/**
* 向xc_learning_course添加记录,为保证不重复添加选课,先查询历史任务表,如果从历史任务表查询不到任务说
* 明此任务还没有处理,此时则添加选课并添加历史任务。
*/
Optional<XcTaskHis> optional = xcTaskHisRepository.findById(xcTask.getId());
if(optional.isPresent()){
return new ResponseResult(CommonCode.SUCCESS);
}
XcLearningCourse xcLearningCourse = xcLearningCourseRepository.findByUserIdAndCourseId(userId, courseId);
if(xcLearningCourse!=null){
//更新选课记录
//课程的开始时间
xcLearningCourse.setStartTime(startTime);
xcLearningCourse.setEndTime(endTime);
xcLearningCourse.setStatus("501001");
xcLearningCourseRepository.save(xcLearningCourse);
}else{
//添加新的选课记录
xcLearningCourse = new XcLearningCourse();
xcLearningCourse.setUserId(userId);
xcLearningCourse.setCourseId(courseId);
xcLearningCourse.setStartTime(startTime);
xcLearningCourse.setEndTime(endTime);
xcLearningCourse.setStatus("501001");
xcLearningCourseRepository.save(xcLearningCourse);
}
/**
* 向xc_learning_course添加记录,为保证不重复添加选课,先查询历史任务表,如果从历史任务表查询不到任务说
* 明此任务还没有处理,此时则添加选课并添加历史任务。
*/
Optional<XcTaskHis> optional = xcTaskHisRepository.findById(xcTask.getId());
if(!optional.isPresent()){
//添加历史任务
XcTaskHis xcTaskHis = new XcTaskHis();
BeanUtils.copyProperties(xcTask,xcTaskHis);
xcTaskHisRepository.save(xcTaskHis);
}
return new ResponseResult(CommonCode.SUCCESS);
}
}
5.4 接收添加选课消息
接收到添加选课的消息调用添加选课方法完成添加选课,并发送完成选课消息。
@Component
public class ChooseCourseTask {
private static final Logger LOGGER = LoggerFactory.getLogger(ChooseCourseTask.class);
@Autowired
LearningService learningService;
@Autowired
RabbitTemplate rabbitTemplate;
/**
* 接收选课任务
*/
@RabbitListener(queues = {RabbitMQConfig.XC_LEARNING_ADDCHOOSECOURSE})
public void receiveChoosecourseTask(XcTask xcTask,Message message,Channel channel) throws IOException {
LOGGER.info("receive choose course task,taskId:{}",xcTask.getId());
//接收到 的消息id
String id = xcTask.getId();
//添加选课
try {
String requestBody = xcTask.getRequestBody();
Map map = JSON.parseObject(requestBody, Map.class);
String userId = (String) map.get("userId");
String courseId = (String) map.get("courseId");
String valid = (String) map.get("valid");
Date startTime = null;
Date endTime = null;
SimpleDateFormat dateFormat = new SimpleDateFormat("YYYY‐MM‐dd HH:mm:ss");
if(map.get("startTime")!=null){
startTime =dateFormat.parse((String) map.get("startTime"));
}
if(map.get("endTime")!=null){
endTime =dateFormat.parse((String) map.get("endTime"));
}
//添加选课
ResponseResult addcourse = learningService.addcourse(userId, courseId,
valid,startTime, endTime,xcTask);
//选课成功发送响应消息
if(addcourse.isSuccess()){
//发送响应消息
rabbitTemplate.convertAndSend(RabbitMQConfig.EX_LEARNING_ADDCHOOSECOURSE,
RabbitMQConfig.XC_LEARNING_FINISHADDCHOOSECOURSE_KEY, xcTask );
LOGGER.info("send finish choose course taskId:{}",id);
}
} catch (Exception e) {
e.printStackTrace();
LOGGER.error("send finish choose course taskId:{}", id);
}
}
}
6.订单服务结束任务
6.1 Dao
1、删除xc_task
2、添加xc_task_his
6.2 Service
//完成任务
@Transactional
public void finishTask(String taskId){
Optional<XcTask> optionalXcTask = xcTaskRepository.findById(taskId);
if(optionalXcTask.isPresent()){
//当前任务
XcTask xcTask = optionalXcTask.get();
//历史任务
XcTaskHis xcTaskHis = new XcTaskHis();
BeanUtils.copyProperties(xcTask,xcTaskHis);
xcTaskHisRepository.save(xcTaskHis);
xcTaskRepository.delete(xcTask);
}
}
6.3 接收完成选课消息
@RabbitListener(queues = RabbitMQConfig.XC_LEARNING_FINISHADDCHOOSECOURSE)
public void receiveFinishChoosecourseTask(XcTask xcTask){
if(xcTask!=null && StringUtils.isNotEmpty(xcTask.getId())){
taskService.finishTask(xcTask.getId());
}
}