【MQ篇】RabbitMQ之消費失敗重試!

在這里插入圖片描述

目錄

    • 引言:消息不丟是底線,失敗了優雅重試是修養!
    • 消費失敗了,為啥不能老是原地復活?🤔
    • 智能重試策略一:本地重試(Spring Retry 的魔法)🏠?
    • 智能重試策略二:失敗策略 - 重試耗盡后“送去勞改”還是“發配邊疆”?📚👨?🔧
    • 策略三:經紀人端延遲重試(DLX + TTL 深度剖析)📦?📬
    • 總結:如何構建可靠的消息消費策略??🛡?

🌟我的其他文章也講解的比較有趣😁,如果喜歡博主的講解方式,可以多多支持一下,感謝🤗!

🌟了解 MQ 請看 : 【MQ篇】初識MQ!

其他優質專欄: 【🎇SpringBoot】【🎉多線程】【🎨Redis】【?設計模式專欄(已完結)】…等

如果喜歡作者的講解方式,可以點贊收藏加關注,你的支持就是我的動力
?更多文章請看個人主頁: 碼熔burning

引言:消息不丟是底線,失敗了優雅重試是修養!

各位技術界的同仁們,大家好!👋 咱們之前聊了 RabbitMQ 消息不丟的“三板斧”(生產者確認、持久化、消費者確認),構建了一個消息安全的“銅墻鐵壁”。但是,消息安全送達了,不代表它就能被“快樂地”消費掉!有時候,消費者在處理消息時會遇到各種“糟心事兒”——數據庫崩了、下游服務掛了、消息格式錯了… 一言不合就拋異常!??

了解RabbitMQ消息不丟的“三板斧”請看:
【MQ篇】RabbitMQ的生產者消息確認實戰!
【MQ篇】RabbitMQ之消息持久化!
【MQ篇】RabbitMQ的消費者確認機制實戰!

這時候問題就來了:如果消費者處理失敗了,這條消息該咋辦?直接丟了?那業務就斷了!重新發給它?如果消費者一直“病著”,或者消息本身就有問題,那就會出現很多可怕場景:
image-20210718172746378

瞧瞧這圖,消息在隊列里和消費者之間“魔鬼”般地反復橫跳,處理次數飆升,MQ 壓力山大,系統岌岌可危!😱 這就是簡單地把失敗消息 requeue=true(重新入隊)可能帶來的災難——無限循環重試,尤其對那種永遠無法處理成功的 “毒藥消息”(Poison Message) ???? 來說,簡直是噩夢!

所以,今天咱們就來聊聊,如何優雅地處理消費失敗,搭建一套智能的消費失敗重試機制,讓消息處理變得更健壯!

消費失敗了,為啥不能老是原地復活?🤔

簡單地 requeue 會導致無限循環。這就像一個得了感冒的快遞員(消費者)反復去送一個“燙手”的包裹(有問題的消息)。每次去都被燙一下,回來,再被派去,再被燙… 結果就是快遞員累垮了(消費者資源耗盡或阻塞),包裹也沒送出去,其他等著送的包裹也耽誤了。

咱們需要根據失敗的原因和預期,采取不同的重試策略:

  • 臨時性錯誤 (Transient Errors): 比如網絡短暫抖動、數據庫連接瞬斷。這種錯誤等一會兒可能就恢復了,適合快速重試。
  • 永久性錯誤 (Permanent Errors): 比如消息格式錯誤、業務數據非法。這種錯誤無論重試多少次都不會成功。
  • 外部依賴錯誤: 比如調用第三方服務失敗。這種錯誤可能需要等待較長時間讓依賴恢復。

所以,咱們需要更精細的控制手段!

智能重試策略一:本地重試(Spring Retry 的魔法)🏠?

“本地重試”顧名思義,就是消費者收到消息后,在把處理結果告訴 RabbitMQ 之前,先在自己家里(應用程序內部)多努力幾次。💪 就像快遞員拿到包裹后,發現地址有點模糊,他不會立刻把包裹退回郵局,而是在附近多轉幾圈,多問問人,嘗試自己解決。

Spring Boot 集成了強大的 Spring Retry 框架,讓實現本地重試變得異常簡單!你只需要在配置文件里動動手指頭:

application.yml 中為 Simple 監聽容器開啟 Retry:

