1. 程式人生 > >synchronized 控制併發(活動秒殺)

synchronized 控制併發(活動秒殺)

1.首先我們新建一個Controller用於秒殺:

package com.imooc.Controller;

import com.imooc.service.impl.SeckillServiceImpl;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

/**
 * Created by zhongliahi on 2018/6/11.
 * 秒殺測試
 */
@RestController
@RequestMapping(value = "/skill")
@Slf4j
public class SeckillController {

    @Autowired
    private SeckillServiceImpl seckillService;


    //@PathVariable 可以將 URL 中佔位符引數繫結到控制器處理方法的入參中
    // URL 中的 {xxx} 佔位符可以通過@PathVariable(“xxx“) 繫結到操作方法的入參中。
    @GetMapping(value = "/query/{productId}")
    public String query(@PathVariable String productId) throws Exception{
        return seckillService.querySeckillProductInfo(productId);
    }


    @GetMapping("/order/{productId}")
    public String skill(@PathVariable String productId) throws Exception{
        log.info("秒殺----productId:"+productId);
        seckillService.orderProductMockDiffUser(productId);

        return seckillService.querySeckillProductInfo(productId);
    }
}

 2.建立一個Service

package com.imooc.service;

/**
 * Created by zhongliahi on 2018/6/11.
 */
public interface SeckillService {

    String queryMap(String productId);


    String querySeckillProductInfo(String productId);


    void orderProductMockDiffUser(String productId);
}

 3.實現Service

package com.imooc.service.impl;

import com.imooc.Exception.SellException;
import com.imooc.enums.ExceptionEnum;
import com.imooc.service.SeckillService;
import com.imooc.util.KeyUtils;
import org.springframework.stereotype.Service;

import java.util.HashMap;
import java.util.Map;

/**
 * Created by zhonglihai on 2018/6/11.
 * 秒殺Serviceimpl
 * 演示
 */
@Service
public class SeckillServiceImpl implements SeckillService {

    /**
     * 秒殺特價 1000000份
     * @param productId
     * @return
     */
    static Map<String,Integer> products;
    static Map<String,Integer> stock;
    static Map<String,String> orders;

    static{
        /**
         * ,模擬多個表,商品資訊表,庫存表,秒殺成功訂單表
         */
        products =new HashMap<>();
        stock=new HashMap<>();
        orders=new HashMap<>();
        products.put("123",1000000);
        stock.put("123",1000000);
    }

    @Override
    public String queryMap(String productId) {
        return "活動特價,限量:"+products.get(productId)+",還剩:"+stock.get(productId)
                +"份"+",成功下單使用者數:"+orders.size()+"人。";
    }

    @Override
    public String querySeckillProductInfo(String productId) {
        return this.queryMap(productId);
    }

    /**
     * 主要秒殺的邏輯
     * @param productId
     */
    @Override
    public synchronized void   orderProductMockDiffUser(String productId) {
        //查詢該商品庫存,為0則活動結束
        int stockNum=stock.get(productId);
        if(stockNum==0){
            throw new SellException(ExceptionEnum.SECKILL_OVER);
        }else{
            //2.下單(模擬不同使用者opendid不同)
            orders.put(KeyUtils.getUniqueKey(),productId);

            //3.減庫存
            stockNum=stockNum-1;
            try{
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            stock.put(productId,stockNum);
        }
    }
}

關於壓測:

壓力測試是一種基本的質量保證行為,在秒殺活動中更為重要,能有效測量超賣(賣出去的比庫存的多)、少賣(有買了但是沒賣)等現象,目前主流的壓測工具有Jmeter、LoadRunner等,老一點的有apache ab,正好本人機器裝有Apace服務,因此使用apache  ab做壓測。

專案中,我們只添加了一件商品,productId為123,啟動專案,在瀏覽器中查詢,URL:http://127.0.0.1:8080/sell/skill/query/123,結果如下:

可以看到,專案能正常訪問,查詢的庫存為1000000份。現在我們咋瀏覽器上進行秒殺,URL:http://127.0.0.1:8080/sell/skill/order/123

我們已經秒殺了一件商品,那麼如何實現高併發秒殺呢?這就需要使用上面介紹的Apache ab進行壓測

使用方法:安裝Apache Http Services 後,配置相關環境變數,保證能在命令列直接呼叫。

測試命令:ab -n 1000 -c 10 http://127.0.0.1:8080/sell/skill/order/123

  其中ab表示在命令列調取apache ab壓測工具,-n表示發起1000條請求,-c 表示10個併發

  http://127.0.0.1:8080/sell/skill/order/123 :表示測試的URL.

  (注意:壓測會佔用大量電腦資源,特別是併發大的時候)

結果:

可以發現,秒殺還是比較快的,僅用了19秒。現在我們來檢視庫存;

-----------------------------------------

重點:檢視庫存發現雖然秒殺都成功了,但是庫存量與下單成功量之和與總量不對應:999878+1000>1000000,出現了超賣現象。

下面我們在秒殺方法上加上synchronized關鍵字,修SeckillServiceImpl,對秒殺方法上鎖

然後重啟專案,在此重新查詢庫存:

 

沒問題!

繼續併發秒殺:

測試完成,明顯可以感覺到,訪問慢了很多,用了100多秒,因為我們使用了資源鎖,保證每次只有一個執行緒去呼叫它。

現在我們在來檢視庫存。

重點:可以發現,庫存與下單都是正確的,使用synchronized是一種資源控制的解決辦法

那麼,秒殺中直接使用synchronized進行鎖控制有什麼不好的地方呢?

  1.無法做到細粒度的控制,在測試中,我們只有一個商品,如果有多個商品呢?

  多個商品參與秒殺活動,有的人秒殺商品A、有的秒殺商品B,都要走秒殺方法,使用synchronized

  會一樣的慢。

  2.只支援單點(單機、伺服器環境),無法做到水平擴充套件,如果專案使用負載均衡,會出現混亂。

那麼,又有什麼好的辦法可以解決上面提到的問題?

答案當然是有,那就是分散式鎖。