秒殺項目用於處理高並發情況,我們采取發放令牌機制,根據用戶的token、商品id、活動商品id和一串uuid產生一個令牌存入redis中
同時引入了秒殺大閘,目的是流量控制,比如當前活動商品只有100件,我們就發放500個令牌,秒殺前會先發放令牌,令牌發放完則把后來的用戶擋在這一層之外,控制了流量
獲取令牌后會對比redis中用戶產生的令牌,對比成功才可以購買商品
首先,獲取活動商品詳情時就要根據庫存設置大閘數量,我們設置大閘為庫存的5倍
在發放令牌時就要根據大閘數量判斷要不要發放
這樣一來就減輕了巨大訪問流量帶來的壓力
但是盡管如此,我們可以試想,如果一個網站做秒殺活動,有10個商品秒殺,每個有1000件,那么就要發放50000個令牌,這樣的數量仍然很大,所以我們采取了隊列泄洪來解決此問題
什么是隊列化泄洪呢,就好比很多人同時進一個門,門很小,人很多,就可以排成隊,假如一次能進20個人,就20個人一波,一波一波的進
同理,隊列化泄洪就是把訪問的用戶分成批次,來減輕同時巨大的訪問量造成的壓力,一般分為令牌桶算法和漏桶算法,我們一般用令牌桶算法,那么怎么實現呢?
隊列化泄洪——ExecutorService, 通過這個可以從隊列中每次獲取一定數據量的請求進行處理,處理的速度取決於下游的窗口的處理速度。
@Autowired
private ExecutorService executorService;
@PostConstruct
public void init(){
executorService = Executors.newFixedThreadPool(20);
}
//封裝下單請求
@RequestMapping(value = "/createorder",method = {RequestMethod.POST},consumes={CONTENT_TYPE_FORMED})
@ResponseBody
public CommonReturnType createOrder(@RequestParam(name="itemId")Integer itemId,
@RequestParam(name="amount")Integer amount,
@RequestParam(name="promoId",required = false)Integer promoId,
@RequestParam(name="promoToken",required = false)String promoToken) throws BusinessException {
-------------省略驗證登錄、令牌等信息
//同步調用線程池的submit方法
//擁塞窗口為20的等待隊列,用來隊列化泄洪
Future<Object> future = executorService.submit(new Callable<Object>() {
@Override
public Object call() throws Exception {
//加入庫存流水init狀態
String stockLogId = itemService.initStockLog(itemId,amount);
//再去完成對應的下單事務型消息機制
if(!mqProducer.transactionAsyncReduceStock(userModel.getId(),itemId,promoId,amount,stockLogId)){
throw new BusinessException(EmBusinessError.UNKNOWN_ERROR,"下單失敗");
}
return null;
}
});
try {
future.get();
} catch (InterruptedException e) {
throw new BusinessException(EmBusinessError.UNKNOWN_ERROR);
} catch (ExecutionException e) {
throw new BusinessException(EmBusinessError.UNKNOWN_ERROR);
}
return CommonReturnType.create(null);
}
為了防止服務器承受不確定的洪峰流量,可以采取限流的策略-- 單機限流,負載均衡還不錯的情況下,效果還是不錯的,每次訪問一個接口,先判斷當前的訪問量是否達到設定值,如果達到了直接回絕。-- Guava Limiter, 設定的訪問值根據我們壓測的結果設定的--300qps
集群限流:依賴redis或者其他的中間件技術做統一的計數器,往往會產生性能瓶頸
單機限流:負載均衡的前提下,單機限流的效果要好
我們使用guava limiter 也就是單機限流,限制單個接口的流量
private RateLimiter orderCreateRateLimiter;
@PostConstruct
public void init(){
executorService = Executors.newFixedThreadPool(20);
orderCreateRateLimiter = RateLimiter.create(300);
}
在下單之前校驗
if(!orderCreateRateLimiter.tryAcquire()){
throw new BusinessException(EmBusinessError.RATELIMIT);
}