1. 程式人生 > >秒殺系統的架構設計及實現

秒殺系統的架構設計及實現

秒殺場景在電商平臺是十分常見的,這種營銷活動往往具有時間短,併發量大的特點。

關於資料庫效能

TPS:資料庫每秒執行的事務數。
QPS:資料庫每秒執行的SQL數。
對於msql資料庫,8核CPU16G記憶體通常TPS:1000 QPS:20000

系統邏輯梳理

使用者介面點選請求 ---->伺服器收到http請求 ------>修改資料庫庫存

對於秒殺系統這種短時間的海量請求往往是通過兩種思路解決

  1. 分流,將使用者操作分散到多CPU處理,例如多執行緒,負載均衡,叢集
  2. 限流,對使用者操作進行攔截,緩衝,丟棄處理,例如前端後端都限制使用者請求頻率,快取,令牌桶演算法

多層次分析架構

使用者操作維度:前端頁面限制使用者請求頻率,防止頁面重複點選

伺服器架設:機器叢集,獲得更高的的處理能力
負載均衡器nginx
在這裡插入圖片描述
使用者請求先到nginx,再由nginx轉發請求到tomcatnginx監聽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.令牌桶演算法 限流

詳情程式碼見秒殺系統