在學習傳智專案庫中的秒殺專案后,我總結出了以下的面試內容
專案庫地址:傳智研究院專案庫(秒殺系統)
-----------以下所有代碼和圖只是為了方便理解,最主要的還是可以用自己的話表達出來---------------------
1.簡單描述秒殺的核心流程(技術)

我們把架構分為了三個部分
第一部分:對商品的請求頁進行了處理,我們使用,靜態化+Nginx+CDN來優化前端頁面的高QPS,具體就是我們使用canal監聽資料庫的變化,當商品變為秒殺商品的時候,我們會生成靜態詳情頁,用戶請求要獲取詳情頁的時候,我們通過nginx 找到對應的靜態頁,并且這里我們考慮到頁面中的所有靜態資源(js、css、圖片等),放在自己的服務器上,會占用自己服務器的網路資源,增加Nginx的壓力,所以我們采用用CDN對靜態資源進行加速,
第二部分:對熱點資料實時分析:這里包括熱點資料的發現和熱點資料的隔離,熱點資料的發現就是用戶獲取詳情頁之前 我們先用lua腳本采集用戶的訪問日志發送給kafka,然后通過apache-druid 訂閱kafka的資料 ;通過我們自己寫的實時熱點分析系統定時查詢apache-druid ,查到資料后對熱點商品進行隔離和鎖定,將商品資訊快取在redis中,
第三部分 : 首先訪問商品詳情頁,在進入頁面之前我們進行了限流和令牌識別,通過之后 才能進行秒殺下單,我們讀取redis快取里的資料 ,能查到就是熱點商品 否則就是非熱點資料;非熱點商品我們就直接下單;熱點商品的話我們排隊下單,將訊息發給kafka訊息佇列,訂單系統訂閱kafka的訊息,生成訂單,并通過WebSocket將訂單狀態推送給用戶,
2.可以介紹下你的秒殺業務的流程么
http://admin-seckill-java.itheima.net/#/users/index

3.如果你進入我們公司,打算如何快速熟悉專案呢?(適當答出幾個)
1.通讀需求檔案,了解專案用途
2.熟悉開發工具
3.準備環境,把專案跑起來
4.整體瀏覽代碼,了解代碼結構
5.抽取部分功能代碼進行細讀
6.嘗試修改一些程式bug
4.如何優化mysql批量匯入(適當答出幾個)
1.一次插入多條資料
2.不使用主鍵,索引,外鍵
3.合理進行事務提交
4.多執行緒批量插入
5.mysql服務器引數優化
5.現在有一個資料遷移的需求,你應該怎么做呢?或者說遷移之前,你會考慮哪現點呢?
考慮的點:1.原有和現有的業務差異 2.原有和現有的資料差異
資料遷移的方式:簡單業務:使用資料遷移工具 復雜業務:實踐資料遷移代碼

6.前端頁面訪問如何進行性能優化

秒殺活動中,熱賣商品的詳情頁訪問頻率非常高,詳情頁的資料加載,我們可以采用直接從資料庫查詢加載,但這種方式會給資料庫帶來極大的壓力,甚至崩潰,這種方式我們并不推薦,
商品詳情頁主要有商品介紹、商品標題、商品圖片、商品價格、商品數量等,大部分資料幾乎不變,可能只有數量會變,因此我們可以考慮把商品詳情頁做成靜態頁,每次訪問只需要加載庫存數量,這樣就可以大大降低資料庫的壓力,
用戶訪問詳情頁,nginx中得到靜態html頁面,從商品微服務中得到動態資料,頁面的靜態資源從cdn(cdn 中存盤的是修改頻率低的靜態資源 如 js,css;用戶端根據這些資料最終形成詳情頁)中獲得
擴展:前端頁面高QPS優化方案:

1)方案1:直接查詢資料庫

