最近啊,收到一個粉絲的投稿,我發現他在美團和餓了麼都去面試過。
這倆企業大家應該都經常用吧,咱點外賣的時候,我有時候就琢磨,到底他倆誰更厲害點。
今天咱們就瞅瞅,在面試這塊兒誰更難一些。
(目前都只有一面的情況,要是想要後續的,私聊我發給你哈)
美團
一面
- 自我介紹
- 專案做完了嗎?背景是什麼?專案初期的背景調研是怎麼做的?現在這個系統做到哪一步了?
- 使用者下單使用者派送的優劣瞭解過嗎?怎麼管理?
- 專案裡面遇到的最大的難題是什麼?為什麼?
- 超賣,重複下單怎麼解決的?
超賣問題:
- 實時庫存更新:確保庫存資料實時更新,避免銷售超出實際庫存量。
- 庫存預留:對於已經下單但尚未發貨的商品,進行庫存預留,避免其他訂單超賣。
- 庫存預警系統:設定庫存預警值,當庫存低於某個閾值時,自動暫停銷售。
- 多渠道同步:如果商品在多個平臺銷售,確保所有渠道的庫存資訊同步更新。
重複下單問題:
- 訂單確認機制:在使用者提交訂單後,透過郵件或簡訊確認訂單詳情,避免使用者誤操作重複下單。
- 訂單鎖定時間:設定一個訂單鎖定時間,在此時間內使用者不能重複下單。
- 使用者行為分析:分析使用者下單行為,識別異常模式,如短時間內多次下單同一商品,可以進行人工稽核或自動攔截。
- 技術手段:使用技術手段,如設定cookie或session,來識別和防止同一使用者在短時間內重複下單。
- 為什麼使用樂觀鎖?你瞭解樂觀鎖的使用場景和實現邏輯嗎?
樂觀鎖的使用場景:
樂觀鎖適用於併發衝突較少、讀操作遠多於寫操作的場景。例如:
- 電商平臺的商品庫存扣減,當併發量不是特別大且庫存更新衝突不頻繁時可以使用。
- 論壇或社交平臺的帖子點贊、評論數量的更新。
- 使用者資訊的更新,如修改個人資料等。
實現邏輯:
樂觀鎖通常基於版本號或時間戳來實現。以下是一個基於版本號的樂觀鎖實現示例:
假設有一張商品表 products
,包含欄位 id
(商品 ID)、stock
(庫存數量)和 version
(版本號)。
CREATE TABLE products (
id INT PRIMARY KEY,
stock INT,
version INT
);
在更新庫存時,先獲取當前的版本號,然後在更新操作中判斷版本號是否未發生變化。如果版本號未變,說明沒有其他併發操作修改了資料,更新成功;否則,更新失敗,需要重新獲取資料再次嘗試更新。
package main
import (
"errors"
"fmt"
)
type Product struct {
ID int
Stock int
Version int
}
// updateStock 更新商品庫存
func updateStock(productID int, quantity int) error {
// 先查詢當前商品的庫存和版本號
product, err := findProductById(productID)
if err!= nil {
return err
}
currentStock := product.Stock
currentVersion := product.Version
// 計算新的庫存
newStock := currentStock - quantity
// 嘗試更新庫存並檢查版本號是否未變
updated, err := updateStockAndVersion(newStock, currentVersion+1, productID, currentVersion)
if err!= nil {
return err
}
if!updated {
return errors.New("Concurrent update occurred. Please retry.")
}
fmt.Println("庫存更新成功")
return nil
}
// findProductById 根據 ID 查詢商品
func findProductById(productID int) (Product, error) {
// 這裡模擬查詢商品,實際可能從資料庫或其他資料來源獲取
for _, p := range products {
if p.ID == productID {
return p, nil
}
}
return Product{}, errors.New("Product not found")
}
// updateStockAndVersion 執行更新庫存和版本號的操作
func updateStockAndVersion(newStock int, newVersion int, productID int, oldVersion int) (bool, error) {
// 這裡模擬資料庫更新操作,實際可能執行 SQL 語句
for i, p := range products {
if p.ID == productID && p.Version == oldVersion {
products[i].Stock = newStock
products[i].Version = newVersion
return true, nil
}
}
return false, nil
}
var products = []Product{
{ID: 1, Stock: 10, Version: 1},
}
func main() {
err := updateStock(1, 5)
if err!= nil {
fmt.Println(err)
}
}
在上述示例中,updateStock
方法是在資料庫中執行的更新操作,對應的 SQL 語句類似於:
UPDATE products
SET stock =?, version =?
WHERE id =? AND version =?;
透過這種方式,實現了樂觀鎖的機制,保證了在併發環境下資料更新的準確性和一致性。
- 樂觀鎖怎麼實現的你瞭解嗎?
- 瞭解悲觀鎖嗎?
悲觀鎖是一種資料庫併發控制的機制,它基於一種悲觀的假設,即認為在資料處理過程中,併發操作很可能會導致衝突,因此在獲取資料時就對資料進行加鎖,以防止其他事務對資料進行修改,直到當前事務完成並釋放鎖。
例如,在關係型資料庫中,常見的悲觀鎖實現方式有 SELECT... FOR UPDATE
語句。當一個事務執行這樣的查詢時,它會鎖定被查詢的資料行,其他事務在該鎖被釋放之前無法修改這些行。
悲觀鎖的優點在於能夠有效地避免併發衝突,保證資料的一致性和準確性。但它也有一些缺點,比如可能導致較大的併發開銷,因為在獲取鎖和釋放鎖的過程中會消耗系統資源,並且可能會造成死鎖等問題。
舉個例子,假設有兩個事務同時嘗試更新同一個使用者的賬戶餘額。事務 A 先獲取了悲觀鎖,對餘額進行修改。在事務 A 未完成之前,事務 B 無法獲取鎖,只能等待事務 A 完成並釋放鎖後才能進行操作。這樣就避免了事務 B 在事務 A 未完成時對餘額進行不一致的修改。
- 最開始有沒有考慮樂觀鎖的適用場景和悲觀鎖的適用場景?
- 樂觀鎖會不會導致頻繁的衝突啊?這種情況下和悲觀鎖誰的效能更好一些呢?
- 樂觀鎖:在衝突較少的情況下,樂觀鎖的效能更好,因為它減少了鎖的開銷。但在高併發或寫操作較多的場景下,可能會頻繁發生衝突,導致更新失敗,從而影響效能。
- 悲觀鎖:在寫操作較多或資料一致性要求較高的場景下,悲觀鎖更可靠。但悲觀鎖會降低系統的併發效能,因為它需要在事務開始時就鎖定資料。
- 一開始為什麼沒考慮到呢?是調研不充分嗎?
- Redis怎麼解決超賣問題的?為什麼能解決這個問題?
主要透過以下幾種方式:
-
使用 Redis 作為庫存計數器:
- 將每個商品的庫存數量儲存在 Redis 中,每次商品被購買時,從 Redis 中減去相應的庫存數量。
- 透過 Redis 的原子操作(如
DECRBY
命令),可以確保庫存的增減是原子性的,從而避免併發問題。
-
設定庫存預警:
- 在 Redis 中設定一個庫存預警值。當庫存數量低於這個值時,觸發相應的操作,如暫停銷售或提醒管理員。
- 這可以透過 Redis 的釋出/訂閱功能實現,當庫存減少到預警值時,釋出一個訊息,訂閱者(如前端頁面或後臺服務)接收到訊息後進行相應的處理。
-
使用 Redis 鎖:
- 使用 Redis 的 SETNX 命令實現分散式鎖。當一個事務需要操作庫存時,先嚐試獲取鎖。如果獲取成功,則進行庫存操作;如果失敗,則等待或重試。
- 這種方式可以避免多個事務同時修改庫存,從而防止超賣。
-
使用 Lua 指令碼:
- 將庫存檢查和減少的操作封裝在一個 Lua 指令碼中,利用 Redis 的
EVAL
命令執行。Lua 指令碼在 Redis 伺服器上執行,保證了操作的原子性。 - 這種方式可以減少網路開銷,提高效能。
- 將庫存檢查和減少的操作封裝在一個 Lua 指令碼中,利用 Redis 的
-
使用事務:
- 雖然 Redis 的單條命令是原子的,但多個命令的組合操作可以透過事務(如
MULTI
和EXEC
命令)來保證原子性。 - 使用事務可以確保一系列操作要麼全部成功,要麼全部失敗,從而避免超賣。
- 雖然 Redis 的單條命令是原子的,但多個命令的組合操作可以透過事務(如
-
監控和日誌:
- 利用 Redis 的監控功能,記錄庫存操作的日誌。這有助於事後分析和排查問題。
- 可以結合 Redis 的慢查詢日誌,監控庫存操作的效能,及時發現並處理潛在的問題。
為什麼 Redis 能解決超賣問題?
- 高效能:Redis 操作速度快,響應時間短,適合高併發場景。
- 原子性:Redis 提供原子操作,如
INCR
、DECR
、SETNX
等,可以保證庫存的增減操作是原子性的。 - 分散式鎖:Redis 支援分散式鎖,可以避免多個程序或執行緒同時修改庫存。
- 可擴充套件性:Redis 可以水平擴充套件,支援大規模的資料儲存和高併發訪問。
- 易於整合:Redis 與許多程式語言和框架都有良好的整合,易於在現有系統中實現庫存管理。
透過這些機制,Redis 能夠有效地幫助解決超賣問題,確保庫存的準確性和一致性。
- 關於Redis的遞減特性你瞭解哪些?
Redis 的遞減操作指的是對儲存在 Redis 中的某個鍵對應的值進行減法操作。Redis 提供了decr
和decrby
命令來實現遞減功能。
decr
命令用於將鍵的值減 1。如果鍵不存在,那麼鍵的值會先被初始化為 0,然後再執行遞減操作。其基本語法如下:
DECR key_name
例如,對一個存在的鍵執行decr
操作:
SET failure_times 10
DECR failure_times
上述操作會將failure_times
鍵對應的值減 1,結果為 9。
decrby
命令用於將鍵的值減去給定的整數值。語法如下:
DECRBY key_name decrement
例如,要將鍵的值減去 5,可以使用:
DECRBY some_key 5
遞減操作常用於計數場景,比如統計網站的訪問量、文章的點贊數的減少等。例如在一個線上論壇中,每篇文章的點贊數可以透過 Redis 的遞減操作來實現取消點贊時點贊數的更新。
- 關於Redis的指令還用到過其他哪些呢?
- setnx的原理你知道嗎?
SETNX
是SET IF NOT EXISTS
的縮寫,意思是“如果不存在,則設定”。它的原理是在指定的鍵不存在時,為鍵設定指定的值。
在 Redis 中,當執行SETNX key value
命令時,如果鍵key
不存在,那麼 Redis 會將值value
設定到鍵key
中,並返回1
,表示設定成功;如果鍵key
已經存在,那麼不會進行任何操作,並返回0
,表示設定失敗。
這種特性使得SETNX
命令常用於實現分散式鎖或在特定條件下進行資料設定的場景。例如,在多個客戶端或執行緒競爭資源的情況下,可以使用SETNX
來確保只有一個客戶端能夠成功設定鍵值,從而獲得某種資源或執行特定操作的許可權。
為了防止獲得鎖的客戶端出現異常而導致鎖無法釋放,造成死鎖問題,通常還會結合設定鍵的過期時間來使用SETNX
。例如,可以使用SETNX key value PX milliseconds
命令,其中PX milliseconds
表示設定鍵的過期時間為指定的毫秒數。這樣,即使客戶端未能正常釋放鎖,當過期時間到達後,Redis 也會自動刪除該鍵,從而釋放鎖資源。
- 有個場景你瞭解嗎:鎖獲取後程式退出了,這樣鎖永遠不會釋放,導致死鎖
- 看你簡歷裡說比較瞭解集合,對集合的瞭解簡單說一下
集合(Set)是一種常見的資料結構。
集合的主要特點包括:
- 元素的唯一性:集合中不會存在重複的元素。
- 無序性:集合中的元素沒有特定的順序。
集合常用於以下場景:
- 去重操作:例如去除一個陣列或列表中的重複元素。
- 快速成員判斷:可以高效地判斷一個元素是否在集合中。
- 瞭解HashMap嗎?長度為什麼是2的冪?
- HashMap的擴容機制
- 為什麼載入因子是0.75
- HashMap什麼時候把連結串列轉換為紅黑樹?什麼時候紅黑樹變為連結串列?
- concurrentHashMap瞭解嗎?實際專案中用過嗎?
- 聊一聊Java的垃圾收集機制吧
- 怎樣判斷記憶體是否需要回收?(物件死亡的判斷方法?)
- 可達性分析是如何對物件進行標記的?三色標記法瞭解嗎?
- 垃圾回收的過程(針對某一個具體的垃圾收集器CMS或G1)
- 清除的過程呢?
- 標記清除演算法的優點和缺點?
- CMS標記階段比較長,如果產生了過多的浮動垃圾,有可能會出現回收趕不上分配的情況,從而導致GC失敗,這種要怎麼解決?
- JDK後續幾個版本的垃圾收集器?更新了什麼你瞭解嗎?
- CMS和G1的比對了解嗎?為什麼廢除了CMS?
- G1怎麼做到指定具體的垃圾清除時間的?
- 執行緒的生命週期你瞭解嗎?
- 每個狀態之間怎麼流轉的你瞭解嗎?
- 執行緒池瞭解哪些?
- 怎麼確定執行緒池引數知道嗎?
- 手撕演算法,相似題目力扣940不同的子序列Ⅱ
餓了麼
一面
- 自我介紹
- Java物件導向的三個特性,什麼是多型
- 雙親委派機制
- HashMap在JDK1.7以前有執行緒不安全,怎麼個情況
- 保證執行緒安全用什麼Map
- ConcurrentHashMap是怎樣的
- 執行緒池用過嗎?核心引數以及有啥作用
- 使用執行緒池有什麼好處
- 悲觀鎖和樂觀鎖是怎麼樣子的
- Java中怎麼實現悲觀鎖和樂觀鎖
- HTTP和HTTPS的區別,加密過程是怎樣的
-
HTTP 是超文字傳輸協議,資訊是明文傳輸,存在安全風險的問題。HTTPS 則解決 HTTP 不安全的缺陷,在 TCP 和 HTTP 網路層之間加入了 SSL/TLS 安全協議,使得報文能夠加密傳輸。
-
HTTP 連線建立相對簡單, TCP 三次握手之後便可進行 HTTP 的報文傳輸。而 HTTPS 在 TCP 三次握手之後,還需進行 SSL/TLS 的握手過程,才可進入加密報文傳輸。
-
兩者的預設埠不一樣,HTTP 預設埠號是 80,HTTPS 預設埠號是 443。
-
HTTPS 協議需要向 CA(證書權威機構)申請數字證書,來保證伺服器的身份是可信的。
加密過程
- 客戶端發起請求:客戶端向伺服器傳送連線請求,請求中包含支援的加密演算法等資訊。
- 伺服器響應:伺服器收到請求後,選擇一種雙方都支援的加密演算法,並返回數字證書給客戶端。
- 客戶端驗證證書:客戶端驗證伺服器證書的合法性,包括證書的頒發機構、有效期、域名等。如果證書驗證透過,客戶端會生成一個隨機的對稱金鑰。
- 金鑰交換:客戶端使用伺服器的公鑰對對稱金鑰進行加密,併傳送給伺服器。
- 伺服器解密:伺服器使用自己的私鑰解密得到對稱金鑰。
- 資料傳輸:此後,雙方使用對稱金鑰對傳輸的資料進行加密和解密,完成安全的資料通訊。
- TCP的粘包是怎樣的現象
TCP 粘包現象:
TCP 是一種面向連線的、可靠的、基於位元組流的傳輸層通訊協議。在 TCP 傳輸資料時,可能會出現“粘包”的現象。
“粘包”指的是接收方收到的資料包並不是按照傳送方傳送的順序和邊界來接收的。例如,傳送方先後傳送了兩個資料包 Packet1
和 Packet2
,但接收方可能一次性接收到了這兩個資料包連在一起的資料,而無法明確區分它們之間的邊界。
造成 TCP 粘包的主要原因有以下幾點:
- 應用程式寫入的資料小於 TCP 傳送緩衝區的大小,TCP 將多次寫入的資料一次性傳送出去,導致粘包。
- 接收方的應用程式讀取資料的速度較慢,而傳送方傳送資料的速度較快,這會導致接收方緩衝區中積累多個資料包,當接收方讀取時,可能會一次性讀出多個資料包,形成粘包。
為了解決 TCP 粘包問題,可以在應用層採取一些措施,比如:
- 明確資料包的邊界:可以在資料包中新增特定的分隔符,接收方根據分隔符來區分不同的資料包。
- 固定資料包的長度:傳送方和接收方約定每個資料包的固定長度,接收方按照固定長度來讀取資料包。
- 在資料包頭部新增長度欄位:傳送方在資料包頭部指明資料包的長度,接收方根據長度來讀取完整的資料包。
例如,一個即時通訊應用中,傳送方傳送多條訊息,如果不處理粘包問題,接收方可能會將多條訊息混在一起,導致顯示混亂。透過上述解決方法,可以確保每條訊息都能被正確地接收和處理。
- Cookie和session的區別
Cookie 和 Session 的區別:
-
儲存位置:
- Cookie 資料儲存在客戶端(瀏覽器)。
- Session 資料儲存在伺服器端。
-
安全性:
- Cookie 儲存在客戶端,容易被篡改或竊取,安全性相對較低。
- Session 資料儲存在伺服器,安全性較高。
-
儲存容量:
- Cookie 有大小限制,通常為 4KB 左右。
- Session 儲存容量通常沒有嚴格的限制,取決於伺服器的配置。
-
有效期:
- Cookie 可以設定較長的有效期,並且在有效期內會自動傳送給伺服器。
- Session 通常依賴於會話,在一段時間內沒有活動後會過期,或者可以透過程式手動設定有效期。
-
效能影響:
- 大量使用 Cookie 會增加客戶端和伺服器之間的通訊資料量。
- Session 儲存在伺服器端,如果儲存的 Session 資料過多,可能會消耗伺服器的記憶體資源。
-
跨域支援:
- Cookie 可以在不同的子域之間共享(透過設定 domain 屬性)。
- Session 一般不能在不同的應用程式或不同的伺服器之間共享。
例如,在一個購物網站中,使用者將商品新增到購物車,購物車的資訊可以儲存在 Cookie 中,以便使用者在下次訪問時仍然能看到之前新增的商品。但如果涉及到更敏感的使用者身份驗證資訊,通常會使用 Session 來儲存。
再比如,一個多伺服器的應用,如果使用 Session 來儲存使用者狀態,可能需要配置共享的 Session 儲存(如 Redis 儲存),以確保使用者在不同伺服器上的請求能夠獲取到正確的 Session 資料;而如果使用 Cookie ,則相對更容易處理,但要注意保護 Cookie 中的敏感資訊。
- 使用者登入之後怎麼找到對應的Session的呢
通常會透過以下幾種方式找到對應的 Session :
- 基於 Cookie :伺服器在使用者登入成功後,會生成一個唯一的 Session ID ,並將其儲存在 Cookie 中傳送給客戶端。後續客戶端每次向伺服器傳送請求時,都會自動攜帶這個 Cookie 。伺服器透過解析 Cookie 中的 Session ID ,就能夠找到對應的 Session 資料。
例如,伺服器設定的 Cookie 可能類似於:JSESSIONID=123456789
,其中 123456789
就是 Session ID 。
-
URL 重寫 :如果客戶端瀏覽器不支援 Cookie ,或者出於某些安全原因不能使用 Cookie ,可以透過在 URL 中新增 Session ID 來實現。例如:
http://example.com/page?SESSIONID=123456789
。 -
隱藏表單欄位 :在登入成功後的頁面中,可以包含一個隱藏的表單欄位,其中儲存了 Session ID 。當表單提交時,伺服器可以獲取到這個 Session ID 來找到對應的 Session 。
- 兩個專案哪個有挑戰一點,挑一個講一講
- 平時做專案或科研會遇到什麼困難,遇到困難一般自己解決嗎
- 最近有了解一些新的技術或者看一些技術書籍之類的嗎
- 演算法題:反轉連結串列
- 其他的offer
- 之後有往上海發展的打算嗎
- 反問
歡迎關注 ❤
我的文章都首發在同名公眾號:王中陽
需要簡歷最佳化或者就業輔導,可以直接加我微信:wangzhongyang1993,備註:部落格園