高并发下的下单功能设计
来源:互联网 发布:上海惠保数据不发工资 编辑:程序博客网 时间:2024/04/26 13:19
功能需求:设计一个秒杀系统
初始方案
商品表设计:热销商品提供给用户秒杀,有初始库存。
@Entity
public class SecKillGoods implements Serializable{
@Id
private String id;
/**
* 剩余库存
*/
private Integer remainNum;
/**
* 秒杀商品名称
*/
private String goodsName;
}
秒杀订单表设计:记录秒杀成功的订单情况
@Entity
public class SecKillOrder implements Serializable {
@Id
@GenericGenerator(name = "PKUUID", strategy = "uuid2")
@GeneratedValue(generator = "PKUUID")
@Column(length = 36)
private String id;
//用户名称
private String consumer;
//秒杀产品编号
private String goodsId;
//购买数量
private Integer num;
}
Dao设计:主要就是一个减少库存方法,其他CRUD使用JPA自带的方法
public interface SecKillGoodsDao extends JpaRepository<SecKillGoods,String>{
@Query("update SecKillGoods g set g.remainNum = g.remainNum - ?2 where g.id=?1")
@Modifying(clearAutomatically = true)
@Transactional
int reduceStock(String id,Integer remainNum);
}
数据初始化以及提供保存订单的操作:
@Service
public class SecKillService {
@Autowired
SecKillGoodsDao secKillGoodsDao;
@Autowired
SecKillOrderDao secKillOrderDao;
/**
* 程序启动时:
* 初始化秒杀商品,清空订单数据
*/
@PostConstruct
public void initSecKillEntity(){
secKillGoodsDao.deleteAll();
secKillOrderDao.deleteAll();
SecKillGoods secKillGoods = new SecKillGoods();
secKillGoods.setId("123456");
secKillGoods.setGoodsName("秒杀产品");
secKillGoods.setRemainNum(10);
secKillGoodsDao.save(secKillGoods);
}
/**
* 购买成功,保存订单
* @param consumer
* @param goodsId
* @param num
*/
public void generateOrder(String consumer, String goodsId, Integer num) {
secKillOrderDao.save(new SecKillOrder(consumer,goodsId,num));
}
}
下面就是controller层的设计
@Controller
public class SecKillController {
@Autowired
SecKillGoodsDao secKillGoodsDao;
@Autowired
SecKillService secKillService;
/**
* 普通写法
* @param consumer
* @param goodsId
* @return
*/
@RequestMapping("/seckill.html")
@ResponseBody
public String SecKill(String consumer,String goodsId,Integer num) throws InterruptedException {
//查找出用户要买的商品
SecKillGoods goods = secKillGoodsDao.findOne(goodsId);
//如果有这么多库存
if(goods.getRemainNum()>=num){
//模拟网络延时
Thread.sleep(1000);
//先减去库存
secKillGoodsDao.reduceStock(num);
//保存订单
secKillService.generateOrder(consumer,goodsId,num);
return "购买成功";
}
return "购买失败,库存不足";
}
}
上面是全部的基础准备,下面使用一个单元测试方法,模拟高并发下,很多人来购买同一个热门商品的情况。
@Controller
public class SecKillSimulationOpController {
final String takeOrderUrl = "http://127.0.0.1:8080/seckill.html";
/**
* 模拟并发下单
*/
@RequestMapping("/simulationCocurrentTakeOrder")
@ResponseBody
public String simulationCocurrentTakeOrder() {
//httpClient工厂
final SimpleClientHttpRequestFactory httpRequestFactory = new SimpleClientHttpRequestFactory();
//开50个线程模拟并发秒杀下单
for (int i = 0; i < 50; i++) {
//购买人姓名
final String consumerName = "consumer" + i;
new Thread(new Runnable() {
@Override
public void run() {
ClientHttpRequest request = null;
try {
URI uri = new URI(takeOrderUrl + "?consumer=consumer" + consumerName + "&goodsId=123456&num=1");
request = httpRequestFactory.createRequest(uri, HttpMethod.POST);
InputStream body = request.execute().getBody();
BufferedReader br = new BufferedReader(new InputStreamReader(body));
String line = "";
String result = "";
while ((line = br.readLine()) != null) {
result += line;//获得页面内容或返回内容
}
System.out.println(consumerName+":"+result);
} catch (Exception e) {
e.printStackTrace();
}
}
}).start();
}
return "simulationCocurrentTakeOrder";
}
}
访问localhost:8080/simulationCocurrentTakeOrder,就可以测试了
预期情况:因为我们只对秒杀商品(123456)初始化了10件,理想情况当然是库存减少到0,订单表也只有10条记录。
实际情况:订单表记录
商品表记录
下面分析一下为啥会出现超库存的情况:
因为多个请求访问,仅仅是使用dao查询了一次数据库有没有库存,但是比较恶劣的情况是很多人都查到了有库存,这个时候因为程序处理的延迟,没有及时的减少库存,那就出现了脏读。如何在设计上避免呢?最笨的方法是对SecKillController的seckill方法做同步,每次只有一个人能下单。但是太影响性能了,下单变成了同步操作。
@RequestMapping("/seckill.html")
@ResponseBody
public synchronized String SecKill
改进方案
根据多线程编程的规范,提倡对共享资源加锁,在最有可能出现并发争抢的情况下加同步块的思想。应该同一时刻只有一个线程去减少库存。但是这里给出一个最好的方案,就是利用Oracle,MySQL的行级锁–同一时间只有一个线程能够操作同一行记录,对SecKillGoodsDao进行改造:
public interface SecKillGoodsDao extends JpaRepository<SecKillGoods,String>{
@Query("update SecKillGoods g set g.remainNum = g.remainNum - ?2 where g.id=?1 and g.remainNum>0")
@Modifying(clearAutomatically = true)
@Transactional
int reduceStock(String id,Integer remainNum);
}
仅仅是加了一个and,却造成了很大的改变,返回int值代表的是影响的行数,对应到controller做出相应的判断。
@RequestMapping("/seckill.html")
@ResponseBody
public String SecKill(String consumer,String goodsId,Integer num) throws InterruptedException {
//查找出用户要买的商品
SecKillGoods goods = secKillGoodsDao.findOne(goodsId);
//如果有这么多库存
if(goods.getRemainNum()>=num){
//模拟网络延时
Thread.sleep(1000);
if(goods.getRemainNum()>0) {
//先减去库存
int i = secKillGoodsDao.reduceStock(goodsId, num);
if(i!=0) {
//保存订单
secKillService.generateOrder(consumer, goodsId, num);
return "购买成功";
}else{
return "购买失败,库存不足";
}
}else {
return "购买失败,库存不足";
}
}
return "购买失败,库存不足";
}
在看看运行情况
订单表:
在高并发问题下的秒杀情况,即使存在网络延时,也得到了保障。
- 高并发下的下单功能设计
- 高并发下的下单功能设计
- 高并发下的下单功能设计
- 秒杀系统设计,高并发下的下单功能设计
- 高并发下的单例模式
- 淘宝下单的高并发解决方案
- 高并发下线程安全的单例模式
- 淘宝下单部分高并发设计 的个人理解
- 高并发下线程安全的单例模式
- 解决高并发下的单例模式
- 高并发下线程安全的单例模式
- 高并发下线程安全的单例模式
- PHP高并发下单解决方案
- 【多线程高并发】ThreadLocal,高并发下的单例模式
- 高并发下的SimpleDateFormat
- 高并发下的HashMap
- 高并发下的HashMap
- 高并发下接口的并发问题
- 深入理解变量 作用域 内存
- 一个利用“永恒之蓝”漏洞传播的挖矿程序分析
- 【UE4教程文档翻译】First Person Shooter C++ Tutoria(第一人称射击C++教程)
- hadoop2.6快速离线部署
- sqoop1.4.6离线部署于hadoop2.6之上与hive导入导出数据
- 高并发下的下单功能设计
- Java I/O 流的基础用法和原理
- Java 线程面试题 Top 50
- sqoop与hbase导入导出数据
- hash table 哈希表(散列表)
- 在巡视整改中,北京大学所面临的尴尬局面
- firefox界面英文換中文
- Spring Boot集成Quartz-动态任务管理
- 织梦dedecms发布文章提示数据库附加表数据保存出错