查詢優化:1.使用索引(主要手段) 2.使用慢查詢定位較慢的sql,再使用explain進行分析
適用場景:1.用戶數量少 2.互動的資料量小
2)方案2:使用快取

流程:1.如果資料在redis存在,應用就可以直接從redis里拿資料,不用訪問資料庫
? 2.如果redis里面沒有,先到資料庫查詢,然后寫入到redis中,再回傳給應用
適用場景:1)查詢頻率高 2)并發比較高
缺點:資料雙寫不一致的問題
- 方案3:頁面靜態化+nginx

適用場景:1)性能優先級高 2)需要減輕后臺服務的壓力
缺點:可能需要大量硬碟
4)方案4:靜態化+Nginx+CDN

優點:提高企業站點的訪問速度
缺點:成本較高
7.如何保證mysql與redis的一致性問題?
一致性問題的定義:
因為這些資料是很少修改的,所以在絕大部分的情況下可以命中快取,但是,一旦被快取的資料發生變化的時候,我們既要操作資料庫的資料,也要操作 Redis 的資料,
所以問題來了,現在我們有兩種選擇:
1、先操作 Redis 的資料再操作資料庫的資料
2、先操作資料庫的資料再操作 Redis 的資料
到底選哪一種?
首先需要明確的是,不管選擇哪一種方案, 我們肯定是希望兩個操作要么都成功,要么一個都不成功,不然就會發生 Redis 跟資料庫的資料不一致的問題,
但是,Redis 的資料和資料庫的資料是不可能通過事務達到統一的,我們只能根據相應的場景和所需要付出的代價來采取一些措施降低資料不一致的問題出現的概率,在資料一致性和性能之間取得一個權衡,
對于資料庫的實時性一致性要求不是特別高的場合,比如 T+1 的報表,可以采用定時任務查詢資料庫資料同步到 Redis 的方案,
由于我們是以資料庫的資料為準的,所以給快取設定一個過期時間,是保證最終一致性的解決方案,
方案選擇:Redis洗掉還是更新?
這里我們先要補充一點,當存盤的資料發生變化,Redis 的資料也要更新的時候,我們有兩種方案,一種就是直接更新,呼叫 set;還有一種是直接洗掉快取,讓應用在下次 查詢的時候重新寫入,
這兩種方案怎么選擇呢?這里我們主要考慮更新快取的代價, 更新快取之前,是不是要經過其他表的查詢、介面呼叫、計算才能得到最新的資料, 而不是直接從資料庫拿到的值,如果是的話,建議直接洗掉快取,這種方案更加簡單, 而且避免了資料庫的資料和快取不一致的情況,在一般情況下,我們也推薦使用洗掉的方案,
這一點明確之后,現在我們就剩一個問題:
1、到底是先更新資料庫,再洗掉快取
2、還是先洗掉快取,再更新資料庫
1.先更新資料庫,再洗掉快取:
正常情況:
更新資料庫,成功,
洗掉快取,成功,
例外情況:
1、更新資料庫失敗,程式捕獲例外,不會走到下一步,所以資料不會出現不一致,
2、更新資料庫成功,洗掉快取失敗,資料庫是新資料,快取是舊資料,發生了不一致的情況,
這種問題怎么解決呢?我們可以提供一個重試的機制,
比如:如果洗掉快取失敗,我們捕獲這個例外,把需要洗掉的 key 發送到訊息佇列, 然后后自己創建一個消費者消費,嘗試再次洗掉這個 key,
這種方式有個缺點,會對業務代碼造成入侵,
所以我們又有了第二種方案(異步更新快取):
因為更新資料庫時會往 binlog 寫入日志,所以我們可以通過一個服務來監聽 binlog 的變化(比如阿里的 canal),然后在客戶端完成洗掉 key 的操作,如果洗掉失敗的話,再發送到訊息佇列,
總之,對于后洗掉快取失敗的情況,我們的做法是不斷地重試洗掉,直到成功, 無論是重試還是異步洗掉,都是最終一致性的思想,
2.先洗掉快取,再更新資料庫
正常情況:
洗掉快取,成功,
更新資料庫,成功,
例外情況:
1、洗掉快取,程式捕獲例外,不會走到下一步,所以資料不會出現不一致,
2、洗掉快取成功,更新資料庫失敗, 因為以資料庫的資料為準,所以不存在資料
不一致的情況,
看起來好像沒問題,但是如果有程式并發操作的情況下:
1)執行緒 A 需要更新資料,首先洗掉了 Redis 快取
2)執行緒 B 查詢資料,發現快取不存在,到資料庫查詢舊值,寫入 Redis,回傳
3)執行緒 A 更新了資料庫
這個時候,Redis 是舊的值,資料庫是新的值,發生了資料不一致的情況,
那問題就變成了:能不能讓對同一條資料的訪問串行化呢?代碼肯定保證不了,因為有多個執行緒,即使做了任務佇列也可能有多個服務實體,資料庫也保證不了,因為會有多個資料庫的連接,只有一個資料庫只提供一個連接的情況下,才能保證讀寫的操作是串行的,或者我們把所有的讀寫請求放到同一個記憶體佇列當中,但是這種情況吞吐量 太低了,
所以我們有一種延時雙刪的策略,在寫入資料之后,再洗掉一次快取,
A 執行緒:
1)洗掉快取
3)休眠 500ms(這個時間,依據讀取資料的耗時而定)
2)更新資料庫
4)再次洗掉快取
偽代碼:
public void write(String key,Object data){
redis.delKey(key);
db.updateData(data);
Thread.sleep(500);
redis.delKey(key);
}
8.如何進行MySQL查詢優化
使用索引(主要手段)
使用慢查詢定位較慢的sql,在使用EXPLAIN進行分析
優化資料庫結構(表拆分,使用中間表)
9.什么情況會造成索引失效
1.條件有or,部分條件沒有索引;
2.復合索引未用左列欄位;
3.like以%開頭;
4.需要型別轉換;
5.where中索引列有運算;
6.where中索引列使用了函式;
7.加索引的列,資料重復率較高;
10.秒殺活動到期時,秒殺商品何時更新
-
秒殺活動需要對秒殺商品下架,手動修改效率低,所以我們采用了定時任務
-
說明為什么動態添加定時任務呢?
因為所有秒殺商品都只是參與一段時間活動,活動時間過了需要將秒殺商品從索引中移除,同時洗掉靜態頁,如果采取靜態的定時任務不停的輪訓,比較耗費cpu的資源,所以采用動態定時任務
-
介紹Elastic-Job:基于quartz 二次開發的彈性分布式任務調度系統,功能豐富強大,采用zookeeper實作分布式協調,實作任務高可用以及分片,
-
當秒殺活動結束的時候,elasticjob會把當前商品的狀態修改,并且此次修改會記錄到binlog日志中,然后canal會監聽binlog的日志,從而同步ES和洗掉商品詳情頁,
11.專案中如何收集用戶訪問日志
-
說明當前業務背景:
首先我們在做秒殺的時候有一個前提,就是我們需要區分熱門商品和冷門商品,針對于熱門商品,我們會把具體的商品資訊放入到redis中,那怎么來定義熱門商品呢?就是通過收集用戶訪問該商品的日志,我們通過nginx+lua來收集用戶訪問商品的日志,并且通過kafka訊息佇列的方式,把用戶日志資料存盤到apachedruid中
-
使用Nginx+Lua收集日志的原因
參考12
12.為什么要使用Nginx+lua
-
說明Tomcat的性能
Tomcat的默認配置作為生產環境,尤其是記憶體和執行緒的配置,默認都很低,容易成為性能瓶頸.
-
說明Nginx的性能
nginx 能夠支支撐 5 萬并發鏈接, 并且 cpu、記憶體等資源消耗卻非常低,運行非常穩定
-
說明秒殺的業務問題(大流量、高并發)
大流量、高并發
-
總結
因為tomcat的承載能力不夠,很可能會被沖垮,就算沒有被沖垮,我們也需要很多很多的tomcat服務器來接收和處理這個請求,為了追求高性能,我們需要使用nginx和lua來進行業務的處理 ,來保證我們的性能,又省成本,又能撐住大量的請求
13.為什么要用Redis集群?資料如何存盤?
- 首先使用redis集群,可以保證我們系統的高可用,提升我們的程式性能
- 具體我們在存盤商品資料的時候,如果我們使用String型別,即使用單個k-v方式存放多條資料,會導致我們商品資訊存盤到Redis集群多個節點中,這樣就沒有辦法保證資料的原子性問題,
- 所以我們把多條資料合并為一條,通過hash的方式,一次性進行存盤到集群的結點中,
14.說下熱點資料的隔離流程

我們采用elastic-job每5秒鐘查詢一次被訪問的商品資訊,如果某一個商品最近一小時的訪問量超過1000,我們就認為是熱點資料,商品資料查出來之后,我們把商品先進行鎖定,然后再使用hash存盤的方式,把商品資料放入redis中,
15.隔離邏輯中同時對MySQL和Redis進行操作,如何保證減庫存資料一致性?

為啥要鎖定:防止我在把商品庫存查詢出來,在放入redis的之前,有用戶呼叫下單介面,遞減庫存,導致我redis中快取的庫存資料不一致,
隔離的實作:通過在資料庫中的秒殺商品表中設定一個islock欄位,初始值為1,在redis快取商品的資料之前,先鎖定商品的資料,把islock改為2 ,那這樣遞減庫存的sql即變為
update tb_sku set seckill_num=seckill_num-#{count} where id=#{id} and seckill_num>=#{count} and islock=1
從而保證資料的一致性
16.如何實作熱點/非熱點商品隔離下單?

首先用戶在點擊購買按鈕的時候,請求經過nginx這一層的時候,我們會通過lua腳本,進行用戶的登錄校驗以及判斷是否有庫存,然后判斷商品是否為熱點商品,如果是非熱點商品,則直接呼叫訂單系統進行下單操作,如果是熱點商品,則向Kafka生產訊息進行排隊下單,訂單系統會訂閱排隊下單資訊,這樣可以降低服務器所直接承受的搶單壓力,這種操作也叫佇列削峰,
17.什么是超賣?如何解決超賣問題?
參考:redis分布式鎖的檔案
超賣:一個商品被多個用戶搶到
1.首先我們減庫存分為非熱點商品減庫存和熱點商品減庫存,非熱點商品減庫存,我們在資料庫層面就可以保證超賣,我們超賣問題主要出現在熱點商品減庫存中,我們熱點商品減庫存的步驟是先去redis中查詢商品的庫存,再去做庫存遞減操作,之后再把遞減后的庫存寫入到redis中,
2.由于判斷庫存和遞減庫存是倆步操作,所以在多執行緒減庫存的時候,庫存的遞減操作就會發生執行緒不安全的問題,進而產生超賣現象
3.我們可以考慮使用單機鎖,比如使用Synchronized或ReentrantLock,但是這樣的鎖,在分布式部署的時候會失效,
4.綜合,我們采用分布式鎖來避免超賣的問題
18. 分布式鎖有哪些實作方式,如何選擇?
1.基于資料庫實作分布式鎖
2.基于快取(Redis等)實作分布式鎖;
3.基于Zookeeper實作分布式鎖
三種方案的比較:
從理解的難易程度角度(從低到高)
資料庫 > 快取 > Zookeeper
從實作的復雜性角度(從低到高)
Zookeeper > 快取 > 資料庫
從性能角度(從高到低)
快取 > Zookeeper >= 資料庫
從可靠性角度(從高到低)
Zookeeper > 快取 > 資料庫
而我們秒殺專案,對性能要求比較高,對于偶爾出現的可靠性問題,我們是可以接受的,所以我們選擇基于redis的分布式鎖
擴展:基于Zookeeper實作分布式鎖一般使用在金融場景比較多,
19.Redisson分布式鎖原理?
參考redis分布式鎖檔案來看

