面試官:Go中的singleflight是如何實(shí)現(xiàn)的?
圖片
go
go singleflight 的底層實(shí)現(xiàn)
singleflight 是 Go 語(yǔ)言標(biāo)準(zhǔn)庫(kù)中的一個(gè)很有用的包,它主要用來(lái)處理并發(fā)請(qǐng)求時(shí)的重復(fù)問(wèn)題。比如在高并發(fā)場(chǎng)景下,如果多個(gè)請(qǐng)求同時(shí)訪問(wèn)同一個(gè)資源,singleflight 可以確保這些請(qǐng)求中只有一個(gè)實(shí)際執(zhí)行,其他請(qǐng)求則等待這個(gè)結(jié)果。
具體來(lái)說(shuō),singleflight 里有一個(gè)核心結(jié)構(gòu)叫做 Group。當(dāng)你調(diào)用 Do 方法時(shí),它接收一個(gè)鍵(key)和一個(gè)函數(shù)(fn)。這個(gè)鍵是用來(lái)標(biāo)識(shí)請(qǐng)求的唯一性,而函數(shù)則是實(shí)際要執(zhí)行的操作。Do 方法首先會(huì)檢查是否已經(jīng)有相同的請(qǐng)求正在處理中。如果有,那么當(dāng)前請(qǐng)求就會(huì)被放入一個(gè)等待隊(duì)列,直到第一個(gè)請(qǐng)求完成并返回結(jié)果。這時(shí),所有等待的請(qǐng)求都會(huì)收到相同的結(jié)果。
內(nèi)部實(shí)現(xiàn)上,singleflight 使用了一個(gè)互斥鎖(mutex)來(lái)保護(hù)其狀態(tài),并且有一個(gè)映射表(map)來(lái)存儲(chǔ)正在進(jìn)行的請(qǐng)求。對(duì)于每個(gè)請(qǐng)求,它會(huì)創(chuàng)建一個(gè) call 對(duì)象,這個(gè)對(duì)象包含了實(shí)際的執(zhí)行函數(shù)以及一個(gè)通道(channel),用于在請(qǐng)求完成后發(fā)送結(jié)果。當(dāng)有多個(gè)請(qǐng)求使用相同的鍵時(shí),它們會(huì)被添加到同一個(gè) call 對(duì)象的等待隊(duì)列中,等到第一個(gè)請(qǐng)求完成后,所有的請(qǐng)求都會(huì)被喚醒并返回相同的結(jié)果。
這種方式特別適用于緩存穿透或者需要避免重復(fù)計(jì)算的場(chǎng)景,因?yàn)樗梢源蟠鬁p少對(duì)后端服務(wù)的壓力,提高系統(tǒng)的性能和效率。
mysql
使用數(shù)據(jù)庫(kù)樂(lè)觀鎖cas操作判斷的時(shí)候,受不受數(shù)據(jù)庫(kù)隔離級(jí)別的影響?
樂(lè)觀鎖(CAS操作)和數(shù)據(jù)庫(kù)的隔離級(jí)別確實(shí)有一定的關(guān)系,但它們的作用方式不同。
樂(lè)觀鎖通常通過(guò)版本號(hào)或時(shí)間戳來(lái)實(shí)現(xiàn)。當(dāng)一個(gè)事務(wù)嘗試更新數(shù)據(jù)時(shí),它會(huì)檢查數(shù)據(jù)的版本號(hào)或時(shí)間戳是否與讀取時(shí)一致。如果不一致,說(shuō)明在這期間數(shù)據(jù)已經(jīng)被其他事務(wù)修改了,那么當(dāng)前事務(wù)就會(huì)失敗并可能需要重試。
數(shù)據(jù)庫(kù)的隔離級(jí)別則決定了事務(wù)之間可見(jiàn)性的規(guī)則。常見(jiàn)的隔離級(jí)別包括讀未提交、讀已提交、可重復(fù)讀和序列化。不同的隔離級(jí)別對(duì)并發(fā)事務(wù)的可見(jiàn)性和一致性有不同的保證。
樂(lè)觀鎖的操作本身并不依賴于特定的隔離級(jí)別,但它可能會(huì)受到隔離級(jí)別選擇的影響。例如:
- 讀未提交:在這種隔離級(jí)別下,事務(wù)可以看到其他事務(wù)未提交的數(shù)據(jù)。這可能會(huì)導(dǎo)致樂(lè)觀鎖的版本檢查出現(xiàn)問(wèn)題,因?yàn)橐粋€(gè)事務(wù)可能會(huì)看到另一個(gè)事務(wù)尚未提交的數(shù)據(jù)。
- 讀已提交:在這種隔離級(jí)別下,事務(wù)只能看到已經(jīng)提交的數(shù)據(jù)。這是最常見(jiàn)的隔離級(jí)別之一,適合使用樂(lè)觀鎖,因?yàn)樗梢员苊馀K讀。
- 可重復(fù)讀:在這種隔離級(jí)別下,事務(wù)在執(zhí)行過(guò)程中多次讀取同一數(shù)據(jù)時(shí),結(jié)果是一致的。這有助于確保樂(lè)觀鎖的版本檢查是基于一致的數(shù)據(jù)視圖。
- 序列化:這是最高的隔離級(jí)別,它提供了最嚴(yán)格的事務(wù)隔離。在這種隔離級(jí)別下,樂(lè)觀鎖通常也能很好地工作,但由于序列化的高開(kāi)銷,實(shí)際應(yīng)用中不常用。
redis
介紹一下redis中常用數(shù)據(jù)結(jié)構(gòu)的底層實(shí)現(xiàn)
1. String
- 用途:用于存儲(chǔ)簡(jiǎn)單的鍵值對(duì),可以是字符串、整數(shù)或浮點(diǎn)數(shù)。
- 底層實(shí)現(xiàn):
Redis 的 String 類型內(nèi)部使用 sds (簡(jiǎn)單動(dòng)態(tài)字符串) 結(jié)構(gòu)來(lái)存儲(chǔ)數(shù)據(jù)。sds 是 Redis 自己實(shí)現(xiàn)的一種字符串結(jié)構(gòu),它在 C 字符串的基礎(chǔ)上增加了長(zhǎng)度信息,并且提供了高效的內(nèi)存管理和擴(kuò)展能力。
2. Hash
- 用途:用于存儲(chǔ)字段和值之間的映射關(guān)系,類似于 Java 中的 HashMap。
- 底層實(shí)現(xiàn):
當(dāng)哈希表中的元素較少時(shí),Redis 使用壓縮列表(ziplist)來(lái)存儲(chǔ)數(shù)據(jù)。壓縮列表是一種特殊的雙向鏈表,它可以高效地存儲(chǔ)小數(shù)量的數(shù)據(jù),并且占用更少的內(nèi)存。
當(dāng)哈希表中的元素較多時(shí),Redis 會(huì)將壓縮列表轉(zhuǎn)換為字典(Dictionary)。字典是一個(gè)由多個(gè)桶(bucket)組成的數(shù)組,每個(gè)桶中包含一個(gè)鏈表,用于處理哈希沖突。
- 轉(zhuǎn)換閾值:
每個(gè)字段的最大長(zhǎng)度:hash-max-ziplist-value(默認(rèn) 64 字節(jié))
哈希表的最大字段數(shù)量:hash-max-ziplist-entries(默認(rèn) 512 個(gè)字段)
3. List
- 用途:有序的字符串列表,可以在列表的兩端進(jìn)行插入和刪除操作。
- 底層實(shí)現(xiàn):
當(dāng)列表中的元素較少時(shí),Redis 使用壓縮列表(ziplist)來(lái)存儲(chǔ)數(shù)據(jù)。壓縮列表可以高效地存儲(chǔ)小數(shù)量的數(shù)據(jù),并且占用更少的內(nèi)存。
當(dāng)列表中的元素較多時(shí),Redis 會(huì)將壓縮列表轉(zhuǎn)換為雙端鏈表(linked list)。雙端鏈表允許在列表的兩端進(jìn)行高效的插入和刪除操作,但訪問(wèn)中間元素的效率較低。
- 轉(zhuǎn)換閾值:
每個(gè)元素的最大長(zhǎng)度:list-max-ziplist-value(默認(rèn) 64 字節(jié))
列表的最大元素?cái)?shù)量:list-max-ziplist-entries(默認(rèn) 512 個(gè)元素)
4. Set
- 用途:無(wú)序的字符串集合,不允許重復(fù)元素。
- 底層實(shí)現(xiàn):
當(dāng)集合中的元素較少時(shí),Redis 使用整數(shù)集合(intset)來(lái)存儲(chǔ)數(shù)據(jù)。整數(shù)集合是一個(gè)有序的整數(shù)數(shù)組,支持快速查找和插入操作。
當(dāng)集合中的元素較多時(shí),Redis 會(huì)將整數(shù)集合轉(zhuǎn)換為字典(Dictionary)。字典提供高效的查找和插入操作,適用于大量數(shù)據(jù)的情況。
- 轉(zhuǎn)換閾值:
整數(shù)集合中的最大元素?cái)?shù)量:沒(méi)有明確的配置項(xiàng),但當(dāng)集合中的元素不再是整數(shù)或元素?cái)?shù)量超過(guò)一定閾值時(shí),會(huì)自動(dòng)轉(zhuǎn)換為字典。
5. Zset (Sorted Set)
- 用途:有序的字符串集合,每個(gè)元素關(guān)聯(lián)一個(gè)分?jǐn)?shù),通過(guò)分?jǐn)?shù)進(jìn)行排序。
- 底層實(shí)現(xiàn):
跳躍表(skiplist):跳躍表是一種概率數(shù)據(jù)結(jié)構(gòu),提供高效的范圍查詢和插入操作。跳躍表通過(guò)多層索引來(lái)加速查找過(guò)程。
字典(Dictionary):字典用于存儲(chǔ)成員到分?jǐn)?shù)的映射,以便快速查找成員的分?jǐn)?shù)。
Zset 內(nèi)部使用兩種數(shù)據(jù)結(jié)構(gòu)來(lái)實(shí)現(xiàn):
跳躍表和字典共同工作,確保 Zset 既能高效地進(jìn)行范圍查詢,又能快速地進(jìn)行成員查找。
- 轉(zhuǎn)換閾值:
每個(gè)成員的最大長(zhǎng)度:zset-max-ziplist-value(默認(rèn) 64 字節(jié))
有序集合的最大成員數(shù)量:zset-max-ziplist-entries(默認(rèn) 128 個(gè)成員)
redis內(nèi)存快把一臺(tái)機(jī)器的內(nèi)存占滿了,例如一共16g,現(xiàn)在用了15.5g這時(shí)候你該怎么辦?
一、監(jiān)控和分析內(nèi)存使用情況
使用 Redis 的監(jiān)控工具(如 RedisInsight)或者命令(如 INFO memory)來(lái)確定哪些數(shù)據(jù)占用了大量?jī)?nèi)存,以便后續(xù)采取針對(duì)性措施。
二、調(diào)整數(shù)據(jù)存儲(chǔ)和過(guò)期策略
檢查是否有一些數(shù)據(jù)可以設(shè)置過(guò)期時(shí)間,對(duì)于臨時(shí)數(shù)據(jù)或者不經(jīng)常使用的數(shù)據(jù),設(shè)置合理的過(guò)期時(shí)間,讓 Redis 自動(dòng)清理這些數(shù)據(jù)。例如,使用 EXPIRE 或 PEXPIRE 命令設(shè)置鍵的過(guò)期時(shí)間。
優(yōu)化數(shù)據(jù)結(jié)構(gòu),避免存儲(chǔ)不必要的大字符串等占用大量?jī)?nèi)存的數(shù)據(jù)結(jié)構(gòu)。
三、啟用內(nèi)存淘汰策略
選擇合適的內(nèi)存淘汰策略,如 allkeys-lru(淘汰最近最少使用的鍵)、volatile-lru(淘汰已設(shè)置過(guò)期時(shí)間且最近最少使用的鍵)等??梢酝ㄟ^(guò) CONFIG SET maxmemory-policy 命令來(lái)設(shè)置淘汰策略。
四、數(shù)據(jù)持久化和清理
利用 Redis 的持久化機(jī)制(如 RDB 或 AOF),將數(shù)據(jù)定期持久化到磁盤上,這樣可以在內(nèi)存不足時(shí),從磁盤恢復(fù)數(shù)據(jù),釋放內(nèi)存空間。
根據(jù)業(yè)務(wù)需求,手動(dòng)清理一些不再需要的數(shù)據(jù),可以使用 DEL 命令刪除單個(gè)鍵。
五、擴(kuò)展 Redis 實(shí)例
如果條件允許,可以考慮為當(dāng)前機(jī)器增加內(nèi)存。
使用 Redis 集群或哨兵模式,將數(shù)據(jù)分片存儲(chǔ)到多個(gè) Redis 實(shí)例中,分散內(nèi)存壓力。
kafka
如何保證kafka消息順序 (包括業(yè)務(wù)內(nèi)有序和全局有序)
要保證 Kafka 消息的業(yè)務(wù)內(nèi)有序,需確保相關(guān)業(yè)務(wù)消息被發(fā)送至同一個(gè)分區(qū),這是因?yàn)橥环謪^(qū)內(nèi)的消息處理是有序的。
而實(shí)現(xiàn)全局有序,通常需嚴(yán)格限制并發(fā)度,僅使用一個(gè)分區(qū),但這會(huì)在一定程度上降低系統(tǒng)的性能和消息的吞吐量。
在實(shí)際應(yīng)用中,要綜合考慮業(yè)務(wù)需求、性能要求和資源配置等多方面因素來(lái)權(quán)衡消息順序和系統(tǒng)效率之間的關(guān)系。
kafka的可用性怎么保證的?
可以通過(guò)多種機(jī)制來(lái)保證高可用性,確保在出現(xiàn)故障時(shí)系統(tǒng)能夠繼續(xù)正常運(yùn)行:
- 多副本:
Kafka 的每個(gè) topic 可以劃分為多個(gè) partition,每個(gè) partition 可以有多個(gè)副本(replica)。這些副本分布在不同的 broker 上。
其中一個(gè)副本被選為 leader,負(fù)責(zé)處理所有的讀寫請(qǐng)求;其他副本是 follower,它們從 leader 復(fù)制數(shù)據(jù)。
如果 leader 副本所在的 broker 宕機(jī),Kafka 會(huì)自動(dòng)從 follower 中選舉一個(gè)新的 leader 繼續(xù)提供服務(wù)。
- ISR:
ISR 是一組與 leader 保持同步的副本集合。只有當(dāng) follower 副本的數(shù)據(jù)與 leader 一致時(shí),才會(huì)被加入 ISR。
Kafka 通過(guò)維護(hù) ISR 來(lái)確保數(shù)據(jù)的一致性和可靠性。如果某個(gè) follower 落后太多或無(wú)法與 leader 通信,它會(huì)被移出 ISR。
ACK 機(jī)制:
acks=0:生產(chǎn)者不等待任何確認(rèn)。
acks=1:生產(chǎn)者等待 leader 副本確認(rèn)。
acks=all:生產(chǎn)者等待所有 ISR 中的副本確認(rèn)。
生產(chǎn)者在發(fā)送消息時(shí)可以設(shè)置 acks 參數(shù)來(lái)控制消息的確認(rèn)級(jí)別:
設(shè)置 acks=all 可以確保消息被所有副本確認(rèn),從而提高數(shù)據(jù)的可靠性。
ZooKeeper 用于元數(shù)據(jù)管理:
Kafka 使用 ZooKeeper 來(lái)管理和協(xié)調(diào)集群中的 broker、topic 和 partition 的狀態(tài)。
ZooKeeper 監(jiān)控 broker 的狀態(tài),并在 broker 宕機(jī)時(shí)觸發(fā) leader 選舉和重新分配。
負(fù)載均衡:
Kafka 通過(guò)將 partition 分散到不同的 broker 上,實(shí)現(xiàn)負(fù)載均衡,避免單點(diǎn)壓力過(guò)大。
這種分散存儲(chǔ)的方式也提高了系統(tǒng)的整體吞吐量和可用性。
配置參數(shù):
通過(guò)調(diào)整 Kafka 的配置參數(shù),如 replication.factor(副本數(shù))、min.insync.replicas(最小同步副本數(shù))等,可以進(jìn)一步優(yōu)化高可用性。
kafka宕機(jī)后那些正在消費(fèi)中的消息該怎么處理?
會(huì)通過(guò)以下方式來(lái)處理那些正在被消費(fèi)的消息:
- 自動(dòng)切換到備份:
每個(gè) topic 的 partition 都有多個(gè)副本(復(fù)制的數(shù)據(jù)),其中一個(gè)副本是 leader,負(fù)責(zé)處理讀寫請(qǐng)求。其他副本是 follower,它們從 leader 復(fù)制數(shù)據(jù)。
如果 leader 所在的 broker 宕機(jī)了,Kafka 會(huì)自動(dòng)選擇一個(gè)健康的 follower 副本作為新的 leader。這個(gè)過(guò)程對(duì)消費(fèi)者是透明的,消費(fèi)者可以繼續(xù)從新的 leader 讀取消息。
- 消費(fèi)者重新分配:
當(dāng) broker 宕機(jī)后,消費(fèi)者的消費(fèi)組會(huì)進(jìn)行一次重新平衡(rebalance)。這意味著 Kafka 會(huì)重新分配 partition 給消費(fèi)者,確保每個(gè) partition 只有一個(gè)消費(fèi)者在讀取。
重新平衡后,消費(fèi)者可以從上次提交的位置繼續(xù)消費(fèi)消息。如果消費(fèi)者之前已經(jīng)提交了偏移量(offset),那么它可以從提交的位置開(kāi)始繼續(xù)消費(fèi),而不會(huì)丟失或重復(fù)消息。
偏移量管理:
消費(fèi)者可以配置為自動(dòng)提交偏移量,也就是每隔一段時(shí)間自動(dòng)告訴 Kafka 已經(jīng)消費(fèi)到哪個(gè)位置了。這樣即使消費(fèi)者宕機(jī),重啟后也可以從上次提交的位置繼續(xù)消費(fèi)。
如果需要更精確的控制,消費(fèi)者可以選擇手動(dòng)提交偏移量,在處理完一條消息后再提交偏移量,這樣可以避免消息丟失或重復(fù)。
客戶端自動(dòng)重連:
Kafka 客戶端(比如消費(fèi)者)通常會(huì)有自動(dòng)重連機(jī)制。如果連接斷開(kāi)了,客戶端會(huì)嘗試重新連接到 Kafka 集群。
客戶端和 broker 之間還有心跳檢測(cè)機(jī)制,如果發(fā)現(xiàn)連接中斷,客戶端會(huì)嘗試重新建立連接。
kafka重復(fù)消費(fèi)問(wèn)題怎么解決?
1. 冪等性處理
- 確保業(yè)務(wù)邏輯是冪等的:設(shè)計(jì)你的業(yè)務(wù)邏輯,使得多次處理同一條消息不會(huì)產(chǎn)生不同的結(jié)果。例如,如果消息涉及更新數(shù)據(jù)庫(kù)記錄,可以使用唯一鍵來(lái)防止重復(fù)插入。
2. 事務(wù)支持
- 使用 Kafka 事務(wù):Kafka 支持事務(wù),可以在同一個(gè)事務(wù)中同時(shí)處理消息和提交偏移量。這樣即使在處理過(guò)程中出現(xiàn)故障,也不會(huì)導(dǎo)致重復(fù)消費(fèi)。
生產(chǎn)者和消費(fèi)者都可以參與事務(wù),確保數(shù)據(jù)的一致性和完整性。
3. 手動(dòng)提交偏移量
- 關(guān)閉自動(dòng)提交:將 enable.auto.commit 設(shè)置為 false,然后在消息成功處理后再手動(dòng)提交偏移量。
同步提交:consumer.commitSync(),這種方式會(huì)阻塞直到提交完成。
異步提交:consumer.commitAsync(),這種方式是非阻塞的,但需要處理提交失敗的情況。
4. 去重機(jī)制
- 使用外部存儲(chǔ)去重:利用外部存儲(chǔ)(如 Redis、數(shù)據(jù)庫(kù))來(lái)記錄已經(jīng)處理過(guò)的消息 ID 或其他唯一標(biāo)識(shí)符。每次處理消息前,先檢查該消息是否已經(jīng)被處理過(guò)。
這種方法適用于對(duì)消息去重有嚴(yán)格要求的場(chǎng)景,但會(huì)增加額外的復(fù)雜性和開(kāi)銷。
5. 布隆過(guò)濾器
- 使用布隆過(guò)濾器:對(duì)于大量數(shù)據(jù),可以使用布隆過(guò)濾器來(lái)高效地檢測(cè)消息是否已經(jīng)被處理過(guò)。布隆過(guò)濾器是一種空間效率很高的概率型數(shù)據(jù)結(jié)構(gòu),適合用于大規(guī)模數(shù)據(jù)的去重。