[{"data":1,"prerenderedAt":988},["ShallowReactive",2],{"article-\u002Farticles\u002Frabbitmq-ack-prefetch-dead-letter":3},{"id":4,"title":5,"body":6,"date":975,"description":105,"excerpt":976,"extension":977,"meta":978,"navigation":129,"path":979,"seo":980,"stem":981,"tags":982,"__hash__":987},"articles\u002Farticles\u002Frabbitmq-ack-prefetch-dead-letter.md","RabbitMQ 訊息怎麼才不會掉？Ack、Prefetch、延遲重試與死信佇列",{"type":7,"value":8,"toc":961},"minimark",[9,14,24,80,84,99,189,200,204,210,236,346,349,353,358,372,376,387,552,563,567,578,620,640,644,655,658,672,679,682,689,696,732,894,908,914,918,925,951,954,957],[10,11,13],"h2",{"id":12},"訊息可能在哪裡掉","訊息可能在哪裡掉？",[15,16,17,18,23],"p",{},"Exchange、Queue、Binding 的基本關係可以先參考 ",[19,20,22],"a",{"href":21},"\u002Farticles\u002Frabbitmq-exchange-queue-binding","RabbitMQ 的 Exchange、Queue、Binding"," 這篇。一則訊息從發出到處理完，會經過三段，每一段都可能出事：",[25,26,27,43],"table",{},[28,29,30],"thead",{},[31,32,33,37,40],"tr",{},[34,35,36],"th",{},"階段",[34,38,39],{},"可能發生的事",[34,41,42],{},"對應的機制",[44,45,46,58,69],"tbody",{},[31,47,48,52,55],{},[49,50,51],"td",{},"Producer → Broker",[49,53,54],{},"網路中斷，訊息根本沒送到",[49,56,57],{},"Publisher Confirm",[31,59,60,63,66],{},[49,61,62],{},"存放在 Broker",[49,64,65],{},"Broker 重啟，記憶體中的訊息消失",[49,67,68],{},"Durable Exchange／Queue、Persistent Message",[31,70,71,74,77],{},[49,72,73],{},"Broker → Consumer",[49,75,76],{},"Consumer 拿到訊息後當掉、處理失敗",[49,78,79],{},"手動 Ack、重試、死信佇列",[10,81,83],{"id":82},"第一段確定訊息真的送到了","第一段：確定訊息真的送到了",[15,85,86,90,91,95,96,98],{},[87,88,89],"code",{},"BasicPublish"," 呼叫成功，只代表訊息被寫進了 TCP 連線，",[92,93,94],"strong",{},"不代表 Broker 收到了","。要確認 Broker 真的接手，需要開啟 ",[92,97,57],{},"：",[100,101,106],"pre",{"className":102,"code":103,"language":104,"meta":105,"style":105},"language-csharp shiki shiki-themes github-dark","channel.ConfirmSelect();\n\nchannel.BasicPublish(\"house.events\", \"house.created.franchise\", props, body);\n\n\u002F\u002F 等待 Broker 確認；逾時或被拒絕會拋出例外，由呼叫端決定重送或記錄\nchannel.WaitForConfirmsOrDie(TimeSpan.FromSeconds(5));\n","csharp","",[87,107,108,124,131,154,159,166],{"__ignoreMap":105},[109,110,113,117,121],"span",{"class":111,"line":112},"line",1,[109,114,116],{"class":115},"s95oV","channel.",[109,118,120],{"class":119},"svObZ","ConfirmSelect",[109,122,123],{"class":115},"();\n",[109,125,127],{"class":111,"line":126},2,[109,128,130],{"emptyLinePlaceholder":129},true,"\n",[109,132,134,136,138,141,145,148,151],{"class":111,"line":133},3,[109,135,116],{"class":115},[109,137,89],{"class":119},[109,139,140],{"class":115},"(",[109,142,144],{"class":143},"sU2Wk","\"house.events\"",[109,146,147],{"class":115},", ",[109,149,150],{"class":143},"\"house.created.franchise\"",[109,152,153],{"class":115},", props, body);\n",[109,155,157],{"class":111,"line":156},4,[109,158,130],{"emptyLinePlaceholder":129},[109,160,162],{"class":111,"line":161},5,[109,163,165],{"class":164},"sAwPA","\u002F\u002F 等待 Broker 確認；逾時或被拒絕會拋出例外，由呼叫端決定重送或記錄\n",[109,167,169,171,174,177,180,182,186],{"class":111,"line":168},6,[109,170,116],{"class":115},[109,172,173],{"class":119},"WaitForConfirmsOrDie",[109,175,176],{"class":115},"(TimeSpan.",[109,178,179],{"class":119},"FromSeconds",[109,181,140],{"class":115},[109,183,185],{"class":184},"sDLfK","5",[109,187,188],{"class":115},"));\n",[15,190,191,192,195,196,199],{},"每發一筆就同步等待確認，吞吐量會下降很多。訊息量大時，可以改成批次發送後再一起等待確認，或是監聽 ",[87,193,194],{},"BasicAcks","／",[87,197,198],{},"BasicNacks"," 事件做非同步確認。",[10,201,203],{"id":202},"第二段broker-重啟也不能掉","第二段：Broker 重啟也不能掉",[15,205,206,207,98],{},"RabbitMQ 預設把訊息放在記憶體裡。要讓訊息在 Broker 重啟後還在，需要",[92,208,209],{},"三個條件同時成立",[211,212,213,220,226],"ul",{},[214,215,216,219],"li",{},[92,217,218],{},"Exchange 是 Durable","：重啟後 Exchange 本身還在",[214,221,222,225],{},[92,223,224],{},"Queue 是 Durable","：重啟後 Queue 本身還在",[214,227,228,231,232,235],{},[92,229,230],{},"訊息是 Persistent","（",[87,233,234],{},"DeliveryMode = 2","）：訊息會寫到磁碟",[100,237,239],{"className":102,"code":238,"language":104,"meta":105,"style":105},"channel.ExchangeDeclare(\"house.events\", ExchangeType.Topic, durable: true);\nchannel.QueueDeclare(\"franchise-sync\", durable: true, exclusive: false, autoDelete: false);\n\nvar props = channel.CreateBasicProperties();\nprops.Persistent = true;\n",[87,240,241,267,308,312,332],{"__ignoreMap":105},[109,242,243,245,248,250,252,255,258,261,264],{"class":111,"line":112},[109,244,116],{"class":115},[109,246,247],{"class":119},"ExchangeDeclare",[109,249,140],{"class":115},[109,251,144],{"class":143},[109,253,254],{"class":115},", ExchangeType.Topic, ",[109,256,257],{"class":119},"durable",[109,259,260],{"class":115},": ",[109,262,263],{"class":184},"true",[109,265,266],{"class":115},");\n",[109,268,269,271,274,276,279,281,283,285,287,289,292,294,297,299,302,304,306],{"class":111,"line":126},[109,270,116],{"class":115},[109,272,273],{"class":119},"QueueDeclare",[109,275,140],{"class":115},[109,277,278],{"class":143},"\"franchise-sync\"",[109,280,147],{"class":115},[109,282,257],{"class":119},[109,284,260],{"class":115},[109,286,263],{"class":184},[109,288,147],{"class":115},[109,290,291],{"class":119},"exclusive",[109,293,260],{"class":115},[109,295,296],{"class":184},"false",[109,298,147],{"class":115},[109,300,301],{"class":119},"autoDelete",[109,303,260],{"class":115},[109,305,296],{"class":184},[109,307,266],{"class":115},[109,309,310],{"class":111,"line":133},[109,311,130],{"emptyLinePlaceholder":129},[109,313,314,318,321,324,327,330],{"class":111,"line":156},[109,315,317],{"class":316},"snl16","var",[109,319,320],{"class":119}," props",[109,322,323],{"class":316}," =",[109,325,326],{"class":115}," channel.",[109,328,329],{"class":119},"CreateBasicProperties",[109,331,123],{"class":115},[109,333,334,337,340,343],{"class":111,"line":161},[109,335,336],{"class":115},"props.Persistent ",[109,338,339],{"class":316},"=",[109,341,342],{"class":184}," true",[109,344,345],{"class":115},";\n",[15,347,348],{},"只設定了 Durable Queue、卻忘了把訊息設成 Persistent，是最常見的漏洞：Queue 重啟後還在，但裡面是空的。",[10,350,352],{"id":351},"第三段consumer-端的-ack","第三段：Consumer 端的 Ack",[354,355,357],"h3",{"id":356},"自動-ack-的問題","自動 Ack 的問題",[15,359,360,363,364,367,368,371],{},[87,361,362],{},"BasicConsume"," 的 ",[87,365,366],{},"autoAck: true"," 代表",[92,369,370],{},"訊息一送出給 Consumer，Broker 就把它刪掉","。如果 Consumer 拿到訊息後、處理完成前當掉了，這則訊息就永遠消失了。",[354,373,375],{"id":374},"手動-ack","手動 Ack",[15,377,378,379,382,383,386],{},"改成 ",[87,380,381],{},"autoAck: false","，處理成功之後才回覆 ",[87,384,385],{},"BasicAck","。Consumer 如果在 Ack 之前斷線，Broker 會把這則訊息重新放回佇列，交給其他 Consumer：",[100,388,390],{"className":102,"code":389,"language":104,"meta":105,"style":105},"var consumer = new EventingBasicConsumer(channel);\nconsumer.Received += (_, ea) =>\n{\n    try\n    {\n        Handle(ea.Body.ToArray());\n        channel.BasicAck(ea.DeliveryTag, multiple: false);\n    }\n    catch (Exception ex)\n    {\n        \u002F\u002F 失敗怎麼處理？見下一節\n    }\n};\nchannel.BasicConsume(\"franchise-sync\", autoAck: false, consumer);\n",[87,391,392,410,435,440,445,450,464,484,490,507,512,518,523,529],{"__ignoreMap":105},[109,393,394,396,399,401,404,407],{"class":111,"line":112},[109,395,317],{"class":316},[109,397,398],{"class":119}," consumer",[109,400,323],{"class":316},[109,402,403],{"class":316}," new",[109,405,406],{"class":119}," EventingBasicConsumer",[109,408,409],{"class":115},"(channel);\n",[109,411,412,415,418,421,424,426,429,432],{"class":111,"line":126},[109,413,414],{"class":115},"consumer.Received ",[109,416,417],{"class":316},"+=",[109,419,420],{"class":115}," (",[109,422,423],{"class":119},"_",[109,425,147],{"class":115},[109,427,428],{"class":119},"ea",[109,430,431],{"class":115},") ",[109,433,434],{"class":316},"=>\n",[109,436,437],{"class":111,"line":133},[109,438,439],{"class":115},"{\n",[109,441,442],{"class":111,"line":156},[109,443,444],{"class":316},"    try\n",[109,446,447],{"class":111,"line":161},[109,448,449],{"class":115},"    {\n",[109,451,452,455,458,461],{"class":111,"line":168},[109,453,454],{"class":119},"        Handle",[109,456,457],{"class":115},"(ea.Body.",[109,459,460],{"class":119},"ToArray",[109,462,463],{"class":115},"());\n",[109,465,467,470,472,475,478,480,482],{"class":111,"line":466},7,[109,468,469],{"class":115},"        channel.",[109,471,385],{"class":119},[109,473,474],{"class":115},"(ea.DeliveryTag, ",[109,476,477],{"class":119},"multiple",[109,479,260],{"class":115},[109,481,296],{"class":184},[109,483,266],{"class":115},[109,485,487],{"class":111,"line":486},8,[109,488,489],{"class":115},"    }\n",[109,491,493,496,498,501,504],{"class":111,"line":492},9,[109,494,495],{"class":316},"    catch",[109,497,420],{"class":115},[109,499,500],{"class":119},"Exception",[109,502,503],{"class":119}," ex",[109,505,506],{"class":115},")\n",[109,508,510],{"class":111,"line":509},10,[109,511,449],{"class":115},[109,513,515],{"class":111,"line":514},11,[109,516,517],{"class":164},"        \u002F\u002F 失敗怎麼處理？見下一節\n",[109,519,521],{"class":111,"line":520},12,[109,522,489],{"class":115},[109,524,526],{"class":111,"line":525},13,[109,527,528],{"class":115},"};\n",[109,530,532,534,536,538,540,542,545,547,549],{"class":111,"line":531},14,[109,533,116],{"class":115},[109,535,362],{"class":119},[109,537,140],{"class":115},[109,539,278],{"class":143},[109,541,147],{"class":115},[109,543,544],{"class":119},"autoAck",[109,546,260],{"class":115},[109,548,296],{"class":184},[109,550,551],{"class":115},", consumer);\n",[15,553,554,555,558,559,562],{},"手動 Ack 帶來一個必然的結果：",[92,556,557],{},"同一則訊息可能被處理不只一次","（例如處理完成、Ack 送出前剛好斷線）。所以 Consumer 的處理邏輯要設計成",[92,560,561],{},"冪等","的——例如寫入 Elasticsearch 時用物件 ID 當文件 ID 做 Upsert，處理兩次的結果和一次相同。",[354,564,566],{"id":565},"prefetch一次拿幾則","Prefetch：一次拿幾則",[15,568,569,570,573,574,577],{},"Prefetch（",[87,571,572],{},"BasicQos","）限制「一個 Consumer 手上最多可以有幾則",[92,575,576],{},"還沒 Ack"," 的訊息」：",[100,579,581],{"className":102,"code":580,"language":104,"meta":105,"style":105},"channel.BasicQos(prefetchSize: 0, prefetchCount: 10, global: false);\n",[87,582,583],{"__ignoreMap":105},[109,584,585,587,589,591,594,596,599,601,604,606,609,611,614,616,618],{"class":111,"line":112},[109,586,116],{"class":115},[109,588,572],{"class":119},[109,590,140],{"class":115},[109,592,593],{"class":119},"prefetchSize",[109,595,260],{"class":115},[109,597,598],{"class":184},"0",[109,600,147],{"class":115},[109,602,603],{"class":119},"prefetchCount",[109,605,260],{"class":115},[109,607,608],{"class":184},"10",[109,610,147],{"class":115},[109,612,613],{"class":119},"global",[109,615,260],{"class":115},[109,617,296],{"class":184},[109,619,266],{"class":115},[211,621,622,628,634],{},[214,623,624,627],{},[92,625,626],{},"不設定（無上限）","：Broker 會把佇列裡的訊息盡可能全部推給第一個連上的 Consumer，後來加入的 Consumer 可能分不到工作；Consumer 的記憶體也可能被大量訊息塞爆",[214,629,630,633],{},[92,631,632],{},"設太小（例如 1）","：每處理完一則才會拿到下一則，網路往返的時間會拖慢吞吐量",[214,635,636,639],{},[92,637,638],{},"一般做法","：從 10～50 開始，依處理時間與 Consumer 數量調整。處理越慢的工作，Prefetch 越小，訊息才能平均分散到每個 Consumer",[10,641,643],{"id":642},"失敗了怎麼辦不要無限-requeue","失敗了怎麼辦：不要無限 Requeue",[15,645,646,647,650,651,654],{},"處理失敗時，最直覺的做法是 ",[87,648,649],{},"BasicNack(requeue: true)"," 把訊息放回佇列。但如果失敗的原因是",[92,652,653],{},"資料本身有問題","（例如欄位格式錯誤），這則訊息會馬上又被拿出來、又失敗、又放回去，形成無限迴圈，CPU 飆高、日誌被洗版，後面的正常訊息也一直被它插隊。",[15,656,657],{},"失敗要分成兩種：",[211,659,660,666],{},[214,661,662,665],{},[92,663,664],{},"暫時性錯誤","：Elasticsearch 短暫無回應、網路抖動、資料庫鎖定逾時——等一下再試就會成功",[214,667,668,671],{},[92,669,670],{},"永久性錯誤","：資料格式錯誤、找不到關聯資料、程式邏輯無法處理——試幾次都一樣",[15,673,674,675,678],{},"對應的做法是",[92,676,677],{},"延遲重試 + 死信佇列","。",[10,680,681],{"id":681},"延遲重試與死信佇列",[15,683,684],{},[685,686],"img",{"alt":687,"src":688},"RabbitMQ 延遲重試與死信佇列流程：訊息從 house.events 進入 franchise-sync 主佇列，Consumer 寫入 Elasticsearch 成功就 Ack；失敗且重試次數小於 3 時，Ack 原訊息並把重試次數加一後發到 retry Exchange，進入沒有 Consumer、TTL 30 秒的 retry-queue，過期後透過死信機制回到主 Exchange 重新投遞；已重試 3 次或無法處理的資料則送到 dead-letter-queue，停止重試並告警後人工處理","\u002Fimages\u002Farticles\u002Frabbitmq-retry-dlq.svg",[15,690,691,692,695],{},"RabbitMQ 沒有內建「延遲 30 秒再投遞」的功能（除非安裝 Delayed Message 外掛），但可以用 ",[92,693,694],{},"TTL + 死信交換器（Dead Letter Exchange, DLX）"," 組出來：",[697,698,699,713,722,725],"ol",{},[214,700,701,702,705,706,709,710],{},"宣告一個 ",[87,703,704],{},"retry-queue","，設定訊息 TTL 為 30 秒，並把它的 DLX 指向",[92,707,708],{},"主 Exchange","。這個佇列",[92,711,712],{},"沒有任何 Consumer",[214,714,715,716,719,720],{},"處理失敗時，Consumer 先 Ack 原訊息，再把它連同 ",[87,717,718],{},"x-retry-count + 1"," 的標頭發到 ",[87,721,704],{},[214,723,724],{},"30 秒後訊息過期，RabbitMQ 依 DLX 設定把它送回主 Exchange，重新進入主佇列再處理一次",[214,726,727,728,731],{},"重試次數超過上限（例如 3 次），或判斷是永久性錯誤，就直接送進 ",[87,729,730],{},"dead-letter-queue","，不再重試",[100,733,735],{"className":102,"code":734,"language":104,"meta":105,"style":105},"\u002F\u002F 重試佇列：TTL 到期後死信回主 Exchange\nchannel.QueueDeclare(\"franchise-sync.retry\", durable: true, exclusive: false, autoDelete: false,\n    arguments: new Dictionary\u003Cstring, object>\n    {\n        [\"x-message-ttl\"] = 30_000,\n        [\"x-dead-letter-exchange\"] = \"house.events\"\n    });\n\n\u002F\u002F 死信佇列：停止重試，等人處理\nchannel.QueueDeclare(\"franchise-sync.dlq\", durable: true, exclusive: false, autoDelete: false);\n",[87,736,737,742,780,807,811,829,843,848,852,857],{"__ignoreMap":105},[109,738,739],{"class":111,"line":112},[109,740,741],{"class":164},"\u002F\u002F 重試佇列：TTL 到期後死信回主 Exchange\n",[109,743,744,746,748,750,753,755,757,759,761,763,765,767,769,771,773,775,777],{"class":111,"line":126},[109,745,116],{"class":115},[109,747,273],{"class":119},[109,749,140],{"class":115},[109,751,752],{"class":143},"\"franchise-sync.retry\"",[109,754,147],{"class":115},[109,756,257],{"class":119},[109,758,260],{"class":115},[109,760,263],{"class":184},[109,762,147],{"class":115},[109,764,291],{"class":119},[109,766,260],{"class":115},[109,768,296],{"class":184},[109,770,147],{"class":115},[109,772,301],{"class":119},[109,774,260],{"class":115},[109,776,296],{"class":184},[109,778,779],{"class":115},",\n",[109,781,782,785,787,790,793,796,799,801,804],{"class":111,"line":133},[109,783,784],{"class":119},"    arguments",[109,786,260],{"class":115},[109,788,789],{"class":316},"new",[109,791,792],{"class":119}," Dictionary",[109,794,795],{"class":115},"\u003C",[109,797,798],{"class":316},"string",[109,800,147],{"class":115},[109,802,803],{"class":316},"object",[109,805,806],{"class":115},">\n",[109,808,809],{"class":111,"line":156},[109,810,449],{"class":115},[109,812,813,816,819,822,824,827],{"class":111,"line":161},[109,814,815],{"class":115},"        [",[109,817,818],{"class":143},"\"x-message-ttl\"",[109,820,821],{"class":115},"] ",[109,823,339],{"class":316},[109,825,826],{"class":184}," 30_000",[109,828,779],{"class":115},[109,830,831,833,836,838,840],{"class":111,"line":168},[109,832,815],{"class":115},[109,834,835],{"class":143},"\"x-dead-letter-exchange\"",[109,837,821],{"class":115},[109,839,339],{"class":316},[109,841,842],{"class":143}," \"house.events\"\n",[109,844,845],{"class":111,"line":466},[109,846,847],{"class":115},"    });\n",[109,849,850],{"class":111,"line":486},[109,851,130],{"emptyLinePlaceholder":129},[109,853,854],{"class":111,"line":492},[109,855,856],{"class":164},"\u002F\u002F 死信佇列：停止重試，等人處理\n",[109,858,859,861,863,865,868,870,872,874,876,878,880,882,884,886,888,890,892],{"class":111,"line":509},[109,860,116],{"class":115},[109,862,273],{"class":119},[109,864,140],{"class":115},[109,866,867],{"class":143},"\"franchise-sync.dlq\"",[109,869,147],{"class":115},[109,871,257],{"class":119},[109,873,260],{"class":115},[109,875,263],{"class":184},[109,877,147],{"class":115},[109,879,291],{"class":119},[109,881,260],{"class":115},[109,883,296],{"class":184},[109,885,147],{"class":115},[109,887,301],{"class":119},[109,889,260],{"class":115},[109,891,296],{"class":184},[109,893,266],{"class":115},[895,896,897],"blockquote",{},[15,898,899,900,903,904,907],{},"回到主 Exchange 時，訊息會以",[92,901,902],{},"原本的 Routing Key"," 重新路由。如果主 Exchange 上還有其他佇列綁定了相同的 Routing Key，它們也會再收到一次。比較乾淨的做法是另外建立一個只綁定主佇列的「重試回流 Exchange」，或在死信時用 ",[87,905,906],{},"x-dead-letter-routing-key"," 指定只有主佇列會收到的 Routing Key。",[15,909,910,913],{},[92,911,912],{},"死信佇列不是垃圾桶","。進到這裡的訊息代表「系統自己處理不了」，要搭配監控：佇列長度大於 0 就告警，由工程師檢查原因、修正資料或程式後，再把訊息重新發回主佇列。",[10,915,917],{"id":916},"使用情境加盟平台的物件同步","使用情境：加盟平台的物件同步",[15,919,920,921,924],{},"加盟平台的同步服務從 ",[87,922,923],{},"franchise-sync"," 佇列取出物件事件，轉換格式後寫入 Elasticsearch 與 MSSQL：",[211,926,927,933,939,945],{},[214,928,929,932],{},[92,930,931],{},"Elasticsearch 叢集重啟或短暫過載","：寫入逾時屬於暫時性錯誤，進入延遲重試，30 秒後通常就恢復了。重試期間主佇列的其他訊息照常處理，不會被這一筆卡住",[214,934,935,938],{},[92,936,937],{},"主力平台送來的物件缺少必要欄位","：屬於永久性錯誤，直接送進死信佇列並告警，不會在系統裡無限重試",[214,940,941,944],{},[92,942,943],{},"同步服務部署更新、Pod 重啟","：使用手動 Ack，處理到一半的訊息會回到佇列，由新的 Pod 接手",[214,946,947,950],{},[92,948,949],{},"同一則事件被處理兩次","：以物件 ID 為文件 ID 做 Upsert，處理兩次結果一樣",[10,952,953],{"id":953},"一句話總結",[15,955,956],{},"訊息可靠性要三段一起顧：Producer 端用 Publisher Confirm 確認送達、Broker 端用 Durable 加 Persistent 撐過重啟、Consumer 端用手動 Ack 確保處理完才刪除；失敗時區分暫時性與永久性錯誤，前者用 TTL 加死信做延遲重試，後者送進死信佇列告警處理，而且因為訊息可能被處理不只一次，Consumer 一定要是冪等的。",[958,959,960],"style",{},"html pre.shiki code .s95oV, html code.shiki .s95oV{--shiki-default:#E1E4E8}html pre.shiki code .svObZ, html code.shiki .svObZ{--shiki-default:#B392F0}html pre.shiki code .sU2Wk, html code.shiki .sU2Wk{--shiki-default:#9ECBFF}html pre.shiki code .sAwPA, html code.shiki .sAwPA{--shiki-default:#6A737D}html pre.shiki code .sDLfK, html code.shiki .sDLfK{--shiki-default:#79B8FF}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html pre.shiki code .snl16, html code.shiki .snl16{--shiki-default:#F97583}",{"title":105,"searchDepth":126,"depth":126,"links":962},[963,964,965,966,971,972,973,974],{"id":12,"depth":126,"text":13},{"id":82,"depth":126,"text":83},{"id":202,"depth":126,"text":203},{"id":351,"depth":126,"text":352,"children":967},[968,969,970],{"id":356,"depth":133,"text":357},{"id":374,"depth":133,"text":375},{"id":565,"depth":133,"text":566},{"id":642,"depth":126,"text":643},{"id":681,"depth":126,"text":681},{"id":916,"depth":126,"text":917},{"id":953,"depth":126,"text":953},"2022-11-08",null,"md",{},"\u002Farticles\u002Frabbitmq-ack-prefetch-dead-letter",{"title":5,"description":105},"articles\u002Frabbitmq-ack-prefetch-dead-letter",[983,984,985,986],"RabbitMQ","Message Queue","可靠性","事件驅動","I6proT5pk7wM7F9MRIi7i-UVEiAibbbO2Yy9O_85LyY",1792959150558]