秒殺系統的架構設計及實現
阿新 • • 發佈:2018-12-01
秒殺場景在電商平臺是十分常見的,這種營銷活動往往具有時間短,併發量大的特點。
關於資料庫效能
TPS:資料庫每秒執行的事務數。
QPS:資料庫每秒執行的SQL數。
對於msql資料庫,8核CPU16G記憶體通常TPS:1000 QPS:20000
系統邏輯梳理
使用者介面點選請求 ---->伺服器收到http請求 ------>修改資料庫庫存
對於秒殺系統這種短時間的海量請求往往是通過兩種思路解決
- 分流,將使用者操作分散到多
CPU
處理,例如多執行緒,負載均衡,叢集 - 限流,對使用者操作進行攔截,緩衝,丟棄處理,例如前端後端都限制使用者請求頻率,快取,令牌桶演算法
多層次分析架構
使用者操作維度:前端頁面限制使用者請求頻率,防止頁面重複點選
伺服器架設:機器叢集,獲得更高的的處理能力
負載均衡器nginx
使用者請求先到nginx
,再由nginx
轉發請求到tomcat
,nginx
監聽80
埠隨機轉發到tomcat
叢集下的機器。配置nginx.conf
檔案如下
#user nobody; #error_log logs/error.log; #error_log logs/error.log notice; #error_log logs/error.log info; #pid logs/nginx.pid; events { #併發連線數 worker_connections 1024; } http { include mime.types; default_type application/octet-stream; sendfile on; keepalive_timeout 65; #tomcat叢集 upstream localhost{ #這裡指定多個源伺服器,ip:埠,80埠的話可寫可不寫 server 127.0.0.1:8080; server 127.0.0.1:8081; } server { #監聽埠 listen 80; server_name localhost; location / { #啟動代理 proxy_pass http://localhost; } } }
Java程式編碼:對於秒殺系統很大的請求量很可能是由指令碼發起的頻繁請求,所以可以在後臺編碼中對請求頻率進行限制。
1.使用redis儲存請求記錄,並設定過期時間,當用戶請求時,如果redis已經存在則代表短時間請求過,不再讓使用者請求,直接返回,如果沒有則同意使用者進行請求。這裡setnx+setex要使用組合命令相當於一條命令,用兩條命令redis就效率減半了。
/** * 如果redis內沒有這個<key,value>則插入,否則不插入,這是一個命令 * 如果是setnx + setex 設定值再設定時間是兩條命令。 * @param key * @param value * @param expire 有效時間 單位 秒 * @return */ public Boolean setIfAbsent(String key,String value,long expire){ Boolean isSuccess = redisTemplate.execute(new RedisCallback<Boolean>() { @Override public Boolean doInRedis(RedisConnection redisConnection) throws DataAccessException { Boolean result = redisConnection.set(key.getBytes(),value.getBytes(),Expiration.seconds(expire),RedisStringCommands.SetOption.SET_IF_ABSENT); return result; } }); return isSuccess; }
controller限制請求
//redis 實現請求頻率限制
boolean result = redisUtil.setIfAbsent(userId,goodsId,5L);
//沒有插入成功,請求頻率太快
if(!result){
System.out.println("您的頻率太快了,請稍後再試");
return "error";
}
2.通過上面的頻率限制後,真實請求量還是十分龐大,下面使用令牌桶演算法進一步限流。
令牌桶:先買票再上車!通過預先(非同步)初始化一個和商品數相當數量的令牌池放在記憶體中,使用者先那令牌再去資料庫操作。
/**
* 初始化一個令牌桶
* @param num
*/
public void loadTokens(int num){
//相當一個Queue 從左邊壓入 右邊取出
for(int i=0;i<num;i++){
redisTemplate.opsForList().leftPush("token_list",String.valueOf(i));
}
System.out.println("令牌桶初始化完畢");
}
/**
* 取一個令牌
* @return
*/
public String getToken(){
String token = (String) redisTemplate.opsForList().rightPop("token_list");
return token;
}
在servlet初始化時載入令牌桶
@PostConstruct
public void init(){
//實際應通過任務排程 非同步載入令牌桶,
redisUtil.loadTokens(100);
}
@ResponseBody
@RequestMapping(value = "/miaosha",method = RequestMethod.POST)
public String miaosha(String goodsId,String userId){
//redis 實現請求頻率限制
boolean result = redisUtil.setIfAbsent(userId,goodsId,5L);
//沒有插入成功,請求頻率太快
if(!result){
System.out.println("您的頻率太快了,請稍後再試");
return "error";
}
//令牌桶演算法
//先取得令牌
String token = redisUtil.getToken();
if(token == null || "".equals(token)){
//沒有搶到令牌,秒殺失敗
System.out.println(userId+"沒有搶到令牌,秒殺失敗");
return "error";
}
//秒殺邏輯 消耗資源
if(orderService.miaoSha(goodsId,userId)){
return "ok";
}
return "error";
}
總結
根據分流限流的思想對海量請求進行處理
1.前端禁止重複請求 限流
2.負載均衡伺服器叢集 分流
3.後端禁止快頻率請求 限流
4.令牌桶演算法 限流
詳情程式碼見秒殺系統