假设场景如下:
库存20瓶茅台53°。
页面发起100万次请求抢购20瓶茅台,用Jmeter测试工具模拟。
要求100万次请求全部得到及时响应,抢购到或者抢购不到。
单机springboot内嵌tomcat,系统服务正常运行,不宕机。
库存不能产生超卖,数据库的库存不能出问题。
Demo设计思路:
1 限流 每秒只放5个请求,其他请求直接返回:本次未抢购到。不走后续业务逻辑。
2 缓存 每次用户请求茅台的剩余库存数时,查询jvm缓存中的剩余数量。
说白了,上谷歌的guava包做。maven依赖引入
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>18.0</version>
</dependency>
mysql表设计
1 用户购买记录表
CREATE TABLE `buy_goods` (
`buyer` varchar(255) DEFAULT NULL,
`sku_name` varchar(255) DEFAULT NULL,
`buydate` datetime DEFAULT NULL,
`amout` int(11) DEFAULT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
2 库存信息表
CREATE TABLE `sku_stock` (
`sku_name` varchar(255) DEFAULT NULL,
`avaliable_amout` bigint(20) DEFAULT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
插入20瓶茅台的一条记录。
INSERT INTO `demoa`.`sku_stock` (`sku_name`, `avaliable_amout`) VALUES ('maotai', '20');
springboot程序。
BuyDao.xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.dao.IBuyDao">
<!--查询单个-->
<insert id="insertBuyGoods">
INSERT INTO buy_goods (
`buyer`,
`sku_name`,
`buydate`,
`amout`
)
VALUES
(
#{param1},
#{param2},
sysdate(),
#{param3}
)
</insert>
<select id="querySkuAmout" parameterType="java.lang.String" resultType="java.lang.Long">
SELECT
avaliable_amout
FROM
sku_stock
WHERE
sku_name =#{param1}
</select>
<update id="updateSkuAmout">
update sku_stock
set avaliable_amout=avaliable_amout-#{param2}
where sku_name =#{param1}
</update>
</mapper>
IBuyDao接口:
package com.dao;
public interface IBuyDao {
int insertBuyGoods(String buyer,String skuname,int amout);
long querySkuAmout(String skuname);
int updateSkuAmout(String skuname,int reduceAmount);
}
BuyDemo。Controller:
package com;
import com.dao.IBuyDao;
import com.google.common.cache.CacheBuilder;
import com.google.common.cache.CacheLoader;
import com.google.common.cache.LoadingCache;
import com.google.common.util.concurrent.RateLimiter;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestController;
import javax.annotation.Resource;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.UUID;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicLong;
@RestController
@Slf4j
public class BuyDemo {
@Resource
IBuyDao buyDao;
final AtomicLong atomicLong=new AtomicLong(0);
final AtomicInteger atomicInteger=new AtomicInteger(0);
public static final SimpleDateFormat simpleDateFormat=new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS");
//流控速率,每秒放行5个请求。
final RateLimiter rateLimiter=RateLimiter.create(5);
//guava_cache本地缓存,JVM级别缓存
volatile LoadingCache<String, Long> Cache_AvaliableAmout = CacheBuilder.newBuilder()
//设置cache的初始大小为10,要合理设置该值
.initialCapacity(10)
//设置并发数为3,即同一时间最多只能有3个线程往cache执行写入操作
.concurrencyLevel(3)
//设置cache中的数据在写入之后的存活时间为10秒
.expireAfterWrite(10, TimeUnit.SECONDS)
//构建cache实例
.build(
new CacheLoader<String, Long>() {
@Override
public Long load(String skuName) throws Exception {
long amout= buyDao.querySkuAmout(skuName);
log.info("查询数据库:"+skuName+"可用数量为:"+amout);
return amout;
}
}
);
@RequestMapping("/buygoods")
@ResponseBody
public String buygoods(String skuName) {
//是否能通过流控,true表示该请求通过流控,可以请求后台业务,false表示该请求被流控,无法请求业务,直接被返回未抢购到
boolean get=rateLimiter.tryAcquire();
//被流控,抢购失败
if(!get)
{
return "buying maotai is too hot,wait later "+atomicLong.incrementAndGet();
}
try {
//商品卖完了,直接返回无货。
if(Cache_AvaliableAmout.get(skuName)<=0)
{
return "no avaliable sku "+atomicLong.incrementAndGet();
}
//简单的锁机制
synchronized (this)
{
//double-check防止过量超卖
if(Cache_AvaliableAmout.get(skuName)<=0)
{
return "no avaliable sku "+atomicLong.incrementAndGet();
}
//更新库存数量,插入购买者的购买记录。
buyDao.updateSkuAmout(skuName,1);
buyDao.insertBuyGoods(UUID.randomUUID().toString(),skuName,1);
//刷新缓存
Cache_AvaliableAmout.refresh(skuName);
log.info(simpleDateFormat.format(new Date())+" buy one "+atomicInteger.incrementAndGet());
}
return "buy success";
} catch (Exception e) {
e.printStackTrace();
return "system error";
}
}
}
Jmeter测试信息:
点击运行后:
Jemter输出信息:
Jmeter一百万请求全部完成。
后续因为无货,请求不走mysql,请求秒回。
前期有货,抢购到需要走sql查询和更新,所以有点耗时,几百毫秒。
idea控制台输出信息:
2021-05-16 20:53:20.657 INFO 23664 --- [http-nio-8900-exec-88] com.BuyDemo : 查询数据库:maotai可用数量为:20
2021-05-16 20:53:20.680 INFO 23664 --- [http-nio-8900-exec-88] com.BuyDemo : 查询数据库:maotai可用数量为:19
2021-05-16 20:53:20.722 INFO 23664 --- [http-nio-8900-exec-88] com.BuyDemo : 2021-05-16 20:53:20.722 buy one 1
2021-05-16 20:53:20.753 INFO 23664 --- [http-nio-8900-exec-130] com.BuyDemo : 查询数据库:maotai可用数量为:18
2021-05-16 20:53:20.843 INFO 23664 --- [http-nio-8900-exec-130] com.BuyDemo : 2021-05-16 20:53:20.753 buy one 2
2021-05-16 20:53:20.861 INFO 23664 --- [http-nio-8900-exec-66] com.BuyDemo : 查询数据库:maotai可用数量为:17
2021-05-16 20:53:20.862 INFO 23664 --- [http-nio-8900-exec-66] com.BuyDemo : 2021-05-16 20:53:20.862 buy one 3
2021-05-16 20:53:20.882 INFO 23664 --- [http-nio-8900-exec-41] com.BuyDemo : 查询数据库:maotai可用数量为:16
2021-05-16 20:53:20.883 INFO 23664 --- [http-nio-8900-exec-41] com.BuyDemo : 2021-05-16 20:53:20.883 buy one 4
2021-05-16 20:53:20.899 INFO 23664 --- [http-nio-8900-exec-102] com.BuyDemo : 查询数据库:maotai可用数量为:15
2021-05-16 20:53:20.900 INFO 23664 --- [http-nio-8900-exec-102] com.BuyDemo : 2021-05-16 20:53:20.899 buy one 5
2021-05-16 20:53:20.922 INFO 23664 --- [http-nio-8900-exec-65] com.BuyDemo : 查询数据库:maotai可用数量为:14
2021-05-16 20:53:20.922 INFO 23664 --- [http-nio-8900-exec-65] com.BuyDemo : 2021-05-16 20:53:20.922 buy one 6
2021-05-16 20:53:20.941 INFO 23664 --- [http-nio-8900-exec-181] com.BuyDemo : 查询数据库:maotai可用数量为:13
2021-05-16 20:53:20.941 INFO 23664 --- [http-nio-8900-exec-181] com.BuyDemo : 2021-05-16 20:53:20.941 buy one 7
2021-05-16 20:53:20.961 INFO 23664 --- [http-nio-8900-exec-97] com.BuyDemo : 查询数据库:maotai可用数量为:12
2021-05-16 20:53:20.962 INFO 23664 --- [http-nio-8900-exec-97] com.BuyDemo : 2021-05-16 20:53:20.962 buy one 8
2021-05-16 20:53:21.181 INFO 23664 --- [http-nio-8900-exec-119] com.BuyDemo : 查询数据库:maotai可用数量为:11
2021-05-16 20:53:21.182 INFO 23664 --- [http-nio-8900-exec-119] com.BuyDemo : 2021-05-16 20:53:21.182 buy one 9
2021-05-16 20:53:21.194 INFO 23664 --- [http-nio-8900-exec-113] com.BuyDemo : 查询数据库:maotai可用数量为:10
2021-05-16 20:53:21.195 INFO 23664 --- [http-nio-8900-exec-113] com.BuyDemo : 2021-05-16 20:53:21.194 buy one 10
2021-05-16 20:53:21.209 INFO 23664 --- [http-nio-8900-exec-61] com.BuyDemo : 查询数据库:maotai可用数量为:9
2021-05-16 20:53:21.210 INFO 23664 --- [http-nio-8900-exec-61] com.BuyDemo : 2021-05-16 20:53:21.210 buy one 11
2021-05-16 20:53:21.225 INFO 23664 --- [http-nio-8900-exec-115] com.BuyDemo : 查询数据库:maotai可用数量为:8
2021-05-16 20:53:21.225 INFO 23664 --- [http-nio-8900-exec-115] com.BuyDemo : 2021-05-16 20:53:21.225 buy one 12
2021-05-16 20:53:21.251 INFO 23664 --- [http-nio-8900-exec-98] com.BuyDemo : 查询数据库:maotai可用数量为:7
2021-05-16 20:53:21.253 INFO 23664 --- [http-nio-8900-exec-98] com.BuyDemo : 2021-05-16 20:53:21.253 buy one 13
2021-05-16 20:53:21.267 INFO 23664 --- [http-nio-8900-exec-94] com.BuyDemo : 查询数据库:maotai可用数量为:6
2021-05-16 20:53:21.268 INFO 23664 --- [http-nio-8900-exec-94] com.BuyDemo : 2021-05-16 20:53:21.268 buy one 14
2021-05-16 20:53:21.285 INFO 23664 --- [http-nio-8900-exec-116] com.BuyDemo : 查询数据库:maotai可用数量为:5
2021-05-16 20:53:21.286 INFO 23664 --- [http-nio-8900-exec-116] com.BuyDemo : 2021-05-16 20:53:21.286 buy one 15
2021-05-16 20:53:21.299 INFO 23664 --- [http-nio-8900-exec-147] com.BuyDemo : 查询数据库:maotai可用数量为:4
2021-05-16 20:53:21.300 INFO 23664 --- [http-nio-8900-exec-147] com.BuyDemo : 2021-05-16 20:53:21.300 buy one 16
2021-05-16 20:53:21.315 INFO 23664 --- [http-nio-8900-exec-38] com.BuyDemo : 查询数据库:maotai可用数量为:3
2021-05-16 20:53:21.315 INFO 23664 --- [http-nio-8900-exec-38] com.BuyDemo : 2021-05-16 20:53:21.315 buy one 17
2021-05-16 20:53:21.335 INFO 23664 --- [http-nio-8900-exec-20] com.BuyDemo : 查询数据库:maotai可用数量为:2
2021-05-16 20:53:21.335 INFO 23664 --- [http-nio-8900-exec-20] com.BuyDemo : 2021-05-16 20:53:21.335 buy one 18
2021-05-16 20:53:21.515 INFO 23664 --- [http-nio-8900-exec-3] com.BuyDemo : 查询数据库:maotai可用数量为:1
2021-05-16 20:53:21.516 INFO 23664 --- [http-nio-8900-exec-3] com.BuyDemo : 2021-05-16 20:53:21.516 buy one 19
2021-05-16 20:53:21.711 INFO 23664 --- [http-nio-8900-exec-159] com.BuyDemo : 查询数据库:maotai可用数量为:0
2021-05-16 20:53:21.712 INFO 23664 --- [http-nio-8900-exec-159] com.BuyDemo : 2021-05-16 20:53:21.712 buy one 20
2021-05-16 20:53:31.915 INFO 23664 --- [http-nio-8900-exec-127] com.BuyDemo : 查询数据库:maotai可用数量为:0
2021-05-16 20:53:42.101 INFO 23664 --- [http-nio-8900-exec-191] com.BuyDemo : 查询数据库:maotai可用数量为:0
2021-05-16 20:53:52.300 INFO 23664 --- [http-nio-8900-exec-117] com.BuyDemo : 查询数据库:maotai可用数量为:0
mysql表信息:
1 库存表茅台卖为0
2 用户购买记录表,共有20条购买记录