【高並發】秒殺系統高並發請求排隊處理


今天無意中看見了這位兄弟的文章 通過請求隊列的方式來緩解高並發搶購(初探)  但文章最后說並發超過500 就會出現超發,看了下代碼,的確有這個問題

抽空簡單完善了下,經壓力測試后發現暫無超發現象, 下面為我的代碼,有不足之處請指正交流:

 

1.請求參數封裝,有個隨機的用戶ID 用來區分不同用戶的請求:

import java.util.Random;

public class OrderRequest {
    private int goodId = new Random().nextInt(100000);// 商品id
    private int userId = new Random().nextInt(100000);// 用戶ID
    private int status;// 0:未處理;1:正常;2:異常

    public int getGoodId() {
        return goodId;
    }

    public void setGoodId(int goodId) {
        this.goodId = goodId;
    }

    public int getUserId() {
        return userId;
    }

    public void setUserId(int userId) {
        this.userId = userId;
    }

    public int getStatus() {
        return status;
    }

    public void setStatus(int status) {
        this.status = status;
    }

}

2.controller 入口:

import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.locks.ReentrantLock;

import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class QueueController {

    private static ConcurrentLinkedQueue<OrderRequest> orderQueue = new ConcurrentLinkedQueue<OrderRequest>();

    private static AtomicInteger totalprodocut;
    private static AtomicInteger totalQueSize;
    private ExecutorService excutorService = Executors.newCachedThreadPool();
    public static ReentrantLock queueLock = new ReentrantLock(true);

    static {
        totalprodocut = new AtomicInteger(15);
        totalQueSize = new AtomicInteger(15);
    }

    @RequestMapping("/queque")
    public String queque(OrderRequest orderRequest) {
        try {
            // queueLock.lock();
            if (totalprodocut.get() < 1) {
                return "error";
            } else {
                if (orderQueue.size() < totalQueSize.get()) {
                    // System.out.println(orderRequest.getGood_id()+" 增加到待處理隊列成功:" + orderQueue.size());
                    orderQueue.add(orderRequest);
                } else {
                    return "error";
                }
            }
            if (!OrderDealThread.dealLock.isLocked()) {
                OrderDealThread dealQueue = new OrderDealThread(orderQueue);
                excutorService.execute(dealQueue);
            }
        } catch (Exception e) {
            e.printStackTrace();
            return "not done";
        } finally {
            // queueLock.unlock();
        }
        return "ok";
    }
}

說明: 如果這里放開lock,可以保證只有允許的請求進入到請求隊列中去,但是效率會降低很多,畢竟每個請求都要去上鎖開鎖

    如果這里不要鎖,進入請求隊列的請求會超過我們設定的個數,但不會多太多;

    其實這里應該不用鎖,應該快速的響應大多數不能進入請求隊列用戶的請求,已經進入請求隊列的請求在后續處理的時候還會進行業務判斷的

3.業務處理線程

import java.util.Iterator;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.locks.ReentrantLock;

public class OrderDealThread implements Runnable {

    ConcurrentLinkedQueue<OrderRequest> orderQueue;

    private static AtomicInteger totalprodocut;

    public static ReentrantLock dealLock = new ReentrantLock(true);

    static {
        totalprodocut = new AtomicInteger(15);
    }

    public OrderDealThread() {
    }

    public OrderDealThread(ConcurrentLinkedQueue<OrderRequest> queque) {
        this.orderQueue = queque;
    }

    @Override
    public void run() {
        while (!orderQueue.isEmpty()) {
            try {
                dealLock.lock();
                Iterator<OrderRequest> it = orderQueue.iterator();
                while (it.hasNext()) {
                    dealQueque(it.next());
                }
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                dealLock.unlock();
            }
        }
    }

    void dealQueque(OrderRequest orderRequest) {
        if (orderRequest.getStatus() == 0) {
            int status = 2;
            if (totalprodocut.get() > 0) {//需再次判斷是否還有商品,加鎖范圍內
                //TODO 下單處理其他邏輯
                totalprodocut.decrementAndGet();// 減庫存
                status =1;
            }

            if (status == 2) {
//                System.out.println(orderRequest.getUserId() + " deal er:" + Thread.currentThread().getName());
                orderRequest.setStatus(2);
            } else {
                System.out.println(orderRequest.getUserId() + " deal ok:" + Thread.currentThread().getName());
                orderRequest.setStatus(1);
            }
        }
    }
}

說明:在真正處理業務的時候還要判斷是否有庫存等邏輯

 

上述代碼中,部分內容,比如產品數目等應該在活動開始前同步到redis等能快速獲取的中間件中去

下面是我的測試結果:有個疑問? 我設置的線程組是1秒內啟動6000個,為啥這里顯示6萬個?  疑問解除,原來我線程組里面循環了10次 沒注意到那個參數

經過多輪測試,暫未發現多發現象, OK項目始終只有15個

 

歡迎指正

 

由於是在windows下測試,並發高了就報錯  java.net.BindException: Address already in use  這個初看上去很像端口被占用,其實是因為已經完成請求的 socket 端口 還處於 TIME_WAIT狀態,把端口耗盡了

處理辦法 參考 https://blog.csdn.net/ywb201314/article/details/51258777

 

=============增加=====================

有同學給我發郵件提了一個問題:

問:你代碼里面請求進入了請求隊列就返回了成功,但是你寫了請求隊列中請求數會超過預期值,那我怎么做下一步的操作呢?比如進行付款?

 

答:很好的問題!  感謝你的提問

說下處理邏輯:1.進入了請求隊列,就有可能被請求到,而且這里是異步,就是說請求收到ok了,但后台邏輯完全可能還沒處理

        所以,在接收到OK后,前端應該發起一個類似倒計時頁面,如下:

        

 

        提示系統正常處理中,同時隔一定時間去后台確認是否處理完成以及狀態

        當獲取到的狀態為完成且成功時,跳轉到下一步如付款操作界面,現在很多秒殺系統都是這么處理的

 

我的博客即將搬運同步至騰訊雲+社區,邀請大家一同入駐:https://cloud.tencent.com/developer/support-plan?invite_code=bunl3uhohufn


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM