mysql抢购表设计_GitHub - zhoujifeng/SecKillDesign: 秒杀与抢购系统架构设计与实现

Java秒杀与抢购模型的架构设计与实现

开发环境:

IntelliJ IDEA + Maven + Workbench

压测工具:

JMeter

使用框架:

Spring Boot + Mybatis + Redis + RabbitMQ

具体内容:

对高并发高负载情形下的应用场景进行分析,以高效地处理资源竞争为目的,设计一个秒杀与抢购模型。

本项目提供了三种解决方案来比较系统的性能:

1.利用MySQL的update行锁实现悲观锁。

2.MySQL加字段version实现乐观锁。

3.使用Redis作为原子计数器(watch事务+decr操作),RabbitMQ作为消息队列记录用户抢购行为,MySQL做异步存储。

上述三个解决方案均使用了JMeter进行压力与性能测试,分析其吞吐量、平均响应时间、错误率等参数,最后得出相应结论。

备注:

1.此项目包含了sql文件,包括表单创建和添加数据。

2.包含了JMeter配置图片与实验结果图片

3.包含了测试数据集:param.txt。第一个参数代表用户ID,第二个参数代表产品ID。

核心代码:

MySQL悲观锁

@Transactional

public SecKillEnum handleByPessLockInMySQL(Map paramMap) {

Jedis jedis = redisCacheHandle.getJedis();

Record record = null;

Integer userId = (Integer) paramMap.get("userId");

Integer productId = (Integer)paramMap.get("productId");

User user = new User(userId);

Product product = secKillMapper.getProductById(productId);

String hasBoughtSetKey = SecKillUtils.getRedisHasBoughtSetKey(product.getProductName());

//判断是否重复购买

boolean isBuy = jedis.sismember(hasBoughtSetKey, user.getId().toString());

if (isBuy){

//重复秒杀

throw new SecKillException(SecKillEnum.REPEAT);

}

boolean secKillSuccess = secKillMapper.updatePessLockInMySQL(product);

if (!secKillSuccess){

//库存不足

throw new SecKillException(SecKillEnum.LOW_STOCKS);

}

//秒杀成功

record = new Record(null,user,product,SecKillEnum.SUCCESS.getCode(),SecKillEnum.SUCCESS.getMessage(),new Date());

log.info(record.toString());

boolean insertFlag = secKillMapper.insertRecord(record);

//插入record成功

if (insertFlag){

long addResult = jedis.sadd(hasBoughtSetKey,user.getId().toString());

if (addResult>0){

log.info("---------秒杀成功");

return SecKillEnum.SUCCESS;

}else {

throw new SecKillException(SecKillEnum.REPEAT);

}

}else {

throw new SecKillException(SecKillEnum.SYSTEM_EXCEPTION);

}

}

MySQL乐观锁

@Transactional

public SecKillEnum handleByPosiLockInMySQL(Map paramMap){

Jedis jedis = redisCacheHandle.getJedis();

Record record = null;

Integer userId = (Integer) paramMap.get("userId");

Integer productId = (Integer)paramMap.get("productId");

User user = new User(userId);

Product product = secKillMapper.getProductById(productId);

String hasBoughtSetKey = SecKillUtils.getRedisHasBoughtSetKey(product.getProductName());

//判断是否重复购买

boolean isBuy = jedis.sismember(hasBoughtSetKey, user.getId().toString());

if (isBuy){

//重复秒杀

throw new SecKillException(SecKillEnum.REPEAT);

}

//库存减一

int lastStock = product.getStock()-1;

if (lastStock>=0){

product.setStock(lastStock);

boolean secKillSuccess = secKillMapper.updatePosiLockInMySQL(product);

if (!secKillSuccess){

//秒杀失败,version被修改

throw new SecKillException(SecKillEnum.FAIL);

}

}else {

//库存不足

throw new SecKillException(SecKillEnum.LOW_STOCKS);

}

record = new Record(null,user,product,SecKillEnum.SUCCESS.getCode(),SecKillEnum.SUCCESS.getMessage(),new Date());

log.info(record.toString());

boolean insertFlag = secKillMapper.insertRecord(record);

//插入record成功

if (insertFlag){

long addResult = jedis.sadd(hasBoughtSetKey,user.getId().toString());

if (addResult>0){

//秒杀成功

return SecKillEnum.SUCCESS;

}else {

//重复秒杀

log.info("---------重复秒杀");

throw new SecKillException(SecKillEnum.REPEAT);

}

}else {

//系统错误

throw new SecKillException(SecKillEnum.SYSTEM_EXCEPTION);

}

}

redis的watch监控

public SecKillEnum handleByRedisWatch(Map paramMap) {

Jedis jedis = redisCacheHandle.getJedis();

Record record = null;

Integer userId = (Integer) paramMap.get("userId");

Integer productId = (Integer)paramMap.get("productId");

User user = new User(userId);

String productName = jedis.get("product_"+productId);

String productStockCacheKey = productName+"_stock";

String hasBoughtSetKey = SecKillUtils.getRedisHasBoughtSetKey(productName);

//watch开启监控

jedis.watch(productStockCacheKey);

//判断是否重复购买,注意这里高并发情形下并不安全

boolean isBuy = jedis.sismember(hasBoughtSetKey, user.getId().toString());

if (isBuy){

//重复秒杀

throw new SecKillException(SecKillEnum.REPEAT);

}

String stock = jedis.get(productStockCacheKey);

if (Integer.parseInt(stock)<=0) {

//库存不足

throw new SecKillException(SecKillEnum.LOW_STOCKS);

}

//开启Redis事务

Transaction tx = jedis.multi();

//库存减一

tx.decrBy(productStockCacheKey,1);

//执行事务

List resultList = tx.exec();

if (resultList == null || resultList.isEmpty()) {

jedis.unwatch();

//watch监控被更改过----物品抢购失败;

throw new SecKillException(SecKillEnum.FAIL);

}

//添加到已买队列

long addResult = jedis.sadd(hasBoughtSetKey,user.getId().toString());

if (addResult>0){

Product product = new Product(productId);

//秒杀成功

record = new Record(null,user,product,SecKillEnum.SUCCESS.getCode(),SecKillEnum.SUCCESS.getMessage(),new Date());

//添加record到rabbitMQ消息队列

rabbitMQSender.send(JSON.toJSONString(record));

//返回秒杀成功

return SecKillEnum.SUCCESS;

}else {

//重复秒杀

//这里抛出RuntimeException异常,redis的decr操作并不会回滚,所以需要手动incr回去

jedis.incrBy(productStockCacheKey,1);

throw new SecKillException(SecKillEnum.REPEAT);

}

}

JMeter压测图片:

1f75bdf47705b914935b19b3c37139c1.png

b55d2d4bea0bf0769c0a1af4d98f5a41.png

实验结果图片:

MySQL悲观锁

2b0a3dba326c607998bd20cbc164ee30.png

5370d93d708824e00eb90fc8df7ae87d.png

972a91beca42ea8af90f2623622cd8e9.png

#####################################################################

MySQL乐观锁

a9039f349d13104348d7b9203afe14b5.png

177de167a88d55b8315b6d069498ee43.png

6e42bb368efc6154c755816133b21efd.png

#####################################################################

redis的watch监控

7eaaa771b8882b0c8c8ae075fbfda9f1.png

cd072ada64f833f4b1188aed3b5cd0d3.png

603479411aee02d64754ed6d76714765.png

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值