模擬秒殺系統:
第一步:編寫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
注:
壓測模擬並發: