springboot項目:Redis分布式鎖的使用(模擬秒殺系統)


模擬秒殺系統:

第一步:編寫Service

package com.payease.service;

/**
 * liuxiaoming
 * 2017-12-14
 */
public interface SecKillService {
    /**
     * 查詢秒殺活動特價商品的信息
     *
     * @param productId
     * @return
     */
    String querySecKillProductInfo(String productId);

    /**
     * 模擬不同用戶秒殺同一商品的請求
     *
     * @param productId
     * @return
     */
    void orderProductMockDiffUser(String productId);
}

第二步:編寫Redis加鎖解鎖工具類

package com.payease.service;

import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;

/**
 * liuxiaoming
 * 2017-12-14
 */
@Component
@Slf4j
public class RedisLock {

    @Autowired
    private StringRedisTemplate stringRedisTemplate;

    /**
     * 加鎖
     * @param key
     * @param value 當前時間+超時時間
     * @return
     */
    public boolean lock(String key, String value) {
        if (stringRedisTemplate.opsForValue().setIfAbsent(key, value)) {
            return true;
        }
        //currentValue=A 這兩個線程的value都是B 其中一個線程拿到鎖
        String currentValue = stringRedisTemplate.opsForValue().get(key);
        //如果鎖過期
        if (!StringUtils.isEmpty(currentValue) && Long.parseLong(currentValue) < System.currentTimeMillis()) {
            //獲取上一個鎖的時間
            String oldValue = stringRedisTemplate.opsForValue().getAndSet(key, value);
            if (!StringUtils.isEmpty(oldValue) && oldValue.equals(currentValue)) {
                return true;
            }
        }
        return false;
    }

    /**
     * 解鎖
     * @param key
     * @param value
     * @return
     */
    public void unlock(String key, String value) {
        String currentVaule = stringRedisTemplate.opsForValue().get(key);
        try {
            if (!StringUtils.isEmpty(currentVaule) && currentVaule.equals(value)) {
                stringRedisTemplate.opsForValue().getOperations().delete(key);
            }
        } catch (Exception e) {
            log.error("【redis分布式鎖】解鎖異常,{}" , e);
        }

    }
}

第三步:編寫Service實現類

package com.payease.service.impl;


import com.payease.exception.SellException;
import com.payease.service.RedisLock;
import com.payease.service.SecKillService;
import com.payease.utils.KeyUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

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

/**
 * liuxiaoming
 * 1027-12-14
 */
@Service
public class SecKillServiceImpl implements SecKillService {

    @Autowired
    private RedisLock redisLock;


    private static final int TIMOUT = 10 * 1000; //超時時間 10秒

    /**
     * 中秋活動 秒殺月餅 限量100000
     */
    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("abc123456", 10000);
        stock.put("abc123456", 10000);
    }

    private String queryMap(String productId) {
        return "中秋活動,月餅特價,限量份"
                + products.get(productId)
                + " 還剩:" + stock.get(productId) + " 份"
                + " 該商品成功下單用戶數目:"
                + orders.size() + " 人";
    }


    @Override
    public String querySecKillProductInfo(String productId) {
        return queryMap(productId);
    }

    /**
     * 描述邏輯
     *
     * @param productId
     */
    @Override
    public void orderProductMockDiffUser(String productId) {

        //加鎖
        long time = System.currentTimeMillis() + TIMOUT;
        if (!redisLock.lock(productId, String.valueOf(time))) {
            throw new SellException(110, "沒搶到,換個姿勢再試一遍呀");
        }
        //1. 查詢該商品庫存,為0則活動結束。
        int stockNum = stock.get(productId);
        if (stockNum == 0) {
            //庫存不足
            throw new SellException(100, "活動已經結束,請留意下次活動");
        } else {

            orders.put(KeyUtil.getUniqueKey(), productId);
            stockNum = stockNum - 1;
            try {
                Thread.sleep(100);
            } catch (InterruptedException ex) {
                ex.printStackTrace();
            }
            stock.put(productId, stockNum);
        }

        //解鎖
        redisLock.unlock(productId, String.valueOf(time));
    }


    /*@Override
    public synchronized void  orderProductMockDiffUser(String productId) {
        int stockNum = stock.get(productId);
        if (stockNum == 0) {
            //庫存不足
            throw new SellException(100, "活動已經結束,請留意下次活動");
        } else {

            orders.put(KeyUtil.genUniqueKey(), productId);
            stockNum = stockNum - 1;
            try {
                Thread.sleep(100);
            } catch (InterruptedException ex) {
                ex.printStackTrace();
            }
            stock.put(productId, stockNum);
        }

    }*/
}

第四步:編寫controller

package com.payease.controller;

import com.payease.service.SecKillService;
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;

@RestController
@RequestMapping("/skill")
@Slf4j
public class SecKillController {

    @Autowired
    private SecKillService secKillService;



    /**
     * 查詢秒殺活動特價商品的信息
     * @param productId
     * @return
     */
    @GetMapping("/query/{productId}")
    public String query(@PathVariable String productId)throws Exception
    {
        return secKillService.querySecKillProductInfo(productId);
    }


    /**
     * 秒殺,沒有搶到獲得"哎呦喂,xxxxx",搶到了會返回剩余的庫存量
     * @param productId
     * @return
     * @throws Exception
     */
    @GetMapping("/order/{productId}")
    public String skill(@PathVariable String productId)throws Exception
    {
        log.info("@skill request, productId:" + productId);
        secKillService.orderProductMockDiffUser(productId);
        return secKillService.querySecKillProductInfo(productId);
    }
}

第五步:啟動項目 查看瀏覽器 進行壓測

1.查看秒殺情況 http://127.0.0.1:8080/sell/skill/query/abc123456 

2. 在Mac終端輸入命令: ab -n 500 -c 100 http://127.0.0.1:8080/sell/skill/order/abc123456
進行壓測

3.查看瀏覽器 http://127.0.0.1:8080/sell/skill/query/abc123456

 

注:

壓測模擬並發:

 


免責聲明!

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



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