Java分布式面試題集合
(給Java耕耘者?加星標(biāo),提高Java技能)
分布式分為分布式緩存(Redis)、分布式鎖(Redis 或 Zookeeper)、分布式服務(wù)(Dubbo 或 SpringCloud)、分布式服務(wù)協(xié)調(diào)(Zookeeper)、分布式消息隊(duì)列(Kafka 、RabbitMq)、分布式 Session 、分布式事務(wù)、分布式搜索(Elasticsearch)等。不可能所有分布式內(nèi)容都熟悉,一定要在某個(gè)領(lǐng)域有所專長。
分布式理論
問:分布式有哪些理論?
CAP 、BASE。分布式 CAP 理論,任何一個(gè)分布式系統(tǒng)都無法同時(shí)滿足 Consistency(一致性)、Availability(可用性)、Partition tolerance(分區(qū)容錯(cuò)性) 這三個(gè)基本需求。最多只能滿足其中兩項(xiàng)。而 Partition tolerance(分區(qū)容錯(cuò)性) 是必須的,因此一般是 CP ,或者 AP。
問:你怎么理解分布式一致性?
數(shù)據(jù)一致性通常指關(guān)聯(lián)數(shù)據(jù)之間的邏輯關(guān)系是否正確和完整。在分布式系統(tǒng)中,數(shù)據(jù)一致性往往指的是由于數(shù)據(jù)的復(fù)制,不同數(shù)據(jù)節(jié)點(diǎn)中的數(shù)據(jù)內(nèi)容是否完整并且相同。
一致性還分為強(qiáng)一致性,弱一致性,還有最終一致性。強(qiáng)一致性就是馬上就保持一致。
最終一致性是指經(jīng)過一段時(shí)間后,可以保持一致。
分布式事務(wù)
問:你怎么理解分布式事務(wù)?分布式事務(wù)的協(xié)議有哪些?
分布式事務(wù)是指會(huì)涉及到操作多個(gè)數(shù)據(jù)庫的事務(wù)。目的是為了保證分布式系統(tǒng)中的數(shù)據(jù)一致性。分布式事務(wù)類型:二階段提交 2PC ,三階段提交 3PC。
?2PC :第一階段:準(zhǔn)備階段(投票階段)和第二階段:提交階段(執(zhí)行階段)。
?3PC :三個(gè)階段:CanCommit 、PreCommit 、DoCommit。
問:分布式事務(wù)的解決方案有哪些?
分布式事務(wù)解決方案:補(bǔ)償機(jī)制 TCC 、XA 、消息隊(duì)列 MQ。
問:講一下 TCC。
?T(Try)鎖資源:鎖定某個(gè)資源,設(shè)置一個(gè)預(yù)備類的狀態(tài),凍結(jié)部分?jǐn)?shù)據(jù)。
比如,訂單的支付狀態(tài),先把狀態(tài)修改為"支付中(PAYING)"。
比如,本來庫存數(shù)量是 100 ,現(xiàn)在賣出了 2 個(gè),不要直接扣減這個(gè)庫存。在一個(gè)單獨(dú)的凍結(jié)庫存的字段,比如 prepare _ remove _ stock 字段,設(shè)置一個(gè) 2。也就是說,有 2 個(gè)庫存是給凍結(jié)了。
積分服務(wù)的也是同理,別直接給用戶增加會(huì)員積分。你可以先在積分表里的一個(gè)預(yù)增加積分字段加入積分。
比如:用戶積分原本是 1190 ,現(xiàn)在要增加 10 個(gè)積分,別直接 1190 + 10 = 1200 個(gè)積分啊!你可以保持積分為 1190 不變,在一個(gè)預(yù)增加字段里,比如說 prepare _ add _ credit 字段,設(shè)置一個(gè) 10 ,表示有 10 個(gè)積分準(zhǔn)備增加。
C(Confirm):在各個(gè)服務(wù)里引入了一個(gè) TCC 分布式事務(wù)的框架,事務(wù)管理器可以感知到各個(gè)服務(wù)的 Try 操作是否都成功了。假如都成功了, TCC 分布式事務(wù)框架會(huì)控制進(jìn)入 TCC 下一個(gè)階段,第一個(gè) C 階段,也就是 Confirm 階段。此時(shí),需要把 Try 階段鎖住的資源進(jìn)行處理。
比如,把訂單的狀態(tài)設(shè)置為“已支付(Payed)”。
比如,扣除掉相應(yīng)的庫存。
比如,增加用戶積分。
?C(Cancel):在 Try 階段,假如某個(gè)服務(wù)執(zhí)行出錯(cuò),比如積分服務(wù)執(zhí)行出錯(cuò)了,那么服務(wù)內(nèi)的 TCC 事務(wù)框架是可以感知到的,然后它會(huì)決定對整個(gè) TCC 分布式事務(wù)進(jìn)行回滾。
TCC 分布式事務(wù)框架只要感知到了任何一個(gè)服務(wù)的 Try 邏輯失敗了,就會(huì)跟各個(gè)服務(wù)內(nèi)的 TCC 分布式事務(wù)框架進(jìn)行通信,然后調(diào)用各個(gè)服務(wù)的 Cancel 邏輯。也就是說,會(huì)執(zhí)行各個(gè)服務(wù)的第二個(gè) C 階段, Cancel 階段。
比如,訂單的支付狀態(tài),先把狀態(tài)修改為" closed "狀態(tài)。
比如,凍結(jié)庫存的字段, prepare _ remove _ stock 字段,將凍結(jié)的庫存 2 清零。
比如,預(yù)增加積分的字段, prepare _ add _ credit 字段,將準(zhǔn)備增加的積分 10 清零。
問:事務(wù)管理器宕掉了,怎么辦?
做冗余,設(shè)置多個(gè)事務(wù)管理器,一個(gè)宕掉了,其他的還可以用。
問:怎么保證分布式系統(tǒng)的冪等性?
狀態(tài)機(jī)制。版本號機(jī)制。
Redis
問:Redis 有哪些優(yōu)勢?
速度快,因?yàn)閿?shù)據(jù)存在內(nèi)存中。
支持豐富數(shù)據(jù)類型,支持 string、list、set 、sorted set、hash。
支持事務(wù),操作都是原子性,所謂的原子性就是對數(shù)據(jù)的更改要么全部執(zhí)行,要么全部不執(zhí)行。
豐富的特性:可用于緩存,消息,按 key 設(shè)置過期時(shí)間,過期后將會(huì)自動(dòng)刪除。
單線程,單進(jìn)程,采用 IO 多路復(fù)用技術(shù)。
問:Redis 的存儲(chǔ)結(jié)構(gòu)是怎樣的?
key-value 鍵值對。
問:Redis 支持哪些數(shù)據(jù)結(jié)構(gòu)?
string(字符串), hash(哈希), list(隊(duì)列), set(集合)及 zset(sorted set 有序集合)。
問:Redis 的數(shù)據(jù)結(jié)構(gòu),有哪些應(yīng)用場景?
string:簡單地 get / set 緩存。
hash:可以緩存用戶資料。比如命令:hmset ?user1 name "lin" sex "male" ?age "25" ,緩存用戶 user1 的資料,姓名為 lin ,性別為男,年齡 25。
list:可以做隊(duì)列。往 list 隊(duì)列里面 push 數(shù)據(jù),然后再 pop 出來。
zset:可以用來做排行榜。
問:Redis 的數(shù)據(jù)結(jié)構(gòu),底層分別是由什么實(shí)現(xiàn)的?
Redis 字符串,卻不是 C 語言中的字符串(即以空字符 ’\0’ 結(jié)尾的字符數(shù)組),它是自己構(gòu)建了一種名為 簡單動(dòng)態(tài)字符串(simple dynamic string , SDS)的抽象類型,并將 SDS 作為 Redis 的默認(rèn)字符串表示。
Redi List ,底層是 ZipList ,不滿足 ZipList 就使用雙向鏈表。ZipList 是為了節(jié)約內(nèi)存而開發(fā)的。和各種語言的數(shù)組類似,它是由連續(xù)的內(nèi)存塊組成的,這樣一來,由于內(nèi)存是連續(xù)的,就減少了很多內(nèi)存碎片和指針的內(nèi)存占用,進(jìn)而節(jié)約了內(nèi)存。
問:Redis 怎么保證可靠性?Redis 的持久化方式有哪些?有哪些優(yōu)缺點(diǎn)?
一個(gè)可靠安全的系統(tǒng),肯定要考慮數(shù)據(jù)的可靠性,尤其對于內(nèi)存為主的 Redis ,就要考慮一旦服務(wù)器掛掉,啟動(dòng)之后,如何恢復(fù)數(shù)據(jù)的問題,也就是說數(shù)據(jù)如何持久化的問題。
AOF 就是備份操作記錄。AOF 由于是備份操作命令,備份快、恢復(fù)慢。
AOF 的優(yōu)點(diǎn):AOF 更好保證數(shù)據(jù)不會(huì)被丟失,最多只丟失一秒內(nèi)的數(shù)據(jù)。另外重寫操作保證了數(shù)據(jù)的有效性,即使日志文件過大也會(huì)進(jìn)行重寫。AOF 的日志文件的記錄可讀性非常的高。
AOF 的缺點(diǎn):對于相同數(shù)量的數(shù)據(jù)集而言, AOF 文件通常要大于 RDB 文件。
RDB 就是備份所有數(shù)據(jù),使用了快照。RDB 恢復(fù)數(shù)據(jù)比較快。
問:AOF 文件過大,怎么處理?
會(huì)進(jìn)行 AOF 文件重寫。
隨著 AOF 文件越來越大,里面會(huì)有大部分是重復(fù)命令或者可以合并的命令。
重寫的好處:減少 AOF 日志尺寸,減少內(nèi)存占用,加快數(shù)據(jù)庫恢復(fù)時(shí)間。
執(zhí)行一個(gè) AOF 文件重寫操作,重寫會(huì)創(chuàng)建一個(gè)當(dāng)前 AOF 文件的體積優(yōu)化版本。
問:講一下 Redis 的事務(wù)。
先以 MULTI 開始一個(gè)事務(wù), 然后將多個(gè)命令入隊(duì)到事務(wù)中, 最后由 EXEC 命令觸發(fā)事務(wù), 一并執(zhí)行事務(wù)中的所有命令。如果想放棄這個(gè)事務(wù),可以使用 DISCARD 命令。
問:Redis 事務(wù)無法回滾,那怎么處理?
問:怎么設(shè)置 Redis 的 key 過期時(shí)間?
key 的的過期時(shí)間通過 EXPIRE key seconds 命令來設(shè)置數(shù)據(jù)的過期時(shí)間。返回 1 表明設(shè)置成功,返回 0 表明 key 不存在或者不能成功設(shè)置過期時(shí)間。
問:Redis 的過期策略有哪些?
惰性刪除:當(dāng)讀/寫一個(gè)已經(jīng)過期的 key 時(shí),會(huì)觸發(fā)惰性刪除策略,直接刪除掉這個(gè)過期 key ,并按照 key 不存在去處理。惰性刪除,對內(nèi)存不太好,已經(jīng)過期的 key 會(huì)占用太多的內(nèi)存。
定期刪除:每隔一段時(shí)間,就會(huì)對 Redis 進(jìn)行檢查,主動(dòng)刪除一批已過期的 key。
問:為什么 Redis 不使用定時(shí)刪除?
定時(shí)刪除,就是在設(shè)置 key 的過期時(shí)間的同時(shí),創(chuàng)建一個(gè)定時(shí)器,讓定時(shí)器在過期時(shí)間來臨時(shí),立即執(zhí)行對 key 的刪除操作。
定時(shí)刪會(huì)占用 CPU ,影響服務(wù)器的響應(yīng)時(shí)間和性能。
問:Redis 的內(nèi)存回收機(jī)制都有哪些?
當(dāng)前已用內(nèi)存超過 maxmemory 限定時(shí),會(huì)觸發(fā)主動(dòng)清理策略,也就是 Redis 的內(nèi)存回收策略。
LRU 、TTL。
noeviction :默認(rèn)策略,不會(huì)刪除任何數(shù)據(jù),拒絕所有寫入操作并返回客戶端錯(cuò)誤信息,此時(shí) Redis 只響應(yīng)讀操作。
volatitle - lru :根據(jù) LRU 算法刪除設(shè)置了超時(shí)屬性的鍵,知道騰出足夠空間為止。如果沒有可刪除的鍵對象,回退到 noeviction 策略。
allkeys - lru :根據(jù) LRU 算法刪除鍵,不管數(shù)據(jù)有沒有設(shè)置超時(shí)屬性,直到騰出足夠空間為止。
allkeys - random :隨機(jī)刪除所有鍵,知道騰出足夠空間為止。
volatitle - random :隨機(jī)刪除過期鍵,知道騰出足夠空間為止。
volatitle - ttl :根據(jù)鍵值對象的 ttl 屬性,刪除最近將要過期數(shù)據(jù)。如果沒有,回退到 noeviction 策略。
問:手寫一下 LRU 算法。
問:Redis 的搭建有哪些模式?
主從模式、哨兵模式、Cluster(集群)模式。最好是用集群模式。
問:你用過的 Redis 是多主多從的,還是一主多從的?集群用到了多少節(jié)點(diǎn)?用到了多少個(gè)哨兵?
集群模式。三主三從。
問:Redis 采用多主多從的集群模式,各個(gè)主節(jié)點(diǎn)的數(shù)據(jù)是否一致?
問:Redis 集群有哪些特性
master 和 slaver。主從復(fù)制。讀寫分離。哨兵模式。
問:Redis 是怎么進(jìn)行水平擴(kuò)容的?
問:Redis 集群數(shù)據(jù)分片的原理是什么?
Redis 數(shù)據(jù)分片原理是哈希槽(hash slot)。
Redis 集群有 16384 個(gè)哈希槽。每一個(gè) Redis 集群中的節(jié)點(diǎn)都承擔(dān)一個(gè)哈希槽的子集。
哈希槽讓在集群中添加和移除節(jié)點(diǎn)非常容易。例如,如果我想添加一個(gè)新節(jié)點(diǎn) D ,我需要從節(jié)點(diǎn) A 、B、C 移動(dòng)一些哈希槽到節(jié)點(diǎn) D。同樣地,如果我想從集群中移除節(jié)點(diǎn) A ,我只需要移動(dòng) A 的哈希槽到 B 和 C。當(dāng)節(jié)點(diǎn) A 變成空的以后,我就可以從集群中徹底刪除它。因?yàn)閺囊粋€(gè)節(jié)點(diǎn)向另一個(gè)節(jié)點(diǎn)移動(dòng)哈希槽并不需要停止操作,所以添加和移除節(jié)點(diǎn),或者改變節(jié)點(diǎn)持有的哈希槽百分比,都不需要任何停機(jī)時(shí)間(downtime)。
問:講一下一致性 Hash 算法。
一致性 Hash 算法將整個(gè)哈希值空間組織成一個(gè)虛擬的圓環(huán), 我們對 key 進(jìn)行哈希計(jì)算,使用哈希后的結(jié)果對 2 ^ 32 取模,hash 環(huán)上必定有一個(gè)點(diǎn)與這個(gè)整數(shù)對應(yīng)。依此確定此數(shù)據(jù)在環(huán)上的位置,從此位置沿環(huán)順時(shí)針“行走”,第一臺(tái)遇到的服務(wù)器就是其應(yīng)該定位到的服務(wù)器。
一致性 Hash 算法對于節(jié)點(diǎn)的增減都只需重定位環(huán)空間中的一小部分?jǐn)?shù)據(jù),具有較好的容錯(cuò)性和可擴(kuò)展性。
比如,集群有四個(gè)節(jié)點(diǎn) Node A 、B 、C 、D ,增加一臺(tái)節(jié)點(diǎn) Node X。Node X 的位置在 Node B 到 Node C 直接,那么受到影響的僅僅是 Node B 到 Node X 間的數(shù)據(jù),它們要重新落到 Node X 上。
所以一致性哈希算法對于容錯(cuò)性和擴(kuò)展性有非常好的支持。
問:為什么 Redis Cluster 分片不使用 Redis 一致性 Hash 算法?
一致性哈希算法也有一個(gè)嚴(yán)重的問題,就是數(shù)據(jù)傾斜。
如果在分片的集群中,節(jié)點(diǎn)太少,并且分布不均,一致性哈希算法就會(huì)出現(xiàn)部分節(jié)點(diǎn)數(shù)據(jù)太多,部分節(jié)點(diǎn)數(shù)據(jù)太少。也就是說無法控制節(jié)點(diǎn)存儲(chǔ)數(shù)據(jù)的分配。
問:集群的拓?fù)浣Y(jié)構(gòu)有沒有了解過?集群是怎么連接的?
無中心結(jié)構(gòu)。Redis-Cluster 采用無中心結(jié)構(gòu),每個(gè)節(jié)點(diǎn)保存數(shù)據(jù)和整個(gè)集群狀態(tài),每個(gè)節(jié)點(diǎn)都和其他所有節(jié)點(diǎn)連接。
問:講一下 Redis 主從復(fù)制的過程。
從機(jī)發(fā)送 SYNC(同步)命令,主機(jī)接收后會(huì)執(zhí)行 BGSAVE(異步保存)命令備份數(shù)據(jù)。
主機(jī)備份后,就會(huì)向從機(jī)發(fā)送備份文件。主機(jī)之后還會(huì)發(fā)送緩沖區(qū)內(nèi)的寫命令給從機(jī)。
當(dāng)緩沖區(qū)命令發(fā)送完成后,主機(jī)執(zhí)行一條寫命令,就會(huì)往從機(jī)發(fā)送同步寫入命令。
問:講一下 Redis 哨兵機(jī)制。
下面是 Redis 官方文檔對于哨兵功能的描述:
監(jiān)控(Monitoring):哨兵會(huì)不斷地檢查主節(jié)點(diǎn)和從節(jié)點(diǎn)是否運(yùn)作正常。
自動(dòng)故障轉(zhuǎn)移(Automatic Failover):當(dāng)主節(jié)點(diǎn)不能正常工作時(shí),哨兵會(huì)開始自動(dòng)故障轉(zhuǎn)移操作,它會(huì)將失效主節(jié)點(diǎn)的其中一個(gè)從節(jié)點(diǎn)升級為新的主節(jié)點(diǎn),并讓其他從節(jié)點(diǎn)改為復(fù)制新的主節(jié)點(diǎn)。
配置提供者(Configuration Provider):客戶端在初始化時(shí),通過連接哨兵來獲得當(dāng)前 Redis 服務(wù)的主節(jié)點(diǎn)地址。
通知(Notification):哨兵可以將故障轉(zhuǎn)移的結(jié)果發(fā)送給客戶端。
問:講一下布隆過濾器。
布隆過濾器的主要是由一個(gè)很長的二進(jìn)制向量和若干個(gè)(k 個(gè))散列映射函數(shù)組成。因?yàn)槊總€(gè)元數(shù)據(jù)的存儲(chǔ)信息值固定,而且總的二進(jìn)制向量固定。所以在內(nèi)存占用和查詢時(shí)間上都遠(yuǎn)遠(yuǎn)超過一般的算法。當(dāng)然存在一定的不準(zhǔn)確率(可以控制)和不容易刪除樣本數(shù)據(jù)。
布隆過濾器的優(yōu)點(diǎn):大批量數(shù)據(jù)去重,特別的占用內(nèi)存。但是用布隆過濾器(Bloom Filter)會(huì)非常的省內(nèi)存。
布隆過濾器的特點(diǎn):當(dāng)布隆過濾器說某個(gè)值存在時(shí),那可能就不存在,如果說某個(gè)值不存在時(shí),那肯定就是不存在了。
布隆過濾器的應(yīng)用場景:新聞推送(不重復(fù)推送)。解決緩存穿透的問題。
緩存
問:緩存雪崩是什么?
如果緩存數(shù)據(jù)設(shè)置的過期時(shí)間是相同的,并且 Redis 恰好將這部分?jǐn)?shù)據(jù)全部刪光了。這就會(huì)導(dǎo)致在這段時(shí)間內(nèi),這些緩存同時(shí)失效,全部請求到數(shù)據(jù)庫中。這就是緩存雪崩。
問:怎么解決緩存雪崩?
解決方法:在緩存的時(shí)候給過期時(shí)間加上一個(gè)隨機(jī)值,這樣就會(huì)大幅度的減少緩存在同一時(shí)間過期。
問:緩存穿透是什么?
緩存穿透是指查詢一個(gè)一定不存在的數(shù)據(jù)。由于緩存不命中,并且出于容錯(cuò)考慮,如果從數(shù)據(jù)庫查不到數(shù)據(jù)則不寫入緩存,這將導(dǎo)致這個(gè)不存在的數(shù)據(jù)每次請求都要到數(shù)據(jù)庫去查詢,失去了緩存的意義。
問:怎么解決緩存穿透?
問:什么是緩存與數(shù)據(jù)庫雙寫一致問題?
問:如何保證緩存與數(shù)據(jù)庫的一致性?
讀的時(shí)候,先讀緩存,緩存沒有的話,就讀數(shù)據(jù)庫,然后取出數(shù)據(jù)后放入緩存,同時(shí)返回響應(yīng)。
先刪除緩存,再更新數(shù)據(jù)庫。
問:為什么是先刪除緩存,而不是先更新緩存?
問:先更新數(shù)據(jù)庫,再刪除緩存,會(huì)有什么問題?
先更新數(shù)據(jù)庫,再刪除緩存。可能出現(xiàn)以下情況:
如果更新完數(shù)據(jù)庫, Java 服務(wù)提交了事務(wù),然后掛掉了,那 Redis 還是會(huì)執(zhí)行,這樣也會(huì)不一致。
如果更新數(shù)據(jù)庫成功,刪除緩存失敗了,那么會(huì)導(dǎo)致數(shù)據(jù)庫中是新數(shù)據(jù),緩存中是舊數(shù)據(jù),數(shù)據(jù)就出現(xiàn)了不一致。
先刪除緩存,再更新數(shù)據(jù)庫。
如果刪除緩存失敗,那就不更新數(shù)據(jù)庫,緩存和數(shù)據(jù)庫的數(shù)據(jù)都是舊數(shù)據(jù),數(shù)據(jù)是一致的。
如果刪除緩存成功,而數(shù)據(jù)庫更新失敗了,那么數(shù)據(jù)庫中是舊數(shù)據(jù),緩存中是空的,數(shù)據(jù)不會(huì)不一致。因?yàn)樽x的時(shí)候緩存沒有,所以去讀了數(shù)據(jù)庫中的舊數(shù)據(jù),然后更新到緩存中。
問:先刪除緩存,在寫數(shù)據(jù)庫成功之前,如果有讀請求發(fā)生,可能導(dǎo)致舊數(shù)據(jù)入緩存,引發(fā)數(shù)據(jù)不一致,怎么處理?
分布式鎖
問:Redis 如何實(shí)現(xiàn)分布式鎖?
使用 set key value ex nx 命令。
當(dāng) key 不存在時(shí),將 key 的值設(shè)為 value ,返回 1。若給定的 key 已經(jīng)存在,則 setnx 不做任何動(dòng)作,返回 0。
當(dāng) setnx 返回 1 時(shí),表示獲取鎖,做完操作以后 del key ,表示釋放鎖,如果 setnx 返回 0 表示獲取鎖失敗。
詳細(xì)的命令如下:
set key value [EX seconds] [PX milliseconds] [NX|XX]EX seconds:設(shè)置失效時(shí)長,單位秒PX milliseconds:設(shè)置失效時(shí)長,單位毫秒NX:key不存在時(shí)設(shè)置value,成功返回OK,失敗返回(nil)XX:key存在時(shí)設(shè)置value,成功返回OK,失敗返回(nil)。
示例如下:
set name fenglin ex 100 nx
問:為什么不先 set nx ,然后再使用 expire 設(shè)置超時(shí)時(shí)間?
我們需要保證 setnx 命令和 expire 命令以原子的方式執(zhí)行,否則如果客戶端執(zhí)行 setnx 獲得鎖后,這時(shí)客戶端宕機(jī)了,那么這把鎖沒有設(shè)置過期時(shí)間,導(dǎo)致其他客戶端永遠(yuǎn)無法獲得鎖了。
問:使用 Redis 分布式鎖, key 和 value 分別設(shè)置成什么?
value 可以使用 json 格式的字符串,示例:
{ "count":1, "expireAt":147506817232, "jvmPid":22224, "mac":"28-D2-44-0E-0D-9A", "threadId":14}
問:Redis 實(shí)現(xiàn)的分布式鎖,如果某個(gè)系統(tǒng)獲取鎖后,宕機(jī)了怎么辦?
系統(tǒng)模塊宕機(jī)的話,可以通過設(shè)置過期時(shí)間(就是設(shè)置緩存失效時(shí)間)解決。系統(tǒng)宕機(jī)時(shí)鎖阻塞,過期后鎖釋放。
問:設(shè)置緩存失效時(shí)間,那如果前一個(gè)線程把這個(gè)鎖給刪除了呢?
問:如果加鎖和解鎖之間的業(yè)務(wù)邏輯執(zhí)行的時(shí)間比較長,超過了鎖過期的時(shí)間,執(zhí)行完了,又刪除了鎖,就會(huì)把別人的鎖給刪了。怎么辦?
這兩個(gè)屬于鎖超時(shí)的問題。
可以將鎖的 value 設(shè)置為 Json 字符串,在其中加入線程的 id 或者請求的 id ,在刪除之前, get 一下這個(gè) key ,判斷 key 對應(yīng)的 value 是不是當(dāng)前線程的。只有是當(dāng)前線程獲取的鎖,當(dāng)前線程才可以刪除。
問:Redis 分布式鎖,怎么保證可重入性?
可以將鎖的 value 設(shè)置為 Json 字符串,在其中加入線程的 id 和 count 變量。
當(dāng) count 變量的值為 0 時(shí),表示當(dāng)前分布式鎖沒有被線程占用。
如果 count 變量的值大于 0 ,線程 id 不是當(dāng)前線程,表示當(dāng)前分布式鎖已經(jīng)被其他線程占用。
如果 count 變量的值大于 0 ,線程 id 是當(dāng)前線程的 id ,表示當(dāng)前線程已經(jīng)拿到了鎖,不必阻塞,可以直接重入,并將 count 變量的值加一即可。
這種思路,其實(shí)就是參考了 ReentrantLock 可重入鎖的機(jī)制。
問:Redis 做分布式鎖, Redis 做了主從,如果設(shè)置鎖之后,主機(jī)在傳輸?shù)綇臋C(jī)的時(shí)候掛掉了,從機(jī)還沒有加鎖信息,如何處理?
可以使用開源框架 Redisson ,采用了 redLock。
問:講一下 Redis 的 redLock。
問:Zookeeper 是怎么實(shí)現(xiàn)分布式鎖的?
分布式鎖:基于 Zookeeper 一致性文件系統(tǒng),實(shí)現(xiàn)鎖服務(wù)。鎖服務(wù)分為保存獨(dú)占及時(shí)序控制兩類。
保存獨(dú)占:將 Zookeeper 上的一個(gè) znode 看作是一把鎖,通過 createznode 的方式來實(shí)現(xiàn)。所有客戶端都去創(chuàng)建 / distribute _ lock 節(jié)點(diǎn),最終成功創(chuàng)建的那個(gè)客戶端也即擁有了這把鎖。用完刪除自己創(chuàng)建的 distribute _ lock 節(jié)點(diǎn)就釋放鎖。
時(shí)序控制:基于/ distribute _ lock 鎖,所有客戶端在它下面創(chuàng)建臨時(shí)順序編號目錄節(jié)點(diǎn),和選 master 一樣,編號最小的獲得鎖,用完刪除,依次方便。
更詳細(xì)的回答如下:
其實(shí)基于 Zookeeper ,就是使用它的臨時(shí)有序節(jié)點(diǎn)來實(shí)現(xiàn)的分布式鎖。
原理就是:當(dāng)某客戶端要進(jìn)行邏輯的加鎖時(shí),就在 Zookeeper 上的某個(gè)指定節(jié)點(diǎn)的目錄下,去生成一個(gè)唯一的臨時(shí)有序節(jié)點(diǎn), 然后判斷自己是否是這些有序節(jié)點(diǎn)中序號最小的一個(gè),如果是,則算是獲取了鎖。如果不是,則說明沒有獲取到鎖,那么就需要在序列中找到比自己小的那個(gè)節(jié)點(diǎn),并對其調(diào)用 exist() 方法,對其注冊事件監(jiān)聽,當(dāng)監(jiān)聽到這個(gè)節(jié)點(diǎn)被刪除了,那就再去判斷一次自己當(dāng)初創(chuàng)建的節(jié)點(diǎn)是否變成了序列中最小的。如果是,則獲取鎖,如果不是,則重復(fù)上述步驟。
當(dāng)釋放鎖的時(shí)候,只需將這個(gè)臨時(shí)節(jié)點(diǎn)刪除即可。
Zookeeper
問:Zookeeper 的原理是什么?
問:Zookeeper 是怎么保證一致性的?
zab 協(xié)議。
zab 協(xié)議有兩種模式,它們分別是恢復(fù)模式(選主)和廣播模式(同步)。當(dāng)服務(wù)啟動(dòng)或者在領(lǐng)導(dǎo)者崩潰后, zab 就進(jìn)入了恢復(fù)模式,當(dāng)領(lǐng)導(dǎo)者被選舉出來,且大多數(shù) server 完成了和 leader 的狀態(tài)同步以后,恢復(fù)模式就結(jié)束了。狀態(tài)同步保證了 leader 和 server 具有相同的系統(tǒng)狀態(tài)。
問:Zookeeper 有哪些應(yīng)用場景?
Zookeeper 可以作為服務(wù)協(xié)調(diào)的注冊中心。還可以做分布式鎖(如果沒有用過分布式鎖就不要說)。
問:Zookeeper 為什么能做注冊中心?
Zookeeper 的數(shù)據(jù)模型是樹型結(jié)構(gòu),由很多數(shù)據(jù)節(jié)點(diǎn)組成, zk 將全量數(shù)據(jù)存儲(chǔ)在內(nèi)存中,可謂是高性能,而且支持集群,可謂高可用。另外支持事件監(jiān)聽(watch 命令)。
Zookeeper 可以作為一個(gè)數(shù)據(jù)發(fā)布/訂閱系統(tǒng)。
問:Zookeeper 的節(jié)點(diǎn)有哪些類型?有什么區(qū)別?
臨時(shí)節(jié)點(diǎn),永久節(jié)點(diǎn)。更加細(xì)分就是臨時(shí)有序節(jié)點(diǎn)、臨時(shí)無序節(jié)點(diǎn)、永久有序節(jié)點(diǎn)、永久無序節(jié)點(diǎn)。
臨時(shí)節(jié)點(diǎn):當(dāng)創(chuàng)建臨時(shí)節(jié)點(diǎn)的程序停掉之后,這個(gè)臨時(shí)節(jié)點(diǎn)就會(huì)消失,存儲(chǔ)的數(shù)據(jù)也沒有了。
問:Zookeeper 做為注冊中心,主要存儲(chǔ)哪些數(shù)據(jù)?存儲(chǔ)在哪里?
IP、端口、還有心跳機(jī)制。數(shù)據(jù)存儲(chǔ)在 Zookeeper 的節(jié)點(diǎn)上面。
問:心跳機(jī)制有什么用?
問:Zookeeper 的廣播模式有什么缺陷?
廣播風(fēng)暴。
問:講一下 Zookeeper 的讀寫機(jī)制。
Leader 主機(jī)負(fù)責(zé)讀和寫。
Follower 負(fù)責(zé)讀,并將寫操作轉(zhuǎn)發(fā)給 Leader。Follower 還參與 Leader 選舉投票,參與事務(wù)請求 Proposal 投票。
Observer 充當(dāng)觀察者的角色。Observer 和 Follower 的唯一區(qū)別在于:Observer 不參與任何投票。
問:講一下 Zookeeper 的選舉機(jī)制。
Leader 不可用時(shí),會(huì)重新選舉 Leader。超過半數(shù)的 Follower 選舉投票即可,Observer 不參與投票。
問:你們的 Zookeeper 集群配置了幾個(gè)節(jié)點(diǎn)?
3 個(gè)節(jié)點(diǎn)。注意:Zookeeper 集群節(jié)點(diǎn),最好是奇數(shù)個(gè)的。
集群中的 Zookeeper 節(jié)點(diǎn)需要超過半數(shù),整個(gè)集群對外才可用。
這里所謂的整個(gè)集群對外才可用,是指整個(gè)集群還能選出一個(gè) Leader 來, Zookeeper 默認(rèn)采用 quorums 來支持 Leader 的選舉。
如果有 2 個(gè) Zookeeper,那么只要有 1 個(gè)死了 Zookeeper 就不能用了,因?yàn)?1 沒有過半,所以 2 個(gè) Zookeeper 的死亡容忍度為 0 ;同理,要是有 3 個(gè) Zookeeper,一個(gè)死了,還剩下 2 個(gè)正常的,過半了,所以 3 個(gè) Zookeeper 的容忍度為 1 ;同理你多列舉幾個(gè):2 -> 0 ; 3 -> 1 ; 4 -> 1 ; 5 -> 2 ; 6 -> 2 會(huì)發(fā)現(xiàn)一個(gè)規(guī)律, 2n 和 2n - 1 的容忍度是一樣的,都是 n - 1 ,所以為了更加高效,何必增加那一個(gè)不必要的 Zookeeper 呢。
問:Zookeeper 的集群節(jié)點(diǎn),如果不是奇數(shù)可能會(huì)出現(xiàn)什么問題?
可能會(huì)出現(xiàn)腦裂。
假死:由于心跳超時(shí)(網(wǎng)絡(luò)原因?qū)е碌模┱J(rèn)為 master 死了,但其實(shí) master 還存活著。
腦裂:由于假死會(huì)發(fā)起新的 master 選舉,選舉出一個(gè)新的 master ,但舊的 master 網(wǎng)絡(luò)又通了,導(dǎo)致出現(xiàn)了兩個(gè) master ,有的客戶端連接到老的 master 有的客戶端鏈接到新的 master。
消息隊(duì)列
問:為什么使用消息隊(duì)列?消息隊(duì)列有什么優(yōu)點(diǎn)和缺點(diǎn)?Kafka 、ActiveMQ 、RabbitMq 、RocketMQ 都有什么優(yōu)點(diǎn)和缺點(diǎn)?
消息隊(duì)列解耦,削峰,限流。
問:如何保證消息隊(duì)列的高可用?(多副本)
問:如何保證消息不被重復(fù)消費(fèi)?(如何保證消息消費(fèi)的冪等性)
問:如何保證消息的可靠性傳輸?(如何處理消息丟失的問題)
問:如何保證消息的順序性?
問:如何解決消息隊(duì)列的延時(shí)以及過期失效問題?消息隊(duì)列滿了以后該怎么處理?有幾百萬消息持續(xù)積壓幾小時(shí),說說怎么解決?
問:如果讓你寫一個(gè)消息隊(duì)列,該如何進(jìn)行架構(gòu)設(shè)計(jì)啊?說一下你的思路。
Kafka
問:講一下 Kafka。
Kafka 的簡單理解
問:Kafka 相對其他消息隊(duì)列,有什么特點(diǎn)?
持久化:Kafka 的持久化能力比較好,通過磁盤持久化。而 RabbitMQ 是通過內(nèi)存持久化的。
吞吐量:Rocket 的并發(fā)量非常高。
消息處理:RabbitMQ 的消息不支持批量處理,而 RocketMQ 和 Kafka 支持批量處理。
高可用:RabbitMQ 采用主從模式。Kafka 也是主從模式,通過 Zookeeper 管理,選舉 Leader ,還有 Replication 副本。
事務(wù):RocketMQ 支持事務(wù),而 Kafka 和 RabbitMQ 不支持。
問:Kafka 有哪些模式?
如果一個(gè)生產(chǎn)者或者多個(gè)生產(chǎn)者產(chǎn)生的消息能夠被多個(gè)消費(fèi)者同時(shí)消費(fèi)的情況,這樣的消息隊(duì)列稱為"發(fā)布訂閱模式"的消息隊(duì)列。
問:Kafka 作為消息隊(duì)列,有哪些優(yōu)勢?
分布式的消息系統(tǒng)。
高吞吐量。即使存儲(chǔ)了許多 TB 的消息,它也保持穩(wěn)定的性能。
數(shù)據(jù)保留在磁盤上,因此它是持久的。
問:Kafka 為什么處理速度會(huì)很快?kafka 的吞吐量為什么高?
零拷貝:Kafka 實(shí)現(xiàn)了"零拷貝"原理來快速移動(dòng)數(shù)據(jù),避免了內(nèi)核之間的切換。
消息壓縮、分批發(fā)送:Kafka 可以將數(shù)據(jù)記錄分批發(fā)送,從生產(chǎn)者到文件系統(tǒng)(Kafka 主題日志)到消費(fèi)者,可以端到端的查看這些批次的數(shù)據(jù)。
批處理能夠進(jìn)行更有效的數(shù)據(jù)壓縮并減少 I / O 延遲。
順序讀寫:Kafka 采取順序?qū)懭氪疟P的方式,避免了隨機(jī)磁盤尋址的浪費(fèi)。
問:講一下 Kafka 中的零拷貝。
數(shù)據(jù)的拷貝從內(nèi)存拷貝到 kafka 服務(wù)進(jìn)程那塊,又拷貝到 socket 緩存那塊,整個(gè)過程耗費(fèi)的時(shí)間比較高, kafka 利用了 Linux 的 sendFile 技術(shù)(NIO),省去了進(jìn)程切換和一次數(shù)據(jù)拷貝,讓性能變得更好。
問:Kafka 的偏移量是什么?
消費(fèi)者每次消費(fèi)數(shù)據(jù)的時(shí)候,消費(fèi)者都會(huì)記錄消費(fèi)的物理偏移量(offset)的位置。等到下次消費(fèi)時(shí),他會(huì)接著上次位置繼續(xù)消費(fèi)
問:Kafka 的生產(chǎn)者,是如何發(fā)送消息的?
生產(chǎn)者的消息是先被寫入分區(qū)中的緩沖區(qū)中,然后分批次發(fā)送給 Kafka Broker。
生產(chǎn)者的消息發(fā)送機(jī)制,有同步發(fā)送和異步發(fā)送。
同步發(fā)送消息都有個(gè)問題,那就是同一時(shí)間只能有一個(gè)消息在發(fā)送,這會(huì)造成許多消息。
無法直接發(fā)送,造成消息滯后,無法發(fā)揮效益最大化。
異步發(fā)送消息的同時(shí)能夠?qū)Ξ惓G闆r進(jìn)行處理,生產(chǎn)者提供了 Callback 回調(diào)。
問:Kafka 生產(chǎn)者發(fā)送消息,有哪些分區(qū)策略?
Kafka 的分區(qū)策略指的就是將生產(chǎn)者發(fā)送到哪個(gè)分區(qū)的算法。有順序輪詢、隨機(jī)輪詢、key - ordering 策略。
key - ordering 策略:Kafka 中每條消息都會(huì)有自己的 key ,一旦消息被定義了 Key ,那么你就可以保證同一個(gè) Key 的所有消息都進(jìn)入到相同的分區(qū)里面,由于每個(gè)分區(qū)下的消息處理都是有順序的,故這個(gè)策略被稱為按消息鍵保序策略。
問:Kafka 為什么要分區(qū)?
實(shí)現(xiàn)負(fù)載均衡和水平擴(kuò)展。Kafka 可以將主題(Topic)劃分為多個(gè)分區(qū)(Partition),會(huì)根據(jù)分區(qū)規(guī)則選擇把消息存儲(chǔ)到哪個(gè)分區(qū)中,只要如果分區(qū)規(guī)則設(shè)置的合理,那么所有的消息將會(huì)被均勻的分布到不同的分區(qū)中,這樣就實(shí)現(xiàn)了負(fù)載均衡和水平擴(kuò)展。另外,多個(gè)訂閱者可以從一個(gè)或者多個(gè)分區(qū)中同時(shí)消費(fèi)數(shù)據(jù),以支撐海量數(shù)據(jù)處理能力。
問:Kafka 是如何在 Broker 間分配分區(qū)的?
在 broker 間平均分布分區(qū)副本。
假設(shè)有 6 個(gè) broker ,打算創(chuàng)建一個(gè)包含 10 個(gè)分區(qū)的 Topic ,復(fù)制系數(shù)為 3 ,那么 Kafka 就會(huì)有 30 個(gè)分區(qū)副本,它可以被分配給這 6 個(gè) broker ,這樣的話,每個(gè) broker 可以有 5 個(gè)副本。
要確保每個(gè)分區(qū)的每個(gè)副本分布在不同的 broker 上面:
假設(shè) Leader 分區(qū) 0 會(huì)在 broker1 上面, Leader 分區(qū) 1 會(huì)在 broker2 上面, Leder 分區(qū) 2 會(huì)在 broker3 上面。
接下來會(huì)分配跟隨者副本。如果分區(qū) 0 的第一個(gè) Follower 在 broker2 上面,第二個(gè) Follower 在 broker3 上面。分區(qū) 1 的第一個(gè) Follower 在 broker3 上面,第二個(gè) Follower 在 broker4 上面。
問:Kafka 如何保證消息的順序性?
Kafka 可以保證同一個(gè)分區(qū)里的消息是有序的。也就是說消息發(fā)送到一個(gè) Partition 是有順序的。
問:Kafka 的消費(fèi)者群組 Consumer Group 訂閱了某個(gè) Topic ,假如這個(gè) Topic 接收到消息并推送,那整個(gè)消費(fèi)者群組能收到消息嗎?
Kafka 官網(wǎng)中有這樣一句" Consumers label themselves with a consumer group name , and each record published to a topic is delivered to one consumer instance within each subscribing consumer group . "
表示推送到 topic 上的 record ,會(huì)被傳遞到已訂閱的消費(fèi)者群組里面的一個(gè)消費(fèi)者實(shí)例。
問:如何提高 Kafka 的消費(fèi)速度?
問:Kafka 出現(xiàn)消息積壓,有哪些原因?怎么解決?
出現(xiàn)消息積壓,可能是因?yàn)橄M(fèi)的速度太慢。
擴(kuò)容消費(fèi)者。之所以消費(fèi)延遲大,就是消費(fèi)者處理能力有限,可以增加消費(fèi)者的數(shù)量。
擴(kuò)大分區(qū)。一個(gè)分區(qū)只能被消費(fèi)者群組中的一個(gè)消費(fèi)者消費(fèi)。消費(fèi)者擴(kuò)大,分區(qū)最好多隨之?dāng)U大。
問:Kafka 消息消費(fèi)者宕機(jī)了,怎么確認(rèn)有沒有收到消息?
ACK 機(jī)制,如果接收方收到消息后,會(huì)返回一個(gè)確認(rèn)字符。
問:講一下 Kafka 的 ACK 機(jī)制。
acks 參數(shù)指定了要有多少個(gè)分區(qū)副本接收消息,生產(chǎn)者才認(rèn)為消息是寫入成功的。此參數(shù)對消息丟失的影響較大。
如果 acks = 0 ,就表示生產(chǎn)者也不知道自己產(chǎn)生的消息是否被服務(wù)器接收了,它才知道它寫成功了。如果發(fā)送的途中產(chǎn)生了錯(cuò)誤,生產(chǎn)者也不知道,它也比較懵逼,因?yàn)闆]有返回任何消息。這就類似于 UDP 的運(yùn)輸層協(xié)議,只管發(fā),服務(wù)器接受不接受它也不關(guān)心。
如果 acks = 1 ,只要集群的 Leader 接收到消息,就會(huì)給生產(chǎn)者返回一條消息,告訴它寫入成功。如果發(fā)送途中造成了網(wǎng)絡(luò)異常或者 Leader 還沒選舉出來等其他情況導(dǎo)致消息寫入失敗,生產(chǎn)者會(huì)受到錯(cuò)誤消息,這時(shí)候生產(chǎn)者往往會(huì)再次重發(fā)數(shù)據(jù)。因?yàn)橄⒌陌l(fā)送也分為 同步 和 異步, Kafka 為了保證消息的高效傳輸會(huì)決定是同步發(fā)送還是異步發(fā)送。如果讓客戶端等待服務(wù)器的響應(yīng)(通過調(diào)用 Future 中的 get() 方法),顯然會(huì)增加延遲,如果客戶端使用回調(diào),就會(huì)解決這個(gè)問題。
如果 acks = all ,這種情況下是只有當(dāng)所有參與復(fù)制的節(jié)點(diǎn)都收到消息時(shí),生產(chǎn)者才會(huì)接收到一個(gè)來自服務(wù)器的消息。不過,它的延遲比 acks = 1 時(shí)更高,因?yàn)槲覀円却恢灰粋€(gè)服務(wù)器節(jié)點(diǎn)接收消息。
問:Kafka 如何避免消息丟失?
1、生產(chǎn)者丟失消息的情況
生產(chǎn)者(Producer) 調(diào)用 send 方法發(fā)送消息之后,消息可能因?yàn)榫W(wǎng)絡(luò)問題并沒有發(fā)送過去。
所以,我們不能默認(rèn)在調(diào)用 send 方法發(fā)送消息之后消息消息發(fā)送成功了。為了確定消息是發(fā)送成功,我們要判斷消息發(fā)送的結(jié)果。
可以采用為其添加回調(diào)函數(shù)的形式,獲取回調(diào)結(jié)果。
如果消息發(fā)送失敗的話,我們檢查失敗的原因之后重新發(fā)送即可!
可以設(shè)置 Producer 的 retries(重試次數(shù))為一個(gè)比較合理的值,一般是 3 ,但是為了保證消息不丟失的話一般會(huì)設(shè)置比較大一點(diǎn)。
設(shè)置完成之后,當(dāng)出現(xiàn)網(wǎng)絡(luò)問題之后能夠自動(dòng)重試消息發(fā)送,避免消息丟失。
2、消費(fèi)者丟失消息的情況
當(dāng)消費(fèi)者拉取到了分區(qū)的某個(gè)消息之后,消費(fèi)者會(huì)自動(dòng)提交了 offset。自動(dòng)提交的話會(huì)有一個(gè)問題,
試想一下,當(dāng)消費(fèi)者剛拿到這個(gè)消息準(zhǔn)備進(jìn)行真正消費(fèi)的時(shí)候,突然掛掉了,消息實(shí)際上并沒有被消費(fèi),但是 offset 卻被自動(dòng)提交了。
手動(dòng)關(guān)閉閉自動(dòng)提交 offset ,每次在真正消費(fèi)完消息之后之后再自己手動(dòng)提交 offset 。
3 、Kafka 丟失消息
a、假如 leader 副本所在的 broker 突然掛掉,那么就要從 follower 副本重新選出一個(gè) leader ,但是 leader 的數(shù)據(jù)還有一些沒有被 follower 副本的同步的話,就會(huì)造成消息丟失。因此可以設(shè)置 ack = all。
b、設(shè)置 replication . factor >= 3 。為了保證 leader 副本能有 follower 副本能同步消息,我們一般會(huì)為 topic 設(shè)置 replication . factor >= 3。這樣就可以保證每個(gè)
分區(qū)(partition) 至少有 3 個(gè)副本。雖然造成了數(shù)據(jù)冗余,但是帶來了數(shù)據(jù)的安全性。
問:Kafka 怎么保證可靠性?
多副本以及 ISR 機(jī)制。
在 Kafka 中主要通過 ISR 機(jī)制來保證消息的可靠性。
ISR(in sync replica):是 Kafka 動(dòng)態(tài)維護(hù)的一組同步副本,在 ISR 中有成員存活時(shí),只有這個(gè)組的成員才可以成為 leader ,內(nèi)部保存的為每次提交信息時(shí)必須同步的副本(acks = all 時(shí)),每當(dāng) leader 掛掉時(shí),在 ISR 集合中選舉出一個(gè) follower 作為 leader 提供服務(wù),當(dāng) ISR 中的副本被認(rèn)為壞掉的時(shí)候,會(huì)被踢出 ISR ,當(dāng)重新跟上 leader 的消息數(shù)據(jù)時(shí),重新進(jìn)入 ISR。
問:什么是 HW ?
HW(high watermark):副本的高水印值, replica 中 leader 副本和 follower 副本都會(huì)有這個(gè)值,通過它可以得知副本中已提交或已備份消息的范圍, leader 副本中的 HW ,決定了消費(fèi)者能消費(fèi)的最新消息能到哪個(gè) offset。
問:什么是 LEO ?
LEO(log end offset):日志末端位移,代表日志文件中下一條待寫入消息的 offset ,這個(gè) offset 上實(shí)際是沒有消息的。不管是 leader 副本還是 follower 副本,都有這個(gè)值。
問:Kafka 怎么保證一致性?(存疑)
一致性定義:若某條消息對 client 可見,那么即使 Leader 掛了,在新 Leader 上數(shù)據(jù)依然可以被讀到。
?HW - HighWaterMark : client 可以從 Leader 讀到的最大 msg offset ,即對外可見的最大 offset , HW = max(replica . offset)
對于 Leader 新收到的 msg , client 不能立刻消費(fèi), Leader 會(huì)等待該消息被所有 ISR 中的 replica 同步后,更新 HW ,此時(shí)該消息才能被 client 消費(fèi),這樣就保證了如果 Leader fail ,該消息仍然可以從新選舉的 Leader 中獲取。
對于來自內(nèi)部 Broker 的讀取請求,沒有 HW 的限制。同時(shí), Follower 也會(huì)維護(hù)一份自己的 HW , Folloer . HW = min(Leader . HW , Follower . offset).
問:Kafka 怎么處理重復(fù)消息?怎么避免重復(fù)消費(fèi)?
偏移量 offset :消費(fèi)者每次消費(fèi)數(shù)據(jù)的時(shí)候,消費(fèi)者都會(huì)記錄消費(fèi)的物理偏移量(offset)的位置。等到下次消費(fèi)時(shí),他會(huì)接著上次位置繼續(xù)消費(fèi)。
一般情況下, Kafka 重復(fù)消費(fèi)都是由于未正常提交 offset 造成的,比如網(wǎng)絡(luò)異常,消費(fèi)者宕機(jī)之類的。
使用的是 spring-Kafka ,所以把 Kafka 消費(fèi)者的配置 enable.auto. commit 設(shè)為 false ,禁止 Kafka 自動(dòng)提交 offset ,從而使用 spring-Kafka 提供的 offset 提交策略。
sprin-Kafka 中的 offset 提交策略可以保證一批消息數(shù)據(jù)沒有完成消費(fèi)的情況下,也能提交 offset ,從而避免了提交失敗而導(dǎo)致永遠(yuǎn)重復(fù)消費(fèi)的問題。
問:怎么避免重復(fù)消費(fèi)?
將消息的唯一標(biāo)識保存起來,每次消費(fèi)時(shí)判斷是否處理過即可。
問:如何保證消息不被重復(fù)消費(fèi)?(如何保證消息消費(fèi)的冪等性)
怎么保證消息隊(duì)列消費(fèi)的冪等性?其實(shí)還是得結(jié)合業(yè)務(wù)來思考,有幾個(gè)思路:
比如你拿個(gè)數(shù)據(jù)要寫庫,你先根據(jù)主鍵查一下,如果這數(shù)據(jù)都有了,你就別插入了, update 一下好吧。
比如你是寫 Redis ,那沒問題了,反正每次都是 set ,天然冪等性。
如果是復(fù)雜一點(diǎn)的業(yè)務(wù),那么每條消息加一個(gè)全局唯一的 id ,類似訂單 id 之類的東西,然后消費(fèi)到了之后,先根據(jù)這個(gè) id 去比如 Redis 里查一下,之前消費(fèi)過嗎?
如果沒有消費(fèi)過,你就處理,然后這個(gè) id 寫 Redis。如果消費(fèi)過了,那你就別處理了,保證別重復(fù)處理相同的消息即可。
問:Kafka 消息是采用 pull 模式,還是 push 模式?
pull 模式。
問:pull 模式和 push 模式,各有哪些特點(diǎn)?
pull 模式,準(zhǔn)確性?可以較大保證消費(fèi)者能獲取到消息。
push 模式,即時(shí)性?可以在 broker 獲取消息后馬上送達(dá)消費(fèi)者。
問:Kafka 是如何存儲(chǔ)消息的?
Kafka 使用日志文件的方式來保存生產(chǎn)者和發(fā)送者的消息,每條消息都有一個(gè) offset 值來表示它在分區(qū)中的偏移量。
Kafka 中存儲(chǔ)的一般都是海量的消息數(shù)據(jù),為了避免日志文件過大,
一個(gè)分片并不是直接對應(yīng)在一個(gè)磁盤上的日志文件,而是對應(yīng)磁盤上的一個(gè)目錄。
數(shù)據(jù)存儲(chǔ)設(shè)計(jì)的特點(diǎn)在于以下幾點(diǎn):
Kafka 把主題中一個(gè)分區(qū)劃分成多個(gè)分段的小文件段,通過多個(gè)小文件段,就容易根據(jù)偏移量查找消息、定期清除和刪除已經(jīng)消費(fèi)完成的數(shù)據(jù)文件,減少磁盤容量的占用;
采用稀疏索引存儲(chǔ)的方式構(gòu)建日志的偏移量索引文件,并將其映射至內(nèi)存中,提高查找消息的效率,同時(shí)減少磁盤 IO 操作;
Kafka 將消息追加的操作邏輯變成為日志數(shù)據(jù)文件的順序?qū)懭耄瑯O大的提高了磁盤 IO 的性能;
問:講一下 Kafka 集群的 Leader 選舉機(jī)制。
Kafka 在 Zookeeper 上針對每個(gè) Topic 都維護(hù)了一個(gè) ISR(in - sync replica ---已同步的副本)的集合,集合的增減 Kafka 都會(huì)更新該記錄。如果某分區(qū)的 Leader 不可用, Kafka 就從 ISR 集合中選擇一個(gè)副本作為新的 Leader。
分庫分表
問:數(shù)據(jù)庫如何處理海量數(shù)據(jù)?
分庫分表,主從架構(gòu),讀寫分離。
問:數(shù)據(jù)庫分庫分表,何時(shí)分?怎么分?
水平分庫/分表,垂直分庫/分表。
水平分庫/表,各個(gè)庫和表的結(jié)構(gòu)一模一樣。
垂直分庫/表,各個(gè)庫和表的結(jié)構(gòu)不一樣。
問:讀寫分離怎么做?
主機(jī)負(fù)責(zé)寫,從機(jī)負(fù)責(zé)讀。
系統(tǒng)設(shè)計(jì)
1、分布式、高并發(fā)場景
遇到高并發(fā)場景,可以使用 Redis 緩存、Redis 限流、MQ 異步、MQ 削峰等。
問:在實(shí)踐中,遇到過哪些并發(fā)的業(yè)務(wù)場景?
秒殺。比如搶商品,搶紅包。
2、秒殺
問:如何設(shè)計(jì)一個(gè)秒殺/搶券系統(tǒng)?
可以通過隊(duì)列配合異步處理實(shí)現(xiàn)秒殺。
使用 redis 的 list ,將商品 push 進(jìn)隊(duì)列, pop 出隊(duì)列。
異步操作不會(huì)阻塞,不會(huì)消耗太多時(shí)間。
問:如何提高搶券系統(tǒng)的性能?
使用多個(gè) list。
使用多線程從隊(duì)列中拉取數(shù)據(jù)。
集群提高可用性。
MQ 異步處理,削峰。
問:秒殺怎么避免少賣或超賣?
redis 是單進(jìn)程單線程的,操作具有原子性,不會(huì)導(dǎo)致少賣或者超賣。另外,也可以設(shè)置一個(gè)版本號 version ,樂觀鎖機(jī)制。
問:考勤打卡,假如高峰期有幾萬人同時(shí)打卡,那么怎么應(yīng)對這種高并發(fā)?
使用 Redis 緩存。員工點(diǎn)擊簽到,可以在緩存中 set 狀態(tài)。將工號作為 key ,打卡狀態(tài)作為 value ,打卡成功為 01 ,未打卡或者打卡失敗為 00 ,然后再將數(shù)據(jù)異步地寫入到數(shù)據(jù)庫里面就可以了。
問:如何應(yīng)對高峰期的超高并發(fā)量?
Redis 限流。Redis 可以用計(jì)數(shù)器限流。使用 INCR 命令,每次都加一,處理完業(yè)務(wù)邏輯就減一。然后設(shè)置一個(gè)最大值,當(dāng)達(dá)到最大值后就直接返回,不處理后續(xù)的邏輯。
Redis 還可以用令牌桶限流。使用 Redis 隊(duì)列,每十個(gè)數(shù)據(jù)中 push 一個(gè)令牌桶,每個(gè)請求進(jìn)入后會(huì)先從隊(duì)列中 pop 數(shù)據(jù),如果是令牌就可以通行,不是令牌就直接返回。
3、短鏈接
問:如何將長鏈接轉(zhuǎn)換成短鏈接,并發(fā)送短信?
短 URL 從生成到使用分為以下幾步:
有一個(gè)服務(wù),將要發(fā)送給你的長 URL 對應(yīng)到一個(gè)短 URL 上.例如 www.baidu.com -> www.t.cn/1。
把短 url 拼接到短信等的內(nèi)容上發(fā)送。
用戶點(diǎn)擊短 URL ,瀏覽器用 301 / 302 進(jìn)行重定向,訪問到對應(yīng)的長 URL。
展示對應(yīng)的內(nèi)容。
問:長鏈接和短鏈接如何互相轉(zhuǎn)換?
思路是建立一個(gè)發(fā)號器。每次有一個(gè)新的長 URL 進(jìn)來,我們就增加一。并且將新的數(shù)值返回.第一個(gè)來的 url 返回"www.x.cn/0",第二個(gè)返回"www.x.cn/1".
問:長鏈接和短鏈接的對應(yīng)關(guān)系如何存儲(chǔ)?
如果數(shù)據(jù)量小且 QPS 低,直接使用數(shù)據(jù)庫的自增主鍵就可以實(shí)現(xiàn)。
還可以將最近/最熱門的對應(yīng)關(guān)系存儲(chǔ)在 K-V 數(shù)據(jù)庫中,這樣子可以節(jié)省空間的同時(shí),加快響應(yīng)速度。
系統(tǒng)架構(gòu)與設(shè)計(jì)
問:如何提高系統(tǒng)的并發(fā)能力?
使用分布式系統(tǒng)。
部署多臺(tái)服務(wù)器,并做負(fù)載均衡。
使用緩存(Redis)集群。
數(shù)據(jù)庫分庫分表 + 讀寫分離。
引入消息中間件集群。
問:設(shè)計(jì)一個(gè)紅包系統(tǒng),需要考慮哪些問題,如何解決?(本質(zhì)上也是秒殺系統(tǒng))
問:如果讓你設(shè)計(jì)一個(gè)消息隊(duì)列,你會(huì)怎么設(shè)計(jì)?
項(xiàng)目經(jīng)驗(yàn)及數(shù)據(jù)量
問:這個(gè)項(xiàng)目的亮點(diǎn)、難點(diǎn)在哪里?
問:如果這個(gè)模塊掛掉了怎么辦?
問:你們的項(xiàng)目有多少臺(tái)機(jī)器?
問:你們的項(xiàng)目有多少個(gè)實(shí)例?
4 個(gè)實(shí)例。
問:你們的系統(tǒng) QPS(TPS)是多少?
QPS ,每秒查詢量。QPS 為幾百/幾千,已經(jīng)算是比較高的了。
TPS ,每秒處理事務(wù)數(shù)。TPS 即每秒處理事務(wù)數(shù),包括:”用戶請求服務(wù)器”、”服務(wù)器自己的內(nèi)部處理”、”服務(wù)器返回給用戶”,這三個(gè)過程,每秒能夠完成 N 個(gè)這三個(gè)過程, TPS 也就是 3。
問:一個(gè)接口,多少秒響應(yīng)才正常?
快的話幾毫秒。慢的話 1-2 秒。異常情況可能會(huì) 10 幾秒;最好保證 99 %以上的請求是正常的。
問:這個(gè)接口的請求時(shí)間,大概多久?主要耗時(shí)在哪里?
問:系統(tǒng)的數(shù)據(jù)量多少?有沒有分庫分表?
正常情況下,幾百萬的數(shù)據(jù)量沒有必要分庫分表。只有超過幾千萬才需要分庫分表。
問:插入/更新一條數(shù)據(jù)要多久?更新十萬/百萬條數(shù)據(jù)要多久?
插入/更新一條數(shù)據(jù)一般要幾毫秒;更新十萬條數(shù)據(jù)最好在 10 秒以內(nèi);
百萬條數(shù)據(jù)最好在 50-100 秒以內(nèi)。
看完本文有收獲?請轉(zhuǎn)發(fā)分享給更多人
關(guān)注「Java耕耘者」,提升Java技能
好文章,我在看??
