Java微服務下的分散式事務介紹及其解決方案2

hengliang_發表於2020-11-02

1.前言

本文將詳細介紹分散式的解決方案–訊息佇列實現分散式事務的解決方案,需要大家對我第一篇對分散式事務的介紹來了解下,會更清楚一點哦,第一篇部落格的地址分散式事務的介紹

2.業務場景介紹

我們模擬慕課網付費課程的下單,你在慕課網買了視訊後,你的學習列表要訂單服務要更新你支付的狀態,此外,學習服務要有你的新增選課資訊

3.解決方案

在這裡插入圖片描述

下面我詳細介紹下,這個解決方案
1、支付成功後,訂單服務向本地資料庫更新訂單狀態並向訊息表寫入“新增選課訊息”,通過本地資料庫保證訂單狀態和新增選課訊息的事務。。
2、定時任務掃描訊息表,取出“新增選課任務“併發向MQ。
3、學習服務接收到新增選課的訊息,先查詢本地資料庫的歷史訊息表是否存在訊息,存在則說明已經新增選課,否則向本地資料庫新增選課,並向歷史訊息表新增選課訊息。這裡選課表和歷史訊息表在同一個資料庫,通過本地事務保證。
4、學習服務接收到新增選課的訊息,通過查詢訊息表判斷如果已經新增選課也向MQ傳送“完成新增選課任務的訊息”,否則則新增選課,完成後向MQ傳送“完成新增選課任務的訊息”,
5、訂單服務接收到完成選課的訊息後刪除訂單資料庫中訊息表的“新增選課訊息”,為保證後期對賬將訊息表的訊息先新增到歷史訊息表再刪除訊息,表示此訊息已經完成。

3需要用到的幾個表

1.待處理的任務表,在訂單系統裡面儲存在這裡插入圖片描述
2.已經完成的任務表:訂單和學習系統都要有
在這裡插入圖片描述
3選課資訊表
在這裡插入圖片描述

4.解決方案的虛擬碼

3.1訂單服務定時傳送訊息

定時任務傳送訊息流程如下:
0、每隔1分鐘掃描一次任務表。
1、定時任務掃描task表,一次取出多個任務,取出超過1分鐘未處理的任務
2、考慮訂單服務可能叢集部署,為避免重複傳送任務使用樂觀鎖的方式每次從任務列表取出要處理的任務
3、任務傳送完畢更新任務傳送時間
關於任務表的新增:
正常的流程是訂單支付成功向更新訂單支付狀態並向任務表寫入“新增選課任務”。
(沒有寫使用者下單,直接預設有使用者下單)
訂單服務定時傳送訊息的程式碼(用的是SpringTask)

 //每隔一分鐘掃描訊息表,向mq傳送訊息
    @Scheduled(cron="0/3 * * * * *")
    public void sendChoosecourseTask(){
        //取出當前一分鐘之前的時間
        Calendar calendar  =new GregorianCalendar();
        calendar.setTime(new Date());
        calendar.add(GregorianCalendar.MINUTE,-1);
        Date time = calendar.getTime();
        List<XcTask> taskList = taskService.findTaskList(time, 100);
        //遍歷任務表,傳送選課資訊
        if (taskList!=null && taskList.size()>0){
            for (XcTask xcTask : taskList){
                //傳送選課資訊
                taskService.publish(xcTask,xcTask.getMqExchange(),xcTask.getMqRoutingkey());
                //記錄日子
                LOGGER.info("send choose course task id:{}",xcTask.getId());
            }
        }
    }
 

 

此時已經檢測到有人下單支付了,就要傳送,取出“新增選課任務“併發向MQ,程式碼如下

//傳送訊息
    @Transactional
    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 xcTask1 = optional.get();
            xcTask1.setUpdateTime(new Date());
            xcTaskRepository.save(xcTask1);
        }
    }

這時候訂單系統前期要處理的事(監聽使用者下單,傳送訊息到mq)已經完成了,下面就需要學習系統的服務來處理事情了(接收訊息,新增選課,傳送新增選課成功)

