springboot 秒殺系統(三)高併發服務間調用

分佈式系統的思想就是:

如果一個系統的壓力過大,可以把一個服務拆分成多個服務,這個叫垂直拆分。

也可以考慮做鏡像集羣,負載平衡,這個叫水平拆分。

這個系統我們可以考慮垂直拆分,將訂單相關的功能拆分出來。

我們將訂單的邏輯拿出來,放到order-service中,通過backend來調用order-service

來創建訂單。

服務:backend   接受客戶端請求,判斷userid是否訂購過(redis判斷userid存在即訂購過),判斷是否有庫存。

          order-service  提供接口,供backend調用,創建訂單,減庫存。

有個問題:當我有1000個併發,同時提交到backend,並且通過了判斷用戶,判斷庫存,

這個時候就會有1000個網絡請求到order-service,網絡消耗很大,很多併發量大的項目都有這類問題

這個時候,我們可以考慮,是否可以在order-service弄個批量創建訂單的接口。

在消費端backend,可以做個隊列,存儲需要調用的數據,

每10MS去把隊列的數據讀出來組成一個批量訂單數據,調用order-service的批量接口,

將返回的數據,對應的之前提交的每個request的task-future,這樣就可以減少服務與服務之間的交互。\

其實很多架構和中間件都有這種方案,有個設置 batchsize或者是limittime等,都是設置當你隊列大小爲多少個爲一批

或者是多少時間段內一批,都是爲了減少建立連接消耗和網絡消耗

思路大概是這樣,代碼傳至git上。快速地址,核心代碼如下:

@Component
@Slf4j
public class OrderService {
    /**
     * 請求包類
     */
    class Request{
        String seckillId;
        String userid;
        //每個請求一個線程觀察者
        CompletableFuture<String> future;
    }
    //存請求的隊列
    LinkedBlockingDeque<Request> queue = new LinkedBlockingDeque<>();

    public String createOrder(String seckillId,String userid) throws ExecutionException, InterruptedException {
        Request request = new Request();
        request.seckillId=seckillId;
        request.userid=userid;

        CompletableFuture<String> future = new CompletableFuture<>();
        request.future=future;
        //加入到請求隊列
        queue.add(request);
        //返回等待,
        return future.get();
    }

    @Autowired
    private RemoteOrderService remoteOrderService;
    /**
     * bean初始化的時候,啓動一個線程
     * 每10MS,把隊列裏的請求批量請求
     */
    @PostConstruct
    public void init(){
        ScheduledExecutorService scheduledExecutorService=
                Executors.newScheduledThreadPool(1);
        scheduledExecutorService.scheduleAtFixedRate(new Runnable() {
            @Override
            public void run() {
                int size = queue.size();
                if(size ==0){
                    return;
                }
                //隊列裏面存在數據
                JSONArray jsonArray = new JSONArray();
                ArrayList<Request> requests = new ArrayList<>();
                JSONObject jsonObject;
                Request request;
                //取出隊列,組成批量數據
                for(int i=0;i<size;i++){
                    jsonObject=new JSONObject();
                    request = queue.poll();
                    requests.add(request);
                    jsonObject.put("seckillId",request.seckillId);
                    jsonObject.put("userid",request.userid);
                    jsonArray.add(jsonObject);
                }
                log.info("批量提交的大小:"+jsonArray.size());
                //調用遠程創建訂單的批量接口
                JSONObject result = remoteOrderService.createOrder(jsonArray);

                //正確返回
                if(result.getString("returnCode").equals("100")){
                    for (Request request1 : requests) {
                        JSONObject jsonObject1 = result.getJSONObject("returnData");
                        //通過userid唯一取出結果並將請求的future完成觸發之前的請求等待
                        String result1 = jsonObject1.getString(request1.userid);
                        request1.future.complete(result1);
                    }
                }
            }
        },0,10,TimeUnit.MICROSECONDS);//定時任務每10毫秒調用一次
    }
}

結果:我們可以看到,本來需要服務間調用500次,現在通過隊列批量只調用了6次,大大減少了網絡消耗。

我感覺這波操作很6,在大併發的情況下,大大的減少了通信消耗。是不是?

其他的判斷用戶在redis是否購買過,減庫存等代碼

//判斷之前userid是否購買
        if(redisTemplate.opsForValue().getAndSet(userid,1)!=null){
            return "0";
        }
        redisTemplate.opsForValue().set(userid,"1",1000L*60,TimeUnit.MICROSECONDS);
        /**
         * 從redis獲取庫存數,因爲redis的單線程,所以也不會出現超賣現象
         */
        long num = redisTemplate.opsForValue().decrement(seckillId);
        if(num<0){
            //庫存已售完
            return "0";
        }

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章