場景題:如何實(shí)現(xiàn)億級用戶在線狀態(tài)統(tǒng)計?
近兩年不知道大家有沒有發(fā)現(xiàn),現(xiàn)在的面試中《場景題》問的越來越多了,一方面是就業(yè)市場競爭者較多所帶來的必然結(jié)果;另一方面是隨著時間的推移,公司對于應(yīng)聘者的技術(shù)要求也越來越高了,這時候只會八股文就不夠了,你還得會更難的場景題才行。
所以,今天我們就來盤 Java 中的常見面試題《如何實(shí)現(xiàn)億級用戶在線狀態(tài)統(tǒng)計?》,這個時候有人就會說了:“億級?你確定你們公司有億級用戶同時在線的場景?”“我會億級系統(tǒng)的設(shè)計還會來你們公司應(yīng)聘嗎?可笑”。
哈哈哈,確實(shí)如此,這些質(zhì)疑都是合理的。但是話說回來,面試的難度本來就比實(shí)際工作的難度大很多;其次,你來應(yīng)聘是想拿到高薪的 Offer,而不是和面試官干仗來的,對吧?所以,搞明白這道題的答案才是我們關(guān)注的重點(diǎn)。
1.億級用戶在線場景分析
例如,QQ 在線狀態(tài)的統(tǒng)計功能就是億級的,它的特征是:數(shù)據(jù)量大、內(nèi)存占用高、實(shí)時性要求高,因此我們使用常規(guī)的解決方案是不能實(shí)現(xiàn)的。例如,在數(shù)據(jù)庫中給每個用戶中添加一個在線狀態(tài),上線設(shè)為 1,下線設(shè)為 0,通過統(tǒng)計狀態(tài)為 1 的數(shù)據(jù),獲取在線人數(shù)。該方案無法承受大規(guī)模用戶頻繁上、下線操作,會給數(shù)據(jù)庫帶來巨大 IO 壓力,且實(shí)時統(tǒng)計需不斷刷新查詢,易拖垮數(shù)據(jù)庫性能,因此不可取。
2.解決方案
此時,我們的統(tǒng)計實(shí)現(xiàn)可分為以下兩類:
- 基于總數(shù)的統(tǒng)計方案:設(shè)置一個總在線人數(shù),上線 +1、下線 -1,從而實(shí)現(xiàn)上線總?cè)藬?shù)的統(tǒng)計。
- 優(yōu)點(diǎn):實(shí)現(xiàn)簡單、效率高、內(nèi)存占用少。
- 缺點(diǎn):不精準(zhǔn),沒辦法精確的查找某些用戶某個時刻的在線狀態(tài);且在異常退出應(yīng)用的情況下,后續(xù)基于在線監(jiān)測機(jī)制的重復(fù)下線判斷很難實(shí)現(xiàn)。
- 基于具體用戶詳情的統(tǒng)計方案:將用戶的標(biāo)識(如 QQ 號)和上線狀態(tài)都存儲在集合中。
- 優(yōu)點(diǎn):統(tǒng)計精準(zhǔn),可以查找某些用戶某個時刻的在線狀態(tài);且在異常退出應(yīng)用的情況下,后續(xù)基于在線監(jiān)測機(jī)制可以精準(zhǔn)的實(shí)現(xiàn)下線用戶的去重功能。
- 缺點(diǎn):內(nèi)存占用大、效率較低。
3.具體實(shí)現(xiàn)
3.1 基于總數(shù)的統(tǒng)計方案
基于總數(shù)的統(tǒng)計,我們可以使用以下兩種方式:
- 基于 Redis 的 incr(+1)和 decr(-1)操作實(shí)現(xiàn),如下圖所示:
圖片
- 基于 Redis 的 HyperLogLog 實(shí)現(xiàn),HyperLogLog (下文簡稱為 HLL) 是 Redis 2.8.9 版本添加的數(shù)據(jù)結(jié)構(gòu),它用于高性能的基數(shù) (去重) 統(tǒng)計功能,它的缺點(diǎn)就是存在極低的誤差率(0.81%)。它只需要 12KB 空間就能統(tǒng)計 2^64(約 18 億)的數(shù)據(jù)。
圖片
此實(shí)現(xiàn)方案不能移除元素、存在誤差,但空間占用率非常低。
3.2 基于用戶的統(tǒng)計實(shí)現(xiàn)
基于用戶標(biāo)識(QQ)我們可以采用 Redis 中提供的 Bitmap(位數(shù)組)來實(shí)現(xiàn),位數(shù)組結(jié)構(gòu)如下:
圖片
其中每個下標(biāo)就可以表示一個具體的數(shù)字,例如以上圖片標(biāo)識 1、3 數(shù)字存在,如果值為 0 表示不存在,這樣的話 10 億數(shù)字占用的位數(shù)組空間位 10 億 bit,也就是 1000000000/1024/1024/1024/8=0.116 ****GB,可以看出它的空間占用量是非常小的。
用戶上線時使用 SetBit 命令將對應(yīng)位置設(shè)為 1 表示在線,下線時設(shè)為 0 。判斷用戶是否在線用 GetBit 命令,統(tǒng)計在線用戶數(shù)用 BigCount 命令,具體操作命令如下圖所示:
圖片
在 Spring Boot 項(xiàng)目中,我們可以使用 RedisTemplate 實(shí)現(xiàn)用戶的上、下線設(shè)置,以及在線個數(shù)統(tǒng)計,具體實(shí)現(xiàn)代碼如下:
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
@Service
public class BitmapService {
@Autowired
private RedisTemplate<String, Object> redisTemplate;
/**
* 設(shè)置Bitmap中的位
* @param key 鍵
* @param offset 偏移量
* @param value 值(0或1)
*/
public void setBit(String key, long offset, boolean value) {
redisTemplate.opsForValue().setBit(key, offset, value);
}
/**
* 獲取Bitmap中的位
* @param key 鍵
* @param offset 偏移量
* @return 位的值(0或1)
*/
public boolean getBit(String key, long offset) {
return redisTemplate.opsForValue().getBit(key, offset);
}
/**
* 計算Bitmap中值為1的位的數(shù)量
* @param key 鍵
* @return 值為1的位的數(shù)量
*/
public Long bitCount(String key) {
return redisTemplate.opsForValue().bitCount(key);
}
}