# application.yml
spring:rabbitmq:listener:simple: # 或者 container type: direct 等,取決于你用的容器類型retry:enabled: true # ? 開啟消費者失敗重試!魔法生效!🧙?♂?initial-interval: 1000ms # ? 初次失敗后等待 1 秒重試multiplier: 2 # ? 下次等待時長 = multiplier * 上次等待時長。這是指數退避!?max-attempts: 3 # ? 最多重試 3 次 (包括第一次處理失敗)stateless: true # ? 無狀態重試。通常用這個,除非涉及到跨方法調用的復雜事務

這配置簡直是傻瓜式!😅

  • enabled: true:一鍵開啟重試功能!
  • initial-interval & multiplier:配置重試的等待時間策略。上面的例子就是經典的指數退避:第一次失敗等 1 秒,第二次等 1 * 2 = 2 秒,第三次等 2 * 2 = 4 秒…(直到 max-attempts 或達到 Spring Retry 默認的最大等待時間)。這樣可以避免失敗后立即重試給依賴服務造成過大壓力。你也可以設置 multiplier: 1 實現固定間隔重試。
  • max-attempts:設置最大重試次數(注意,這個次數是總嘗試次數,包括第一次失敗)。比如設為 3,意味著“1次初次嘗試 + 最多 2 次重試”。

本地重試的結果:

  • 如果在 max-attempts 內,消費者成功處理了消息:Spring Retry 攔截器會認為處理成功,消息監聽容器最終會向 RabbitMQ 發送 ACK,消息從隊列中刪除。🥳
  • 如果在 max-attempts 后,消費者依然失敗:Spring Retry 攔截器會放棄重試,并向上拋出一個 AmqpRejectAndDontRequeueException 異常(或者其他表示重試耗盡的異常)。這時候,消息監聽容器的錯誤處理器就會介入,根據默認策略或者你的自定義配置來處理這條消息。在默認情況下,消息會被丟棄。這是因為 Spring AMQP 默認的錯誤處理器 RejectAndDontRequeueRecoverer 會對異常消息執行 Reject 并設置 requeue=false,通常這會導致消息被 RabbitMQ 丟棄(如果沒有配置 DLX)。

本地重試的優缺點:

  • 優點: 配置簡單,對于瞬時錯誤處理效率高,不增加 RabbitMQ broker 的負擔。
  • 缺點: 重試期間阻塞消費者線程 ??;如果消費者應用在重試過程中崩潰,正在重試的消息會丟失 👻;不適合長時間等待的場景。

智能重試策略二:失敗策略 - 重試耗盡后“送去勞改”還是“發配邊疆”?📚👨?🔧

本地重試次數用完了,消息還是處理不了,怎么辦?難道就這么丟棄了?對于重要消息來說,這絕對不能接受!😠 這時候就需要一個“善后”策略,或者叫“消息恢復”策略,由 Spring AMQP 的 MessageRecoverer 接口來定義。

Spring AMQP 提供的幾種 MessageRecoverer 實現:

  1. RejectAndDontRequeueRecoverer默認選項。 重試耗盡后,直接對消息執行 reject 并 requeue=false。如前所述,這通常意味著消息被 RabbitMQ 丟棄(如果沒有 DLX)。簡單粗暴,但可能丟消息。🚮
  2. ImmediateRequeueMessageRecoverer:重試耗盡后,發送 NACK 并 requeue=true請注意! 這又回到了最開始的問題,可能導致無限重試!😨 除了非常特殊的場景,慎用!🔄
  3. RepublishMessageRecoverer推薦的優雅方案。 重試耗盡后,將失敗消息重新發布到指定的交換機!🚀 這就像把那些怎么都送不到收件人手里的疑難包裹,統一退回到一個“問題包裹處理中心”。

首先,定義一個專門處理錯誤消息的交換機和隊列(通常用 Direct 交換機,綁定一個隊列):

// RabbitConfig.java 或單獨的 ErrorMessageConfig.java 文件
@Bean
public DirectExchange errorMessageExchange(){System.out.println("🛠? 定義錯誤消息交換機: error.direct");return new DirectExchange("error.direct");
}
@Bean
public Queue errorQueue(){System.out.println("🛠? 定義錯誤消息隊列: error.queue");return new Queue("error.queue", true); // 隊列通常要持久化 ?
}
@Bean
public Binding errorBinding(Queue errorQueue, DirectExchange errorMessageExchange){System.out.println("🛠? 綁定錯誤隊列到錯誤交換機,路由鍵為 'error'");return BindingBuilder.bind(errorQueue).to(errorMessageExchange).with("error"); //
}

然后,定義一個 RepublishMessageRecoverer Bean,告訴它把失敗消息發到哪個交換機和使用哪個路由鍵:

// RabbitConfig.java 或單獨的 ErrorMessageConfig.java 文件
import org.springframework.amqp.rabbit.retry.MessageRecoverer; //
import org.springframework.amqp.rabbit.retry.RepublishMessageRecoverer; //
import org.springframework.amqp.rabbit.core.RabbitTemplate; //@Bean
public MessageRecoverer republishMessageRecoverer(RabbitTemplate rabbitTemplate){System.out.println("🛠? 配置 RepublishMessageRecoverer,將失敗消息發送到 error.direct 交換機,路由鍵 'error'");// 參數1: rabbitTemplate 用于發送消息// 參數2: 目標交換機名稱// 參數3: 目標路由鍵return new RepublishMessageRecoverer(rabbitTemplate, "error.direct", "error"); //
}

最后,你需要將這個 republishMessageRecoverer Bean 配置到你的消費者容器中,通常是通過設置 SimpleRabbitListenerContainerFactorysetMessageRecoverer 方法,或者像 Spring Boot 默認配置那樣,只要容器中有 MessageRecoverer 類型的 Bean,它會自動關聯上去。

// 在你的 SimpleRabbitListenerContainerFactory 配置中(如果有的話)
// factory.setMessageRecoverer(republishMessageRecoverer);// 或者如果你沒有自定義 SimpleRabbitListenerContainerFactory,并且 republishMessageRecoverer Bean 在 Spring Context 中
// Spring Boot 的 auto-configuration 會嘗試自動關聯 MessageRecoverer Bean

通過這種方式,那些經過本地重試依然失敗的消息,就不會被簡單丟棄或無限循環,而是被整整齊齊地發送到 error.queue 里。你可以有專門的服務去監聽這個隊列,對這些異常消息進行統一分析、人工處理或報警,極大地提高了系統的可運維性!報警!🚨🚨🚨

策略三:經紀人端延遲重試(DLX + TTL 深度剖析)📦?📬

咱們之前說了,本地重試是在消費者內部掙扎,而經紀人端重試是把消息“踢”回給 RabbitMQ,讓它幫忙等待一段時間再派送。DLX + TTL 就是實現這種“踢回去,等會兒再發”邏輯的“官方推薦”組合拳!👊💥

想象一下,你的消息處理失敗了,就像一個包裹送到了,但收件人現在不在家。快遞員(消費者)不能一直拿著包裹(阻塞),也不能直接丟棄(丟消息)。他最好的辦法是把包裹帶回快遞站,告訴調度中心:“這個包裹現在送不了,麻煩你過 N 分鐘再重新派送一次吧!” DLX + TTL 在 RabbitMQ 中扮演的就是“調度中心”和“臨時存放區”的角色。

核心思想:利用“死信”和“過期”的特性,讓消息“曲線救國”回到原隊列!

要玩轉 DLX + TTL,我們需要在 RabbitMQ 里配置幾個關鍵的“地點”和“規則”:

  1. 業務隊列 (Original Queue): 這是你的消費者正常監聽的隊列。當消費者處理失敗并決定進行延遲重試時,它會把消息變成“死信”,然后這個業務隊列要被配置成,把它的死信發送到一個指定的 DLX。

    • 關鍵配置: 在隊列的 arguments 里設置 x-dead-letter-exchange,指向你的死信交換機。
  2. 死信交換機 (Dead Letter Exchange - DLX): 這是一個普通的交換機,但它很特殊,因為它專門接收來自那些配置了 x-dead-letter-exchange 的隊列的“死信”。

    • 關鍵作用: 接收死信,并根據死信的路由鍵將它們路由出去。注意,死信的路由鍵默認是原消息的路由鍵,你也可以通過隊列的 arguments 里的 x-dead-letter-routing-key 來指定死信的路由鍵。
  3. 重試隊列 (Retry Queue): 這是一個關鍵的“臨時存放區”。它不直接給消費者監聽,它的作用就是“關禁閉”——讓消息在里面等待一段時間。

    • 關鍵配置 1: 設置 x-message-ttl,指定消息在這個隊列里的存活時間(毫秒)。消息超過這個時間就會變成新的死信。🕰?
    • 關鍵配置 2: 設置 x-dead-letter-exchange指向原來的業務交換機! 🤯 這是最騷的操作!這樣重試隊列里的消息過期后,會變成死信,然后被發送回原來的業務交換機。
    • 關鍵綁定: 這個重試隊列需要綁定死信交換機 (DLX)。綁定時使用的路由鍵,要和從業務隊列出來的死信的路由鍵匹配(默認就是原消息路由鍵)。
  4. 業務交換機 (Original Exchange): 你的生產者發送消息的目標。重試隊列里“刑滿釋放”的消息(過期死信),會回到這里,然后根據消息的原路由鍵再次被路由到業務隊列。