1)加鎖:將業務封裝在lua中發給redis,保障業務執行的原子性,并且redisson還提供了不停重試功能,不停地去加鎖
if (redis.call('exists', KEYS[1]) == 0) then
redis.call('hset', KEYS[1], ARGV[2], 1);
redis.call('pexpire', KEYS[1], ARGV[1]);
return nil;
end;
if (redis.call('hexists', KEYS[1], ARGV[2]) == 1) then
redis.call('hincrby', KEYS[1], ARGV[2], 1);
redis.call('pexpire', KEYS[1], ARGV[1]);
return nil;
end;
return redis.call('pttl', KEYS[1]);
2)解鎖:redisson和Synchronized,ReentrantLock一樣,都是可重入鎖, 執行lock.unlock(),每次都對myLock資料結構中的那個加鎖次數減1,如果發現加鎖次數是0了,說明這個客戶端已經不再持有鎖了,此時就會用:“del myLock”命令,從redis里洗掉這個key,另外的客戶端2就可以嘗試完成加鎖了,
if (redis.call('exists', KEYS[1]) == 0) then
redis.call('publish', KEYS[2], ARGV[1]);
return 1;
end;
if (redis.call('hexists', KEYS[1], ARGV[3]) == 0) then
return nil;
end;
local counter = redis.call('hincrby', KEYS[1], ARGV[3], -1);
if (counter > 0) then
redis.call('pexpire', KEYS[1], ARGV[2]);
return 0;
else redis.call('del', KEYS[1]);
redis.call('publish', KEYS[2], ARGV[1]);
return 1;
end;
return nil;
3)鎖的續期問題:redisson底層通過一個看門狗的策略,每隔一定時間掃瞄下,如果還持有鎖,延長鎖時間
4)缺點:Redisson存在一個問題,就是如果你對某個redis master實體,寫入了myLock這種鎖key的value,此時會異步復制給對應的master slave實體,但是這個程序中一旦發生redis master宕機,主備切換,redis slave變為了redis master,接著就會導致,客戶端2來嘗試加鎖的時候,在新的redis master上完成了加鎖,而客戶端1也以為自己成功加了鎖,此時就會導致多個客戶端對一個分布式鎖完成了加鎖,這時系統在業務上一定會出現問題,導致臟資料的產生,
20.分布式事務
1.為啥會存在分布式事務呢?簡單的說,就是一次大的操作由不同的小操作組成,這些小的操作分布在不同的服務器上,且屬于不同的應用,分布式事務需要保證這些小操作要么全部成功,要么全部失敗,本質上來說,分布式事務就是為了保證不同資料庫的資料一致性,
2.什么時候會產生分布式事務呢?1.多個Service(服務) 2.多個Resource(資料源)
3.CAP定理:
C (一致性):對于資料分布在不同節點上的資料來說,如果在某個節點更新了資料,那么在其他節點如果都能讀取到這個的資料,那么就稱為強一致,如果有某個節點沒有讀取到,那就是分布式不一致,
A (可用性):非故障的節點在合理的時間內回傳合理的回應(不是錯誤和超時的回應),
P (磁區容錯性):當出現網路磁區后,系統能夠繼續作業,打個比方,這里集群有多臺機器,有臺機器網路出現了問題,但是這個集群仍然可以正常作業,
在分布式系統中,網路無法 100% 可靠,磁區其實是一個必然現象,并且所以我們只能選擇CP或者AP
對于 CP 來說,放棄可用性,追求一致性和磁區容錯性,我們的 ZooKeeper 其實就是追求的強一致,
對于 AP 來說,放棄一致性(這里說的一致性是強一致性),追求磁區容錯性和可用性,這是很多分布式系統設計時的選擇,后面的 BASE 也是根據 AP 來擴展,
21.異步下單如何通知用戶訂單創建成功?
-
用戶搶單操作完成后,無論是非熱點商品還是熱點商品搶單,搶單完成后,我們應該要通知用戶搶單狀態,非熱點商品可以直接回應搶單結果,而熱點商品也應該有通知用戶這樣的機制,
一般常見的有倆種方式:
-
頁面定時向后臺發請求查詢訂單狀態,
-
使用基于長連接的WebSocket ,
-
-
第一種方式效率低,會和服務器進行多次通信,比如當前有一萬個用戶下單,可能每秒光查詢訂單狀態就有1萬次請求,所以這塊我們可以使用長連接websocket實作,
-
WebSocket 是一種基于長連接的通信方式,使得客戶端和服務器之間的資料交換變得更加簡單,允許服務端主動向客戶端推送資料
-
具體的實作

