多线程、Redis、rabbitmq面试题
設計模式:
1.簡單工廠模式:定義一個實例化對象的類,根據輸入參數的不同,來實例化對象
2.單例模式:確保一個類只有一個實例,提供一個公共的方法來獲取對象的實例
3.原型模式:復制一個現有的實例創建一個新的對象
4.外觀模式:隱藏系統的復雜性,并向客戶端提供了一個可以訪問系統的接口。
5.命令模式:將一個請求封裝為一個對象,使發出請求的責任和執行請求的責任分割開。這樣兩者之間通過命令對象進行溝通,這樣方便將命令對象進行儲存、傳遞、調用、增加與管理。
多線程
1.緩存一致性,解決緩存不一致的辦法;
當程序在運行過程中,會將運算需要的數據從主存復制一份到 CPU 的高速
緩存當中,那么 CPU 進行計算時就可以直接從它的高速緩存讀取數據和向其中
寫入數據,當運算結束之后,再將高速緩存中的數據刷新到主存當中。
這種在單線程中是沒有什么問題的,但是在多線程中就會遇到緩存一致性的問題,因為每個CPU都有一個自己的高度緩存,比如主存中有一個x值,然后每個高速緩存中的值都是x,如果這時y個線程要對x進行+1,
那么這時運行完是2,并不是結果x+y。
解決緩存不一致的方法就是緩存一致性協議,緩存一致性協議要求當線程對高度緩存中的共享變量進行改變時,其他cpu緩存中也有這個副本,那么就會把其他CPU中的該變量置為無效狀態,因此其他CPU需要讀取這個變量的時候會發現該變量是無效的,那么它就會從主存重新讀取。
2.volatile關鍵字的作用是什么;
使用 volatile 關鍵字會強制將修改的值立即寫入主存;如果一個共享變量被volatile修飾了,那么對不同的線程來說,這個新值是立即可見的。
同時volatile 關鍵字禁止指令重新排序。
3.內存的內存模型;
每個線程都有自己的工作內存(類似于前面的高速緩存)。線程對變量的所有操作都必須在工作內存中進行,而不能直接對主存進行操作,并且每個線程不能訪問其他線程的工作內存。
4.Thread和task的區別;
Task和Thread都能創建用多線程的方式執行代碼,Task不止能創建新線程,還能使用線程池(默認),Thread是創建新的線程。
5.死鎖、活鎖、饑餓;
死鎖,是多線程中最差的一種情況,多個線程相互占用對方的資源的鎖,而又相互等對方釋放鎖,此時若無外力干預,這些線程則一直處理阻塞的假死狀態,形成死鎖。
活鎖,與死鎖相反,死鎖是線程都拿不到資源,且都占用著對方的資源,活鎖是拿到資源卻又相互釋放不執行。當多線程中出現了相互謙讓,都主動將資源釋放給別的線程使用,缺點:這個資源在多個線程之間跳動卻有得不到執行,這就是活鎖。
多線程執行中存在線程優先級,優先級高的線程能夠插隊并優先執行,如此若高優先級的線程一直搶占低優先級線程的資源,導致低優先級線程無法得到執行,這就是饑餓。當然還有一種饑餓的情況,某一個線程一直占著一個資源不放而導致其他線程得不到執行,與死鎖不同的是饑餓在以后一段時間內還是能夠得到執行的,比如那個占用資源的線程執行結束并釋放了資源。
6.線程和進程
線程是程序運行的基本單位,進程是資源分配的最小單位,同一個進程中的多個線程共享進程的堆和方法區資源,每個線程擁有自己的程序計數器、虛擬棧,棧內存,
7.什么是線程池,線程池的工作原理,使用線程池有什么好處;
一個線程池管理了一組工作線程,默認情況下,在創建了線程池后,線程池中的線程數為 0。當任務提交給
線程池之后的處理策略如下:
1)如果此時線程池中的數量小于 corePoolSize(核心池的大小),即
使線程池中的線程都處于空閑狀態,也要創建新的線程來處理被添加的任務(也
就是每來一個任務,就要創建一個線程來執行任務)。
2).如果此時線程池中的數量大于等于 corePoolSize,但是緩沖隊列
workQueue 未滿,那么任務被放入緩沖隊列,則該任務會等待空閑線程將其
取出去執行。
3) 如 果 此 時 線 程 池 中 的 數 量 大 于 等 于 corePoolSize , 緩 沖 隊 列
workQueue 滿,并且線程池中的數量小于 maximumPoolSize(線程池
最大線程數),建新的線程來處理被添加的任務。
4) 如果 此時 線程 池中 的數量 大 于 等 于 corePoolSize, 緩 沖 隊列
workQueue 滿,并且線程池中的數量等于 maximumPoolSize,那么通
過 RejectedExecutionHandler 所指定的策略(任務拒絕策略)來處理此任務。
也就是處理任務的優先級為:核心線程 corePoolSize、任務隊列
workQueue、最大線程 maximumPoolSize,如果三者都滿了,使用
handler
8.網站高并發怎么解決;
.HTML 頁面靜態化
.圖片服務器與應用服務器分離
.緩存(用戶緩存,cdn緩存,反向代理)
.負載均衡(f服務器集群)
.消息隊列
網絡協議
1.三次握手
1.第一次握手:建立連接。客戶端發送連接請求報文段,將SYN位置為1,Sequence Number為x;然后,客戶端進入SYN_SEND狀態,等待服務器的確認;
2.第二次握手:服務器收到SYN報文段。服務器收到客戶端的SYN報文段,需要對這個SYN報文段進行確認,設置Acknowledgment Number為x+1;同時,自己還要發送SYN請求信息,將SYN位置為1,Sequence Number為y;服務器端將上述所有信息放到一個報文段(即SYN+ACK報文段)中,一并發送給客戶端,此時服務器進入SYN_RECV狀態;
3.第三次握手:客戶端收到服務器的SYN+ACK報文段。然后將Acknowledgment Number設置為y+1,向服務器發送ACK報文段,這個報文段發送完畢以后,客戶端和服務器端都進入ESTABLISHED狀態,完成TCP三次握手。
2.四次分手
1.第一次分手:主機1(可以使客戶端,也可以是服務器端),設置Sequence Number和Acknowledgment Number,向主機2發送一個FIN報文段;此時,主機1進入FIN_WAIT_1狀態;這表示主機1沒有數據要發送給主機2了;
2.第二次分手:主機2收到了主機1發送的FIN報文段,向主機1回一個ACK報文段,Acknowledgment Number為Sequence Number加1;主機1進入FIN_WAIT_2狀態;主機2告訴主機1,我知道了;
3.第三次分手:主機2向主機1發送FIN報文段,請求關閉連接,同時主機2進入CLOSE_WAIT狀態;
4.第四次分手:主機1收到主機2發送的FIN報文段,向主機2發送ACK報文段,然后主機1進入TIME_WAIT狀態;主機2收到主機1的ACK報文段以后,就關閉連接;此時,主機1等待2MSL后依然沒有收到回復,則證明Server端已正常關閉,那好,主機1也可以關閉連接了。
http 和 socket 的區別,兩個協議哪個更高效一點。
創建 Socket 連接時,可以指定使用的傳輸層協議,Socket 可以支持不同的傳輸層協
議(TCP 或 UDP),當使用 TCP 協議進行連接時,該 Socket 連接就是一個 TCP 連接。
Socket 連接一旦建立,通信雙方即可開始相互發送數據內容,直到雙方連接斷開。注意,
同 HTTP 不同的是 http 只能基于 tcp,socket 不僅能走 tcp,而且還能走 udp,這個是 socket
的第一個特點
Redis
Redis相比memcached有哪些優勢?
(1) memcached所有的值均是簡單的字符串,redis作為其替代者,支持更為豐富的數據類型
(2) redis的速度比memcached快很多
(3) redis可以持久化其數據
Redis集群方案應該怎么做?都有哪些方案?
1.codis。
目前用的最多的集群方案,基本和twemproxy一致的效果,但它支持在 節點數量改變情況下,舊節點
數據可恢復到新hash節點。
2.redis cluster3.0自帶的集群,特點在于他的分布式算法不是一致性hash,而是hash槽的概念,以及
自身支持節點設置從節點。具體看官方文檔介紹。
3.在業務代碼層實現,起幾個毫無關聯的redis實例,在代碼層,對key 進行hash計算,然后去對應的
redis實例操作數據。 這種方式對hash層代碼要求比較高,考慮部分包括,節點失效后的替代算法方
案,數據震蕩后的自動腳本恢復,實例的監控,等等。
Redis集群方案什么情況下會導致整個集群不可用?
有A,B,C三個節點的集群,在沒有復制模型的情況下,如果節點B失敗了,那么整個集群就會以為缺少
5501-11000這個范圍的槽而不可用。
Redis有哪些適合的場景?
(1)會話緩存(Session Cache)
最常用的一種使用Redis的情景是會話緩存(session cache)。用Redis緩存會話比其他存儲(如
Memcached)的優勢在于:Redis提供持久化。當維護一個不是嚴格要求一致性的緩存時,如果用戶的
購物車信息全部丟失,大部分人都會不高興的,現在,他們還會這樣嗎?
幸運的是,隨著 Redis 這些年的改進,很容易找到怎么恰當的使用Redis來緩存會話的文檔。甚至廣為
人知的商業平臺Magento也提供Redis的插件。
(2)全頁緩存(FPC)
除基本的會話token之外,Redis還提供很簡便的FPC平臺。回到一致性問題,即使重啟了Redis實例,
因為有磁盤的持久化,用戶也不會看到頁面加載速度的下降,這是一個極大改進,類似PHP本地FPC。
再次以Magento為例,Magento提供一個插件來使用Redis作為全頁緩存后端。
此外,對WordPress的用戶來說,Pantheon有一個非常好的插件 wp-redis,這個插件能幫助你以最快
速度加載你曾瀏覽過的頁面。
(3)隊列
Reids在內存存儲引擎領域的一大優點是提供 list 和 set 操作,這使得Redis能作為一個很好的消息隊列
平臺來使用。Redis作為隊列使用的操作,就類似于本地程序語言(如Python)對 list 的 push/pop 操
作。
如果你快速的在Google中搜索“Redis queues”,你馬上就能找到大量的開源項目,這些項目的目的就是
利用Redis創建非常好的后端工具,以滿足各種隊列需求。例如,Celery有一個后臺就是使用Redis作為
broker,你可以從這里去查看。
(4)排行榜/計數器
Redis在內存中對數字進行遞增或遞減的操作實現的非常好。集合(Set)和有序集合(Sorted Set)也
使得我們在執行這些操作的時候變的非常簡單,Redis只是正好提供了這兩種數據結構。
所以,我們要從排序集合中獲取到排名最靠前的10個用戶–我們稱之為“user_scores”,我們只需要像下
面一樣執行即可:
當然,這是假定你是根據你用戶的分數做遞增的排序。如果你想返回用戶及用戶的分數,你需要這樣執
行:
ZRANGE user_scores 0 10 WITHSCORES
Agora Games就是一個很好的例子,用Ruby實現的,它的排行榜就是使用Redis來存儲數據的,你可以
在這里看到。
(5)發布/訂閱
最后(但肯定不是最不重要的)是Redis的發布/訂閱功能。發布/訂閱的使用場景確實非常多。我已看見
人們在社交網絡連接中使用,還可作為基于發布/訂閱的腳本觸發器,甚至用Redis的發布/訂閱功能來建
立聊天系統!
簡單說說緩存雪崩及解決方法
緩存雪崩我們可以簡單的理解為:由于原有緩存失效,新緩存未到期間
(例如:我們設置緩存時采用了相同的過期時間,在同一時刻出現大面積的緩存過期),所有原本應該訪
問緩存的請求都去查詢數據庫了,而對數據庫CPU和內存造成巨大壓力,嚴重的會造成數據庫宕機。從
而形成一系列連鎖反應,造成整個系統崩潰。)
解決辦法:
大多數系統設計者考慮用加鎖( 最多的解決方案)或者隊列的方式保證來保證不會有大量的線程對數據
庫一次性進行讀寫,從而避免失效時大量的并發請求落到底層存儲系統上。還有一個簡單方案就時講緩
存失效時間分散開。
緩存穿透怎么導致的?
在高并發下查詢key不存在的數據,會穿過緩去存查詢數據庫。導致數據庫壓力過大而宕機。
解決方法:
之后清理緩存。
缺點:緩存太多空值占用了更多的空間
存在,如果不存在就直接返回,存在再查緩存和DB。
布隆過濾器原理: 當一個元素被加入集合時,將這個元素通過n次Hash函數結果映射成一個數組中的n
個點,把它們置為1。檢索時,我們只要看看這些點是不是都是1就(大約)知道集合中有沒有它了:如
果這些點有任何一個0,則被檢元素一定不在;如果都是1,則被檢元素很可能在。總之布隆過濾器是一
個很大二進制的位數組,數組里面只存0和1。
Redis的內存淘汰策略有哪些?
Redis的內存淘汰策略是指在Redis的用于緩存的內存不足時,怎么處理需要新寫入且需要申請額外空間
的數據。
1、全局的鍵空間選擇性移除
noeviction:當內存不足以容納新寫入數據時,新寫入操作會報錯。
allkeys-lru:當內存不足以容納新寫入數據時,在鍵空間中,移除最近最少使用的key。(這個是
最常用的)
allkeys-random:當內存不足以容納新寫入數據時,在鍵空間中,隨機移除某個key。
2、設置過期時間的鍵空間選擇性移除
volatile-lru:當內存不足以容納新寫入數據時,在設置了過期時間的鍵空間中,移除最近最少使用
的key。
volatile-random:當內存不足以容納新寫入數據時,在設置了過期時間的鍵空間中,隨機移除某
個key。
volatile-ttl:當內存不足以容納新寫入數據時,在設置了過期時間的鍵空間中,有更早過期時間的
key優先移除。
什么時候需要緩存降級?
當訪問量劇增、服務出現問題(如響應時間慢或不響應)或非核心服務影響到核心流程的性能時,仍然
需要保證服務還是可用的,即使是有損服務。系統可以根據一些關鍵數據進行自動降級,也可以配置開
關實現人工降級。
緩存降級的最終目的是保證核心服務可用,即使是有損的。而且有些服務是無法降級的(如加入購物
車、結算)。
在進行降級之前要對系統進行梳理,看看系統是不是可以丟卒保帥;從而梳理出哪些必須誓死保護,哪
些可降級;比如可以參考日志級別設置預案:
并發送告警;
大閥值,此時可以根據情況自動降級或者人工降級;
服務降級的目的,是為了防止Redis服務故障,導致數據庫跟著一起發生雪崩問題。因此,對于不重要
的緩存數據,可以采取服務降級策略,例如一個比較常見的做法就是,Redis出現問題,不去數據庫查
詢,而是直接返回默認值給用戶。
如何保證緩存與數據庫雙寫時的數據一致性?
你只要用緩存,就可能會涉及到緩存與數據庫雙存儲雙寫,你只要是雙寫,就一定會有數據一致性的問
題,那么你如何解決一致性問題?
一般來說,就是如果你的系統不是嚴格要求緩存+數據庫必須一致性的話,緩存可以稍微的跟數據庫偶
爾有不一致的情況,最好不要做這個方案,讀請求和寫請求串行化,串到一個內存隊列里去,這樣就可
以保證一定不會出現不一致的情況
串行化之后,就會導致系統的吞吐量會大幅度的降低,用比正常情況下多幾倍的機器去支撐線上的一個
請求。
還有一種方式就是可能會暫時產生不一致的情況,但是發生的幾率特別小,就是先更新數據庫,然后再
刪除緩存
消息隊列有什么缺點
4.1. 系統可用性降低
本來系統運行好好的,現在你非要加入個消息隊列進去,那消息隊列掛了,你的系統不是呵呵了。因
此,系統可用性會降低;
4.2. 系統復雜度提高
加入了消息隊列,要多考慮很多方面的問題,比如:一致性問題、如何保證消息不被重復消費、如何
保證消息可靠性傳輸等。因此,需要考慮的東西更多,復雜性增大。
4.3. 一致性問題
A 系統處理完了直接返回成功了,人都以為你這個請求就成功了;但是問題是,要是 BCD 三個系統那
里,BD 兩個系統寫庫成功了,結果 C 系統寫庫失敗了,咋整?你這數據就不一致了。
Rabbitmq一般用在什么場景
(1)服務間異步通信
(2)順序消費
(3)定時任務
(4)請求削峰
RabbitMQ有幾種工作模式
simple模式(即最簡單的收發模式)
消息產生消息,將消息放入隊列
消息的消費者(consumer) 監聽 消息隊列,如果隊列中有消息,就消費掉,消息被拿走后,自動從隊列中
刪除(隱患 消息可能沒有被消費者正確處理,已經從隊列中消失了,造成消息的丟失,這里可以設置成
手動的ack,但如果設置成手動ack,處理完后要及時發送ack消息給隊列,否則會造成內存溢出)。
7.2 work工作模式(資源的競爭)
消息產生者將消息放入隊列消費者可以有多個,消費者1,消費者2同時監聽同一個隊列,消息被消費。
C1 C2共同爭搶當前的消息隊列內容,誰先拿到誰負責消費消息(隱患:高并發情況下,默認會產生某
一個消息被多個消費者共同使用,可以設置一個開關(syncronize) 保證一條消息只能被一個消費者使
用)
publish/subscribe發布訂閱(共享資源)
每個消費者監聽自己的隊列;
生產者將消息發給broker,由交換機將消息轉發到綁定此交換機的每個隊列,每個綁定交換機的隊
列都將接收到消息。
routing路由模式
消息生產者將消息發送給交換機按照路由判斷,路由是字符串(info) 當前產生的消息攜帶路由字符(對
象的方法),交換機根據路由的key,只能匹配上路由key對應的消息隊列,對應的消費者才能消費消息;
根據業務功能定義路由字符串
從系統的代碼邏輯中獲取對應的功能字符串,將消息任務扔到對應的隊列中。
業務場景:error 通知;EXCEPTION;錯誤通知的功能;傳統意義的錯誤通知;客戶通知;利用key路由,可
以將程序中的錯誤封裝成消息傳入到消息隊列中,開發者可以自定義消費者,實時接收錯誤;
topic 主題模式(路由模式的一種)
- , # 代表通配符
- 代表多個單詞, # 代表一個單詞
路由功能添加模糊匹配
消息產生者產生消息,把消息交給交換機
交換機根據key的規則模糊匹配到對應的隊列,由隊列的監聽消費者接收消息消費
如何保證RabbitMQ消息的順序性
將原來的一個queue拆分成多個queue,每個queue都有一個自己的consumer。該種方案的核心
是生產者在投遞消息的時候根據業務數據關鍵值(例如訂單ID哈希值對訂單隊列數取模)來將需要
保證先后順序的同一類數據(同一個訂單的數據) 發送到同一個queue當中。
一個queue就一個consumer,在consumer中維護多個內存隊列,根據業務數據關鍵值(例如訂
單ID哈希值對內存隊列數取模)將消息加入到不同的內存隊列中,然后多個真正負責處理消息的線
程去各自對應的內存隊列當中獲取消息進行消費。
如何保證消息不被重復消費
分析重復消費原因
生產時消息重復
由于生產者發送消息給MQ,在MQ確認的時候出現了網絡波動,生產者沒有收到確認,實際上MQ
已經接收到了消息。這時候生產者就會重新發送一遍這條消息。生產者中如果消息未被確認,或確
認失敗,我們可以使用定時任務+(redis/db)來進行消息重試。
消費時消息重復
消費者消費成功后,再給MQ確認的時候出現了網絡波動,MQ沒有接收到確認,為了保證消息被
消費,MQ就會繼續給消費者投遞之前的消息。這時候消費者就接收到了兩條一樣的消息。
解決方案
讓每個消息攜帶一個全局的唯一ID,即可保證消息的冪等性
消費者獲取到消息后先根據id去查詢redis/db是否存在該消息。
如果不存在,則正常消費,消費完畢后寫入redis/db。
如果存在,則證明消息被消費過,直接丟棄。
、如何確保消息接收方消費了消息?
發送方確認模式
將信道設置成 confirm 模式(發送方確認模式),則所有在信道上發布的消息都會被指派一個唯一
的 ID。
一旦消息被投遞到目的隊列后,或者消息被寫入磁盤后(可持久化的消息),信道會發送一個確認
給生產者(包含消息唯一 ID)。
如果 RabbitMQ 發生內部錯誤從而導致消息丟失,會發送一條 nack(notacknowledged,未確
認)消息。
發送方確認模式是異步的,生產者應用程序在等待確認的同時,可以繼續發送消息。當確認消息到
達生產者應用程序,生產者應用程序的回調方法就會被觸發來處理確認消息。
接收方確認機制
消費者接收每一條消息后都必須進行確認(消息接收和消息確認是兩個不同操作)。只有消費者確
認了消息,RabbitMQ 才能安全地把消息從隊列中刪除。
這里并沒有用到超時機制,RabbitMQ 僅通過 Consumer 的連接中斷來確認是否需要重新發送消
息。也就是說,只要連接不中斷,RabbitMQ 給了 Consumer 足夠長的時間來處理消息。保證數據
的最終一致性;
下面羅列幾種特殊情況
如果消費者接收到消息,在確認之前斷開了連接或取消訂閱,RabbitMQ 會認為消息沒有被分發,
然后重新分發給下一個訂閱的消費者。(可能存在消息重復消費的隱患,需要去重)
如果消費者接收到消息卻沒有確認消息,連接也未斷開,則 RabbitMQ 認為該消費者繁忙,將不會
給該消費者分發更多的消息。
如何保證RabbitMQ消息的可靠傳輸?
題型分析:
消息不可靠的情況可能是消息丟失,劫持等原因;
丟失又分為:生產者丟失消息、消息列表丟失消息、消費者丟失消息;
生產者丟失消息:
從生產者弄丟數據這個角度來看,RabbitMQ提供transaction和confirm模式來確保生產者不丟消
息;
事務機制:發送消息前,開啟事務(channel.txSelect()),然后發送消息,如果發送過程中出現什
么異常,事務就會回滾(channel.txRollback()),如果發送成功則提交事務
(channel.txCommit())。然而,這種方式有個缺點:吞吐量下降;
confirm模式:一旦channel進入confirm模式,所有在該信道上發布的消息都將會被指派一個唯一
的ID(從1開始),一旦消息被投遞到所有匹配的隊列之后;rabbitMQ就會發送一個ACK給生產者
(包含消息的唯一ID),這就使得生產者知道消息已經正確到達目的隊列了;如果rabbitMQ沒能
處理該消息,則會發送一個Nack消息給你,你可以進行重試操作。
消息隊列丟數據:
處理消息隊列丟數據的情況,一般是開啟持久化磁盤的配置。這個持久化配置可以和confirm機制
配合使用,你可以在消息持久化磁盤后,再給生產者發送一個Ack信號。這樣,如果消息持久化磁
盤之前,rabbitMQ陣亡了,那么生產者收不到Ack信號,生產者會自動重發。
RabbitMQ 是比較有代表性的,因為是基于主從(非分布式)做高可用性的,我們就以 RabbitMQ
為例子講解第一種 MQ 的高可用性怎么實現。RabbitMQ 有三種模式:單機模式、普通集群模
式、鏡像集群模式。
單機模式,就是 Demo 級別的,一般就是學習時候用的,沒人在生產環境使用單機模式
普通集群模式:
意思就是在多臺機器上啟動多個 RabbitMQ 實例,每個機器啟動一個。
你創建的 queue,只會放在一個 RabbitMQ 實例上,但是每個實例都同步 queue 的元數據
(元數據可以認為是 queue 的一些配置信息,通過元數據,可以找到 queue 所在實例)。你
消費的時候,實際上如果連接到了另外一個實例,那么那個實例會從 queue 所在實例上拉取
數據過來。這方案主要是提高吞吐量的,就是說讓集群中多個節點來服務某個 queue 的讀寫
操作。
鏡像集群模式:
這種模式,才是所謂的 RabbitMQ 的高可用模式。跟普通集群模式不一樣的是,在鏡像集群
模式下,你創建的 queue,無論元數據還是 queue 里的消息都會存在于多個實例上,就是
說,每個 RabbitMQ 節點都有這個 queue 的一個完整鏡像,包含 queue 的全部數據的意
思。然后每次你寫消息到 queue 的時候,都會自動把消息同步到多個實例的 queue 上。
RabbitMQ 有很好的管理控制臺,就是在后臺新增一個策略,這個策略是鏡像集群模式的策
略,指定的時候是可以要求數據同步到所有節點的,也可以要求同步到指定數量的節點,再次
創建 queue 的時候,應用這個策略,就會自動將數據同步到其他的節點上去了。
這樣的好處在于,你任何一個機器宕機了,沒事兒,其它機器(節點)還包含了這個 queue
的完整數據,別的 consumer 都可以到其它節點上去消費數據。壞處在于,第一,這個性能
開銷也太大了吧,消息需要同步到所有機器上,導致網絡帶寬壓力和消耗很重!RabbitMQ 一
個 queue 的數據都是放在一個節點里的,鏡像集群下,也是每個節點都放這個 queue 的完整
數據。
如何解決消息隊列的延時以及過期失效問題?消息隊
列滿了以后該怎么處理?有幾百萬消息持續積壓幾小時,說
說怎么解決?
線上挺常見的,一般不出,一出就是大 case。一般常見于,舉個例子,消費端每次消費之后要寫
mysql,結果 mysql 掛了,消費端 hang 那兒了,不動了;或者是消費端出了個什么岔子,導致消
費速度極其慢。
一般這個時候,只能臨時緊急擴容了,具體操作步驟和思路如下:
先修復 consumer 的問題,確保其恢復消費速度,然后將現有 consumer 都停掉。
新建一個 topic,partition 是原來的 10 倍,臨時建立好原先 10 倍的 queue 數量。
然后寫一個臨時的分發數據的 consumer 程序,這個程序部署上去消費積壓的數據, 消費之后不
做耗時的處理,直接均勻輪詢寫入臨時建立好的 10 倍數量的 queue。
接著臨時征用 10 倍的機器來部署 consumer,每一批 consumer 消費一個臨時 queue 的數據。這
種做法相當于是臨時將 queue 資源和 consumer 資源擴大 10 倍,以正常的 10 倍速度來消費數
據。
等快速消費完積壓數據之后, 得恢復原先部署的架構, 重新用原先的 consumer 機器來消費消
息。
消息過期失效了
假設你用的是 RabbitMQ,RabbtiMQ 是可以設置過期時間的,也就是 TTL。如果消息在 queue 中積壓
超過一定的時間就會被 RabbitMQ 給清理掉,這個數據就沒了。那這就是第二個坑了。這就不是說數據
會大量積壓在 mq 里,而是大量的數據會直接搞丟。
這個情況下,就不是說要增加 consumer 消費積壓的消息,因為實際上沒啥積壓,而是丟了大量的消
息。我們可以采取一個方案,就是批量重導,這個我們之前線上也有類似的場景干過。就是大量積壓的
時候,我們當時就直接丟棄數據了,然后等過了高峰期以后,比如大家一起喝咖啡熬夜到晚上12點以
后,用戶都睡覺了。這個時候我們就開始寫程序,將丟失的那批數據,寫個臨時程序,一點一點的查出
來,然后重新灌入 mq 里面去,把白天丟的數據給他補回來。也只能是這樣了。
假設 1 萬個訂單積壓在 mq 里面,沒有處理,其中 1000 個訂單都丟了,你只能手動寫程序把那 1000
個訂單給查出來,手動發到 mq 里去再補一次。
都快寫滿了
如果消息積壓在 mq 里,你很長時間都沒有處理掉,此時導致 mq 都快寫滿了,咋辦?這個還有別的辦
法嗎?沒有,誰讓你第一個方案執行的太慢了,你臨時寫程序,接入數據來消費,消費一個丟棄一個,
都不要了,快速消費掉所有的消息。然后走第二個方案,到了晚上再補數據吧。
1.MongoDB 分片機制;
2.MongoDB有哪些優勢;
3.MongoDB的使用場景;
總結
以上是生活随笔為你收集整理的多线程、Redis、rabbitmq面试题的全部內容,希望文章能夠幫你解決所遇到的問題。