模拟秒杀系统:
第一步:编写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
注:
压测模拟并发: