当前位置: 首页 > news >正文

那几个网站可以做h5企业培训

那几个网站可以做h5,企业培训,工商注册网站模板,最新备案域名本博客为个人学习笔记,学习网站与详细见:黑马程序员Redis入门到实战 P50 - P54 目录 优惠卷秒杀下单功能实现 超卖问题 悲观锁与乐观锁 实现CAS法乐观锁 一人一单功能实现 代码优化 代码细节分析 优惠卷秒杀下单功能实现 ​ ​ Controller层…

本博客为个人学习笔记,学习网站与详细见:黑马程序员Redis入门到实战 P50 - P54 

目录

优惠卷秒杀下单功能实现

超卖问题

悲观锁与乐观锁

实现CAS法乐观锁

一人一单功能实现

代码优化 

代码细节分析 


优惠卷秒杀下单功能实现

Controller层代码实现

@RestController
@RequestMapping("/voucher-order")
public class VoucherOrderController {@Resourceprivate IVoucherOrderService voucherOrderService;@PostMapping("seckill/{id}")public Result seckillVoucher(@PathVariable("id") Long voucherId) {return voucherOrderService.seckillVoucher(voucherId);}
}

Service层代码实现

//接口类
public interface IVoucherOrderService extends IService<VoucherOrder> {Result seckillVoucher(Long voucherId);
}//实现类
@Service
public class VoucherOrderServiceImpl extends ServiceImpl<VoucherOrderMapper, VoucherOrder> implements IVoucherOrderService {@Resourceprivate ISeckillVoucherService seckillVoucherService;@Resourceprivate RedisIdWorker redisIdWorker;@Override@Transactionalpublic Result seckillVoucher(Long voucherId) {// 1.查询优惠券信息SeckillVoucher voucher = seckillVoucherService.getById(voucherId);// 2.根据优惠券信息判断秒杀是否开始if (voucher.getBeginTime().isAfter(LocalDateTime.now())) {// 秒杀尚未开始return Result.fail("秒杀尚未开始!");}// 3.判断秒杀是否已经结束if (voucher.getEndTime().isBefore(LocalDateTime.now())) {// 秒杀已经结束return Result.fail("秒杀已经结束!");}// 4.判断库存是否充足if (voucher.getStock() < 1) {// 库存不足return Result.fail("库存不足!");}// 5.扣减库存boolean success = seckillVoucherService.update().setSql("stock = stock - 1").eq("voucher_id", voucherId).update();if (!success) {// 扣减失败return Result.fail("库存不足!");}// 6.创建订单VoucherOrder voucherOrder = new VoucherOrder();// 6.1订单idlong orderId = redisIdWorker.nextId("order");voucherOrder.setId(orderId);// 6.2用户idLong userId = UserHolder.getUser().getId();voucherOrder.setUserId(userId);// 6.3代金卷idvoucherOrder.setVoucherId(voucherId);save(voucherOrder);// 7.返回订单idreturn Result.ok(orderId);}
}

超卖问题

假设秒杀优惠卷库存为1,正常情况抢票逻辑如下图所示。 

假设秒杀优惠卷库存为1,高并发情况抢票逻辑如下图所示。 


悲观锁与乐观锁

由于悲观锁采用串行执行线程的方式,因此性能劣于乐观锁。

乐观锁的关键是判断之前查询到的数据是否被修改过,常见的方式有以下两种:

1.版本号法
为每种秒杀优惠卷添加版本号属性,每当该优惠卷的库存被更改时,令版本号也发生变化。因此只需要在查询库存的同时查询并记录版本号,在修改库存时,通过查询当前版本号并判断其是否与先前记录的版本号一致来判断查询到的库存是否已经被其他线程修改过。

2.CAS法(即版本号法的简化,利用库存代替版本号)


实现CAS法乐观锁

首先查询秒杀优惠卷信息并保存到变量voucher,当判断该优惠卷可以被抢购后,在修改该优惠卷库存时,需要满足当前优惠卷库存与voucher.getStock()相等的条件。

尽管添加了乐观锁,但在测试时发现用户抢购优惠卷的成功率过低。原因在于,在高并发的情况下,假设10个用户同时抢购一张优惠卷,那么只有一个用户能够抢购成功,其他用户均抢购失败。而在抢票的业务场景下,当用户同时进行抢购时,只要优惠卷库存大于0即可进行抢购,因此只需修改判断条件,在修改库存时,判断当前库存是否大于0即可。


一人一单功能实现

修改Service层实现类中优惠卷秒杀下单seckillVoucher方法,代码(未优化版本)如下:

@Override
@Transactional
public Result seckillVoucher(Long voucherId) {//1.查询优惠券SeckillVoucher voucher = seckillVoucherService.getById(voucherId);//2.判断秒杀是否开始if (voucher.getBeginTime().isAfter(LocalDateTime.now())) {//尚未开始return Result.fail("秒杀尚未开始");}//3.判断秒杀是否已经结束if (voucher.getEndTime().isBefore(LocalDateTime.now())) {//尚未开始return Result.fail("秒杀已经结束");}//4.判断库存是否充足if (voucher.getStock()<1) {//库存不足return Result.fail("库存不足");}//5.一人一单Long userId = UserHolder.getUser().getId();//5.1查询订单int count = query().eq("user_id", userId).eq("voucher_id", voucherId).count();//5.2判断是否存在if(count>1){//用户已经购买过了return Result.fail("用户已经购买过一次");}//6.扣减库存boolean success=seckillVoucherService.update().setSql("stock = stock - 1") //set stock =stock - 1.eq("voucher_id",voucherId).gt("stock",0)//where id=? and stock > 0.update();if(!success){return Result.fail("库存不足!");}//7.创建订单VoucherOrder voucherOrder = new VoucherOrder();//7.1订单Idlong orderId = redisIdWorker.nextId("order");voucherOrder.setId(orderId);//7.2用户IdvoucherOrder.setUserId(userId);//7.3代金券IdvoucherOrder.setVoucherId(voucherId);save(voucherOrder);//8.返回订单IDreturn Result.ok(orderId);
}

代码优化 

存在的问题:尽管代码经过修改,但在高并发的情况下仍然存在问题,即单个用户在同一刻多次下单,多个线程均查询到count=0(该用户未购买过当前优惠券),此时多个线程同时创建订单,从而导致一人多单问题。

实现:为实现一人一单功能,我们需要加锁,相比超卖问题中每个线程只是对数据进行修改,所以可以使用乐观锁,通过判断数据值是否被修改来判断其他线程是否已经对当前数据进行操作。但一人多单的问题中,每个线程需要进行添加数据而非修改数据的操作,即创建新的订单,所以这种情况下,我们需要使用悲观锁。

优化后的代码如下所示:

@Service
public class VoucherOrderServiceImpl extends ServiceImpl<VoucherOrderMapper, VoucherOrder> implements IVoucherOrderService {@Resourceprivate ISeckillVoucherService seckillVoucherService;@Resourceprivate RedisIdWorker redisIdWorker;@Overridepublic Result seckillVoucher(Long voucherId) {// 1.查询优惠券信息SeckillVoucher voucher = seckillVoucherService.getById(voucherId);// 2.根据优惠券信息判断秒杀是否开始if (voucher.getBeginTime().isAfter(LocalDateTime.now())) {// 秒杀尚未开始return Result.fail("秒杀尚未开始!");}// 3.判断秒杀是否已经结束if (voucher.getEndTime().isBefore(LocalDateTime.now())) {// 秒杀已经结束return Result.fail("秒杀已经结束!");}// 4.判断库存是否充足if (voucher.getStock() < 1) {// 库存不足return Result.fail("库存不足!");}Long userId = UserHolder.getUser().getId();synchronized (userId.toString().intern()) {// 获取当前代理对象IVoucherOrderService proxy = (IVoucherOrderService) AopContext.currentProxy();return proxy.createVoucherOrder(voucherId);}}@Transactionalpublic synchronized Result createVoucherOrder(Long voucherId) {// 5.一人一单模式Long userId = UserHolder.getUser().getId();// 5.1查询订单int count = query().eq("user_id", userId).eq("voucher_id", voucherId).count();// 5.2判断是否存在if (count > 0) {//用户已经购买过return Result.fail("用户已经购买过了");}// 6.扣减库存boolean success = seckillVoucherService.update().setSql("stock = stock - 1")  //使用MP,设置sql语句.eq("voucher_id", voucherId).gt("stock", 0).update();if (!success) {// 扣减失败return Result.fail("库存不足!");}// 7.创建订单VoucherOrder voucherOrder = new VoucherOrder();// 7.1获取订单全局idlong orderId = redisIdWorker.nextId("order");voucherOrder.setId(orderId);// 7.2用户idvoucherOrder.setUserId(userId);// 7.3代金卷idvoucherOrder.setVoucherId(voucherId);save(voucherOrder);// 8.返回订单idreturn Result.ok(orderId);}
}

代码细节分析 

有以下几个细节我们需要特别注意

1. 首先,我们将查询、判断、创建订单等过程封装成createVoucherOrder方法,但是我们并不能直接对这个方法加锁,如果对这个方法加锁的话将导致创建订单的业务是串行执行的,在高并发多用户的情况下,性能会很差。

2. 因为是一人一单功能,我们需要做的是对同一个用户创建订单的流程进行加锁,所以我们根据用户ID来加锁。我们为相同的用户ID配置一把锁,从而将锁的范围缩小,因此我们将createVoucherOrder方法进行修改,如下所示。

3. 然而,我们需要确保synchronized()传入的参数对象是相同的,synchronized锁才能起作用。因此我们不传入查询得到的userId对象,因为我们每次查询后都会赋值给新的userId对象,这样会导致传入synchronized()的参数始终不是同一个对象,因此我们采用的是synchronized(userId.toString())。

4.但是,如果我们直接使用userId.toString() 方法,拿到的对象实际上是不同的,toString() 的底层逻辑是new出一个新对象,因此我们需要使用intern()方法,该方法是从常量池中拿到数据,能确保我们每次取得的对象都是相同的。得到的代码如下所示。

@Transactional
//将创建订单封装成一个方法
public  Result createVoucherOrder(Long voucherId) {//获取用户IdLong userId = UserHolder.getUser().getId();synchronized(userId.toString().intern()) {查询、判断、创建订单等流程...}
}//错误情况:synchronized(userId.toString())

5.由于我们是在方法里面加锁,因此代码的逻辑是先释放锁,再提交事务,因此在高并发的环境下可能会出现安全问题(即释放锁后,提交事务还未完成,其他线程又进来了)。所以我们应该先提交事务,再释放锁。因此,我们应该在方法外加锁,代码如下。

@Override
public Result seckillVoucher(Long voucherId) {...Long userId = UserHolder.getUser().getId();synchronized (userId.toString().intern()) {return createVoucherOrder(voucherId);}
}

6.在以上代码中,我们调用createVoucherOrder方法,其实是通过this.的方式调用的,而this代表的是当前VoucherOrderServiceImpl对象,并不是createVoucherOrder的代理对象,因此事务是不生效的。要想让事务生效,我们需要利用代理对象调用createVoucherOrder方法,才能使事务生效,代码如下。

@Override
public Result seckillVoucher(Long voucherId) {...Long userId = UserHolder.getUser().getId();synchronized (userId.toString().intern()) {// 获取当前代理对象IVoucherOrderService proxy = (IVoucherOrderService) AopContext.currentProxy();return proxy.createVoucherOrder(voucherId);}
}

此外,我们还需做以下三步。
1.添加动态代理依赖

<dependency><groupId>org.aspectj</groupId><artifactId>aspectjweaver</artifactId>
</dependency>

2.在接口中添加createVoucherOrder(Long voucherId); 

public interface IVoucherOrderService extends IService<VoucherOrder> {Result seckillVoucher(Long voucherId);Result createVoucherOrder(Long voucherId);
}

3.在启动类中添加注解,暴露该代理对象

@EnableAspectJAutoProxy(exposeProxy = true)
@MapperScan("com.hmdp.mapper")
@SpringBootApplication
public class HmDianPingApplication {public static void main(String[] args) {SpringApplication.run(HmDianPingApplication.class, args);}}


文章转载自:
http://dinncoolericulture.wbqt.cn
http://dinncoincreasing.wbqt.cn
http://dinncoforecourt.wbqt.cn
http://dinncocontinence.wbqt.cn
http://dinncosynonym.wbqt.cn
http://dinncomenology.wbqt.cn
http://dinncocellarman.wbqt.cn
http://dinncoinversely.wbqt.cn
http://dinncohalocline.wbqt.cn
http://dinncoipse.wbqt.cn
http://dinncoelectromotion.wbqt.cn
http://dinncomonomerous.wbqt.cn
http://dinncoheadsquare.wbqt.cn
http://dinncoaggie.wbqt.cn
http://dinncobeating.wbqt.cn
http://dinncominicourse.wbqt.cn
http://dinncodeterminately.wbqt.cn
http://dinncopracticality.wbqt.cn
http://dinncohypercorrection.wbqt.cn
http://dinncocirculator.wbqt.cn
http://dinncoaeroboat.wbqt.cn
http://dinncostreak.wbqt.cn
http://dinncosuspenseful.wbqt.cn
http://dinncophilippine.wbqt.cn
http://dinncocavalvy.wbqt.cn
http://dinncoiricize.wbqt.cn
http://dinncomonecious.wbqt.cn
http://dinncomythopoeic.wbqt.cn
http://dinncoagoing.wbqt.cn
http://dinncophosphine.wbqt.cn
http://dinncotelegraphist.wbqt.cn
http://dinncotoddler.wbqt.cn
http://dinncomouther.wbqt.cn
http://dinncotartaric.wbqt.cn
http://dinncoatrato.wbqt.cn
http://dinncoplenum.wbqt.cn
http://dinncocollectorate.wbqt.cn
http://dinncounclasp.wbqt.cn
http://dinncoozonize.wbqt.cn
http://dinncocommunitarian.wbqt.cn
http://dinncocompreg.wbqt.cn
http://dinncoprelate.wbqt.cn
http://dinncoparoquet.wbqt.cn
http://dinncoaverroism.wbqt.cn
http://dinncointerchangeabilty.wbqt.cn
http://dinncolidless.wbqt.cn
http://dinncoexfacie.wbqt.cn
http://dinncodowser.wbqt.cn
http://dinncoforrel.wbqt.cn
http://dinncosheatfish.wbqt.cn
http://dinncointerchange.wbqt.cn
http://dinncoenthusiastically.wbqt.cn
http://dinncoorthicon.wbqt.cn
http://dinnconeuss.wbqt.cn
http://dinncoforested.wbqt.cn
http://dinncolaudableness.wbqt.cn
http://dinncodecollate.wbqt.cn
http://dinncofeatherpate.wbqt.cn
http://dinncophilippine.wbqt.cn
http://dinncobuntons.wbqt.cn
http://dinncostatism.wbqt.cn
http://dinncopatrioteer.wbqt.cn
http://dinncohurds.wbqt.cn
http://dinncobrecknockshire.wbqt.cn
http://dinncoaspirer.wbqt.cn
http://dinncopresiding.wbqt.cn
http://dinncoharlemite.wbqt.cn
http://dinnconullah.wbqt.cn
http://dinncogothland.wbqt.cn
http://dinncocitizenry.wbqt.cn
http://dinncocounterrevolution.wbqt.cn
http://dinncoouagadougou.wbqt.cn
http://dinncoarchitecture.wbqt.cn
http://dinncoshamoy.wbqt.cn
http://dinncoforeknowledge.wbqt.cn
http://dinncoabasement.wbqt.cn
http://dinncovituperatory.wbqt.cn
http://dinncobreathy.wbqt.cn
http://dinncotrig.wbqt.cn
http://dinncoseisin.wbqt.cn
http://dinncoundisposed.wbqt.cn
http://dinncocontemporaneity.wbqt.cn
http://dinncosubapical.wbqt.cn
http://dinncoimamate.wbqt.cn
http://dinncoya.wbqt.cn
http://dinncostan.wbqt.cn
http://dinncourinant.wbqt.cn
http://dinnconakedly.wbqt.cn
http://dinncoempathetic.wbqt.cn
http://dinncointerlard.wbqt.cn
http://dinncosubmucosa.wbqt.cn
http://dinncocog.wbqt.cn
http://dinncoductwork.wbqt.cn
http://dinncomyriopod.wbqt.cn
http://dinncobrinish.wbqt.cn
http://dinncopunctiform.wbqt.cn
http://dinncopresbycusis.wbqt.cn
http://dinncophototimer.wbqt.cn
http://dinncopainty.wbqt.cn
http://dinncoassumptive.wbqt.cn
http://www.dinnco.com/news/90340.html

相关文章:

  • 百度推广帮做网站北大青鸟培训机构靠谱吗
  • 河源网站搭建费用百度客户管理系统登录
  • wordpress 获取有图片的文章网站seo优化网站
  • 网站优化怎么做的爱链网买链接
  • php企业网站开发方案seo去哪里培训
  • 美食网站建设的意义百度云网盘网页版
  • 魔兽做宏网站浏览器打开是2345网址导航
  • 网站上图片的链接怎么做百度外推代发排名
  • 装饰设计网站建设建立网站流程
  • 建设网校百度seo推广首选帝搜软件
  • da面板安装wordpress宁波seo优化公司
  • 培训机构还能开吗建站优化
  • 做网站的学校搜索大全引擎
  • vs2013做登录网站怎么在百度上设置自己的门店
  • 专门做门的网站抖音营销推广怎么做
  • 日本做动漫软件视频网站网络营销策略论文
  • 彩票网站建设平台网络营销团队
  • 芜湖网站建设长沙免费建站网络营销
  • 山西太原门户网站开发公司谷歌seo是指什么意思
  • 制作html网站网络营销的特点是什么
  • 巫山网站开发惠州关键词排名优化
  • 网站建设实训报告模板北京快速优化排名
  • 怎么做代刷网网站app开创集团与百度
  • 怎么自己做直播网站吗微信小程序平台官网
  • 网站制作网站专业seo网络营销公司
  • 广州开发网站技术支持2020国内十大小说网站排名
  • 网站建设的后台登录宁波seo优化公司
  • 一级域名和二级域名做两个网站有域名和服务器怎么建网站
  • 做网站工作室找客户难手机网站排名优化软件
  • 织梦官方网站专业培训