分布式系統消息隊列:可靠投遞與延時消息實戰

在分布式系統架構中,消息隊列(MQ)作為解耦服務、削峰填谷、異步通信的核心組件,其消息投遞的可靠性與延時消息的精準性直接影響業務系統的穩定性。本文結合實際業務場景,詳細解析消息投遞的全流程設計與延時消息的通用實現方案,提供可落地的代碼思路,助力開發者解決高并發場景下的消息處理難題。

一、消息投遞的核心目標與挑戰

消息投遞的本質是實現跨服務的異步通信,但其背后隱藏著兩大核心挑戰:

  • 可靠性:確保消息不丟失、不重復,且業務操作與消息發送保持一致性(即 “業務成功則消息必達,業務失敗則消息不發”)。
  • 高效性:在高并發場景下,消息投遞不能成為系統瓶頸,需兼顧吞吐量與實時性。

針對這些挑戰,業界普遍采用 “本地消息表 + 事務同步 + 重試機制” 的方案,通過 “先存后發” 的思路確保消息可靠投遞。

二、可靠消息投遞:基于本地消息表的事務消息方案

事務消息是解決 “業務操作與消息發送原子性” 的關鍵技術,其核心思想是將消息發送納入本地事務管理,通過本地消息表記錄消息狀態,再通過異步投遞與補償機制確保最終一致性。

1. 事務消息的核心流程

事務消息的執行遵循 “本地事務優先,消息異步跟進” 的原則,具體流程如下:

  1. 開啟本地事務:在業務方法中開啟數據庫事務(如用戶注冊、訂單創建等場景)。
  2. 執行業務邏輯:完成核心業務操作(如插入用戶記錄、創建訂單)。
  3. 寫入消息表:將待發送的消息(含消息體、狀態、創建時間等)寫入本地消息表,狀態標記為 “待投遞”。
  4. 提交本地事務:若業務邏輯無異常,提交事務;若異常,則回滾(消息表記錄也會被回滾,確保消息不被發送)。
  5. 異步投遞消息:事務提交后,異步將消息表中 “待投遞” 的消息發送至 MQ。
  6. 狀態更新與重試:若投遞成功,更新消息狀態為 “成功”;若失敗,記錄失敗原因并觸發重試機制。

2. 核心組件設計與實現

(1)本地消息表設計

消息表是事務消息的核心載體,需記錄消息的全生命周期狀態,表結構示例如下:

CREATE TABLE `t_msg` (`id` varchar(32) NOT NULL COMMENT '消息唯一ID',`body_json` text NOT NULL COMMENT '消息體(JSON格式)',`topic` varchar(100) NOT NULL COMMENT 'MQ主題',`status` tinyint NOT NULL DEFAULT 0 COMMENT '狀態:0-待投遞,1-投遞成功,2-投遞失敗',`fail_msg` text COMMENT '失敗原因(status=2時記錄)',`fail_count` int NOT NULL DEFAULT 0 COMMENT '失敗次數',`next_retry_time` datetime DEFAULT NULL COMMENT '下次重試時間',`create_time` datetime NOT NULL COMMENT '創建時間',`update_time` datetime NOT NULL COMMENT '更新時間',PRIMARY KEY (`id`),KEY `idx_status_retry` (`status`,`next_retry_time`) COMMENT '用于查詢待重試消息') ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='本地消息表';
(2)消息發送核心接口與實現

定義消息發送接口IMsgSender,封裝消息發送與重試邏輯,業務方通過接口調用即可完成消息投遞:

public interface IMsgSender {/*** 發送單條消息* @param msg 消息體(任意可序列化對象)* @param topic MQ主題*/void send(Object msg, String topic);/*** 批量發送消息* @param msgList 消息列表* @param topic MQ主題*/void sendBatch(List<Object> msgList, String topic);/*** 重試發送消息* @param msgId 消息ID*/void retrySend(String msgId);}

其實現類DefaultMsgSender是核心,負責消息表寫入、事務同步與 MQ 投遞:


@Servicepublic class DefaultMsgSender implements IMsgSender {@Autowiredprivate MsgMapper msgMapper;@Autowiredprivate MQTemplate mqTemplate; // 封裝MQ客戶端的發送工具@Autowiredprivate RetryStrategy retryStrategy; // 重試策略@Overridepublic void send(Object msg, String topic) {// 1. 生成消息記錄MsgPO msgPO = buildMsgPO(msg, topic);// 2. 寫入消息表(與業務事務同享一個事務)msgMapper.insert(msgPO);// 3. 注冊事務同步器,事務提交后異步發送消息registerTransactionSync(msgPO);}private MsgPO buildMsgPO(Object msg, String topic) {MsgPO po = new MsgPO();po.setId(UUID.randomUUID().toString().replaceAll("-", ""));po.setBodyJson(JSON.toJSONString(msg));po.setTopic(topic);po.setStatus(0); // 待投遞po.setCreateTime(new Date());po.setUpdateTime(new Date());return po;}private void registerTransactionSync(MsgPO msgPO) {if (TransactionSynchronizationManager.isSynchronizationActive()) {// 若存在活躍事務,注冊同步器TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronizationAdapter() {@Overridepublic void afterCompletion(int status) {if (status == TransactionSynchronization.STATUS_COMMITTED) {// 事務提交成功,異步發送消息CompletableFuture.runAsync(() -> sendToMQ(msgPO));}}});} else {// 無事務環境,直接發送sendToMQ(msgPO);}}// 實際發送消息到MQprivate void sendToMQ(MsgPO msgPO) {try {// 發送消息到MQmqTemplate.send(msgPO.getTopic(), msgPO.getBodyJson());// 發送成功,更新狀態msgMapper.updateStatusSuccess(msgPO.getId(), new Date());} catch (Exception e) {// 發送失敗,計算重試時間int newFailCount = msgPO.getFailCount() + 1;Date nextRetryTime = retryStrategy.calculateNextRetryTime(newFailCount);boolean needRetry = retryStrategy.needRetry(newFailCount);// 更新失敗狀態msgMapper.updateStatusFail(msgPO.getId(),e.getMessage(),newFailCount,needRetry ? nextRetryTime : null,new Date());}}@Overridepublic void sendBatch(List<Object> msgList, String topic) {// 批量處理邏輯,類似單條發送,省略...}@Overridepublic void retrySend(String msgId) {MsgPO msgPO = msgMapper.selectById(msgId);if (msgPO == null || msgPO.getStatus() != 2) {return;}sendToMQ(msgPO); // 復用發送邏輯}}
(3)重試策略與補償定時任務

為避免消息因網絡波動等臨時問題丟失,需設計重試機制。采用衰減式重試策略(失敗次數越多,重試間隔越長),示例如下:


public class DecayRetryStrategy implements RetryStrategy {private static final int MAX_RETRY_COUNT = 5; // 最大重試次數// 重試間隔(秒):第1次失敗后10s,第2次30s,第3次60s,以此類推private static final int[] INTERVALS = {10, 30, 60, 120, 300};@Overridepublic Date calculateNextRetryTime(int failCount) {if (failCount >= MAX_RETRY_COUNT) {return null; // 超過最大次數,不再重試}int interval = INTERVALS[Math.min(failCount, INTERVALS.length - 1)];return new Date(System.currentTimeMillis() + interval * 1000L);}@Overridepublic boolean needRetry(int failCount) {return failCount < MAX_RETRY_COUNT;}}

同時,通過定時任務(Job)掃描待重試消息,觸發重試:


@Componentpublic class MsgRetryJob {@Autowiredprivate MsgMapper msgMapper;@Autowiredprivate IMsgSender msgSender;@Scheduled(fixedRate = 60000) // 每分鐘執行一次public void retryFailedMsgs() {// 查詢狀態為失敗且到達重試時間的消息List<MsgPO> needRetryMsgs = msgMapper.selectNeedRetryMsgs(new Date());for (MsgPO msg : needRetryMsgs) {msgSender.retrySend(msg.getId());}}}

3. 業務方使用示例

在業務方法中,只需注入IMsgSender并調用send方法,即可完成事務消息的發送:


@Servicepublic class UserService {@Autowiredprivate UserMapper userMapper;@Autowiredprivate IMsgSender msgSender;@Transactional(rollbackFor = Exception.class)public void register(UserRegisterDTO dto) {// 1. 執行業務邏輯:創建用戶UserPO user = new UserPO();user.setId(UUID.randomUUID().toString());user.setUsername(dto.getUsername());userMapper.insert(user);// 2. 發送用戶注冊消息(事務提交后自動發送)UserRegisterMsg msg = new UserRegisterMsg(user.getId(), user.getUsername());msgSender.send(msg, "user-register-topic");}}

三、延時消息:通用實現方案與場景落地

延時消息指消息發送后,并不立即投遞到 MQ,而是延遲指定時間后再被消費,典型場景包括:訂單 15 分鐘未支付自動取消、超時任務提醒、失敗操作重試等。

1. 延時消息的實現方案對比

常見的延時消息實現方案各有優劣,需根據業務場景選擇:

方案

實現方式

優點

缺點

數據庫定時輪詢

消息表記錄expect_send_time,定時任務掃描并發送

實現簡單,不依賴特定 MQ

輪詢間隔影響實時性,高頻率輪詢壓力大

MQ 自帶延時隊列

如 RabbitMQ 的 TTL + 死信隊列、RocketMQ 的延時等級

依賴 MQ 原生能力,性能好

受限于 MQ 支持的延時等級,靈活性差

內存延遲隊列 + 持久化

結合 Java DelayQueue 與數據庫,定時預加載消息

實時性高,支持任意延時

需處理服務重啟后內存數據丟失問題

本文推薦 **“數據庫 + DelayQueue + 定時預加載”** 方案,兼顧可靠性與靈活性。

2. 延時消息的核心實現

(1)消息表擴展

在原有消息表基礎上增加延時相關字段:

ALTER TABLE `t_msg` ADD COLUMN `expect_send_time` datetime NOT NULL COMMENT '期望發送時間';

ALTER TABLE `t_msg` ADD COLUMN `is_delay` tinyint NOT NULL DEFAULT 0 COMMENT '是否延時消息:0-否,1-是';

(2)延時消息發送接口

擴展IMsgSender接口,支持發送延時消息:


public interface IMsgSender {// 發送延時消息(指定延遲時間)void sendDelay(Object msg, String topic, long delay, TimeUnit unit);// 發送延時消息(指定期望發送時間)void sendDelayAt(Object msg, String topic, Date expectSendTime);}
(3)基于 DelayQueue 的內存延遲隊列

利用 Java 的DelayQueue(阻塞隊列,支持按延時時間排序)實現內存級延時消息管理,結合定時任務預加載消息:

@Componentpublic class DelayMsgManager {private final DelayQueue<DelayMsgTask> delayQueue = new DelayQueue<>();@Autowiredprivate MsgMapper msgMapper;@Autowiredprivate IMsgSender msgSender;// 初始化時啟動消費者線程@PostConstructpublic void startConsumer() {new Thread(() -> {while (true) {try {// 阻塞獲取到期的消息任務DelayMsgTask task = delayQueue.take();// 發送消息msgSender.retrySend(task.getMsgId());} catch (Exception e) {// 異常處理}}}, "delay-msg-consumer").start();}// 定時預加載近期需要發送的延時消息@Scheduled(fixedRate = 60000) // 每分鐘執行一次public void preloadDelayMsgs() {Date now = new Date();Date nextHour = new Date(now.getTime() + 3600 * 1000); // 加載未來1小時內的消息List<MsgPO> delayMsgs = msgMapper.selectDelayMsgs(now, nextHour);for (MsgPO msg : delayMsgs) {long delayMs = msg.getExpectSendTime().getTime() - now.getTime();if (delayMs > 0) {delayQueue.put(new DelayMsgTask(msg.getId(), delayMs));}}}// 延時任務封裝static class DelayMsgTask implements Delayed {private final String msgId;private final long triggerTime; // 觸發時間(毫秒)public DelayMsgTask(String msgId, long delayMs) {this.msgId = msgId;this.triggerTime = System.currentTimeMillis() + delayMs;}@Overridepublic long getDelay(TimeUnit unit) {return unit.convert(triggerTime - System.currentTimeMillis(), TimeUnit.MILLISECONDS);}@Overridepublic int compareTo(Delayed o) {return Long.compare(this.triggerTime, ((DelayMsgTask) o).triggerTime);}// getterpublic String getMsgId() { return msgId; }}}
(4)延時消息發送實現

在DefaultMsgSender中實現延時消息發送邏輯:


@Overridepublic void sendDelay(Object msg, String topic, long delay, TimeUnit unit) {Date expectSendTime = new Date(System.currentTimeMillis() + unit.toMillis(delay));sendDelayAt(msg, topic, expectSendTime);}@Overridepublic void sendDelayAt(Object msg, String topic, Date expectSendTime) {MsgPO msgPO = buildMsgPO(msg, topic);msgPO.setIsDelay(1);msgPO.setExpectSendTime(expectSendTime);msgMapper.insert(msgPO); // 寫入消息表// 若期望時間在近期(如1小時內),直接加入內存延遲隊列long now = System.currentTimeMillis();long delayMs = expectSendTime.getTime() - now;if (delayMs > 0 && delayMs <= 3600 * 1000) {delayMsgManager.getDelayQueue().put(new DelayMsgTask(msgPO.getId(), delayMs));}}

3. 延時消息的業務場景示例

以訂單超時取消為例,演示延時消息的使用:


@Servicepublic class OrderService {@Autowiredprivate OrderMapper orderMapper;@Autowiredprivate IMsgSender msgSender;@Transactional(rollbackFor = Exception.class)public String createOrder(OrderCreateDTO dto) {// 1. 創建訂單OrderPO order = new OrderPO();order.setId(UUID.randomUUID().toString());order.setGoodsId(dto.getGoodsId());order.setStatus(0); // 未支付orderMapper.insert(order);// 2. 發送15分鐘后執行的延時消息(訂單超時取消)OrderTimeoutMsg msg = new OrderTimeoutMsg(order.getId());msgSender.sendDelay(msg, "order-timeout-topic", 15, TimeUnit.MINUTES);return order.getId();}}// 訂單超時消息消費者@Componentpublic class OrderTimeoutConsumer {@Autowiredprivate OrderMapper orderMapper;@Consumer(topic = "order-timeout-topic")public void handle(OrderTimeoutMsg msg) {OrderPO order = orderMapper.selectById(msg.getOrderId());if (order != null && order.getStatus() == 0) { // 仍未支付// 取消訂單邏輯(如更新狀態、釋放庫存等)orderMapper.updateStatus(msg.getOrderId(), 2); // 2-已取消}}}

四、可靠性與性能優化建議

  1. 分布式鎖防重復:在消息發送與重試時,通過分布式鎖(如 Redis 鎖)避免集群環境下的重復投遞。
  2. 消息表歸檔:定期將已成功或超過最大重試次數的消息遷移至歷史表,提升查詢性能。
  3. 批量操作優化:消息寫入與查詢采用批量處理,減少數據庫交互次數。
  4. 線程池隔離:消息發送與業務線程池隔離,避免相互影響。
  5. 監控告警:對消息發送成功率、重試次數、延時消息觸發時效等指標進行監控,異常時及時告警。

五、總結

消息投遞的可靠性與延時消息的精準性是分布式系統的重要基石。本文提出的 “本地消息表 + 事務同步” 方案確保了消息與業務的原子性,而 “數據庫 + DelayQueue” 的組合則實現了通用、靈活的延時消息功能。

這些方案不依賴特定 MQ 中間件,可適配各種消息隊列(如 RabbitMQ、Kafka、RocketMQ),且代碼模塊化程度高,易于集成到現有系統中。在實際應用中,需根據業務量級與實時性要求,靈活調整重試策略與延時消息的預加載頻率,以達到可靠性與性能的最佳平衡。

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/919474.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/919474.shtml
英文地址,請注明出處:http://en.pswp.cn/news/919474.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

Java 學習筆記(基礎篇6)

面向對象基礎1. 類和對象(1) 示例&#xff1a;public class Student {String name "張三";int age 23;public void study() {System.out.println("學習 Java");}public void eat() {System.out.println("吃飯");} }public class Test {public …

光學件加工廠倚光科技:陪跑光學未來力量

在光學創新的漫漫長路上&#xff0c;總有一些看似 “不劃算” 的堅持&#xff0c;卻在悄然改寫行業的未來。倚光科技的故事&#xff0c;就始于這樣一種選擇 —— 明知光學打樣利潤微薄&#xff0c;明知上百個項目中能走到量產的寥寥無幾&#xff0c;仍愿意投入全球頂尖的設備與…

RabbitMQ:生產者可靠性(生產者重連、生產者確認)

目錄一、生產者重連二、生產者確認一、生產者重連 當網絡不穩定的時候&#xff0c;利用重試機制可以有效提高消息發送的成功率。不過SpringAMQP提供的重試機制是阻塞式的重試&#xff0c;也就是說多次重試過程中&#xff0c;當前線程是被阻塞的&#xff0c;會影響業務性能。 …

【深度學習新浪潮】空天地數據融合技術在城市三維重建中的應用

空天地數據融合技術在城市三維重建中的應用已取得顯著進展,尤其在提升精度以滿足具身智能機器人仿真訓練需求方面,研究和產品均呈現多樣化發展。以下是關鍵研究進展、產品方案及精度要求的詳細分析: 一、研究進展與技術路徑 1. 多源數據融合的技術突破 時空基準統一:通過…

Selenium自動化測試入門:cookie處理

&#x1f345; 點擊文末小卡片&#xff0c;免費獲取軟件測試全套資料&#xff0c;資料在手&#xff0c;漲薪更快driver.get_cookies() # 獲得cookie 信息driver.get_cookies(name) # 獲得對應name的cookie信息add_cookie(cookie_dict) # 向cookie 添加會話信息delete_cookie(na…

快解析如何讓遠程訪問更安全?

一、勒索病毒攻擊服務器的途徑很多用戶服務器對外開放&#xff0c;實現外網訪問&#xff0c;擔心服務器被勒索病毒攻擊&#xff01;勒索病毒攻擊服務器的途徑之一是通過路由器開放的端口進行掃描攻擊&#xff0c;所以盡量不要在服務器的路由器和防火墻中開放端口二、快解析如何…

Linux下編譯ARPACK

本文記錄Linux下編譯ARPACK的流程。 零、環境 操作系統Ubuntu 22.04.4 LTSVS Code1.92.1Git2.34.1GCC11.4.0CMake3.22.1oneAPI2024.2.1 一、依賴 1.1 安裝oneAPI 參見&#xff1a;Get the Intel oneAPI Base Toolkit , Get the Intel oneAPI HPC Toolkit 二、編譯ARPACK …

芋道RBAC實現介紹

說明&#xff1a;之前寫過一篇博客&#xff0c;介紹如何搭建一個基于角色的權限驗證框架 搭建一個基于角色的權限驗證框架 本文介紹在非常受歡迎的開源框架——芋道中是如何實現 RBAC 的&#xff0c;芋道的部署參考下面這篇文章&#xff1a; 芋道微服務代碼部署 介紹 一般…

Docker部署Jellyfin,沒有公網IP如何使用內網穿透遠程訪問?

Jellyfin是一款完全開源、免費的媒體服務器&#xff0c;可幫助你快速搭建屬于自己的私人流媒體平臺&#xff1a;電影、劇集、音樂、照片統統收納&#xff0c;跨設備隨點隨播。本文將以最簡潔的步驟&#xff0c;演示如何在Docker容器中部署Jellyfin&#xff0c;并通過貝銳花生殼…

Podman:Mysql(使用卷)

下載鏡像hpphcomp:~$ podman pull docker.1ms.run/mysql:latest Trying to pull docker.1ms.run/mysql:latest... Getting image source signatures Copying blob c81e70a25040 done | Copying blob 31f7d8dc4024 done | Copying blob b9916866e45f done | Copying blob …

2025年滲透測試面試題總結-21(題目+回答)

安全領域各種資源&#xff0c;學習文檔&#xff0c;以及工具分享、前沿信息分享、POC、EXP分享。不定期分享各種好玩的項目及好用的工具&#xff0c;歡迎關注。 目錄 一、文件上傳繞過方式&#xff08;Top 5&#xff09; 二、文件包含高危函數&#xff08;PHP為例&#xff0…

像海綿一樣吸收技術書籍的高效學習方法

像海綿一樣吸收技術書籍的高效學習方法前言六步高效閱讀法步驟1&#xff1a;快速瀏覽章節步驟2&#xff1a;先讀章末測驗步驟3&#xff1a;只讀粗體字步驟4&#xff1a;只讀每段的首句和末句步驟5&#xff1a;通讀整章步驟6&#xff1a;復習與重復高效學習技術書籍的實用技巧1.…

Day60--圖論--94. 城市間貨物運輸 I(卡碼網),95. 城市間貨物運輸 II(卡碼網),96. 城市間貨物運輸 III(卡碼網)

Day60–圖論–94. 城市間貨物運輸 I&#xff08;卡碼網&#xff09;&#xff0c;95. 城市間貨物運輸 II&#xff08;卡碼網&#xff09;&#xff0c;96. 城市間貨物運輸 III&#xff08;卡碼網&#xff09; 今天是Bellman_ford專場。帶你從普通的Bellman_ford&#xff0c;到隊列…

Jenkins服務器SSH公鑰配置步驟

步驟1. 在Jenkins服務器上生成SSH密鑰在Jenkins服務器上執行以下命令&#xff1a;# 1. 生成SSH密鑰對 ssh-keygen -t rsa -b 4096 -f ~/.ssh/id_rsa -N ""# 2. 設置正確的權限 chmod 700 ~/.ssh chmod 600 ~/.ssh/id_rsa chmod 644 ~/.ssh/id_rsa.pub# 3. 查看公鑰內…

數據鏈路層-網絡層-傳輸層

文章目錄深入淺出理解網絡核心&#xff1a;從交換機到TCP/UDP一、數據鏈路層&#xff1a;交換機的"地盤"1. 數據鏈路層的核心功能2. 以太網的發展歷程3. 以太網中的MAC地址4. 以太網幀格式&#xff1a;數據的"快遞包裝"5. 交換機的工作原理&#xff1a;高效…

專題:2025跨境電商市場布局、供應鏈與產業帶賦能報告 |附130+份報告PDF、原數據表匯總下載

原文鏈接&#xff1a;https://tecdat.cn/?p43616 2025年&#xff0c;跨境圈的老板們集體焦慮&#xff1a;美國關稅飆到145%&#xff0c;亞馬遜封號潮卷土重來&#xff0c;而東南亞卻悄悄漲了246%&#xff01;這不是危言聳聽——66%的美國消費者說&#xff0c;海外貨漲10%就換本…

LINUX 818 shell:random;for for

問題 [rootweb ~]# a$(echo $[$RANDOM%10]) 您在 /var/spool/mail/root 中有郵件 [rootweb ~]# echo $a 3 [rootweb ~]# echo 139$a$a$a$a$a$a$a$a 13933333333 您在 /var/spool/mail/root 中有郵件 [rootweb ~]# echo 139 $a 139 3 [rootweb ~]# echo $a 3 [rootweb ~]# echo …

JavaScript 原型機制詳解:從概念到實戰(附個人學習方法)

原型是 JavaScript 實現繼承與代碼復用的核心機制,也是面試高頻考點。本文結合個人學習經驗、核心概念解析與實戰案例,幫你徹底搞懂原型、prototype、__proto__ 及相關知識點,同時分享高效的學習方法。 一、個人學習方法:高效掌握復雜知識點 復雜概念(如原型)的學習,關…

【人工智能】2025年AI代理失控危機:構建安全壁壘,守護智能未來

還在為高昂的AI開發成本發愁?這本書教你如何在個人電腦上引爆DeepSeek的澎湃算力! 在2025年,AI代理(AI Agents)已成為日常生活和企業運營的核心組成部分,它們能夠自主決策、執行任務并與環境互動。然而,隨著AI代理能力的指數級提升,其安全隱患也日益凸顯,包括數據泄露…

從噪聲到動作:Diffusion Policy 如何改變機器人學習?

從噪聲到動作&#xff1a;Diffusion Policy 如何改變機器人學習&#xff1f; 引言 在機器人手臂操作方面一直存在諸多挑戰。我們熟悉的工業場景中的組裝機械臂&#xff0c;往往依賴于寫死的程序指令進行控制&#xff0c;具有高度規范化與高精度的特點。而當機械臂需要在復雜、…