整個“生死輪回”流程細講:

  1. 生產者發送消息到 業務交換機,使用 業務路由鍵
  2. 消息被路由到 業務隊列
  3. 消費者從 業務隊列 取出消息,處理失敗。
  4. 消費者發送 NACK 并設置 requeue=false
  5. 消息變成死信。因為 業務隊列 配置了 x-dead-letter-exchange 指向 DLX,消息被發送到 DLX。💀??
  6. DLX 收到死信,使用死信的路由鍵(默認是原業務路由鍵)查找綁定。
  7. 找到了 重試隊列DLX 的綁定(綁定鍵是業務路由鍵)。消息被路由到 重試隊列。📬
  8. 消息進入 重試隊列,開始計時 TTL。消息在隊列里“沉睡”。?
  9. TTL 時間到!消息在 重試隊列 里過期,再次變成死信。
  10. 因為 重試隊列 配置了 x-dead-letter-exchange 指向 業務交換機,消息被發送回 業務交換機。??
  11. 業務交換機 收到消息(此時消息會帶有一些死信頭信息),再次根據消息的原路由鍵路由。
  12. 消息再次被路由回 業務隊列。🎉
  13. 消費者從 業務隊列 再次收到消息,進行重試處理。這次收到的消息 Redelivered 標志通常為 true

這個流程可以循環多次,通過設置多個重試隊列,每個隊列綁定到前一個隊列的 DLX,并設置不同的 TTL 和指向下一個隊列的 DLX,可以實現多級、階梯式的延遲重試!比如:失敗 -> 等 1 分鐘 -> 失敗 -> 等 5 分鐘 -> 失敗 -> 等 30 分鐘 -> 最終失敗進入人工處理隊列。🛣???

代碼怎么配置這些“地點”和“規則”?

這主要是在你的 Spring Boot 項目的 RabbitConfig.java 里定義 Bean 時,通過隊列的 arguments 參數來設置。

package com.example.rabbitmqconfirmdemo.config; // 使用你自己的包名// ... 必要的導入,包括 Queue, DirectExchange, Binding, BindingBuilder ...
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.amqp.core.*; // 導入這些類
import java.util.HashMap; // 引入 HashMap
import java.util.Map; // 引入 Map@Configuration
public class RabbitConfig {// 業務交換機 (同上)@Beanpublic TopicExchange myDurableExchange() {System.out.println("🛠? 正在創建業務交換機: my.durable.exchange");return new TopicExchange("my.durable.exchange", true, false);}// ? 定義死信交換機 (DLX) ?@Beanpublic DirectExchange dlxExchange() {System.out.println("🛠? 正在創建死信交換機 (DLX): my.dlx.exchange");// DLX 通常用 Direct 類型,因為路由鍵不變或指定死信路由鍵return new DirectExchange("my.dlx.exchange", true, false); // 持久化}// ? 定義業務隊列,并配置其死信發往 DLX ?@Beanpublic Queue myDurableQueue() {System.out.println("🛠? 正在創建持久化業務隊列: my.durable.queue");Map<String, Object> args = new HashMap<>();// ? 核心配置 1: 指定當前隊列的死信發往哪個交換機 ?// 當消息被 NACK(requeue=false) 或在當前隊列過期,會被發到這個交換機args.put("x-dead-letter-exchange", "my.dlx.exchange"); // 指向上面定義的 DLX// ? 可選配置:指定死信的路由鍵,不指定則使用原消息的路由鍵 ?// args.put("x-dead-letter-routing-key", "some-dlx-routing-key");// ? 可選配置:給業務隊列的消息設置 TTL (如果在隊列里長時間沒被消費也會死信) ?// args.put("x-message-ttl", 300000); // 比如 5 分鐘,防止消息永遠積壓在業務隊列// 隊列本身也要持久化return new Queue("my.durable.queue", true, false, false, args);}// ? 定義重試隊列 (這是第一個延遲等待層級) ?@Beanpublic Queue retryQueue() {System.out.println("🛠? 正在創建重試隊列 (延遲 60秒): my.retry.queue");Map<String, Object> args = new HashMap<>();// ? 核心配置 2: 消息在這個隊列里等待多久變成死信(毫秒)?args.put("x-message-ttl", 60000); // 例子:等待 60 秒// ? 核心配置 3: 消息過期變成死信后,發回哪個交換機?指向業務交換機! ?args.put("x-dead-letter-exchange", "my.durable.exchange"); // 死信發回業務交換機// ? 核心配置 4: 消息過期變成死信后,使用哪個路由鍵發回?通常用原路由鍵 ?args.put("x-dead-letter-routing-key", "my.routing.key"); // 指定死信發回業務交換機時的路由鍵// 隊列本身也要持久化return new Queue("my.retry.queue", true, false, false, args);}// ? 定義重試隊列綁定到 DLX ?@Beanpublic Binding retryBinding(Queue retryQueue, DirectExchange dlxExchange) {System.out.println("🛠? 將重試隊列綁定到 DLX");// 綁定鍵要和從業務隊列出來到 DLX 的死信路由鍵匹配 (默認是原路由鍵 "my.routing.key")return BindingBuilder.bind(retryQueue).to(dlxExchange).with("my.routing.key"); //}// ? 可選:定義最終失敗隊列 (Final DLQ) ?// 用于接收從重試隊列出來,但沒有被正確路由(比如業務交換機或隊列被刪了)// 或者你設計了多層重試,這是最后一層重試隊列的死信目的地@Beanpublic Queue finalDlxQueue() {System.out.println("🛠? 正在創建最終死信隊列: my.final.dlq.queue");return new Queue("my.final.dlq.queue", true); // 持久化}// ? 可選:定義一個交換機用于接收所有最終的死信 (如果需要統一處理) ?@Beanpublic DirectExchange finalDlxExchange() {System.out.println("🛠? 正在創建最終死信交換機: my.final.dlx.exchange");return new DirectExchange("my.final.dlx.exchange", true, false);}// ? 可選:將最終失敗隊列綁定到最終死信交換機 ?@Beanpublic Binding finalDlxBinding(Queue finalDlxQueue, DirectExchange finalDlxExchange) {System.out.println("🛠? 將最終死信隊列綁定到最終死信交換機");return BindingBuilder.bind(finalDlxQueue).to(finalDlxExchange).with("final.dlq.key"); // 使用一個特定的路由鍵}// ? 如果需要多層延遲重試,就需要定義更多重試隊列和綁定 ?// retryQueue2 (TTL 5分鐘), retryQueue3 (TTL 30分鐘) ...// retryQueue -> DLX (my.dlx.exchange) -> retryQueue2// retryQueue2 -> DLX (my.dlx.exchange) -> retryQueue3// retryQueue3 -> DLX (my.dlx.exchange) -> my.durable.exchange (或指向最終死信交換機)// 業務隊列綁定到業務交換機 (同上)@Beanpublic Binding binding(Queue myDurableQueue, TopicExchange myDurableExchange) {return BindingBuilder.bind(myDurableQueue).to(myDurableExchange).with("my.routing.key");}// ... RabbitTemplate 和 ListenerContainer 配置 (同上,確保 ListenerContainer 模式是 MANUAL) ...
}

消費者代碼中的關鍵:

在你的 ChannelAwareMessageListener 實現中,當處理失敗并決定進行延遲重試時,一定要發送 NACK 并設置 requeue=false

