最近看到不少同學都在面試蝦皮,有同學反饋 「蝦皮約面是要搶的」!
如果你收到了約面通知,第一時間一定要選好你方便的時間,如果超過 10 分鐘沒有處理,就可能被約滿了,就錯過了約面的機會了,看來蝦皮面試的人真多啊。
蝦皮的薪資開的還是挺高的,有校招同學跟我反饋給他開到了 30k+,由於太高,超過他的預期,導至猶豫要不要去,這真是幸福的選擇,換我來選擇好不好?
那麽蝦皮的面試難度如何?
今天就給大家拆解 蝦皮後端面經 ,跟大廠流程一樣,技術八股+計畫+演算法,這次的面經考察後端元件的原理比較多,對語言的八股考察較少。
這次面經考察的知識,我給大家羅列一下:
網路:HTTPS和HTTP、限流演算法
Java:執行緒池
Redis:記憶體淘汰演算法、過期刪除策略
MySQL:隔離級別、索引結構、MVCC、SQL最佳化、redolog和 binlog日誌
RocektMQ:分布式事務、訊息有序、訊息積壓
演算法:輪轉陣列
下面從中選擇部份題目給大家講解。
網路
限流了解麽?
限流是當高並行或者瞬時高並行時,為了保證系統的穩定性、可用性,對超出服務處理能力之外的請求進行攔截,對存取服務的流量進行限制。
常見的限流演算法有四種:固定視窗限流演算法、滑動視窗限流演算法、漏桶限流演算法和令牌桶限流演算法。
固定視窗限流演算法 實作簡單,容易理解,但是流量曲線可能不夠平滑,有「突刺現象」,在視窗切換時可能會產生兩倍於閾值流量的請求。
滑動視窗限流演算法 是對固定視窗限流演算法的改進,有效解決了視窗切換時可能會產生兩倍於閾值流量請求的問題。
漏桶限流演算法能夠對流量起到整流的作用,讓隨機不穩定的流量以固定的速率流出,但是不能解決 流量突發 的問題。
令牌桶演算法 作為漏鬥演算法的一種改進,除了能夠起到平滑流量的作用,還允許一定程度的流量突發。
固定視窗限流演算法
固定視窗限流演算法就是對一段固定時間視窗內的請求進行計數,如果請求數超過了閾值,則舍棄該請求;如果沒有達到設定的閾值,則接受該請求,且計數加1。當時間視窗結束時,重設計數器為0。
固定視窗限流優點是實作簡單,但是會有「流量吐刺」的問題,假設視窗大小為1s,限流大小為100,然後恰好在某個視窗的第999ms來了100個請求,視窗前期沒有請求,所以這100個請求都會透過。再恰好,下一個視窗的第1ms有來了100個請求,也全部透過了,那也就是在2ms之內透過了200個請求,而我們設定的閾值是100,透過的請求達到了閾值的兩倍,這樣可能會給系統造成巨大的負載壓力。滑動視窗限流演算法
改進固定視窗缺陷的方法是采用滑動視窗限流演算法,滑動視窗就是將限流視窗內部切分成一些更小的時間片,然後在時間軸上滑動,每次滑動,滑過一個小時間片,就形成一個新的限流視窗,即滑動視窗。然後在這個滑動視窗內執行固定視窗演算法即可。滑動視窗可以避免固定視窗出現的放過兩倍請求的問題,因為一個短時間內出現的所有請求必然在一個滑動視窗內,所以一定會被滑動視窗限流。
漏桶限流演算法
漏桶限流演算法是模擬水流過一個有漏洞的桶進而限流的思路,如圖。 水龍頭的水先流入漏桶,再透過漏桶底部的孔流出。如果流入的水量太大,底部的孔來不及流出,就會導致水桶太滿溢位去。從系統的角度來看,我們不知道什麽時候會有請求來,也不知道請求會以多大的速率來,這就給系統的安全性埋下了隱患。但是如果加了一層漏鬥演算法限流之後,就能夠保證請求以恒定的速率流出。在系統看來,請求永遠是以平滑的傳輸速率過來,從而起到了保護系統的作用。使用漏桶限流演算法,缺點有兩個:
即使系統資源很空閑,多個請求同時到達時,漏桶也是慢慢地一個接一個地去處理請求,這其實並不符合人們的期望,因為這樣就是在浪費計算資源。
不能解決流量突發的問題,假設漏鬥速率是2個/秒,然後突然來了10個請求,受限於漏鬥的容量,只有5個請求被接受,另外5個被拒絕。你可能會說,漏鬥速率是2個/秒,然後瞬間接受了5個請求,這不就解決了流量突發的問題嗎?不,這5個請求只是被接受了,但是沒有馬上被處理,處理的速度仍然是我們設定的2個/秒,所以沒有解決流量突發的問題
令牌桶限流演算法
令牌桶是另一種桶限流演算法,模擬一個特定大小的桶,然後向桶中以特定的速度放入令牌(token),請求到達後,必須從桶中取出一個令牌才能繼續處理。如果桶中已經沒有令牌了,那麽當前請求就被限流。如果桶中的令牌放滿了,令牌桶也會溢位。放令牌的動作是持續不斷進行的,如果桶中令牌數達到上限,則丟棄令牌,因此桶中可能一直持有大量的可用令牌。此時請求進來可以直接拿到令牌執行。比如設定 qps 為 100,那麽限流器初始化完成 1 秒後,桶中就已經有 100 個令牌了,如果此前還沒有請求過來,這時突然來了 100 個請求,該限流器可以抵擋瞬時的 100 個請求。由此可見,只有桶中沒有令牌時,請求才會進行等待,最終表現的效果即為以一定的速率執行。令牌桶的示意圖如下: 令牌桶限流演算法綜合效果比較好,能在最大程度利用系統資源處理請求的基礎上,實作限流的目標,建議通常場景中優先使用該演算法。
Java
為什麽要用執行緒池?
執行緒池是為了減少頻繁的建立執行緒和銷毀執行緒帶來的效能損耗。執行緒池分為核心執行緒池,執行緒池的最大容量,還有等待任務的佇列,送出一個任務,如果核心執行緒沒有滿,就建立一個執行緒,如果滿了,就是會加入等待佇列,如果等待佇列滿了,就會增加執行緒,如果達到最大執行緒數量,如果都達到最大執行緒數量,就會按照一些丟棄的策略進行處理。
執行緒池的建構函式有7個參數:
corePoolSize :執行緒池核心執行緒數量。預設情況下,執行緒池中執行緒的數量如果 <= corePoolSize,那麽即使這些執行緒處於空閑狀態,那也不會被銷毀。
maximumPoolSize :執行緒池中最多可容納的執行緒數量。當一個新任務交給執行緒池,如果此時執行緒池中有空閑的執行緒,就會直接執行,如果沒有空閑的執行緒且當前執行緒池的執行緒數量小於corePoolSize,就會建立新的執行緒來執行任務,否則就會將該任務加入到阻塞佇列中,如果阻塞佇列滿了,就會建立一個新執行緒,從阻塞佇列頭部取出一個任務來執行,並將新任務加入到阻塞佇列末尾。如果當前執行緒池中執行緒的數量等於maximumPoolSize,就不會建立新執行緒,就會去執行拒絕策略。
keepAliveTime :當執行緒池中執行緒的數量大於corePoolSize,並且某個執行緒的空閑時間超過了keepAliveTime,那麽這個執行緒就會被銷毀。
unit :就是keepAliveTime時間的單位。
workQueue :工作佇列。當沒有空閑的執行緒執行新任務時,該任務就會被放入工作佇列中,等待執行。
threadFactory :執行緒工廠。可以用來給執行緒取名字等等
handler :拒絕策略。當一個新任務交給執行緒池,如果此時執行緒池中有空閑的執行緒,就會直接執行,如果沒有空閑的執行緒,就會將該任務加入到阻塞佇列中,如果阻塞佇列滿了,就會建立一個新執行緒,從阻塞佇列頭部取出一個任務來執行,並將新任務加入到阻塞佇列末尾。如果當前執行緒池中執行緒的數量等於maximumPoolSize,就不會建立新執行緒,就會去執行拒絕策略。
有哪些執行緒池?
ScheduledThreadPool:可以設定定期的執行任務,它支持定時或周期性執行任務,比如每隔 10 秒鐘執行一次任務,我透過這個實作類設定定期執行任務的策略。
FixedThreadPool:它的核心執行緒數和最大執行緒數是一樣的,所以可以把它看作是固定執行緒數的執行緒池,它的特點是執行緒池中的執行緒數除了初始階段需要從 0 開始增加外,之後的執行緒數量就是固定的,就算任務數超過執行緒數,執行緒池也不會再建立更多的執行緒來處理任務,而是會把超出執行緒處理能力的任務放到任務佇列中進行等待。而且就算任務佇列滿了,到了本該繼續增加執行緒數的時候,由於它的最大執行緒數和核心執行緒數是一樣的,所以也無法再增加新的執行緒了。
CachedThreadPool:可以稱作可緩存執行緒池,它的特點在於執行緒數是幾乎可以無限增加的(實際最大可以達到 Integer.MAX_VALUE,為 2^31-1,這個數非常大,所以基本不可能達到),而當執行緒閑置時還可以對執行緒進行回收。也就是說該執行緒池的執行緒數量不是固定不變的,當然它也有一個用於儲存送出任務的佇列,但這個佇列是 SynchronousQueue,佇列的容量為0,實際不儲存任何任務,它只負責對任務進行中轉和傳遞,所以效率比較高。
SingleThreadExecutor:它會使用唯一的執行緒去執行任務,原理和 FixedThreadPool 是一樣的,只不過這裏執行緒只有一個,如果執行緒在執行任務的過程中發生異常,執行緒池也會重新建立一個執行緒來執行後續的任務。這種執行緒池由於只有一個執行緒,所以非常適合用於所有任務都需要按被送出的順序依次執行的場景,而前幾種執行緒池不一定能夠保障任務的執行順序等於被送出的順序,因為它們是多執行緒並列執行的。
SingleThreadScheduledExecutor:它實際和 ScheduledThreadPool 執行緒池非常相似,它只是 ScheduledThreadPool 的一個特例,內部只有一個執行緒。
Redis
Redis記憶體淘汰策略有哪些?
在配置檔 redis.conf 中,可以透過參數 maxmemory
在 64 位作業系統中,maxmemory 的預設值是 0,表示沒有記憶體大小限制,那麽不管使用者存放多少數據到 Redis 中,Redis 也不會對可用記憶體進行檢查,直到 Redis 例項因記憶體不足而崩潰也無作為。
在 32 位作業系統中,maxmemory 的預設值是 3G,因為 32 位的機器最大只支持 4GB 的記憶體,而系統本身就需要一定的記憶體資源來支持執行,所以 32 位作業系統限制最大 3 GB 的可用記憶體是非常合理的,這樣可以避免因為記憶體不足而導致 Redis 例項崩潰。
Redis 記憶體淘汰策略共有八種,這八種策略大體分為「不進行數據淘汰」和「進行數據淘汰」兩類策略。
1、不進行數據淘汰的策略
noeviction (Redis3.0之後,預設的記憶體淘汰策略) :它表示當執行記憶體超過最大設定記憶體時,不淘汰任何數據,這時如果有新的數據寫入,會報錯通知禁止寫入,不淘汰任何數據,但是如果沒用數據寫入的話,只是單純的查詢或者刪除操作的話,還是可以正常工作。
2、進行數據淘汰的策略
針對「進行數據淘汰」這一類策略,又可以細分為「在設定了過期時間的數據中進行淘汰」和「在所有數據範圍內進行淘汰」這兩類策略。在設定了過期時間的數據中進行淘汰:
volatile-random :隨機淘汰設定了過期時間的任意鍵值;
volatile-ttl :優先淘汰更早過期的鍵值。
volatile-lru (Redis3.0 之前,預設的記憶體淘汰策略):淘汰所有設定了過期時間的鍵值中,最久未使用的鍵值;
volatile-lfu (Redis 4.0 後新增的記憶體淘汰策略):淘汰所有設定了過期時間的鍵值中,最少使用的鍵值;
在所有數據範圍內進行淘汰:
allkeys-random :隨機淘汰任意鍵值;
allkeys-lru :淘汰整個鍵值中最久未使用的鍵值;
allkeys-lfu (Redis 4.0 後新增的記憶體淘汰策略):淘汰整個鍵值中最少使用的鍵值。
Redis過期刪除策略是什麽? Redis 選擇「惰性刪除+定期刪除」這兩種策略配和使用 ,以求在合理使用 CPU 時間和避免記憶體浪費之間取得平衡。Redis 是怎麽實作惰性刪除的?
Redis 的惰性刪除策略由 db.c 檔中的 expireIfNeeded 函式實作,程式碼如下:
int expireIfNeeded(redisDb *db, robj *key) {
// 判斷 key 是否過期
if (!keyIsExpired(db,key)) return 0;
....
/* 刪除過期鍵 */
....
// 如果 server.lazyfree_lazy_expire 為 1 表示異步刪除,反之同步刪除;
return server.lazyfree_lazy_expire ? dbAsyncDelete(db,key) :
dbSyncDelete(db,key);
}
Redis 在存取或者修改 key 之前,都會呼叫 expireIfNeeded 函式對其進行檢查,檢查 key 是否過期:
如果過期,則刪除該 key,至於選擇異步刪除,還是選擇同步刪除,根據 lazyfree_lazy_expire 參數配置決定(Redis 4.0版本開始提供參數),然後返回 null 客戶端;
如果沒有過期,不做任何處理,然後返回正常的鍵值對給客戶端;
惰性刪除的流程圖如下:
Redis 是怎麽實作定期刪除的?
再回憶一下,定期刪除策略的做法: 每隔一段時間「隨機」從資料庫中取出一定數量的 key 進行檢查,並刪除其中的過期key。
1、這個間隔檢查的時間是多長呢?
在 Redis 中,預設每秒進行 10 次過期檢查一次資料庫,此配置可透過 Redis 的配置檔 redis.conf 進行配置,配置鍵為 hz 它的預設值是 hz 10。特別強調下,每次檢查資料庫並不是遍歷過期字典中的所有 key,而是從資料庫中隨機抽取一定數量的 key 進行過期檢查。
2、隨機抽查的數量是多少呢?
我查了下源碼,定期刪除的實作在 expire.c 檔下的 activeExpireCycle 函式中,其中隨機抽查的數量由 ACTIVE_EXPIRE_CYCLE_LOOKUPS_PER_LOOP 定義的,它是寫死在程式碼中的,數值是 20。也就是說,資料庫每輪抽查時,會隨機選擇 20 個 key 判斷是否過期。接下來,詳細說說 Redis 的定期刪除的流程:
從過期字典中隨機抽取 20 個 key;
檢查這 20 個 key 是否過期,並刪除已過期的 key;
如果本輪檢查的已過期 key 的數量,超過 5 個(20/4),也就是「已過期 key 的數量」占比「隨機抽取 key 的數量」大於 25%,則繼續重復步驟 1;如果已過期的 key 比例小於 25%,則停止繼續刪除過期 key,然後等待下一輪再檢查。
可以看到,定期刪除是一個迴圈的流程。那 Redis 為了保證定期刪除不會出現迴圈過度,導致執行緒卡死現象,為此增加了定期刪除迴圈流程的時間上限,預設不會超過 25ms。針對定期刪除的流程,我寫了個虛擬碼:
do {
//已過期的數量
expired = 0;
//隨機抽取的數量
num = 20;
while (num--) {
//1. 從過期字典中隨機抽取 1 個 key
//2. 判斷該 key 是否過期,如果已過期則進行刪除,同時對 expired++
}
// 超過時間限制則結束
if (timelimit_exit) return;
/* 如果本輪檢查的已過期 key 的數量,超過 25%,則繼續隨機抽查,否則結束本輪檢查 */
} while (expired > 20/4);
定期刪除的流程如下:
MySQL
MySQL隔離級別有哪些?
讀未送出 ,指一個事務還沒送出時,它做的變更就能被其他事務看到;
讀送出 ,指一個事務送出之後,它做的變更才能被其他事務看到;
可重復讀 ,指一個事務執行過程中看到的數據,一直跟這個事務啟動時看到的數據是一致的, MySQL InnoDB 引擎的預設隔離級別 ;
序列化 ;會對記錄加上讀寫鎖,在多個事務對這條記錄進行讀寫操作時,如果發生了讀寫沖突的時候,後存取的事務必須等前一個事務執行完成,才能繼續執行;
按隔離水平高低排序如下: 針對不同的隔離級別,並行事務時可能發生的現象也會不同。
慢查詢怎麽解決?
透過 explain 執行結果,檢視 sql 是否走索引,如果不走索引,考慮增加索引。
可以透過建立聯合索引,實作覆蓋索引最佳化,減少回表,使用聯合索引符合最左匹配原則,不然會索引失效
避免索引失效,比如不要用左模糊匹配、函式計算、運算式計算等等。
聯表查詢最好要以小表驅動大表,並且被驅動表的欄位要有索引,當然最好透過冗余欄位的設計,避免聯表查詢。
針對 limit n,y 深分頁的查詢最佳化,可以把Limit查詢轉換成某個位置的查詢:select * from tb_sku where id>20000 limit 10,該方案適用於主鍵自增的表,
將欄位多的表分解成多個表,有些欄位使用頻率高,有些低,數據量大時,會由於使用頻率低的存在而變慢,可以考慮分開
訊息佇列
RocektMQ怎麽處理分布式事務?
RocketMQ是一種最終一致性的分布式事務 ,就是說它保證的是訊息最終一致性,而不是像2PC、3PC、TCC那樣強一致分布式事務
假設 A 給 B 轉 100塊錢 ,同時它們不是同一個服務上,現在目標是就是 A 減100塊錢, B 加100塊錢。實際情況可能有四種:
1)就是A帳戶減100 (成功),B帳戶加100 (成功)
2)就是A帳戶減100(失敗),B帳戶加100 (失敗)
3)就是A帳戶減100(成功),B帳戶加100 (失敗)
4)就是A帳戶減100 (失敗),B帳戶加100 (成功)
這裏 第1和第2 種情況是能夠保證事務的一致性的,但是 第3和第4 是無法保證事務的一致性的。那我們來看下RocketMQ是如何來保證事務的一致性的。
分布式事務的流程如上圖:1、A服務先發送個Half Message( 是指暫不能被Consumer消費的訊息 。Producer 已經把訊息成功發送到了Broker 端,但此訊息被標記為暫不能投遞狀態,處於該種狀態下的訊息稱為半訊息。需要 Producer對訊息的二次確認後,Consumer才能去消費它_)給Brock端,訊息中攜帶 B服務 即將要+100元的資訊。
2、當A服務知道Half Message發送成功後,那麽開始第3步執行本地事務。
3、執行本地事務(會有三種情況1、執行成功。2、執行失敗。3、網路等原因導致沒有響應)
4.1)、如果本地事務成功,那麽Product像Brock伺服器發送Commit,這樣B服務就可以消費該message。
4.2)、如果本地事務失敗,那麽Product像Brock伺服器發送Rollback,那麽就會直接刪除上面這條半訊息。
4.3)、如果因為網路等原因遲遲沒有返回失敗還是成功,那麽會執行RocketMQ的回呼介面,來進行事務的回查。
從上面流程可以得知 只有A服務本地事務執行成功 ,B服務才能消費該message。
那麽 A帳戶減100 (成功),B帳戶加100 (失敗),這時候B服務失敗怎麽辦?
如果B最終執行失敗,幾乎可以斷定就是程式碼有問題所以才引起的異常,因為消費端RocketMQ有重試機制,如果不是程式碼問題一般重試幾次就能成功。如果是程式碼的原因引起多次重試失敗後,也沒有關系,將該異常記錄下來,由人工處理,人工兜底處理後,就可以讓事務達到最終的一致性。
導致訊息積壓突然增加,最粗粒度的原因,只有兩種:要麽是發送變快了,要麽是消費變慢了。
要解決積壓的問題,可以透過 擴容消費端的例項數來提升總體的消費能力 。
如果短時間內沒有足夠的伺服器資源進行擴容,沒辦法的辦法是,將
系統降級
,透過關閉一些不重要的業務,減少發送方發送的數據量,最低限度讓系統還能正常運轉,服務一些重要業務
。
👇🏻 點選下方閱讀原文,獲取魚皮往期編程幹貨。
往期推薦