22. 為什么要使用Netty+WebSocket?
-
介紹WebSocket連接過多的問題
傳統的BIO編程:每個客戶端連接過來后,服務端都會啟動一個執行緒去處理該客戶端的請求,阻塞I/O的通信模型示意圖如下:

-
介紹NIO的多路復用效果:一個執行緒可以處理多個連接,并且NIO是非阻塞的,不需要一直等待操作完成才能干其他事情,而是在等待的程序中可以同時去做別的事情,所以能最大限度地使用服務器的資源,

- 介紹Netty
? Netty封裝了JDK的NIO,使用netty之后,你不用再寫一大堆復雜的代碼了,
23.服務器的配置
58.3萬
| 性能測驗環境 | |
|---|---|
| Jdk版本 | Jdk1.8 |
| 測驗工具 | Jmeter5.4.1 |
| Jmeter負載服務器主機 | 2臺32核64G |
| Jmeter負載服務器從機 | 10臺16核32G |
| 監控機 | 1臺8核16G |
23264/32 ==
| 服務器部署環境 | |
|---|---|
| Nginx服務器 | 4臺16核32G |
| Redis服務器 | 1臺8核16G |
| Kafka服務器 | 1臺8核16G |
| 熱點訂單服務器 | 9臺8核16G |
| Mysql存盤服務器 | 與熱點訂單服務器共用 |
| 公共微服務部署服務器 | 1臺4核8G |
1萬(壓測是1萬,實際結果3000左右)
| 性能測驗環境 | |
|---|---|
| Jdk版本 | Jdk1.8 |
| 測驗工具 | Jmeter5.4.1 |
| Jmeter負載服務器主機 | 1臺8核16G |
| 監控機 | 1臺2核4G |
| 服務器部署環境 | |
|---|---|
| Nginx服務器 | 2臺 2核4G(k8s容器化部署) |
| Redis服務器 | 3臺2核4G (一主二從)(k8s容器化部署) |
| Kafka服務器 | 1臺2核4G (看實際情況,由于防止資料丟失,可以不用容器部署,由于不用容器化部署,2核4G的服務器不好購買,可以使用阿里的kafka) |
| 熱點訂單服務器 | 2臺2核4G(k8s容器化部署) |
| Mysql存盤服務器 | 3臺2核4G(3主3從)AB BC AC |
| 公共微服務部署服務器 | 2臺4核8G (網關,用戶服務,商品服務等等)(考慮訂單用容器化部署,這里為了部署方便 ,也使用容器化的方式進行部署,每臺機器都部署一份,保證高可用) |
轉載請註明出處,本文鏈接:https://www.uj5u.com/ruanti/396141.html
標籤:其他
上一篇:nginx根據引數路由
下一篇:網路篇面試題