// ManualAckMessageListener.java (onMessage 方法中的失敗處理部分)// ... catch (Exception e) { ...
try {// ? 核心:手動發送 NACK,并將 requeue 設為 FALSE! ?// 這樣消息就會變成死信發往業務隊列配置的 DLX!channel.basicNack(deliveryTag, false, false); // 拒絕當前消息,不批量,不重新入隊 (進入DLX)System.err.println("💔 消息處理失敗,手動發送 NACK 并發送到 DLX!Delivery Tag: " + deliveryTag);
} catch (Exception nackException) {System.err.println("🔥 發送 NACK 時也發生異常! Delivery Tag: " + deliveryTag + ", 錯誤: " + nackException.getMessage());// 極少發生,需重點關注
}
// ...

這樣配置并配合消費者端的 basicNack(false, false) 操作,你就搭建起了一個完整的 DLX + TTL 延遲重試機制!🥳

優點總結:

  • 實現了消息處理失敗后的延遲重試,不會立即給消費者和依賴服務造成壓力。
  • 重試等待期間不阻塞消費者線程。
  • 即使消費者應用崩潰,消息也安全地待在 RabbitMQ 的重試隊列里等待。
  • 通過配置多層重試隊列,可以實現靈活的階梯式延遲重試策略。🛣???
  • 最終失敗的消息可以被導入專門的 DLQ,方便統一管理和處理。📂

注意事項:

  • DLX + TTL 機制依賴于 RabbitMQ broker 的穩定運行和正確配置。
  • 如果你的業務邏輯需要根據重試次數采取不同策略(比如重試 3 次失敗后換個處理邏輯),你需要在消息的 Header 里記錄重試次數,并在消費者端讀取和判斷。RabbitMQ 在死信過程中會添加一些 Header 信息,比如 x-death 頭部,包含了消息的死信原因、次數、隊列等信息,你可以利用它來判斷重試次數。
  • 確保重試隊列的 TTL 和業務處理時間、外部依賴恢復時間相匹配。

把 DLX + TTL 玩明白了,你的 RabbitMQ 消息可靠性就又上了一個大臺階!它和本地重試(Spring Retry)常常是好搭檔,一個負責內部快速消化,一個負責外部排隊等待,強強聯合,構建最穩固的消息處理防線!🛡?💪

總結:如何構建可靠的消息消費策略??🛡?

結合咱們的討論,一套可靠的 RabbitMQ 消息處理策略,就像給你的消息穿上多層“保險服”:

  1. 生產者確認機制: 確保消息安全送達 RabbitMQ 的 Exchange。?📬
  2. 持久化功能: 確保 Exchange、Queue 和消息本身在 RabbitMQ 重啟或崩潰時不會丟失。🏛?💾??
  3. 消費者確認機制: 確保消息被消費者成功處理后才從隊列刪除。對于需要精細控制重試行為的復雜場景,強烈推薦使用 AcknowledgeMode.MANUAL 手動確認模式!??🔒(對于 auto 模式,它在方法不拋異常時自動 ACK,拋異常時自動 NACK。對于簡單的場景夠用,但如果你需要靈活控制 NACK(true)/NACK(false) 或結合 MessageRecoverer,手動模式是必選項)。
  4. 消費者失敗重試機制: 這是處理“非成功”消息的核心!
    • 可以開啟 本地重試 (Spring Retry),在消費者內部進行幾次快速重試。🏠🔄
    • 重試多次或遇到特定錯誤后,通過發送 NACK 并 requeue=false 將消息送入 DLX + TTL 流程,實現延遲重試。📦?
    • 可以配置 MessageRecoverer (特別是 RepublishMessageRecoverer),將那些重試耗盡依然失敗的消息隔離到專門的異常隊列進行處理。📚??📂

通過合理組合這些機制,你就可以構建出一個既高效又可靠的 RabbitMQ 消息消費系統,從容應對各種“幺蛾子”般的消費失敗場景,讓你的應用更加健壯!🏆

希望這篇文章對你有所啟發!快去試試搭建你的智能重試策略吧!Go~Go~Go!🚀😄

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

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

相關文章

制作一款打飛機游戲33:碰撞體編輯

我們設置系統的方式使得編輯碰撞檢測框&#xff08;即碰撞盒&#xff09;并不容易。所以&#xff0c;我們的下一步是擴展我們的編輯器&#xff0c;尤其是精靈編輯器&#xff0c;以便我們能夠在編輯器中直接編輯碰撞盒。 編輯碰撞盒 讓我們加載Sprite編輯器。例如&#xff0c;這…