新增選課的程式碼:
備註:向xc_learning_course新增記錄,為保證不重複新增選課,先查詢歷史任務表,如果從歷史任務表查詢不到任務說明此任務還沒有處理,此時則新增選課並新增歷史任務。

 //新增選課
    @Transactional
    public ResponseResult addCourse(String userId, String courseId, String valid, Date startTime, Date endTime, XcTask xcTask){
        //校驗引數
        //查詢歷史任務,如果歷史任務有,說明已經新增過選課了,沒有則新增
        //查詢歷史任務
        Optional<XcTaskHis> optional = xcTaskHisRepository.findById(xcTask.getId());
        if(optional.isPresent()){
            return new ResponseResult(CommonCode.SUCCESS);
        }
        //歷史表裡面沒有,要新增課程
        XcLearningCourse xcLearningCourse = xcLearningCourseRepository.findXcLearningCourseByUserIdAndCourseId(userId, courseId);
        if (xcLearningCourse == null) {//沒有選課記錄則新增
            xcLearningCourse = new XcLearningCourse();
            xcLearningCourse.setUserId(userId);
            xcLearningCourse.setCourseId(courseId);
            xcLearningCourse.setValid(valid);
            xcLearningCourse.setStartTime(startTime);
            xcLearningCourse.setEndTime(endTime);
            xcLearningCourse.setStatus("501001");
            xcLearningCourseRepository.save(xcLearningCourse);
        } else {//有選課記錄則更新日期
            xcLearningCourse.setValid(valid);
            xcLearningCourse.setStartTime(startTime);
            xcLearningCourse.setEndTime(endTime);
            xcLearningCourse.setStatus("501001");
            xcLearningCourseRepository.save(xcLearningCourse);
        }
        //向歷史記錄表插入記錄
        Optional<XcTaskHis> optional1= xcTaskHisRepository.findById(xcTask.getId());
        if(!optional1.isPresent()){
            //新增歷史任務
            XcTaskHis xcTaskHis = new XcTaskHis();
            BeanUtils.copyProperties(xcTask,xcTaskHis);
            xcTaskHisRepository.save(xcTaskHis);
        }
        return new ResponseResult(CommonCode.SUCCESS);

    }

接收選課的訊息:
接收到新增選課的訊息呼叫新增選課方法完成新增選課,併傳送完成選課訊息。

 @RabbitListener(queues = RabbitMQConfig.XC_LEARNING_ADDCHOOSECOURSE)
    public void receiveChoosecourseTask(XcTask xcTask){
        //取出訊息內容
        //取出訊息的內容
        String requestBody = xcTask.getRequestBody();
        Map map = JSON.parseObject(requestBody, Map.class);
        String userId = (String) map.get("userId");
        String courseId = (String) map.get("courseId");
        //新增選課
        ResponseResult responseResult = learningService.addCourse(userId, courseId, null, null, null, xcTask);
        if (responseResult.isSuccess()){
            //新增選課成功,向mq傳送完成新增選課的訊息
            rabbitTemplate.convertAndSend(RabbitMQConfig.EX_LEARNING_ADDCHOOSECOURSE,RabbitMQConfig.XC_LEARNING_FINISHADDCHOOSECOURSE_KEY,xcTask);
        }
    }

到這時候,學習系統做的事(接收訂單系統發過來的訊息,新增課程資訊,給訂單資訊傳送已經新增課程成功的訊息),接下來就要回到訂單系統來接收新增課程成功的訊息了,將任務從當前任務表刪除,將完成的任務新增到完成任務表。

 /**
     * 完成任務
     * @param xcTask
     */
    @RabbitListener(queues = RabbitMQConfig.XC_LEARNING_FINISHADDCHOOSECOURSE)
    public void receiveFinishChoosecourseTask(XcTask xcTask){
        if(xcTask!=null && StringUtils.isNotEmpty(xcTask.getId())){
            taskService.finishTask(xcTask.getId());
        }
    }



//下面是刪除任務的程式碼
   //刪除任務
    @Transactional
    public void finishTask(String taskId){
        Optional<XcTask> taskOptional = xcTaskRepository.findById(taskId);
        if (taskOptional.isPresent()){
            XcTask xcTask = taskOptional.get();
            xcTask.setDeleteTime(new Date());
            XcTaskHis xcTaskHis = new XcTaskHis();
            BeanUtils.copyProperties(xcTask,xcTaskHis);
            //把記錄新增到歷史任務裡面,為了以前對賬能夠清楚點,即把任務表的資料複製到歷史任務1表裡面
            xcTaskHisRepository.save(xcTaskHis);
            xcTaskRepository.delete(xcTask);
        }
    }

總結:我覺得分散式事務的處理需要對業務很瞭解才能夠做的更好,不知道我的講解能給大家帶來什麼收穫,我希望大家能夠多看幾遍我這個流程,感謝。

相關文章