Kotlin和JavaScript的對比

Kotlin和JavaScript有一些相似之處&#xff0c;但也存在顯著的差異&#xff0c;下面從多個方面為你詳細分析&#xff1a; 相似點 1. 語法靈活性 變量聲明&#xff1a;二者在變量聲明上都較為靈活。在JavaScript里&#xff0c;借助var、let和const可以聲明變量。其中&#xf…

生活需要一些思考

總分總 寫文章、做事情、寫郵件、寫信&#xff0c;都是要【總分總】。 先總【因為沒人有耐心一上來就看細節&#xff0c;先總結&#xff0c;別人感興趣才會看分】 然后分【分中包括多個子部分&#xff0c;或子章節、子目標&#xff0c;他們之間層層遞進&#xff0c;最終引出最…

JAVA設計模式——(九)工廠模式

JAVA設計模式——&#xff08;九&#xff09;工廠模式 介紹理解實現ProductFactory測試泛型擴展 應用 介紹 定義一個工廠類的接口&#xff0c;幫助一個實際對象 創建實例&#xff0c;并讓其工廠類的子類決定實例化哪個類。 理解 工廠模式中&#xff0c;必定分為了兩部分&…

Java后端接口調用攔截處理:注解與攔截器的實現

在Java開發中&#xff0c;對后端接口調用進行攔截處理是一種常見的需求&#xff0c;通常用于權限驗證、Token校驗、狀態更新等操作。本文將圍繞 Spring框架的攔截器&#xff08;Interceptor&#xff09;、Spring AOP&#xff08;面向切面編程&#xff09; 和 Spring Security 三…

第14講:科研圖表的導出與排版藝術——高質量 PDF、TIFF 輸出與投稿規范全攻略!

目錄 ?? 前言:導出,不只是“保存”! ?? 一、你需要掌握的導出目標 ??? 二、TIFF / PNG 導出規范(適用于投稿) ?? 三、PDF 矢量圖導出(排版首選) ?? 四、強烈推薦組合:showtext + Cairo ?? 五、多個圖的組合導出技巧 ?? 六、特殊投稿需求處理 ?…

對 FormCalc 語言支持較好的 PDF 編輯軟件綜述

FormCalc是一種專為PDF表單計算設計的腳本語言&#xff0c;主要應用于Adobe生態及SAP相關工具。以下是對FormCalc支持較好的主流軟件及其特點&#xff1a; 1. Adobe LiveCycle Designer 作為FormCalc的原生開發環境&#xff0c;LiveCycle Designer提供最佳支持&#xff1a; …

第二階段:基礎加強階段總體介紹

Java語法的學習筆記 下面放復習的文檔鏈接&#xff0c;如果有需要可以前往下載獲取&#xff0c;這個倉庫還有關于mysql、hadoop、python等的復習部分&#xff0c;并且每個文檔有著對應的代碼部分。文章作為復習使用&#xff0c;更多代碼內容見鏈接如下: https://gitee.com/zha…

大前端開發——前端知識漸變分層講解 利用金字塔原理簡化前端知識體系

Web開發基礎 核心概念 HTML、CSS和JavaScript&#xff1a;Web開發的三大基石&#xff0c;分別負責結構、樣式和行為。 代碼管理&#xff1a;隨著項目規模擴大&#xff0c;需要將代碼拆分成小塊&#xff0c;便于維護。 作用域污染&#xff1a;早期所有代碼共享全局作用域&…

Mixture-of-Experts(MoE)原理與在DeepSeek中的應用

MoE機制簡介 Mixture-of-Experts(MoE,混合專家)是一種“分而治之”的神經網絡架構思想。在MoE模型中,存在多個并行的子網絡,被稱為“專家”。每個專家通常擅長處理特定類型的輸入特征或知識片段。而在模型前向計算時,并非激活所有專家參與運算,而是通過一個專門的門控網…

SpringCloud學習筆記

個人學習進度&#xff1a;視頻跟敲筆記&#xff08;12天&#xff09; 學習視頻&#xff1a;尚硅谷微服務速通&#xff08;7小時左右課程&#xff09; 資源&#xff1a; 1.pdf&#xff1a;微服務pdf&#xff08;課程&#xff09;&#xff1a;https://pan.baidu.com/s/1g_TAuBjQ…

【大模型】Coze AI 智能體工作流從配置到使用實戰詳解

目錄 一、前言 二、工作流介紹 2.1 什么是工作流 2.2 工作流與對話流 2.2.1 兩者區別 2.3 工作流節點介紹 2.3.1 工作流節點說明 2.3.2 開始節點與結束節點 2.4 工作流入口 2.4.1 自定義智能體入口 2.4.2 從資源庫新增工作流 2.5 工作流使用限制 三、工作流配置與使…

Discord多賬號注冊登錄:如何同時管理多個賬戶?

Discord是許多人、特別是游戲玩家和社區管理者的重要溝通工具。隨著用戶需求的增長&#xff0c;越來越多的人開始在Discord上注冊多個賬號進行管理。例如&#xff0c;個人和工作賬號的區分&#xff0c;多個游戲社區的參與&#xff0c;或者通過不同的身份進行更靈活的社交互動。…

前端如何使用Mock模擬數據實現前后端并行開發,提升項目整體效率

1. 安裝 Mock.js npm install mockjs --save-dev # 或使用 CDN <script src"https://cdn.bootcdn.net/ajax/libs/Mock.js/1.0.0/mock-min.js"></script>2. 創建 Mock 數據文件 在項目中新建 mock 目錄&#xff0c;創建 mock.js 文件&#xff1a; // m…

AimRT 從零到一:官方示例精講 —— 二、HelloWorld示例.md

HelloWorld示例 官方倉庫&#xff1a;helloworld 配置文件&#xff08;configuration_helloworld.yaml? &#xff09; 依據官方示例項目結構自行編寫YAML配置文件 # 基礎信息 base_info:project_name: helloworld # 項目名稱build_mode_tags: ["EXAMPLE", &quo…

Tauri 跨平臺開發指南及實戰:用前端技術征服桌面應用(合集-萬字長文)

厭倦了笨重的Electron應用&#xff1f;想要構建體積小、性能高、安全可靠的跨平臺桌面應用&#xff1f;Tauri將是你的不二之選&#xff01;本教程帶你從入門到精通&#xff0c;掌握這個下一代桌面應用開發框架&#xff0c;并通過實戰APK分析工具項目&#xff0c;將理論知識轉化…

【LeetCode 熱題 100】矩陣置零 / 螺旋矩陣 / 旋轉圖像 / 搜索二維矩陣 II

??個人主頁&#xff1a;小羊 ??所屬專欄&#xff1a;LeetCode 熱題 100 很榮幸您能閱讀我的文章&#xff0c;誠請評論指點&#xff0c;歡迎歡迎 ~ 目錄 矩陣矩陣置零螺旋矩陣旋轉圖像搜索二維矩陣 II 矩陣 矩陣置零 矩陣置零 用兩個數組分別標記行和列&#xff0c;判斷…

JavaScript進階(三十一): === 與 == 比較運算符

文章目錄 一、前言二、嚴格相等運算符 ()三、寬松相等運算符 ()四、推薦做法五、特殊情況 一、前言 在 JavaScript 中&#xff0c; 和 都是比較運算符&#xff0c;但它們在比較時有重要區別&#xff1a; 二、嚴格相等運算符 () 不進行類型轉換只有當兩個操作數的值和類型都…

HTML與安全性:XSS、防御與最佳實踐

HTML 與安全性&#xff1a;XSS、防御與最佳實踐 前言 現代 Web 應用程序無處不在&#xff0c;而 HTML 作為其基礎結構&#xff0c;承載著巨大的安全責任。跨站腳本攻擊&#xff08;XSS&#xff09;仍然是 OWASP Top 10 安全威脅之一&#xff0c;對用戶數據和網站完整性構成嚴…

安達發|破解醫療器械多BOM困局:APS生產計劃排產軟件解決方案

在醫療器械設備制造行業&#xff0c;生產計劃與排程&#xff08;Advanced Planning and Scheduling, APS&#xff09;系統的應用至關重要。由于醫療器械行業具有嚴格的法規要求&#xff08;如FDA、ISO 13485&#xff09;、復雜的多級BOM&#xff08;Bill of Materials&#xff…